diff options
-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') |