From 3bfcda4aabcaf66876a4f115f903a19b13a7d190 Mon Sep 17 00:00:00 2001 From: Danila Vershinin Date: Sun, 7 Jan 2024 19:56:36 +0700 Subject: [PATCH] Add new plugin to detect Nginx version disclosure and enhance add_header_redefinition Introduced a new plugin that checks if the Nginx version is being disclosed which is a security vulnerability. At the same time, the previous "add_header_redefinition" plugin has been enhanced to check if any secure headers were dropped. Severity level of this issue is now determined based on whether a secure header was dropped or not. --- .github/FUNDING.yml | 2 +- .github/workflows/pythonpackage.yml | 3 +- .readthedocs.yml | 9 ++ README.md | 28 ++-- docs/en/plugins/validreferers.md | 5 +- docs/en/plugins/version_disclosure.md | 17 ++ docs/index.md | 152 ++++++++++++++++++ docs/requirements.txt | 4 + gixy/plugins/add_header_redefinition.py | 23 ++- gixy/plugins/version_disclosure.py | 22 +++ mkdocs.yml | 45 ++++++ .../add_header_redefinition/config.json | 4 +- ...e_both_fp.conf => not_secure_dropped.conf} | 0 ...re_outer_fp.conf => not_secure_outer.conf} | 0 .../server_tokens_off_fp.conf | 1 + .../version_disclosure/server_tokens_on.conf | 1 + 16 files changed, 292 insertions(+), 24 deletions(-) create mode 100644 .readthedocs.yml create mode 100644 docs/en/plugins/version_disclosure.md create mode 100644 docs/index.md create mode 100644 docs/requirements.txt create mode 100644 gixy/plugins/version_disclosure.py create mode 100644 mkdocs.yml rename tests/plugins/simply/add_header_redefinition/{not_secure_both_fp.conf => not_secure_dropped.conf} (100%) rename tests/plugins/simply/add_header_redefinition/{not_secure_outer_fp.conf => not_secure_outer.conf} (100%) create mode 100644 tests/plugins/simply/version_disclosure/server_tokens_off_fp.conf create mode 100644 tests/plugins/simply/version_disclosure/server_tokens_on.conf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 6cbeb3c..c79f81b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: GetPageSpeed +github: dvershinin patreon: getpagespeed open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 3eb24e3..6897ad8 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [2.7, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} @@ -30,4 +30,3 @@ jobs: nosetests --with-coverage --cover-package gixy -v env: GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..23f05b5 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,9 @@ +version: 2 + +mkdocs: + configuration: mkdocs.yml + +python: + version: 3.7 + install: + - requirements: docs/requirements.txt diff --git a/README.md b/README.md index 27f0f27..a7b3a4b 100644 --- a/README.md +++ b/README.md @@ -12,24 +12,26 @@ GIXY Gixy is a tool to analyze Nginx configuration. The main goal of Gixy is to prevent security misconfiguration and automate flaw detection. -Currently supported Python versions are 2.7, 3.6, 3.7, 3.8 and 3.9. +Currently supported Python versions are 3.6, 3.7, 3.8 and 3.9. Disclaimer: Gixy is well tested only on GNU/Linux, other OSs may have some issues. # What it can do + Right now Gixy can find: - * [[ssrf] Server Side Request Forgery](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/ssrf.md) - * [[http_splitting] HTTP Splitting](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/httpsplitting.md) - * [[origins] Problems with referrer/origin validation](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/origins.md) - * [[add_header_redefinition] Redefining of response headers by "add_header" directive](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/addheaderredefinition.md) - * [[host_spoofing] Request's Host header forgery](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/hostspoofing.md) - * [[valid_referers] none in valid_referers](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/validreferers.md) - * [[add_header_multiline] Multiline response headers](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/addheadermultiline.md) - * [[alias_traversal] Path traversal via misconfigured alias](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/aliastraversal.md) - * [[if_is_evil] If is evil when used in location context](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/if_is_evil.md) - * [[allow_without_deny] Allow specified without deny](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/allow_without_deny.md) - * [[add_header_content_type] Setting Content-Type via add_header](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/add_header_content_type.md) - * [[resolver_external] Using external DNS nameservers](https://blog.zorinaq.com/nginx-resolver-vulns/) + +* [[ssrf] Server Side Request Forgery](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/ssrf.md) +* [[http_splitting] HTTP Splitting](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/httpsplitting.md) +* [[origins] Problems with referrer/origin validation](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/origins.md) +* [[add_header_redefinition] Redefining of response headers by "add_header" directive](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/addheaderredefinition.md) +* [[host_spoofing] Request's Host header forgery](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/hostspoofing.md) +* [[valid_referers] none in valid_referers](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/validreferers.md) +* [[add_header_multiline] Multiline response headers](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/addheadermultiline.md) +* [[alias_traversal] Path traversal via misconfigured alias](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/aliastraversal.md) +* [[if_is_evil] If is evil when used in location context](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/if_is_evil.md) +* [[allow_without_deny] Allow specified without deny](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/allow_without_deny.md) +* [[add_header_content_type] Setting Content-Type via add_header](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/add_header_content_type.md) +* [[resolver_external] Using external DNS nameservers](https://blog.zorinaq.com/nginx-resolver-vulns/) You can find things that Gixy is learning to detect at [Issues labeled with "new plugin"](https://github.com/dvershinin/gixy/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+plugin%22) diff --git a/docs/en/plugins/validreferers.md b/docs/en/plugins/validreferers.md index e13c32d..da9b858 100644 --- a/docs/en/plugins/validreferers.md +++ b/docs/en/plugins/validreferers.md @@ -9,7 +9,8 @@ Typical problems with this module's config: > Notice: at the moment, Gixy can only detect the use of `none` as a valid referer. -## Why none is bad? +## 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; @@ -19,4 +20,4 @@ E.g.: - by setting up the [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. \ No newline at end of file +So, by using `none` as a valid referer, you nullify any attemps in refferer validation. diff --git a/docs/en/plugins/version_disclosure.md b/docs/en/plugins/version_disclosure.md new file mode 100644 index 0000000..aabc45e --- /dev/null +++ b/docs/en/plugins/version_disclosure.md @@ -0,0 +1,17 @@ +# [version_disclosure] Disclosure of version information + +## Problem + +Nginx version disclosure. + +## Description + +Nginx version disclosure is a security vulnerability that allows an attacker to obtain information about the version of Nginx running on the server. + +## Recommendation + +Disable version disclosure by adding the following directive to your `nginx.conf`: + +```nginx +server_tokens off; +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..f550d64 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,152 @@ +GIXY +==== +[![Mozilla Public License 2.0](https://img.shields.io/github/license/dvershinin/gixy.svg?style=flat-square)](https://github.com/dvershinin/gixy/blob/master/LICENSE) +[![Python tests](https://github.com/dvershinin/gixy/actions/workflows/pythonpackage.yml/badge.svg)](https://github.com/dvershinin/gixy/actions/workflows/pythonpackage.yml) +[![Your feedback is greatly appreciated](https://img.shields.io/maintenance/yes/2023.svg?style=flat-square)](https://github.com/dvershinin/gixy/issues/new) +[![GitHub issues](https://img.shields.io/github/issues/dvershinin/gixy.svg?style=flat-square)](https://github.com/dvershinin/gixy/issues) +[![GitHub pull requests](https://img.shields.io/github/issues-pr/dvershinin/gixy.svg?style=flat-square)](https://github.com/dvershinin/gixy/pulls) + +# Overview + + +Gixy is a tool to analyze Nginx configuration. +The main goal of Gixy is to prevent security misconfiguration and automate flaw detection. + +Currently supported Python versions are 2.7, 3.6, 3.7, 3.8 and 3.9. + +Disclaimer: Gixy is well tested only on GNU/Linux, other OSs may have some issues. + +# What it can do +Right now Gixy can find: + +* [[ssrf] Server Side Request Forgery](en/plugins/ssrf.md) +* [[http_splitting] HTTP Splitting](en/plugins/httpsplitting.md) +* [[origins] Problems with referrer/origin validation](en/plugins/origins.md) +* [[add_header_redefinition] Redefining of response headers by "add_header" directive](en/plugins/addheaderredefinition.md) +* [[host_spoofing] Request's Host header forgery](en/plugins/hostspoofing.md) +* [[valid_referers] none in valid_referers](en/plugins/validreferers.md) +* [[add_header_multiline] Multiline response headers](en/plugins/addheadermultiline.md) +* [[alias_traversal] Path traversal via misconfigured alias](en/plugins/aliastraversal.md) +* [[if_is_evil] If is evil when used in location context](en/plugins/if_is_evil.md) +* [[allow_without_deny] Allow specified without deny](en/plugins/allow_without_deny.md) +* [[add_header_content_type] Setting Content-Type via add_header](en/plugins/add_header_content_type.md) +* [[resolver_external] Using external DNS nameservers](https://blog.zorinaq.com/nginx-resolver-vulns/) +* [[version_disclosure] Using external DNS nameservers](en/plugins/version_disclosure.md) + +You can find things that Gixy is learning to detect at [Issues labeled with "new plugin"](https://github.com/dvershinin/gixy/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+plugin%22) + +# Installation + +## CentOS/RHEL and other RPM-based systems + +```bash +yum -y install https://extras.getpagespeed.com/release-latest.rpm +yum -y install gixy +``` +### Other systems + +Gixy is distributed on [PyPI](https://pypi.python.org/pypi/gixy-ng). The best way to install it is with pip: + +```bash +pip install gixy-ng +``` + +Run Gixy and check results: +```bash +gixy +``` + +# Usage + +By default, Gixy will try to analyze Nginx configuration placed in `/etc/nginx/nginx.conf`. + +But you can always specify needed path: +``` +$ 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/dvershinin/gixy/blob/master/docs/ru/plugins/httpsplitting.md +Reason: At least variable "$action" can contain "\n" +Pseudo config: +include /etc/nginx/sites/default.conf; + + server { + + location ~ /v1/((?[^.]*)\.json)?$ { + add_header X-Action $action; + } + } + + +==================== Summary =================== +Total issues: + Unspecified: 0 + Low: 0 + Medium: 0 + High: 1 +``` + +Or skip some tests: +``` +$ gixy --skips http_splitting /etc/nginx/nginx.conf + +==================== Results =================== +No issues found. + +==================== Summary =================== +Total issues: + Unspecified: 0 + Low: 0 + Medium: 0 + High: 0 +``` + +Or something else, you can find all other `gixy` arguments with the help command: `gixy --help` + +You can also make `gixy` use pipes (stdin), like so: + +```bash +echo "resolver 1.1.1.1;" | gixy - +``` + +## Docker usage + +Gixy is available as a Docker image [from the Docker hub](https://hub.docker.com/r/getpagespeed/gixy/). To +use it, mount the configuration that you want to analyse as a volume and provide the path to the +configuration file when running the Gixy image. +``` +$ docker run --rm -v `pwd`/nginx.conf:/etc/nginx/conf/nginx.conf getpagespeed/gixy /etc/nginx/conf/nginx.conf +``` + +If you have an image that already contains your nginx configuration, you can share the configuration +with the Gixy container as a volume. +``` +$ docker run --rm --name nginx -d -v /etc/nginx +nginx:alpinef68f2833e986ae69c0a5375f9980dc7a70684a6c233a9535c2a837189f14e905 + +$ docker run --rm --volumes-from nginx dvershinin/gixy /etc/nginx/nginx.conf + +==================== Results =================== +No issues found. + +==================== Summary =================== +Total issues: + Unspecified: 0 + Low: 0 + Medium: 0 + High: 0 + +``` + +# Contributing +Contributions to Gixy are always welcome! You can help us in different ways: + * Open an issue with suggestions for improvements and errors you're facing; + * Fork this repository and submit a pull request; + * Improve the documentation. + +Code guidelines: + * Python code style should follow [pep8](https://www.python.org/dev/peps/pep-0008/) standards whenever possible; + * Pull requests with new plugins must have unit tests for it. diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..b8c1fcc --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +mkdocs +mkdocs-material +markdown-include +pymdown-extensions diff --git a/gixy/plugins/add_header_redefinition.py b/gixy/plugins/add_header_redefinition.py index e73a282..0342b8b 100644 --- a/gixy/plugins/add_header_redefinition.py +++ b/gixy/plugins/add_header_redefinition.py @@ -13,7 +13,7 @@ class add_header_redefinition(Plugin): } """ summary = 'Nested "add_header" drops parent headers.' - severity = gixy.severity.MEDIUM + severity = gixy.severity.LOW 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/dvershinin/gixy/blob/master/docs/en/plugins/addheaderredefinition.md' @@ -23,6 +23,14 @@ class add_header_redefinition(Plugin): def __init__(self, config): super(add_header_redefinition, self).__init__(config) self.interesting_headers = self.config.get('headers') + # Define secure headers that should escalate severity + self.secure_headers = [ + 'x-frame-options', + 'x-content-type-options', + 'x-xss-protection', + 'content-security-policy', + 'cache-control' + ] def audit(self, directive): if not directive.is_block: @@ -37,7 +45,7 @@ def audit(self, directive): parent_headers = get_headers(parent) if not parent_headers: continue - + diff = parent_headers - actual_headers if len(self.interesting_headers): @@ -52,10 +60,17 @@ def _report_issue(self, current, parent, diff): directives = [] # Add headers from parent level directives.extend(parent.find('add_header')) - # Add headers from current level + # Add headers from the current level directives.extend(current.find('add_header')) + + # Check if any dropped header is a secure header + is_secure_header_dropped = any(header in self.secure_headers for header in diff) + + # Set severity based on whether a secure header was dropped + issue_severity = gixy.severity.MEDIUM if is_secure_header_dropped else self.severity + reason = 'Parent headers "{headers}" was dropped in current level'.format(headers='", "'.join(diff)) - self.add_issue(directive=directives, reason=reason) + self.add_issue(directive=directives, reason=reason, severity=issue_severity) def get_headers(directive): diff --git a/gixy/plugins/version_disclosure.py b/gixy/plugins/version_disclosure.py new file mode 100644 index 0000000..dc0ead9 --- /dev/null +++ b/gixy/plugins/version_disclosure.py @@ -0,0 +1,22 @@ +import gixy +from gixy.plugins.plugin import Plugin + + +class version_disclosure(Plugin): + """ + Syntax for the directive: resolver 127.0.0.1 [::1]:5353 valid=30s; + """ + summary = 'Do not use external nameservers for "resolver"' + severity = gixy.severity.HIGH + description = 'Using external nameservers allows someone to send spoofed DNS replies to poison the resolver ' \ + 'cache, causing NGINX to proxy HTTP requests to an arbitrary upstream server.' + help_url = 'https://blog.zorinaq.com/nginx-resolver-vulns/' + directives = ['server_tokens'] + + def audit(self, directive): + if directive.args[0] in ['on', 'build']: + self.add_issue( + severity=gixy.severity.HIGH, + directive=[directive, directive.parent], + reason="User server_tokens off to hide nginx version" + ) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..567f592 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,45 @@ +site_name: Gixy docs +site_description: "Automatic documentation from sources, for Gixy." +site_url: https://gixy.getpagespeed.com/ +repo_url: https://github.com/dvershinin/gixy +theme: + name: "material" + palette: + scheme: slate + primary: teal + accent: purple + features: + - navigation.expand +plugins: + - search +nav: + - Overview: index.md + - Plugins: + - Server Side Request Forgery: en/plugins/ssrf.md + - HTTP Splitting: en/plugins/httpsplitting.md + - Problems with referrer/origin validation: en/plugins/origins.md + - Redefining of response headers by "add_header" directive: en/plugins/addheaderredefinition.md + - Request's Host header forgery: en/plugins/hostspoofing.md + - none in valid_referers: en/plugins/validreferers.md + - Multiline response headers: en/plugins/addheadermultiline.md + - Path traversal via misconfigured alias: en/plugins/aliastraversal.md + - If is evil when used in location context: en/plugins/if_is_evil.md + - Allow specified without deny: en/plugins/allow_without_deny.md + - Setting Content-Type via add_header: en/plugins/add_header_content_type.md + - Using external DNS nameservers: https://blog.zorinaq.com/nginx-resolver-vulns/ + - Version Disclosure: en/plugins/version_disclosure.md + - 'Blog': 'https://www.getpagespeed.com/posts' +markdown_extensions: + - admonition + - markdown_include.include + - pymdownx.emoji + - pymdownx.magiclink + - pymdownx.superfences + - pymdownx.tabbed + - pymdownx.tasklist + - pymdownx.snippets: + check_paths: true + - toc: + permalink: "¤" +extra: + generator: false diff --git a/tests/plugins/simply/add_header_redefinition/config.json b/tests/plugins/simply/add_header_redefinition/config.json index ffb101a..39879e6 100644 --- a/tests/plugins/simply/add_header_redefinition/config.json +++ b/tests/plugins/simply/add_header_redefinition/config.json @@ -1,3 +1,3 @@ { - "severity": "MEDIUM" -} \ No newline at end of file + "severity": ["LOW", "MEDIUM"] +} diff --git a/tests/plugins/simply/add_header_redefinition/not_secure_both_fp.conf b/tests/plugins/simply/add_header_redefinition/not_secure_dropped.conf similarity index 100% rename from tests/plugins/simply/add_header_redefinition/not_secure_both_fp.conf rename to tests/plugins/simply/add_header_redefinition/not_secure_dropped.conf diff --git a/tests/plugins/simply/add_header_redefinition/not_secure_outer_fp.conf b/tests/plugins/simply/add_header_redefinition/not_secure_outer.conf similarity index 100% rename from tests/plugins/simply/add_header_redefinition/not_secure_outer_fp.conf rename to tests/plugins/simply/add_header_redefinition/not_secure_outer.conf diff --git a/tests/plugins/simply/version_disclosure/server_tokens_off_fp.conf b/tests/plugins/simply/version_disclosure/server_tokens_off_fp.conf new file mode 100644 index 0000000..b3a5f35 --- /dev/null +++ b/tests/plugins/simply/version_disclosure/server_tokens_off_fp.conf @@ -0,0 +1 @@ +server_tokens off; diff --git a/tests/plugins/simply/version_disclosure/server_tokens_on.conf b/tests/plugins/simply/version_disclosure/server_tokens_on.conf new file mode 100644 index 0000000..0ae3ab3 --- /dev/null +++ b/tests/plugins/simply/version_disclosure/server_tokens_on.conf @@ -0,0 +1 @@ +server_tokens on;