利用GIXY发现错误的Nginx配置

##概要
Gixy是一个分析Nginx配置的工具。Gixy的主要目标是防止安全性错误配置并自动进行缺陷检测。
目前支持的Python版本为2.7和3.5+。

免责声明:Gixy仅在GNU/Linux上经过良好测试,其他操作系统可能存在一些问题。

目前在Gixy可以找到以下问题:

[add_header_multiline]多行响应头

您应该避免使用多行响应头,原因如下:

  • 它们已被弃用(参见RFC 7230
  • 一些HTTP客户端和浏览器从不支持它们(例如 IE/Edge/Nginx)。

如何发现

配置错误的示例:

1
2
3
4
5
6
7
8
9
10
# http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header
add_header Content-Security-Policy "
default-src: 'none';
script-src data: https://yastatic.net;
style-src data: https://yastatic.net;
img-src data: https://yastatic.net;
font-src data: https://yastatic.net;";
# https://www.nginx.com/resources/wiki/modules/headers_more/
more_set_headers -t 'text/html text/plain'
'X-Foo: Bar multiline';

如何解决

唯一的解决方案是永远不要使用多行响应头。

[add_header_redefinition]通过add_header指令重新定义响应头

不幸的是,许多人不知道指令的继承是如何工作的。大多数情况下,这会导致add_header在尝试在嵌套级别添加新响应头时滥用指令。Nginx 文档中提到了此功能:

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

逻辑非常简单:如果您在一个级别(例如,在server部分)设置了响应头,然后在较低的级别location(比方说)中设置了其他的响应头,那么第一个响应头将被丢弃。
这很容易检查:

  • 配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    server {
    listen 80;
    add_header X-Frame-Options "DENY" always;
    location / {
    return 200 "index";
    }
    location /new-headers {
    # Add special cache control
    add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"
    always;
    add_header Pragma "no-cache" always;
    return 200 "new-headers";
    }
    }
  • 请求location /X-Frame-Options消息头在服务器响应中):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    GET / HTTP/1.0
    HTTP/1.1 200 OK
    Server: nginx/1.10.2
    Date: Mon, 09 Jan 2017 19:28:33 GMT
    Content-Type: application/octet-stream
    Content-Length: 5
    Connection: close
    X-Frame-Options: DENY
    index
  • 请求location /new-headers(请求头中有Cache-ControlPragma,但没有X-Frame-Options

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    GET /new-headers HTTP/1.0
    HTTP/1.1 200 OK
    Server: nginx/1.10.2
    Date: Mon, 09 Jan 2017 19:29:46 GMT
    Content-Type: application/octet-stream
    Content-Length: 11
    Connection: close
    Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    Pragma: no-cache
    new-headers

如何解决

有几种方法可以解决这个问题:

  • 重复重要的协议头
  • 将所有协议头设置为同一级别(server部分是一个不错的选择)
  • 使用ngx_headers_more模块。

[alias_traversal]通过配置错误的alias遍历路径

alias指令用于替换指定位置的路径。例如,配置如下:

1
2
3
location /i/ {
alias /data/w3/images/;
}

/i/top.gif的请求下,会发送/data/w3/images/top.gif文件。
但是,如果location不以目录分隔符(即/)结尾,则:

1
2
3
location /i {
alias /data/w3/images/;
}

在/ i .. / app/config.py的请求下,会发送/ data/w3/app/config.py文件。
换句话说,错误的alias配置可以允许攻击者读取存储在目标文件夹之外的文件。

如何解决

这很简单:

  • 必须找出所有的alias指令;
  • 确保父前缀位置以目录分隔符结尾。

[host_spoofing]伪造请求报文的HOST头

通常,位于nginx后面的应用程序需要正确的Host头来生成URL(重定向、资源、邮件中的链接等)。欺骗此协议头,可能导致从网络钓鱼到SSRF等各种问题。

注意:您的应用程序也可以使用X-Forwarded-Host协议头来实现此功能。在这种情况下,您必须确保协议头设置正确;

如何发现

大多数情况下它使用$http_host变量代替$host
而且它们是完全不同的:

  • $host - HOST按此优先顺序排列:来自请求行的host名,或来自“Host”请求标头字段的host名,或与请求匹配的服务器名称;
  • $http_host - “Host”请求头
    配置样例:
    1
    2
    3
    4
    5
    location @app {
    proxy_set_header Host $http_host;
    # Other proxy params
    proxy_pass http://backend;
    }

如何解决

幸运的是,一切都很明显:

[http_splitting]HTTP Splitting

HTTP Splitting - 使用不正确验证的输入进行攻击。它通常针对位于Nginx(HTTP请求拆分)或其用户(HTTP响应拆分)的Web应用程序。
当攻击者将新行字符\n或\r\n插入到Nginx创建的请求或响应中时,就会创建该漏洞。

如何发现

你应该时刻注意:

  • 指令和创建请求中的变量(因为它们可能包含CRLF),e.g. rewrite, return, add_header, proxy_set_headerproxy_pass;
  • $uri$document_uri变量,以及使用它们的指令,因为这些变量会包含URL-encoded解码后的值;
  • 用于在一定范围内快速匹配的变量,例如(?P[^.]+)。
    一个在特定范围内进行匹配的变量的配置示例如下:
1
2
3
4
5
6
7
server {
listen 80 default;
location ~ /v1/((?<action>[^.]*)\.json)?$ {
add_header X-Action $action;
return 200 "OK";
}
}

利用点:

1
2
3
4
5
6
7
8
9
10
11
GET /v1/see%20below%0d%0ax-crlf-header:injected.json HTTP/1.0
Host: localhost
HTTP/1.1 200 OK
Server: nginx/1.11.10
Date: Mon, 13 Mar 2017 21:21:29 GMT
Content-Type: application/octet-stream
Content-Length: 2
Connection: close
X-Action: see below
x-crlf-header:injected
OK

正如您所看到的,攻击者可以添加响应头x-crlf-header: injected,可能的原因是:

  • add_header不编码或验证输入值
  • 路径值在location处理之前规范化;
  • $action值由特定范围内的正则表达式给定: [^.]*;
  • 因此,$action的值等于see below下面的x-crlf-header: injected,并且在其使用时添加了响应头。

如何解决

  • 使用安全的变量,例如$request_uri代替$uri
  • 在特定范围内禁止使用新的行符号,使用/some/(?<action>[^/\s]+)代替/some/(?<action>[^/]+
  • 验证$uri可能是一个不错的办法(除非你确信你知道你在做什么)。

[origins]referrer/origin验证问题

使用正则表达式验证RefererOrigin协议头并不罕见,通常需要设置X-Frame-Options协议头(ClickJacking保护)或跨源资源共享。
这种配置中最常见的错误是:

  • 正则表达式错误;
  • 允许第三方来源;

    注意:默认情况下,Gixy不会检查第三方来源匹配的正则表达式。您可以使用--original-domain example.com,for.bar选项传递受信任的域列表

如何发现

  • 您必须找到负责检查$http_origin$http_referer的所有if指令
  • 确保你的正则表达式没问题
    错误的配置如下:
    1
    2
    3
    4
    if ($http_origin ~* ((^https://www\.yandex\.ru)|(^https://ya\.ru)$)) {
    add_header 'Access-Control-Allow-Origin' "$http_origin";
    add_header 'Access-Control-Allow-Credentials' 'true';
    }

如何解决

  • 修正你的正则表达式或不使用
  • 如果你对Referer请求头使用正则表达式验证,可以使用ngx_http_referer_module模块
  • 有时候,最好使用没有任何正则表达式的map指令

[ssrf]服务器端请求伪造

服务器端请求伪造攻击,迫使服务器执行任意请求(在我们的例子中来自Nginx)。当攻击者控制代理服务器的地址(proxy_pass指令的第二个参数),这是可能发生的。

如何发现

有两种类型的错误会使服务器容易受到攻击:

  • 缺乏internal指令。它用于指出仅可用于内部请求的位置;
  • 不安全的内部重定向

缺乏internal指令

经典的错误配置是缺乏internal指令,这使SSRF成为可能:

1
2
3
location ~ /proxy/(.*)/(.*)/(.*)$ {
proxy_pass $1://$2/$3;
}

攻击者可以完全控制代理地址,这样就可以代表Nginx发送请求。

不安全的内部重定向

假设您在您的配置中有内部位置,并且该位置使用一些请求数据作为代理服务器的地址。
例如:

1
2
3
4
5
6
location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?
<proxy_path>.*)$ {
internal;
proxy_pass $proxy_proto://$proxy_host/$proxy_path ;
proxy_set_header Host $proxy_host;
}

根据Nginx文档,内部请求如下:

  • requests redirected by the error_page, index, random_index, and try_files directives;
  • requests redirected by the “X-Accel-Redirect” response header field from an upstream server;
  • subrequests formed by the “include virtual” command of the ngx_http_ssi_module module and by the ngx_http_addition_module module directives;
  • requests changed by the rewrite directive

因此,任何不安全重写会导致攻击者发起内部请求并控制被代理服务器的地址。
错误的配置如下:

1
2
3
4
5
6
7
rewrite ^/(.*)/some$ /$1/ last;
location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?
<proxy_path>.*)$ {
internal;
proxy_pass $proxy_proto://$proxy_host/$proxy_path ;
proxy_set_header Host $proxy_host;
}

如何解决

在编写此类配置时,您应该遵循以下几个规则:

  • 只使用“internal locations”进行代理;
  • 如果可能,禁止用户传输数据;
  • 保护被代理服务器地址:
    1. 如果被代理主机的数量有限(当您有S3或smc时),您最好对它们进行硬编码,并用map选择或以其他方式进行;
    2. 如果您可以列出所有可能的主机代理,您应该对地址进行签名。

[valid_referers]valid_referers为none

模块ngx_http_referer_module允许阻止对具有错误Referer值的请求的服务访问。它通常用于设置X-Frame-Options header(点击劫持保护),但可能还有其他情况。
配置该模块的典型问题:

  • 使用server_names错误的服务器名称(server_name指令);
  • 过于宽泛或不好的正则表达;
  • 使用none。

    注意:目前,Gixy只能检测出none作为有效referer的用法

为什么使用none是不好的

根据文档

none-请求头中缺少“Referer”字段;

不过,重要的是要记住,任何资源都可以让用户的浏览器在没有Referer请求头的情况下发起请求。例如:

  • 从HTTPS重定向到HTTP的情况;
  • 通过设置Referer策略
  • 不可见的请求,例如 data: scheme
    因此,通过使用none作为有效的referer,您就可以取消验证referer。

原文地址: https://github.com/yandex/gixy

文章作者: Ginove
文章链接: https://ginove.github.io/2018/09/09/利用GIXY发现错误的Nginx配置/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ginove