← all walkthroughs

WifineticTwo

Linux· Medium
owned
2026-09-03
time to own
16m42s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

My found only two exposed services on the target — SSH and a web console on port 8080 that turned out to be OpenPLC, an industrial control (ICS) management platform. OpenPLC was left on its factory default login ([REDACTED: recovered credential]), and because the OpenPLC runtime itself runs as root, the built-in 'Hardware Layer Code Box' — a feature that compiles operator-supplied C code into the PLC runtime (CVE-2021-31630) — gave me a root shell on the container the instant they logged in. From that root foothold the container was found to have a physical Wi-Fi adapter (wlan0) attached, exposing a second, air-gapped-looking network.

A nearby access point, 'plcrouter', had WPS enabled, and a Pixie Dust attack recovered its WPA2 passphrase in seconds. Joining that Wi-Fi network revealed the real prize: the wireless router itself ($INTERNAL_HOST, OpenWRT/LuCI) accepted SSH as root with a blank password, handing over full control of the network's gateway device.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export INTERNAL_HOST="<another-host-reached-after-pivoting>"
export INTERNAL_HOST2="<another-host-reached-after-pivoting>"
export INTERNAL_HOST3="<another-host-reached-after-pivoting>"
export INTERNAL_HOST4="<another-host-reached-after-pivoting>"
export INTERNAL_HOST5="<another-host-reached-after-pivoting>"
export PASSWORD6="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationNetwork service enumeration
Scanned the target and identified an OpenPLC management console
A port scan showed only two services exposed: SSH (22/tcp) and an HTTP service (8080/tcp). Requesting the web root redirected to /login and returned a Werkzeug/Python-served page branded 'Welcome to OpenPLC' — an industrial control system (ICS) programmable logic controller management console.
Nmap found 22/tcp ssh and 8080/tcp http-proxy; HTTP 302 redirect to /login with 'Welcome to OpenPLC' login form (Werkzeug/1.0.1 Python/2.7.18).
Exact commands 2
Confirm the two open ports and identify OpenPLC/Werkzeug.
nmap -p22,8080 -sV -T4 $TARGET
Follow the redirect to the OpenPLC login page.
curl -ksS -L --max-time 15 -D - http://$TARGET:8080/
2Initial AccessDefault/hardcoded credentials (CWE-798)
Logged into OpenPLC using unchanged default credentials
OpenPLC ships with a well-known default account, [REDACTED: recovered credential] intended to be changed on first setup. It had not been changed on this host, so submitting those credentials authenticated the session and returned a valid dashboard session cookie.
POST /login with username=[REDACTED: recovered credential][REDACTED: recovered credential] returned HTTP 302 to /dashboard with a valid session cookie.
Exact commands 2
Fetch the login form and CSRF/session cookie.
curl -sS -c cookies.txt http://$TARGET:8080/login
Authenticate with the OpenPLC default credentials.
curl -sS -i -b cookies.txt -c cookies.txt -X POST -d 'username=$PASSWORD6&password=$PASSWORD6' http://$TARGET:8080/login
FixChange the OpenPLC default administrator credentialsCritical
WeaknessThe OpenPLC web console was left on its out-of-the-box default account ([REDACTED: recovered credential]), so anyone who could reach port 8080 could log in as an administrator without any prior knowledge of the environment.
FixChange the default [REDACTED: recovered credential] account password immediately after deployment, enforce a strong unique password policy, and place the OpenPLC management interface behind network access controls (VPN/firewall) so it is not reachable from untrusted networks.
3ExploitationOS command/code injection via compiled hardware-layer code (CVE-2021-31630)
Compiled a malicious C payload through the Hardware tab to get code execution as root
OpenPLC's Hardware tab includes a 'Hardware Layer Code Box' where an authenticated user can paste raw C code (normally used to interface custom I/O hardware) into functions like updateCustomOut(). Saving the change triggers the server to compile that code into the PLC runtime, and clicking Start PLC on the Dashboard executes it. This is CVE-2021-31630 — because the OpenPLC service itself runs as root, the compiled reverse-shell payload executed with root privileges on the underlying container (hostname attica01).
Target_shell immediately returned uid=0(root) gid=0(root) groups=0(root) on host attica01 after the Start PLC action.
Exact commands 3
Start a listener before triggering the payload.
nc -lvnp 4444
Compile step; replace listener IP/port in the payload.
# In the browser: Hardware tab -> paste a C reverse-shell payload into updateCustomOut() -> Save changes (compiles)
Executes the compiled code as root; catches the shell on the nc listener.
# Dashboard -> Start PLC
FixPatch OpenPLC and stop running the PLC runtime as rootCritical
WeaknessOpenPLC's Hardware Layer Code Box compiles operator-supplied C code and runs it as part of the runtime with no sandboxing or restriction, and that runtime process itself ran as root — so any authenticated user could compile and execute arbitrary code with full system privileges (CVE-2021-31630).
FixUpgrade OpenPLC to a version that fixes CVE-2021-31630, restrict who can edit hardware/custom-code fields to trusted engineers only, and run the OpenPLC service under a dedicated low-privilege system account rather than root so that even a successful code-injection is contained.
4Post-ExploitationContainer/host capability over-exposure
Confirmed root access and an over-privileged container
The obtained shell was verified as root inside a container-style network namespace (eth0@if18 on $INTERNAL_HOST3/24, plus a physical wlan0 adapter). The process held an unusually broad Linux capability set — including cap_sys_module, cap_sys_admin and cap_sys_rawio — far beyond what a PLC runtime needs, which is what later allowed direct control of the host's wireless hardware from inside the container.
Id: uid=0(root); ip -brief address showed eth0@if18 $INTERNAL_HOST4/24 and wlan0; capsh --print showed cap_sys_module,cap_sys_rawio,cap_sys_admin in the current set.
Exact commands 4
Confirm root and map network interfaces.
id; hostname; ip -brief address; ip route
Confirm unrestricted sudo (ALL:ALL) ALL, though root was already held.
sudo -n -l 2>/dev/null
Enumerate the process's Linux capabilities.
capsh --print 2>/dev/null || getcap -r / 2>/dev/null
Locate the local flag file (result: <user.txt>).
find / -type f -name user.txt -exec cat {} \; 2>/dev/null
FixReduce container privileges and remove unnecessary hardware accessHigh
WeaknessThe OpenPLC container was granted a broad Linux capability set (cap_sys_module, cap_sys_admin, cap_sys_rawio, etc.) and direct access to a physical wlan0 wireless adapter — far more than an application container needs — turning a single web-app compromise into full control over host-level and wireless hardware.
FixRun the OpenPLC container with a minimal capability set (drop ALL and add back only what's required), do not pass host network hardware such as wireless adapters into application containers, and apply a seccomp/AppArmor profile to further restrict syscalls.
5DiscoveryWireless network discovery
Discovered a WPS-enabled Wi-Fi access point reachable from the container
The container's wlan0 adapter could see a single nearby access point, SSID 'plcrouter', broadcasting WPS support with WPA2/CCMP. WPS being enabled meant the AP's PIN-based enrollment mechanism was exposed to attack, regardless of how strong the actual Wi-Fi password was.
Iw dev wlan0 scan showed BSS 02:00:00:00:01:00 SSID: plcrouter, freq 2412 MHz (channel 1), RSN CCMP, signal -30.00 dBm.
Exact commands 2
Confirm the wireless adapter is present and bring it up if down.
ip link show wlan0
Enumerate nearby APs and check for WPS.
iw dev wlan0 scan | grep -E 'SSID|BSS|WPS'
6ExploitationWPS Pixie Dust attack (offline WPS PIN recovery)
Ran a Pixie Dust attack against the WPS-enabled AP to recover the Wi-Fi password
WPS's PIN exchange is vulnerable to an offline 'Pixie Dust' attack that recovers the PIN (and from it the WPA passphrase) in seconds when the AP uses a weak or predictable random-number generator, without needing to brute-force it online. Running oneshot.py against the plcrouter AP's BSSID recovered its WPA2 pre-shared key.
Exact commands 1
Pixie Dust attack against the plcrouter AP; recovers the WPS PIN and WPA passphrase.
python3 oneshot.py -i wlan0 -b 02:00:00:00:01:00 -K
FixDisable WPS on the plcrouter wireless access pointHigh
WeaknessThe plcrouter access point had WPS enabled, and WPS's PIN-exchange design is vulnerable to the Pixie Dust attack, which recovers the WPA passphrase offline in seconds regardless of how strong that passphrase is.
FixDisable WPS entirely on the access point and rely on standard WPA2/WPA3 authentication with a strong passphrase; if WPS is required for a specific device, use it only briefly and re-disable it immediately after pairing.
7Lateral MovementSSH authentication with a blank/empty root password (CWE-521 / valid accounts)
Joined the recovered Wi-Fi network and SSH'd into the gateway as root with a blank password
With the WPA passphrase in hand, the container associated to the plcrouter network and picked up an address on $INTERNAL_HOST2/24 with the router itself as the default gateway. That router ($INTERNAL_HOST) turned out to be running OpenWRT/LuCI, and its root SSH account had no password set at all — I could simply SSH in as root with an empty password and gain full control of the wireless gateway.
Wlan0 came up as $INTERNAL_HOST5/24 with default route via $INTERNAL_HOST; ssh root@$INTERNAL_HOST returned uid=0(root) and disclosed /root/root.txt.
Exact commands 4
Build a wpa_supplicant config with the recovered passphrase.
wpa_passphrase plcrouter '<recovered-PSK>' > /tmp/w.conf
Associate to the plcrouter AP.
wpa_supplicant -B -c /tmp/w.conf -i wlan0
Obtain a DHCP lease on the $INTERNAL_HOST2/24 network (busybox target).
udhcpc -i wlan0
Log in as root with no password; result: <root.txt>.
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$INTERNAL_HOST 'id; hostname; cat /root/root.txt'
FixSet a root password and restrict SSH access on the OpenWRT gatewayCritical
WeaknessThe wireless gateway router ($INTERNAL_HOST, OpenWRT/LuCI) had no password set on its root account at all, so SSH accepted 'root' with an empty password and handed over full administrative control of the router.
FixSet a strong root password (or disable password auth entirely) immediately after OpenWRT setup, restrict SSH access to trusted management networks/interfaces, and prefer key-based authentication over passwords for router administration.

Exposed services

22/tcp
8080/tcp