Skip to content

Commit 3777469

Browse files
committed
issue2551023: Fix CSRF headers for use with wsgi and cgi. The
env variable array used - separators rather than _. Compare: HTTP_X-REQUESTED-WITH to HTTP_X_REQUESTED_WITH. The last is correct. Also fix roundup-server to produce the latter form. (Patch by Cédric Krier)
1 parent c3b30bf commit 3777469

File tree

4 files changed

+23
-18
lines changed

4 files changed

+23
-18
lines changed

CHANGES.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ Fixed:
2222

2323
- issue2550994: avoid breakage caused by use of backports of Python 3
2424
configparser module to Python 2. (Joseph Myers)
25+
- issue2551023: Fix CSRF headers for use with wsgi and cgi. The
26+
env variable array used - separators rather than _. Compare:
27+
HTTP_X-REQUESTED-WITH to HTTP_X_REQUESTED_WITH. The last is
28+
correct. Also fix roundup-server to produce the latter form. (Patch
29+
by C�dric Krier, reviewed/applied John Rouillard.)
2530

2631

2732
2018-07-13 1.6.0

roundup/cgi/client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,7 @@ def handle_csrf(self, xmlrpc=False):
10261026
# If required headers are missing, raise an error
10271027
for header in header_names:
10281028
if (config["WEB_CSRF_ENFORCE_HEADER_%s"%header] == 'required'
1029-
and "HTTP_%s"%header not in self.env):
1029+
and "HTTP_%s" % header.replace('-', '_') not in self.env):
10301030
logger.error(self._("csrf header %s required but missing for user%s."), header, current_user)
10311031
raise Unauthorised, self._("Missing header: %s")%header
10321032

@@ -1062,9 +1062,9 @@ def handle_csrf(self, xmlrpc=False):
10621062
header_pass += 1
10631063

10641064
enforce=config['WEB_CSRF_ENFORCE_HEADER_X-FORWARDED-HOST']
1065-
if 'HTTP_X-FORWARDED-HOST' in self.env:
1065+
if 'HTTP_X_FORWARDED_HOST' in self.env:
10661066
if enforce != "no":
1067-
host = self.env['HTTP_X-FORWARDED-HOST']
1067+
host = self.env['HTTP_X_FORWARDED_HOST']
10681068
foundat = self.base.find('://' + host + '/')
10691069
# 4 means self.base has http:/ prefix, 5 means https:/ prefix
10701070
if foundat not in [4, 5]:
@@ -1111,7 +1111,7 @@ def handle_csrf(self, xmlrpc=False):
11111111
# Note we do not use CSRF nonces for xmlrpc requests.
11121112
#
11131113
# see: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers
1114-
if 'HTTP_X-REQUESTED-WITH' not in self.env:
1114+
if 'HTTP_X_REQUESTED_WITH' not in self.env:
11151115
logger.error(self._("csrf X-REQUESTED-WITH xmlrpc required header check failed for user%s."), current_user)
11161116
raise UsageError, self._("Required Header Missing")
11171117

roundup/scripts/roundup_server.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,8 @@ def inner_run_cgi(self):
384384
# If behind a proxy, this is the hostname supplied
385385
# via the Host header to the proxy. Used by core code.
386386
# Controlled by the CSRF settings.
387-
env['HTTP_X-FORWARDED-HOST'] = xfh
388-
xff = self.headers.getheader('X-Forwarded-For', None)
387+
env['HTTP_X_FORWARDED_HOST'] = xfh
388+
xff = self.headers.get('X-Forwarded-For', None)
389389
if xff:
390390
# xff is a list of ip addresses for original client/proxies:
391391
# X-Forwarded-For: clientIP, proxy1IP, proxy2IP
@@ -394,8 +394,8 @@ def inner_run_cgi(self):
394394
# Made available for extensions if the user trusts it.
395395
# E.g. you may wish to disable recaptcha validation extension
396396
# if the ip of the client matches 172.16.0.0.
397-
env['HTTP_X-FORWARDED-FOR'] = xff
398-
xfp = self.headers.getheader('X-Forwarded-Proto', None)
397+
env['HTTP_X_FORWARDED_FOR'] = xff
398+
xfp = self.headers.get('X-Forwarded-Proto', None)
399399
if xfp:
400400
# xfp is the protocol (http/https) seen by proxies in the
401401
# path of the request. I am not sure if there is only
@@ -408,8 +408,8 @@ def inner_run_cgi(self):
408408
# May not be trustworthy. Do not use in core without
409409
# config option to control its use.
410410
# Made available for extensions if the user trusts it.
411-
env['HTTP_X-FORWARDED-PROTO'] = xfp
412-
if os.environ.has_key('CGI_SHOW_TIMING'):
411+
env['HTTP_X_FORWARDED_PROTO'] = xfp
412+
if 'CGI_SHOW_TIMING' in os.environ:
413413
env['CGI_SHOW_TIMING'] = os.environ['CGI_SHOW_TIMING']
414414
env['HTTP_ACCEPT_LANGUAGE'] = self.headers.get('accept-language')
415415
referer = self.headers.get('Referer')
@@ -420,8 +420,8 @@ def inner_run_cgi(self):
420420
env['HTTP_ORIGIN'] = origin
421421
xrw = self.headers.get('x-requested-with')
422422
if xrw:
423-
env['HTTP_X-REQUESTED-WITH'] = xrw
424-
range = self.headers.getheader('range')
423+
env['HTTP_X_REQUESTED_WITH'] = xrw
424+
range = self.headers.get('range')
425425
if range:
426426
env['HTTP_RANGE'] = range
427427

test/test_cgi.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ def hasPermission(s, p, classname=None, d=None, e=None, **kw):
888888
del(cl.env['HTTP_ORIGIN'])
889889
del(out[0])
890890

891-
cl.env['HTTP_X-FORWARDED-HOST'] = 'whoami.com'
891+
cl.env['HTTP_X_FORWARDED_HOST'] = 'whoami.com'
892892
# if there is an X-FORWARDED-HOST header it is used and
893893
# HOST header is ignored. X-FORWARDED-HOST should only be
894894
# passed/set by a proxy. In this case the HOST header is
@@ -899,7 +899,7 @@ def hasPermission(s, p, classname=None, d=None, e=None, **kw):
899899
match_at=out[0].find('Redirecting to <a href="http://whoami.com/path/issue1?@ok_message')
900900
print "result of subtest 4:", out[0]
901901
self.assertNotEqual(match_at, -1)
902-
del(cl.env['HTTP_X-FORWARDED-HOST'])
902+
del(cl.env['HTTP_X_FORWARDED_HOST'])
903903
del(cl.env['HTTP_HOST'])
904904
del(out[0])
905905

@@ -912,14 +912,14 @@ def hasPermission(s, p, classname=None, d=None, e=None, **kw):
912912
del(out[0])
913913

914914
# try failing headers
915-
cl.env['HTTP_X-FORWARDED-HOST'] = 'whoami.net'
915+
cl.env['HTTP_X_FORWARDED_HOST'] = 'whoami.net'
916916
# this raises an error as the header check passes and
917917
# it did the edit and tries to send mail.
918918
cl.inner_main()
919919
match_at=out[0].find('Invalid X-FORWARDED-HOST whoami.net')
920920
print "result of subtest 6:", out[0]
921921
self.assertNotEqual(match_at, -1)
922-
del(cl.env['HTTP_X-FORWARDED-HOST'])
922+
del(cl.env['HTTP_X_FORWARDED_HOST'])
923923
del(out[0])
924924

925925
# header checks succeed
@@ -1031,7 +1031,7 @@ def wh(s):
10311031
'CONTENT_TYPE': 'text/plain',
10321032
'HTTP_AUTHORIZATION': 'Basic YWRtaW46YWRtaW4=',
10331033
'HTTP_REFERER': 'http://whoami.com/path/',
1034-
'HTTP_X-REQUESTED-WITH': "XMLHttpRequest"
1034+
'HTTP_X_REQUESTED_WITH': "XMLHttpRequest"
10351035
}, form)
10361036
cl.db = self.db
10371037
cl.base = 'http://whoami.com/path/'
@@ -1059,7 +1059,7 @@ def wh(s):
10591059
del(out[0])
10601060

10611061
# remove the X-REQUESTED-WITH header and get an xmlrpc fault returned
1062-
del(cl.env['HTTP_X-REQUESTED-WITH'])
1062+
del(cl.env['HTTP_X_REQUESTED_WITH'])
10631063
cl.handle_xmlrpc()
10641064
output="<?xml version='1.0'?>\n<methodResponse>\n<fault>\n<value><struct>\n<member>\n<name>faultCode</name>\n<value><int>1</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>&lt;class 'roundup.exceptions.UsageError'&gt;:Required Header Missing</string></value>\n</member>\n</struct></value>\n</fault>\n</methodResponse>\n"
10651065
print out[0]

0 commit comments

Comments
 (0)