上云精选
2核2G云服务器 每月9.33元起,还有更多云产品低至0.02元
作为互联网上最常用的 Web 服务器之一,Nginx 因轻巧、模块化并且有对用户友好的配置格式而广受欢迎。一旦 Nginx 出现错误配置,那么你的网站就很危险。Detectify 分析了从 GitHub 下载的近 50000 个不重复的 Nginx 配置文件,发现了一些常见的错误配置:
merge_slashes
server { root /etc/nginx; location /hello.txt { try_files $uri $uri/ =404; proxy_pass http://127.0.0.1:8080/;
root 指令指定 Nginx 的根文件夹。在上面的示例中,根文件夹是 /etc/nginx ,这意味着我们可以访问该文件夹中的文件。上面的配置没有针对 / (location / {...}) 的位置,只有 /hello.txt 的位置。因此,root 指令会被设置为全局,这意味着对 / 的请求会将你带到本地路径 /etc/nginx 。
/etc/nginx
/ (location / {...})
/hello.txt
/
像 GET /nginx.conf 这样简单的请求都能显示存储在/ etc/nginx/nginx.conf 中 Nginx 配置文件的内容。如果将根设置为 /etc ,则对 /nginx/nginx.conf 的 GET 请求将显示配置文件。在某些情况下,访问者可能会访问其他配置文件、访问日志甚至 HTTP 基本身份验证的加密凭据。
GET /nginx.conf
etc/nginx/nginx.conf
/etc
/nginx/nginx.conf
GET
在我们收集的近 50000 个 Nginx 配置文件中,最常见的根路径如下:
经常配置错误的 Nginx 根路径
server { listen 80 default_server; server_name _; location /static { alias /usr/share/nginx/static/; } location /api { proxy_pass http://apiserver/v1/; }}
这个配置错误指的是由于缺少一个斜杠,所以有可能沿路径上移一步。OrangeTsai 在 Blackhat 演讲 “Breaking Parser Logic!” 中让这项技术广为人知。
在这个演讲中,他展示了如何结合一条缺少尾斜杠的 location 指令与一条 alias 指令,来读取 Web 应用程序的源代码。鲜为人知的是,它还可以与其他指令(例如 proxy_pass )一起使用。我们来分解一下究竟发生了什么事情,以及为什么它能起作用。
location
alias
proxy_pass
location /api { proxy_pass http://apiserver/v1/; }
如果一个 Nginx 服务器运行能在 server 访问的以下配置,则可以假定访问者只能访问 http://apiserver/v1/ 下的路径。
http://apiserver/v1/
http://server/api/user -> http://apiserver/v1//user
当请求 http://server/api/user 时,Nginx 将首先规范化 URL。然后,它会查看前缀/ api 是否与 URL 匹配,本例中是匹配的。
http://server/api/user
api
然后,服务器从 URL 中删除该前缀,保留 /user 路径。再将此路径添加到 proxy_pass URL 中,从而得到最终 URL http://apiserver/v1//user 。
/user
http://apiserver/v1//user
请注意,这个 URL 中存在双斜杠,因为 location 指令不以单斜杠结尾,并且 proxy_pass URL 路径以单斜杠结尾。大多数 Web 服务器会将 http://apiserver/v1//user 标准化为 http://apiserver/v1/user ,这意味着即使配置错误,所有内容仍将按预期运行,并且可能不会引起注意。请求 http://server/api../ 可以利用这种错误配置,这将导致 Nginx 请求 URL http://apiserver/v1/../ ,其标准化为 http://apiserver/ 。这可能产生的影响取决于利用这种错误配置可以达到的效果。例如,这可能导致 Apache 服务器状态通过 URL http://server/api../server-status 公开,或者可能让不希望公开访问的路径可访问。
http://apiserver/v1/user
http://server/api../
http://apiserver/v1/../
http://apiserver/
http://server/api../server-status
Nginx 服务器配置错误的一个迹象是,当 URL 中的一个斜杠被删除时,服务器仍会返回相同的响应。例如,如果 http://server/api/user 和 http://server/apiuser 返回相同的响应,则服务器可能容易受到攻击。这将导致发送以下请求:
http://server/apiuser
http://server/api/user -> http://apiserver/v1//userhttp://server/apiuser -> http://apiserver/v1/user
一些框架、脚本和 Nginx 配置会不安全地使用 Nginx 存储的变量。这可能会导致诸如 XSS、绕过 HttpOnly 保护、信息泄露,甚至在某些情况下的 RCE 之类的问题。
像下面这样的配置:
location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9000; }
主要问题是 Nginx 会将所有 URL 发送到以 .php 结尾的 PHP 解释器,即使该文件在磁盘上不存在。这是 Nginx 创建的“ 陷阱和常见错误 ”文档中提到的,在许多 Nginx 配置中都常见的错误。如果这个 PHP 脚本试图基于 SCRIPT_NAME 定义一个基本 URL,则将发生 XSS。
.php
SCRIPT_NAME
<?php if(basename($_SERVER['SCRIPT_NAME']) == basename($_SERVER['SCRIPT_FILENAME'])) echo dirname($_SERVER['SCRIPT_NAME']); GET /index.php/<script>alert(1)</script>/index.php SCRIPT_NAME = /index.php/<script>alert(1)</script>/index.php
与 Nginx 变量有关的另一个错误配置是使用 $uri 或 $document_uri 代替 $request_uri 。 $uri 和 $document_uri 包含标准化的 URI,而 Nginx 中的 normalization 包括对 URI 解码的 URL。Volema 发现,在 Nginx 配置中创建重定向时经常会使用 $uri ,结果导致 CRLF 注入。
$uri
$document_uri
$request_uri
normalization
一个易受攻击的 Nginx 配置的示例如下:
location / {return 302 https://example.com$uri;}
HTTP 请求的换行符为\r(回车)和\n(换行)。对换行符进行 URL 编码将导致以下字符表示形式 %0d%0a 。如果将这些字符包含在对配置错误的服务器的一个请求中(例如 http://localhost/%0d%0aDetectify:%20clrf ),则该服务器将使用一个名为 Detectify 的新标头进行响应,因为 $uri 变量包含 URL 解码的换行符。
%0d%0a
http://localhost/%0d%0aDetectify:%20clrf
Detectify
HTTP/1.1 302 Moved Temporarily Server: nginx/1.19.3 Content-Type: text/html Content-Length: 145 Connection: keep-alive Location: https://example.com/ Detectify: clrf
在某些情况下,用户提供的数据可以视为 Nginx 变量。目前尚不清楚为什么会发生这种情况,但如这份 H1报告 所示,这种情况并不罕见或不容易测试。如果搜索错误消息,我们可以看到它是在 SSI过滤器模块 中找到的,表明这是由 SSI 引起的。
一种测试方法是设置一个引用标头值:
$ curl -H ‘Referer: bar’ http://localhost/foo$http_referer | grep ‘foobar’
我们扫描了这种错误配置,发现了几个实例,用户可以在其中打印 Nginx 变量的值。我们发现易受攻击实例的数量有所下降,这可能表明这个漏洞已经做了修补。
使用 Nginx 的 proxy_pass ,可以拦截后端创建的错误和 HTTP 标头。如果你要隐藏内部错误消息和标头以便 Nginx 处理,这个方法会非常有用。如果后端回答一个错误,Nginx 将自动提供一个自定义错误页面。但如果 Nginx 无法理解这是一个 HTTP 响应怎么办?
如果一个客户端向 Nginx 发送了一个无效的 HTTP 请求,则该请求将按原样转发到后端,后端将使用其原始内容来应答。然后,Nginx 将无法理解无效的 HTTP 响应,而将其转发给客户端。想象一个这样的 uWSGI 应用程序:
def application(environ, start_response): start_response('500 Error', [('Content-Type', 'text/html'),('Secret-Header','secret-info')]) return [b"Secret info, should not be visible!"]
并在 Nginx 中使用以下指令:
http { error_page 500 /html/error.html; proxy_intercept_errors on; proxy_hide_header Secret-Header; }
如果后端的响应状态大于 300, proxy_intercept_errors 将提供一个自定义响应。在上面的 uWSGI 应用程序中,我们将发送一个 500 Error ,Nginx 将拦截该错误。
500 Error
proxy_hide_header 可以自解释;它将从客户端隐藏任何指定的 HTTP 标头。
如果我们发送一个普通的 GET 请求,则 Nginx 将返回:
HTTP/1.1 500 Internal Server Error Server: nginx/1.10.3 Content-Type: text/html Content-Length: 34