[origins] Added https_only tests

pull/7/head^2
Andrew Krasichkov 2017-04-05 01:23:16 +03:00
parent 94350243ee
commit 91bbf05f29
16 changed files with 403 additions and 8 deletions

View File

@ -71,7 +71,8 @@ Total issues:
Or something else, you can find all other `gixy` arguments with the help command: `gixy --help`
# Documentation
Full documentation and recommendations can be found [here](https://github.com/yandex/gixy/wiki/ru/) (sorry, but Russian language only so far)
Full documentation and recommendations can be found [here](https://github.com/yandex/gixy/docs/ru/README.md) (sorry, but Russian language only so far)
# Contributing
Contributions to Gixy are always welcome! You can help us in different ways:

108
docs/ru/README.md Normal file
View File

@ -0,0 +1,108 @@
Gixy — это утилита для анализа конфигурации Nginx. Большей частью служит для обнаружения проблем безопасности, но может искать и иные ошибки.
Перечень проблем, которые Gixy способна обнаружить:
* [[ssrf] Server Side Request Forgery](https://github.com/yandex/gixy/docs/ru/plugins/ssrf.md)
* [[http_splitting] HTTP Splitting](https://github.com/yandex/gixy/docs/ru/plugins/httpsplitting.md)
* [[origins] Проблемы валидации referrer/origin](https://github.com/yandex/gixy/docs/ru/plugins/origins.md)
* [[add_header_redefinition] Переопределение "вышестоящих" заголовков ответа директивой "add_header"](https://github.com/yandex/gixy/docs/ru/plugins/addheaderredefinition.md)
* [[host_spoofing] Подделка заголовка запроса Host](https://github.com/yandex/gixy/docs/ru/plugins/hostspoofing.md)
* [[valid_referers] none in valid_referers](https://github.com/yandex/gixy/docs/ru/plugins/validreferers.md)
* [[add_header_multiline] Многострочные заголовоки ответа](https://github.com/yandex/gixy/docs/ru/plugins/addheadermultiline.md)
Проблемы, которым Gixy только учится можно найти в [Issues с меткой "new plugin"](https://github.com/yandex/gixy/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+plugin%22)
## Установка
Наиболее простой способ установки Gixy - воспользоваться pip для установки из Pypi:
```bash
pip install gixy
```
Официально поддерживаются версии Python 2.7, 3.5 и 3.6
## Использование
После установки должна стать доступна консольная утилита `gixy`.
По умолчанию Gixy ищет конфигурацию по стандартному пути `/etc/nginx/nginx.conf`, однако вы можете указать специфичное расположение:
```
$ gixy /etc/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 умеет обрабатывать директиву `include` и попробует максимально корректно обработать все зависимости, если что-то пошло не так можно попробовать запустить `gixy` с флагом `-d` для вывода дополнительной информации.
Все доступные опции:
```
$ 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
```

View File

@ -0,0 +1,25 @@
# [add_header_multiline] Многострочные заголовоки ответа
Многострочных заголовков ответа стоит избегать по нескольким причинам:
* они признаны устаревшими (см. [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4));
* они никогда не поддерживались многими HTTP-клиентами и браузерами. Например, IE/Edge/Nginx.
## Как самостоятельно обнаружить?
Пример плохой конфигурации:
```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';
```
## Что делать?
Единственный выход - отказ от многострочных заголовок ответа.

View File

@ -0,0 +1,65 @@
# [add_header_redefinition] Переопределение "вышестоящих" заголовков ответа директивой "add_header"
К сожалению, многие считают что с помощью директивы `add_header` можно произвольно доопределять заголовки ответа.
Это не так, о чем сказано в [документации](http://nginx.org/ru/docs/http/ngx_http_headers_module.html#add_header) к Nginx:
> Директив add_header может быть несколько. Директивы наследуются с предыдущего уровня при условии, что на данном уровне не описаны свои директивы add_header.
Суть крайне проста - если у вас устанавливаются заголовки на одном уровне (например, в серверной секции), а уровнем ниже (например, в локейшене) устанавливаются какие-либо еще, то первый не будет применен.
В этом довольно легко убедится:
- Конфигурация:
```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";
}
}
```
- Запрос к локейшену `/` (заголовок `X-Frame-Options` есть в ответе сервера):
```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
```
- Запрос к локейшену `/new-headers` (есть заголовки `Cache-Control` и `Pragma`, но нет `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
```
## Что делать?
Существует несколько способов решить эту проблему:
- продублировать важные заголовки;
- устанавливать заголовки на одном уровне, например, в серверной секции;
- использовать модуль [ngx_headers_more](https://www.nginx.com/resources/wiki/modules/headers_more/).
Каждый из способов имеет свои преимущества и недостатки, какой предпочесть зависит от ваших потребностей.

View File

@ -0,0 +1,32 @@
# [host_spoofing] Подделка заголовка запроса Host
Зачастую, приложению, стоящему за Nginx, необходимо передать корректный заголовок `Host` для корректной генерации различных URL-адресов (редиректы, ресурсы, ссылки в письмах и т.д.).
Возможность его подмены злоумышленником может повлечь множестве проблем от фишинговых атак до SSRF, поэтому следует избегать таких ситуаций.
> Возможно, ваше приложение так же ориентируется на заголовок запроса `X-Forwarded-Host`.
> В этом случае вам необходимо самостоятельно позаботится о его корректной установке при проксировании.
## Как самостоятельно обнаружить?
Чаще всего эта проблема возникает в результате использования переменной `$http_host` вместо `$host`.
Несмотря на их схожесть, они сильно отличаются:
* `$http` - хост в порядке приоритета: имя хоста из строки запроса, или имя хоста из заголовка `Host` заголовка запроса, или имя сервера, соответствующего запросу;
* `$http_host` - заголовок запроса "Host".
Пример такой конфигурации:
```nginx
location @app {
proxy_set_header Host $http_host;
# Other proxy params
proxy_pass http://backend;
}
```
## Что делать?
К счастью, все довольно очевидно:
* перечислить корректные имена сервера в директиве `server_name`;
* всегда использовать переменную `$host`, вместо `$http_host`.
## Дополнительная информация
* [Host of Troubles Vulnerabilities](https://hostoftroubles.com/)
* [Practical HTTP Host header attacks](http://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html)

View File

@ -0,0 +1,51 @@
# [http_splitting] HTTP Splitting
HTTP Splitting - уязвимость, возникающая из-за неправильной обработки входных данных.
Зачастую может быть для атак на приложение стоящее за Nginx (HTTP Request Splitting) или на клиентов приложения (HTTP Response Splitting).
Уязвимость возникает в случае, когда атакующий может внедрить символ перевода строки `\n` в запрос или ответ формируемый Nginx.
## Как самостоятельно обнаружить?
При анализе конфигурации всега стоит обращать внимание на:
- какие переменные используются в директивах, отвечающих за формирование запросов (могут ли они содержать CRLF), например: `rewrite`, `return`, `add_header`, `proxy_set_header` или `proxy_pass`;
- используются ли переменные `$uri` и `$document_uri` и если да, то в каких директивах, т.к. они гарантированно содержат урлдекодированное значение;
- переменные, выделенные из групп с исключающим диапазоном: `(?P<myvar>[^.]+)`.
Пример плохой конфигурации с переменной, полученной из группы с исключающим диапазоном:
```nginx
server {
listen 80 default;
location ~ /v1/((?<action>[^.]*)\.json)?$ {
add_header X-Action $action;
return 200 "OK";
}
}
```
Пример эксплуатации данной конфигурации:
```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
```
Из примера видно, что злоумышленник смог добавить заголовок ответа `x-crlf-header: injected`. Это случилось благодаря стечению нескольких обстоятельств:
- `add_header` не кодирует/валидирует переданные ему значения, считая что автор знает о последствиях;
- значение пути нормализуется перед обработкой локейшена;
- переменная `$action` была выделена из группы регулярного выражения с исключающим диапазоном: `[^.]*`;
- таким образом, значение переменной `$action` равно `see below\r\nx-crlf-header:injected` и при её использовании в формировании ответа добавился заголовок.
## Что делать?
- старайтесь использовать более безопасные переменные, например, `$request_uri` вместо `$uri`;
- запретите перевод строки в исключающем диапазоне, например, `/some/(?<action>[^/\s]+)` вместо `/some/(?<action>[^/]+`;
- возможно, хорошей идеей будет добавить валидацию `$uri` (только если вы знаете, что делаете).

View File

@ -0,0 +1,31 @@
# [origins] Проблемы валидации referrer/origin
Нередко валидация заголовка запроса `Referer` или `Origin` делается при помощи регулярного выражения.
Зачастую, это необходимо для условного выставления заголовка `X-Frame-Options` (защита от ClickJacking) или реализации Cross-Origin Resource Sharing.
Наиболее распространенно два класса ошибок конфигурации, которые приводят к этой проблеме:
- ошибки в составлении регулярного выражения;
- разрешение не доверенных third-party доменов.
> По умолчанию Gixy не валидирует регулярные выражение на предмет матчинга third-party доменов, т.к. не знает кому можно верить.
Передать список доверенных доменом можно при помощи опции `--origins-domains example.com,foo.bar`
## Как самостоятельно обнаружить?
Все довольно "просто":
- необходимо найти все директивы `if`, которые делают проверку переменной `$http_origin` или `$http_referer`;
- убедится что в регулярном выражении нет проблем.
Пример плохой конфигурации:
```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): описать типичные проблемы при составлении регулярных выражений
TODO(buglloc): Regex Ninja?
## Что делать?
Исправить регулярное выражение или отказаться от него вовсе.
Например, если вы используете валидацию на основе регулярного выражения для проверки заголовка запроса `Referer` то, возможно (имеются противопоказания), лучшим решением было бы воспользоваться модулем [ngx_http_referer_module](http://nginx.org/ru/docs/http/ngx_http_referer_module.html)

60
docs/ru/plugins/ssrf.md Normal file
View File

@ -0,0 +1,60 @@
# [ssrf] Server Side Request Forgery
Server Side Request Forgery - уязвимость, позволяющая выполнять различного рода запросы от имени веб-приложения (в нашем случае от имени Nginx).
Возникает, когда атакующий может контролировать адрес проксируемого сервера (второй аргумент директивы `proxy_pass`).
## Как самостоятельно обнаружить?
Наиболее распространенно два класса ошибок конфигурации, которые приводят к этой проблеме:
- отсутствие директивы [internal](http://nginx.org/ru/docs/http/ngx_http_core_module.html#internal). Её смысл заключается в указании того, что определенный location может использоваться только для внутренних запросов;
- небезопасное внутреннее перенаправление.
### Отсутствие директивы internal
Классический пример уязвимости типа SSRF в виду отсутствия директивы `internal` выглядит следующим образом:
```nginx
location ~ /proxy/(.*)/(.*)/(.*)$ {
proxy_pass $1://$2/$3;
}
```
Злоумышленник, полностью контролируя адрес проксируемого сервера, может выполнять произвольные запросы от имени Nginx.
### Небезопасное внутреннее перенаправление
Подразумевается, что в вашей конфигурации есть internal location, которые использует какие-либо данные из запроса в качестве адреса проксируемого сервера.
Например:
```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;
}
```
Согласно документации Nginx внутренними запросами являются:
> - запросы, перенаправленные директивами **error_page**, index, random_index и **try_files**;
> - запросы, перенаправленные с помощью поля “X-Accel-Redirect” заголовка ответа вышестоящего сервера;
> - подзапросы, формируемые командой “include virtual” модуля ngx_http_ssi_module и директивами модуля ngx_http_addition_module;
> - запросы, изменённые директивой **rewrite**.]>
Соответственно, любой "не осторожный" реврайт позволит злоумышленнику сделать внутренний запрос и контролировать адрес проксируемого сервера.
Пример плохой конфигурации:
```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;
}
```
## Что делать?
Есть несколько правил, которых стоит придерживаться в подобного рода конфигурациях:
- использовать только internal location для проксирования;
- по возможности запретить передачу пользовательских данных;
- обезопасить адрес проксируемого сервера:
* если количество проксируемых хостов ограниченно (например, у вас S3), то лучше их захардкодить и выбирать при помощи `map` или иным удобным для вас образом;
* если по какой-то причине нет возможности перечислить все возможные хосты для проксирования, его стоит подписать.

View File

@ -0,0 +1,22 @@
# [valid_referers] none in valid_referers
Модуль [ngx_http_referer_module](http://nginx.org/ru/docs/http/ngx_http_referer_module.html) позволяет блокировать доступ к сервису для запросов с неверными значениями заголовка запроса `Referer`.
Зачастую используется для условного выставления заголовка `X-Frame-Options` (защита от ClickJacking), но могут быть и иные случаи.
Типичные проблемы при конфигурировании этого модуля:
* использование `server_names` при не корректном имени сервера (директива `server_name`);
* слишком общие и/или не корректные регулярные выражения;
* использование `none`.
> На текущий момент, Gixy умеет определять только использование `none` в качестве валидного реферера.
## Чем плох none?
Согласно [документации](http://nginx.org/ru/docs/http/ngx_http_referer_module.html#valid_referers):
> `none` - поле “Referer” в заголовке запроса отсутствует;
Однако, важно помнить, что любой ресурс может заставить браузер пользователя выполнить запрос без заголовка запроса `Referer`, к примеру:
- в случае редиректа в HTTPS на HTTP;
- указав соответствующую [Referrer Policy](https://www.w3.org/TR/referrer-policy/);
- обращение с opaque origin, например, используя схему `data:`.
Таким образом, используя `none` в качестве валидного реферера вы сводиде на нет любые попытки валидации реферера.

View File

@ -14,7 +14,7 @@ add_header Content-Security-Policy "
severity = gixy.severity.LOW
description = ('Multi-line headers are deprecated (see RFC 7230). '
'Some clients never supports them (e.g. IE/Edge).')
help_url = 'https://github.com/yandex/gixy/wiki/ru/addheadermultiline'
help_url = 'https://github.com/yandex/gixy/docs/ru/plugins/addheadermultiline.md'
directives = ['add_header', 'more_set_headers']
def audit(self, directive):

View File

@ -16,7 +16,7 @@ class add_header_redefinition(Plugin):
severity = gixy.severity.MEDIUM
description = ('"add_header" replaces ALL parent headers. '
'See documentation: http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header')
help_url = 'https://github.com/yandex/gixy/wiki/ru/addheaderredefinition'
help_url = 'https://github.com/yandex/gixy/docs/ru/plugins/addheaderredefinition.md'
directives = ['server', 'location', 'if']
options = {'headers': {'x-frame-options',
'x-content-type-options',

View File

@ -10,7 +10,7 @@ class host_spoofing(Plugin):
summary = 'The proxied Host header may be spoofed.'
severity = gixy.severity.MEDIUM
description = 'In most cases "$host" variable are more appropriate, just use it.'
help_url = 'https://github.com/yandex/gixy/wiki/ru/hostspoofing'
help_url = 'https://github.com/yandex/gixy/docs/ru/plugins/hostspoofing.md'
directives = ['proxy_set_header']
def audit(self, directive):

View File

@ -20,7 +20,7 @@ class http_splitting(Plugin):
summary = 'Possible HTTP-Splitting vulnerability.'
severity = gixy.severity.HIGH
description = 'Using variables that can contain "\\n" may lead to http injection.'
help_url = 'https://github.com/yandex/gixy/wiki/ru/httpsplitting'
help_url = 'https://github.com/yandex/gixy/docs/ru/plugins/httpsplitting.md'
directives = ['rewrite', 'return', 'add_header', 'proxy_set_header', 'proxy_pass']
def audit(self, directive):

View File

@ -17,7 +17,7 @@ class origins(Plugin):
summary = 'Validation regex for "origin" or "referrer" matches untrusted domain.'
severity = gixy.severity.MEDIUM
description = 'Improve the regular expression to match only trusted referrers.'
help_url = 'https://github.com/yandex/gixy/wiki/ru/origins'
help_url = 'https://github.com/yandex/gixy/docs/ru/plugins/origins.md'
directives = ['if']
options = {
'domains': ['*'],

View File

@ -24,7 +24,7 @@ class ssrf(Plugin):
summary = 'Possible SSRF (Server Side Request Forgery) vulnerability.'
severity = gixy.severity.HIGH
description = 'The configuration may allow attacker to create a arbitrary requests from the vulnerable server.'
help_url = 'https://github.com/yandex/gixy/wiki/ru/ssrf'
help_url = 'https://github.com/yandex/gixy/docs/ru/plugins/ssrf.md'
directives = ['proxy_pass']
def __init__(self, config):

View File

@ -10,7 +10,7 @@ class valid_referers(Plugin):
summary = 'Used "none" as valid referer.'
severity = gixy.severity.HIGH
description = 'Never trust undefined referer.'
help_url = 'https://github.com/yandex/gixy/wiki/ru/validreferers'
help_url = 'https://github.com/yandex/gixy/docs/ru/plugins/validreferers.md'
directives = ['valid_referers']
def audit(self, directive):