Skip to content
GrrrDog edited this page Feb 8, 2019 · 25 revisions

Basics

  • case-sensitive for verb (400 error)
  • doesn't treat // as a directory (/images/1.jpg/..//../1.jpg -> /1.jpg)
  • doesn't allow in the path: %00 0x00 %
  • doesn't allow %2f as the first slash
  • doesn't path normalize /..
  • doesn't allow underscore (_) in header name (doesn't forward it)
  • doesn't allow 0x0d in header value

Fingerprint

  • Server: nginx
  • 400 error
<html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx/1.15.3</center>
</body>
</html>
  • 403
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>

Absolute-URI

  • supports Absolute-URI with higher priority under host header
  • any scheme in Absolute-URI
  • doesn't allow @ in Absolute-URI (400 error)

location match rules

  • (none): If no modifiers are present, the location is interpreted as a prefix match. This means that the location given will be matched against the beginning of the request URI to determine a match.
  • =: If an equal sign is used, this block will be considered a match if the request URI exactly matches the location given.
  • ~: If a tilde modifier is present, this location will be interpreted as a case-sensitive regular expression match.
  • ~*: If a tilde and asterisk modifier is used, the location block will be interpreted as a case-insensitive regular expression match.
  • ^~: If a carat and tilde modifier is present, and if this block is selected as the best non-regular expression match, regular expression matching will not take place.

https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

proxy_pass

  • backend (URL to origin) is uncontrollable
  • parses, url-decodes, normalizes, finds location
    • cut off #fragment
    • doesn't normalize /..
    • // -> /
  • if trailing slash is in proxy_pass(proxy_pass http://backend/), it forwards the processed request(path)
    • %01-%FF in path -> !"$&'()*+,-./:;<=>@[\]^_`{|}~, 0-9, a-Z, %23 %25 %3F, %01-20, =>%7F
      • %2f to /, which useful for %2f..
      • <> ' " - useful for xss
  • if no trailing slash is in proxy_pass (proxy_pass http://backend), it forwards the initial request(path)
    • /!"$&'()*+,-./:;<=>@[\]^_`{|}~?a#z -> /!"$&'()*+,-./:;<=>@[\]^_`{|}~?a#z
    • %01-%FF -> %01-%FF
  • proxy_pass http://$host/ (with ending /) doesn't proxy path-part
    • proxy_pass http://192.168.78.111:9999 -> http://192.168.78.111:9999/path_from_location/
  • forwards raw bytes (0x01-0x20, > 0x80) in path as-is
  • set HTTP/1.0 by default
  • $host - from the request's Host header ; $http_host- host from config (default)
  • allows >1 Host header
    • forwards only the first one
    • in case of fastcgi_pass or uwsgi_pass, the app gets the second Host header
  • doesn't forward headers with space symbols in name ( AnyHeader: or AnyHeader :)
  • no additional headers to backend

rewrite

  • similar to proxy_pass with trailing slash
  • %0a cuts the path
    • /rewrite_slash/123%0a456?a=b -> /rewrite_slash/123?a=b
location  /rewrite_slash/ {
   rewrite /rewrite_slash/(.*) /$1  break;
   proxy_pass         http://backend:9999/;
}

Caching

  • Nginx only caches GET and HEAD requests
  • It respects the Cache-Control and Expires headers from origin server
    • It does not cache responses with Cache-Control set to Private, No-Cache, or No-Store or with Set-Cookie in the response header.
  • Does not honor the Pragma and the client's Cache-Control
  • Doesn't care about Vary header
  • key for cache: host header and path+query
    • #- is ordinary symbol here

Caching detections

  • X-Cache-Status: MISS - custom header which shows caching
  • If caching is enabled, the header fields “If-Modified-Since”, “If-Unmodified-Since”, “If-None-Match”, “If-Match”, “Range”, and “If-Range” from the original request are not passed to the origin server.
  • doesn't care If-Match for uncached content
  • cares If-Match for cached content:
    • W/"0815" - returns 412 Precondition Failed
    • If-Match: * returns body
  • doesn't care Range headers

Vulnerable configs

  • one level traversal
    • /host_noslash_path../something/ -> /lala/../something/
location /host_noslash_path {
    proxy_pass http://192.168.78.111:9999/lala/;
}
  • no first /
    • /without/slash/here -> GET without/slash/here HTTP/1.1
    • (absolute uri?)
rewrite /(.*) $1  break;
Clone this wiki locally