Skip to content

Commit fbf1f8c

Browse files
author
Joshua Rogers
committed
Ensure that a variable is used in the path when using 'rewrite ^ $request_uri;', to ensure double-encoding does not occur.
Also add testcases. Signed-off-by: Joshua Rogers <Joshua@Joshua.Hu>
1 parent 797fb23 commit fbf1f8c

File tree

9 files changed

+79
-10
lines changed

9 files changed

+79
-10
lines changed

gixy/plugins/proxy_pass_normalized.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class proxy_pass_normalized(Plugin):
1414

1515
summary = 'Detect path after host in proxy_pass (potential URL decoding issue)'
1616
severity = gixy.severity.MEDIUM
17-
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.")
17+
description = ("A path (beginning with a slash) 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 like %2F..%2F. Likewise, the usage of 'rewrite ^ $request_uri;' without using '$1' or '$uri' (or another captured group) in the path of proxy_pass leads to double-encoding of paths.")
1818
help_url = 'https://joshua.hu/proxy-pass-nginx-decoding-normalizing-url-path-dangerous#nginx-proxy_pass'
1919
directives = ['proxy_pass']
2020

@@ -24,6 +24,8 @@ def __init__(self, config):
2424

2525
def audit(self, directive):
2626
proxy_pass_args = directive.args
27+
rewrite_fail = False
28+
num_pattern = r'\$\d+'
2729

2830
if not proxy_pass_args:
2931
return
@@ -33,20 +35,34 @@ def audit(self, directive):
3335
if not parsed:
3436
return
3537

36-
if not parsed.group('path'):
37-
return
38-
39-
4038
for rewrite in directive.find_directives_in_scope("rewrite"):
4139
if hasattr(rewrite, 'pattern') and hasattr(rewrite, 'replace'):
4240
if rewrite.pattern == '^' and rewrite.replace == '$request_uri':
43-
return
41+
if parsed.group('path'):
42+
match = re.search(num_pattern, parsed.group('path'))
43+
if match or '$uri' in parsed.group('path'):
44+
return
45+
else:
46+
rewrite_fail = True
47+
break
48+
else:
49+
if not parsed.group('host'):
50+
return # ?!
51+
match = re.search(num_pattern, parsed.group('host'))
52+
if match or '$uri' in parsed.group('host'):
53+
return
54+
else:
55+
rewrite_fail = True
56+
break
57+
58+
if not parsed.group('path') and not rewrite_fail:
59+
return
4460

4561
self.add_issue(
4662
severity=self.severity,
4763
directive=[directive, directive.parent],
4864
reason=(
49-
"Found a slash (and possibly more) after the hostname in proxy_pass, without using $request_uri."
50-
"This can lead to path decoding issues."
65+
"Found a path after the host in proxy_pass, without using $request_uri and a variable (such as $1 or $uri). "
66+
"This can lead to path decoding issues or double-encoding issues."
5167
)
5268
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
location /b {
2+
rewrite ^ $request_uri; # Sets the $1/$uri variable to the raw path
3+
proxy_pass http://127.0.0.1:8000/; # No $1 or $uri or other variable, resulting in path being double-encoded
4+
}
5+
6+
# Request received by nginx: /%2F
7+
# Request received by backend: /%252F
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
location /b {
2+
rewrite ^ $request_uri; # Sets the $1/$uri variable to the raw path
3+
proxy_pass http://127.0.0.1:8000/$1; # $1 used, resulting in path being passed as the raw path from the original request.
4+
}
5+
6+
# Request received by nginx: /%2F
7+
# Request received by backend: /%2F # Possibly also receives //%2F)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
location /b {
2+
rewrite ^ $request_uri; # Sets the $1/$uri variable to the raw path
3+
proxy_pass http://127.0.0.1:8000; # No $1 or $uri or other variable in either host or path, resulting in path being double-encoded
4+
}
5+
6+
# Request received by nginx: /%2F
7+
# Request received by backend: /%252F
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
location /b {
2+
rewrite ^ $request_uri; # Sets the $1/$uri variable to the raw path
3+
proxy_pass http://127.0.0.1:8000$1; # $1 used in host, resulting in path being passed as the raw path from the original request.
4+
}
5+
6+
# Request received by nginx: /%2F
7+
# Request received by backend: /%2F # Possibly also receives //%2F)
8+
# This is actually no different than proxy_pass_path_fp.conf since $1 is not a path.
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
location / {
2-
proxy_pass http://server/;
2+
proxy_pass http://server/; # Path "/" used, resulting in proxy_pass urldecoding the path.
33
}
4+
5+
# Request received by nginx: /%2F
6+
# Request received by backend: //
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
location / {
2-
proxy_pass http://downstream;
2+
proxy_pass http://downstream; # No rewrite rules, and no path used: no extra/missing urldecoding/urlencoding occurs.
33
}
4+
5+
# Request received by nginx: /%2F
6+
# Request received by backend: /%2F
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
location /1/ {
2+
rewrite ^ $request_uri; # Sets $1/$uri to raw path.
3+
rewrite ^/1(/.*) $1 break; # Extracts everything after /1 and places it into $1/$uri.
4+
return 400; # extremely important! # If rewrite rule does not break (e.g. //1/), return 400, otherwise the location-block will fall-through and proxy_pass will not actually happen at all.
5+
proxy_pass http://127.0.0.1:8080/$1; # $1 used, resulting in path being passed as the raw path from the original request.
6+
}
7+
8+
# Request received by nginx: /1/%2F
9+
# Request received by backend: /%2F (or possibly //%2F)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
location /1/ {
2+
rewrite ^ $request_uri; # Sets $1/$uri to raw path
3+
rewrite ^/1(/.*) $1 break; # Extracts everything after /1
4+
return 400; # extremely important! # If rewrite rule does not break (e.g. //1/), return 400, otherwise the location-block will fall-through.
5+
proxy_pass http://127.0.0.1:8080; # No $1 or $uri or other variable, resulting in path being double-encoded
6+
}
7+
8+
# Request received by nginx: /1/%2F
9+
# Request received by backend: /%252F (or possibly //%252F)

0 commit comments

Comments
 (0)