diff options
author | Jason R. Coombs <jaraco@jaraco.com> | 2022-07-17 16:36:25 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-17 16:36:25 -0400 |
commit | 5964c48b466448a14703eecc062223fda46caf4e (patch) | |
tree | 8589e0b16b01ed6f522ef5eb597b3202884d282d | |
parent | 12a06bf717effe21973953f30771a56c74130621 (diff) | |
parent | 72d94e3adf2786aaa723f3f1b5bf8a9b34f28332 (diff) | |
download | cherrypy-git-5964c48b466448a14703eecc062223fda46caf4e.tar.gz |
Merge pull request #1976 from cherrypy/bugfix/1974-dangerous-hostv18.8.0
Fix issues with dangerous host headers.
-rw-r--r-- | CHANGES.rst | 9 | ||||
-rw-r--r-- | cherrypy/_cprequest.py | 3 | ||||
-rw-r--r-- | cherrypy/lib/httputil.py | 30 | ||||
-rw-r--r-- | cherrypy/test/test_request_obj.py | 10 |
4 files changed, 52 insertions, 0 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 697d77ef..35b3f895 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,12 @@ +v18.8.0 +------- + +* :issue:`1974`: Dangerous characters received in a host header + encoded using RFC 2047 are now elided by default. Currently, + dangerous characters are defined as CR and LF. The original + value is still available as ``cherrypy.request.headers['Host'].raw`` + if needed. + v18.7.0 ------- diff --git a/cherrypy/_cprequest.py b/cherrypy/_cprequest.py index b380bb75..a661112c 100644 --- a/cherrypy/_cprequest.py +++ b/cherrypy/_cprequest.py @@ -742,6 +742,9 @@ class Request(object): if self.protocol >= (1, 1): msg = "HTTP/1.1 requires a 'Host' request header." raise cherrypy.HTTPError(400, msg) + else: + headers['Host'] = httputil.SanitizedHost(dict.get(headers, 'Host')) + host = dict.get(headers, 'Host') if not host: host = self.local.name or self.local.ip diff --git a/cherrypy/lib/httputil.py b/cherrypy/lib/httputil.py index eedf8d89..ced310a0 100644 --- a/cherrypy/lib/httputil.py +++ b/cherrypy/lib/httputil.py @@ -516,3 +516,33 @@ class Host(object): def __repr__(self): return 'httputil.Host(%r, %r, %r)' % (self.ip, self.port, self.name) + + +class SanitizedHost(str): + r""" + Wraps a raw host header received from the network in + a sanitized version that elides dangerous characters. + + >>> SanitizedHost('foo\nbar') + 'foobar' + >>> SanitizedHost('foo\nbar').raw + 'foo\nbar' + + A SanitizedInstance is only returned if sanitization was performed. + + >>> isinstance(SanitizedHost('foobar'), SanitizedHost) + False + """ + dangerous = re.compile(r'[\n\r]') + + def __new__(cls, raw): + sanitized = cls._sanitize(raw) + if sanitized == raw: + return raw + instance = super().__new__(cls, sanitized) + instance.raw = raw + return instance + + @classmethod + def _sanitize(cls, raw): + return cls.dangerous.sub('', raw) diff --git a/cherrypy/test/test_request_obj.py b/cherrypy/test/test_request_obj.py index 3aaa8e81..2478aabe 100644 --- a/cherrypy/test/test_request_obj.py +++ b/cherrypy/test/test_request_obj.py @@ -756,6 +756,16 @@ class RequestObjectTests(helper.CPWebCase): headers=[('Content-type', 'application/json')]) self.assertBody('application/json') + def test_dangerous_host(self): + """ + Dangerous characters like newlines should be elided. + Ref #1974. + """ + # foo\nbar + encoded = '=?iso-8859-1?q?foo=0Abar?=' + self.getPage('/headers/Host', headers=[('Host', encoded)]) + self.assertBody('foobar') + def test_basic_HTTPMethods(self): helper.webtest.methods_with_bodies = ('POST', 'PUT', 'PROPFIND', 'PATCH') |