mirror of https://github.com/yandex/gixy
Added eng documentation.
parent
0ba9b217d8
commit
c5094571f8
|
@ -0,0 +1,109 @@
|
|||
Gixy is a static analysis tool for Ngix config files. It is best at finding security misconfigurations, but can be also used to detect general errors.
|
||||
|
||||
## What it can do
|
||||
Right now Gixy can find:
|
||||
* [[ssrf] Server Side Request Forgery](https://github.com/yandex/gixy/blob/master/docs/en/plugins/ssrf.md)
|
||||
* [[http_splitting] HTTP Splitting](https://github.com/yandex/gixy/blob/master/docs/en/plugins/httpsplitting.md)
|
||||
* [[origins] Problems with referrer/origin validation](https://github.com/yandex/gixy/blob/master/docs/en/plugins/origins.md)
|
||||
* [[add_header_redefinition] Redefining of upstream response headers with directive "add_header"](https://github.com/yandex/gixy/blob/master/docs/en/plugins/addheaderredefinition.md)
|
||||
* [[host_spoofing] Request's Host header forgery](https://github.com/yandex/gixy/blob/master/docs/en/plugins/hostspoofing.md)
|
||||
* [[valid_referers] none in valid_referers](https://github.com/yandex/gixy/blob/master/docs/en/plugins/validreferers.md)
|
||||
* [[add_header_multiline] Multiline response headers](https://github.com/yandex/gixy/blob/master/docs/en/plugins/addheadermultiline.md)
|
||||
|
||||
You can find things that Gixy is learning to detect at [Issues labeled with "new plugin"](https://github.com/yandex/gixy/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+plugin%22)
|
||||
|
||||
##Installation
|
||||
The easiest way to install Gixy is to use pip:
|
||||
```bash
|
||||
pip install gixy
|
||||
```
|
||||
The tool is compatible with Python 2.7/3.5/3.6
|
||||
|
||||
## Using Gixy
|
||||
After installation you can find `gixy` in your command line. Buy default Gixy is working with `/etc/nginx/nginx.conf` folder, which is standart for Nginx installations, but you can provide a custom path as an argument:
|
||||
```
|
||||
$ gixy /www/configs/nginx/nginx.conf
|
||||
```
|
||||
|
||||
==================== Results ===================
|
||||
|
||||
Problem: [http_splitting] Possible HTTP-Splitting vulnerability.
|
||||
Description: Using variables that can contain "\n" may lead to http injection.
|
||||
Additional info: https://github.com/yandex/gixy/wiki/ru/httpsplitting
|
||||
Reason: At least variable "$action" can contain "\n"
|
||||
Pseudo config:
|
||||
include /etc/nginx/sites/default.conf;
|
||||
|
||||
server {
|
||||
|
||||
location ~ /v1/((?<action>[^.]*)\.json)?$ {
|
||||
add_header X-Action $action;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
==================== Summary ===================
|
||||
Total issues:
|
||||
Unspecified: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 1
|
||||
```
|
||||
Gixy can process `include` directive and tries to handle all the dependencies. If something went wrong, you can launch Gixy with the `d` flag, which enables debug mode for extra information.
|
||||
|
||||
To view all options:
|
||||
```
|
||||
$ gixy -h
|
||||
usage: gixy [-h] [-c CONFIG_FILE] [--write-config CONFIG_OUTPUT_PATH]
|
||||
[-v] [-l] [-f {console,text,json}] [-o OUTPUT_FILE] [-d]
|
||||
[--tests TESTS] [--skips SKIPS] [--disable-includes]
|
||||
[--origins-domains domains]
|
||||
[--origins-https-only https_only]
|
||||
[--add-header-redefinition-headers headers]
|
||||
[nginx.conf]
|
||||
|
||||
Gixy - a Nginx configuration [sec]analyzer
|
||||
|
||||
positional arguments:
|
||||
nginx.conf Path to nginx.conf, e.g. /etc/nginx/nginx.conf
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c CONFIG_FILE, --config CONFIG_FILE
|
||||
config file path
|
||||
--write-config CONFIG_OUTPUT_PATH
|
||||
takes the current command line args and writes them
|
||||
out to a config file at the given path, then exits
|
||||
-v, --version show program's version number and exit
|
||||
-l, --level Report issues of a given severity level or higher (-l
|
||||
for LOW, -ll for MEDIUM, -lll for HIGH)
|
||||
-f {console,text,json}, --format {console,text,json}
|
||||
Specify output format
|
||||
-o OUTPUT_FILE, --output OUTPUT_FILE
|
||||
Write report to file
|
||||
-d, --debug Turn on debug mode
|
||||
--tests TESTS Comma-separated list of tests to run
|
||||
--skips SKIPS Comma-separated list of tests to skip
|
||||
--disable-includes Disable "include" directive processing
|
||||
|
||||
plugins options:
|
||||
--origins-domains domains
|
||||
Default: *
|
||||
--origins-https-only https_only
|
||||
Default: False
|
||||
--add-header-redefinition-headers headers
|
||||
Default: content-security-policy,x-xss-
|
||||
protection,x-frame-options,x-content-type-
|
||||
options,strict-transport-security,cache-control
|
||||
|
||||
|
||||
available plugins:
|
||||
host_spoofing
|
||||
add_header_multiline
|
||||
http_splitting
|
||||
valid_referers
|
||||
origins
|
||||
add_header_redefinition
|
||||
ssrf
|
||||
```
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# [add_header_multiline] Multiline response headers
|
||||
|
||||
You should avoid using multiline response headers, because:
|
||||
* they sre considered depricated (см. [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4));
|
||||
* Some HTTP-clients and web browser never supported them (e.g. IE/Edge/Nginx).
|
||||
|
||||
## How can I find it?
|
||||
Misconfiguration example:
|
||||
```nginx
|
||||
# http://nginx.org/ru/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';
|
||||
```
|
||||
|
||||
## What can I do?
|
||||
The only solution is to never use multiline response headers.
|
|
@ -0,0 +1,65 @@
|
|||
# [add_header_redefinition] Redefining of upstream response headers with directive "add_header"
|
||||
|
||||
Unfortunately, many people consider the use of `add_header` directive for headers redefining a good practice.
|
||||
This approach is flawed, which is discussed in Nginx [docs](http://nginx.org/ru/docs/http/ngx_http_headers_module.html#add_header):
|
||||
> 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.
|
||||
|
||||
The logic is quite simple: if you set headers at one level (for example, in `server` section) and then at a lower level (let's say `location`) you set some other headers, then the first group won't apply.
|
||||
|
||||
It's easy to observe:
|
||||
- Configuration:
|
||||
```nginx
|
||||
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 request `/` (`X-Frame-Options` header is in server response):
|
||||
```http
|
||||
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 request `/new-headers` (headers `Cache-Control` and `Pragma` are present, but there's no `X-Frame-Options`):
|
||||
```http
|
||||
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
|
||||
```
|
||||
|
||||
## What can I do?
|
||||
There are several ways to solve this problem:
|
||||
- dublicate important headers;
|
||||
- set all headers at one level (`server` section is a good choice)
|
||||
- use [ngx_headers_more](https://www.nginx.com/resources/wiki/modules/headers_more/) module.
|
||||
|
||||
No solution is perfect, so choose one based on your needs.
|
|
@ -0,0 +1,31 @@
|
|||
# [host_spoofing] Request's Host header forgery
|
||||
|
||||
Often, an application located behind Nginx needs a correct `Host` header for URL generation (redirects, resources, links in emails etc.).
|
||||
An attacker can spoof this header, which leads to a variety of problems, from phishing to SSRF. To prevent this, avoid:
|
||||
> Relience on `X-Forwarded-Host` request header;
|
||||
> In this case you have to ensure the header is set correctly at proxies;
|
||||
|
||||
## How can I find it?
|
||||
Most of the time it's a result of using `$http_host` variable instead of `$host`.
|
||||
|
||||
And they are quite different:
|
||||
* `$http` - host in order of priority: host name from request string, host name form `Host` request header, or a server name, compliant to the request;
|
||||
* `$http_host` - "Host" request header.
|
||||
|
||||
Config sample:
|
||||
```nginx
|
||||
location @app {
|
||||
proxy_set_header Host $http_host;
|
||||
# Other proxy params
|
||||
proxy_pass http://backend;
|
||||
}
|
||||
```
|
||||
|
||||
## What can I do?
|
||||
Luckly, all is quite obvious:
|
||||
* list all the correct server names in `server name` directive;
|
||||
* always use `$host` instead of `$http_host`.
|
||||
|
||||
## Additional info
|
||||
* [Host of Troubles Vulnerabilities](https://hostoftroubles.com/)
|
||||
* [Practical HTTP Host header attacks](http://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html)
|
|
@ -0,0 +1,51 @@
|
|||
# [http_splitting] HTTP Splitting
|
||||
|
||||
HTTP Splitting - attack that use improper input validation. It usually targets web application located behind Nginx (HTTP Request Splitting) or its users (HTTP Response Splitting).
|
||||
|
||||
Vulnerability is created when an attacker can insert new line symbol "\n" into request or into response, created by Nginx server.
|
||||
|
||||
## How can I find it?
|
||||
You should always pay attention to:
|
||||
- variables that are used in directives, responsible for the request creation (for they may contain CRLF), e.g. `rewrite`, `return`, `add_header`, `proxy_set_header` or `proxy_pass`;
|
||||
- `$uri` and `$document_uri` variables, and the directive that process them, because these variables contain url-encoded value;
|
||||
- variables, that are selected from an exclusive range, e.g. `(?P<myvar>[^.]+)`.
|
||||
|
||||
|
||||
An example of configuration that contains vriable, selected from an exclusive range:
|
||||
```nginx
|
||||
server {
|
||||
listen 80 default;
|
||||
|
||||
location ~ /v1/((?<action>[^.]*)\.json)?$ {
|
||||
add_header X-Action $action;
|
||||
return 200 "OK";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Explotation:
|
||||
```http
|
||||
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
|
||||
```
|
||||
As you can see, an attacker could add `x-crlf-header: injected` response header. This was possible because:
|
||||
- `add_header` doesn't encode or validate input value on suggestion that author knows about the consiquences;
|
||||
- the path value is normalized before processing of the location;
|
||||
- `$action` value was given from a regexp with an exclusive range: `[^.]*`;
|
||||
- as the result, `$action` value is equal to that of `see below\r\nx-crlf-header:injected` and on its use the header was added to the response.
|
||||
|
||||
## What can I do?
|
||||
- try to use safe variables, e.g. `$request_uri` instead of `$uri`;
|
||||
- forbid the use of the new line symbol in the exclusive range by using `/some/(?<action>[^/\s]+)` instead of `/some/(?<action>[^/]+`
|
||||
- it could be a good idea to validate `$uri` (only if you're sure you know what are you getting into).
|
|
@ -0,0 +1,30 @@
|
|||
# [origins] Problems with referrer/origin validation
|
||||
|
||||
It's not unusual to use regexp for `Referer` or `Origin` headers validation.
|
||||
Often it is needed for setting the `X-Frame-Options` header (ClickJacking protection) or Cross-Origin Resource Sharing.
|
||||
|
||||
The most common errors with this configuration are:
|
||||
- regexp errors;
|
||||
- 3rd-party domain permissions.
|
||||
|
||||
> By default Gixy doesn't check regexps for 3rd-party domains matching, cause it's unclear wether you can should them. You can pass a list of trusted domains by using the option `--origins-domains example.com,foo.bar`
|
||||
|
||||
## How can I find it?
|
||||
"Eazy"-breezy:
|
||||
- you have to find all the `if` directives that are in charge of `$http_origin` or `$http_referer` check;
|
||||
- make sure your regexps are a-ok.
|
||||
|
||||
Misconfig example:
|
||||
```nginx
|
||||
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';
|
||||
}
|
||||
```
|
||||
|
||||
TODO(buglloc): cover typical regexp-writing problems
|
||||
TODO(buglloc): Regex Ninja?
|
||||
|
||||
## What can I do?
|
||||
Fix your regexp or toss it away.
|
||||
If you use regexp validation for `Referer` request header, then, possibly (not 100%), you could use [ngx_http_referer_module](http://nginx.org/ru/docs/http/ngx_http_referer_module.html).
|
|
@ -0,0 +1,60 @@
|
|||
# [ssrf] Server Side Request Forgery
|
||||
|
||||
Server Side Request Forgery - attack that forces a server to perform requests on behalf of an attacker (Nginx in our case).
|
||||
It's possible when an attacker controls the address of a proxied server (second argument of the `proxy_pass` directive).
|
||||
|
||||
|
||||
## How can I find it?
|
||||
There are two types of errors that make a server vulnerable:
|
||||
- lack of the [internal](http://nginx.org/ru/docs/http/ngx_http_core_module.html#internal) directive. It is used to point out a location that can be used for internal requests only;
|
||||
- unsafe internal redirection.
|
||||
|
||||
### Lack of the internal directive
|
||||
Classical misconfig, based on lack of the internal directive, that makes SSRF possible:
|
||||
```nginx
|
||||
location ~ /proxy/(.*)/(.*)/(.*)$ {
|
||||
proxy_pass $1://$2/$3;
|
||||
}
|
||||
```
|
||||
An attacker has complete control over the proxied address, that makes sending requests on behalf of Nginx possible.
|
||||
|
||||
### Unsafe internal redirection
|
||||
Let's say you have internal location in your config and that location uses some request data as proxied server's address.
|
||||
|
||||
E.g.:
|
||||
```nginx
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
According to Nginx docs, internal requests are:
|
||||
> - 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.]>
|
||||
|
||||
Accordingly, any unsafe rewrite allows an attacker to make an internal request and control a proxied server's address.
|
||||
|
||||
Misconfig example:
|
||||
```nginx
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
## What can I do?
|
||||
There are everal rules you better follow when writing such configurations:
|
||||
- use only `internal location` for proxying;
|
||||
- if possible, forbid user data transmission;
|
||||
- protect proxied server's address:
|
||||
* if the quantity of proxied hosts is limited (when you have S3 or smth), you better hardcode them and choose them with `map` or do it some other way;
|
||||
* if you can' list all possible hosts to proxy, you should sign the address.
|
|
@ -0,0 +1,22 @@
|
|||
# [valid_referers] none in valid_referers
|
||||
Module [ngx_http_referer_module](http://nginx.org/ru/docs/http/ngx_http_referer_module.html) allows to block the access to service for requests with wrong `Referer~ value.
|
||||
It's often used for setting `X-Frame-Options` header (ClickJacking protection), but there may be other cases.
|
||||
|
||||
Typical problems with this module's config:
|
||||
* use of `server_names` with bad server name (`server_name` directive);
|
||||
* too broad and/or bad regexps;
|
||||
* use of `none`.
|
||||
|
||||
> At the moment, Gixy can only detect the use of `none` as a valid referer.
|
||||
|
||||
## Why none is bad?
|
||||
According to [docs](http://nginx.org/ru/docs/http/ngx_http_referer_module.html#valid_referers):
|
||||
> `none` - the “Referer” field is missing in the request header;
|
||||
|
||||
Still, it's important to remember that any resource can make user's browser to make a request without a `Referer` request header.
|
||||
E.g.:
|
||||
- in case of redirect from HTTP to HTTPS;
|
||||
- by setting up a properc[Referrer Policy](https://www.w3.org/TR/referrer-policy/);
|
||||
- a request with opaque origin, `data:` scheme, for example.
|
||||
|
||||
So, by using `none` as a valid referer, you nullify any attemps in refferer validation.
|
|
@ -32,12 +32,12 @@ location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?<proxy_
|
|||
```
|
||||
|
||||
Согласно документации Nginx внутренними запросами являются:
|
||||
> - запросы, перенаправленные директивами **error_page**, index, random_index и **try_files**;
|
||||
> - запросы, перенаправленные директивами **error_page**, **index**, **random_index** и **try_files**;
|
||||
> - запросы, перенаправленные с помощью поля “X-Accel-Redirect” заголовка ответа вышестоящего сервера;
|
||||
> - подзапросы, формируемые командой “include virtual” модуля ngx_http_ssi_module и директивами модуля ngx_http_addition_module;
|
||||
> - запросы, изменённые директивой **rewrite**.]>
|
||||
|
||||
Соответственно, любой "не осторожный" реврайт позволит злоумышленнику сделать внутренний запрос и контролировать адрес проксируемого сервера.
|
||||
Соответственно, любой "неосторожный" реврайт позволит злоумышленнику сделать внутренний запрос и контролировать адрес проксируемого сервера.
|
||||
|
||||
Пример плохой конфигурации:
|
||||
```nginx
|
||||
|
|
|
@ -19,4 +19,4 @@
|
|||
- указав соответствующую [Referrer Policy](https://www.w3.org/TR/referrer-policy/);
|
||||
- обращение с opaque origin, например, используя схему `data:`.
|
||||
|
||||
Таким образом, используя `none` в качестве валидного реферера вы сводиде на нет любые попытки валидации реферера.
|
||||
Таким образом, используя `none` в качестве валидного реферера вы сводите на нет любые попытки валидации реферера.
|
Loading…
Reference in New Issue