diff --git a/Makefile b/Makefile index a2efec59..67dad52e 100644 --- a/Makefile +++ b/Makefile @@ -6,5 +6,5 @@ build: python setup.py bdist_wheel --universal sdist publish: - twine upload dist/gixy-`grep -oP "(?<=version\s=\s['\"])[^'\"]*(?=['\"])" gixy/__init__.py`* + twine upload dist/gixy_ng-`grep -oP "(?<=version\s=\s['\"])[^'\"]*(?=['\"])" gixy/__init__.py`* diff --git a/README.md b/README.md index 62efca2c..ea128fbf 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Right now Gixy can find: * [[resolver_external] Using external DNS nameservers](https://blog.zorinaq.com/nginx-resolver-vulns/) * [[version_disclosure] Using insecure values for server_tokens](https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/version_disclosure.md) * [[try_files_is_evil_too] The try_files directive is evil without open_file_cache](https://www.getpagespeed.com/server-setup/nginx-try_files-is-evil-too) +* [[proxy_pass_normalized] proxy_pass will decode and normalize paths when specified with a path](https://joshua.hu/proxy-pass-nginx-decoding-normalizing-url-path-dangerous#nginx-proxy_pass) 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/index.md b/docs/index.md index 06a1bf61..ac9f309a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,6 +32,7 @@ Right now Gixy can find: * [[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 insecure values for server_tokens](en/plugins/version_disclosure.md) +* [[proxy_pass_normalized] Using proxy_pass with a pathname will normalize and decode the requested path when proxying](https://joshua.hu/proxy-pass-nginx-decoding-normalizing-url-path-dangerous#nginx-proxy_pass) 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/gixy/cli/argparser.py b/gixy/cli/argparser.py index 0a6dc6e1..9408a793 100644 --- a/gixy/cli/argparser.py +++ b/gixy/cli/argparser.py @@ -30,9 +30,9 @@ def parse(self, stream): continue white_space = '\\s*' - key = '(?P[^:=;#\s]+?)' - value = white_space + '[:=\s]' + white_space + '(?P.+?)' - comment = white_space + '(?P\\s[;#].*)?' + key = r'(?P[^:=;#\s]+?)' + value = white_space + r'[:=\s]' + white_space + r'(?P.+?)' + comment = white_space + r'(?P\s[;#].*)?' key_only_match = re.match('^' + key + comment + '$', line) if key_only_match: diff --git a/gixy/plugins/proxy_pass_normalized.py b/gixy/plugins/proxy_pass_normalized.py new file mode 100644 index 00000000..6808ac43 --- /dev/null +++ b/gixy/plugins/proxy_pass_normalized.py @@ -0,0 +1,44 @@ +import re +import gixy +from gixy.plugins.plugin import Plugin + +class proxy_pass_normalized(Plugin): + r""" + This plugin detects if there is any path component (slash or more) + after the host in a proxy_pass directive. + Example flagged directives: + proxy_pass http://backend/; + proxy_pass http://backend/foo/bar; + """ + + summary = 'Detect path after host in proxy_pass (potential URL decoding issue)' + severity = gixy.severity.LOW + description = ("A slash immediately after the host in proxy_pass leads to the path being decoded and normalized before proxying downstream, leading to unexpected behavior related to encoded slashes.") + help_url = 'https://joshua.hu/proxy-pass-nginx-decoding-normalizing-url-path-dangerous#nginx-proxy_pass' + directives = ['proxy_pass'] + + def __init__(self, config): + super(proxy_pass_normalized, self).__init__(config) + self.parse_uri_re = re.compile(r'(?P[^?#/)]+://)?(?P[^?#/)]+)(?P/.*)?') + + def audit(self, directive): + proxy_pass_arg = directive.args[0] + if not proxy_pass_arg: + return + + parsed = self.parse_uri_re.match(proxy_pass_arg) + + if not parsed: + return + + if not parsed.group('path'): + return + + self.add_issue( + severity=self.severity, + directive=[directive, directive.parent], + reason=( + "Found a slash (and possibly more) after the hostname in proxy_pass. " + "This can lead to path decoding issues." + ) + ) diff --git a/mkdocs.yml b/mkdocs.yml index 567f5927..ba1bf826 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,6 +27,7 @@ nav: - 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/ + - Unsafe path decoding with proxy_pass: https://joshua.hu/proxy-pass-nginx-decoding-normalizing-url-path-dangerous#nginx-proxy_pass - Version Disclosure: en/plugins/version_disclosure.md - 'Blog': 'https://www.getpagespeed.com/posts' markdown_extensions: diff --git a/requirements.dev.txt b/requirements.dev.txt index 6ab6f494..0058c344 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -3,3 +3,5 @@ coverage>=4.3 flake8>=3.2 tox>=2.7.0 pytest-xdist +setuptools +twine diff --git a/tests/plugins/simply/proxy_pass_normalized/proxy_pass_path.conf b/tests/plugins/simply/proxy_pass_normalized/proxy_pass_path.conf new file mode 100644 index 00000000..fefd5b9a --- /dev/null +++ b/tests/plugins/simply/proxy_pass_normalized/proxy_pass_path.conf @@ -0,0 +1,3 @@ +location / { + proxy_pass http://server/; +} diff --git a/tests/plugins/simply/proxy_pass_normalized/proxy_pass_path_fp.conf b/tests/plugins/simply/proxy_pass_normalized/proxy_pass_path_fp.conf new file mode 100644 index 00000000..5b18eb7b --- /dev/null +++ b/tests/plugins/simply/proxy_pass_normalized/proxy_pass_path_fp.conf @@ -0,0 +1,3 @@ +location / { + proxy_pass http://downstream; +}