CVE-2019-25101 (published 2023-02-04T08:15Z)

A vulnerability classified as critical has been found in OnShift TurboGears 1.0.11.10. This affects an unknown part of the file turbogears/controllers.py of the component HTTP Header Handler. The manipulation leads to http response splitting. It is possible to initiate the attack remotely. Upgrading to version 1.0.11.11 is able to address this issue. The patch is named f68bbaba47f4474e1da553aa51564a73e1d92a84. It is recommended to upgrade the affected component. The associated identifier of this vulnerability is VDB-220059.

CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-436
cwe_name: Interpretation Conflict
description: Product A handles inputs or steps differently than Product B, which causes A to perform incorrect actions based on its perception of B's state.
extended_description: This is generally found in proxies, firewalls, anti-virus software, and other intermediary devices that monitor, allow, deny, or modify traffic based on how the client or server is expected to behave.
         

Fix information:
repo_url: https://github.com/OnShift/turbogears
commit_details: repo_url: https://github.com/OnShift/turbogears
author: Tim Martin
author_date: 2019-01-28 21:07:15-05:00
msg: SWAT-20: Remove header injection vulnerability on redirect

There was a Header Injection issue (remember HTTP is a text protocol and new
lines matter).  Basically you could inject a header or even the text with
certain parameters.  Typically, this failed on redirects because of the location
header.  We now throw an exception if the redirect url includes a new line or
carriage return.  Neither of these are valid (though you can presumably escape
them).
repository: repo_url: https://github.com/OnShift/turbogears
repo_name: OnShift/turbogears
description: OnShift fork of svn://svn.code.sf.net/p/turbogears1/code/branches/1.0
repo_language: Python
owner: OnShift
file_changes: filename: controllers.py
change_type: ModificationType.MODIFY
diff: @@ -325,7 +325,7 @@ def expose(template=None, validators=None, allow_json=None, html=None,
             applied to that arg
     @keyparam inputform deprecated. A form object that generates the
             input to this method
-    @keyparam exclude_from_memory_profiling allows to exclude individual end points from memory profiling. Can be 
+    @keyparam exclude_from_memory_profiling allows to exclude individual end points from memory profiling. Can be
             used for performance or in case profiling generates errors
     """
     if html:
@@ -602,60 +602,67 @@ def check_app_root():
     request.app_root = app_root
 
 
-def get_server_name(): 
-    """Return name of the server this application runs on. 
- 
-    Respects 'Host' and 'X-Forwarded-Host' header. 
- 
-    See the docstring of the 'absolute_url' function for more information. 
- 
-    """ 
-    get = config.get 
-    h = request.headers 
-    host = get('tg.url_domain') or h.get('X-Forwarded-Host', h.get('Host')) 
-    if not host: 
-        host = '%s:%s' % (get('server.socket_host', 'localhost'), 
-            get('server.socket_port', 8080)) 
-    return host 
-
-
-def absolute_url(tgpath='/', params=None, **kw): 
-    """Return absolute URL (including schema and host to this server). 
- 
-    Tries to account for 'Host' header and reverse proxying 
-    ('X-Forwarded-Host'). 
- 
-    The host name is determined this way: 
- 
-    * If the config setting 'tg.url_domain' is set and non-null, use this value. 
-    * Else, if the 'base_url_filter.use_x_forwarded_host' config setting is 
-      True, use the value from the 'Host' or 'X-Forwarded-Host' request header. 
-    * Else, if config setting 'base_url_filter.on' is True and 
-      'base_url_filter.base_url' is non-null, use its value for the host AND 
-      scheme part of the URL. 
-    * As a last fallback, use the value of 'server.socket_host' and 
-      'server.socket_port' config settings (defaults to 'localhost:8080'). 
- 
-    The URL scheme ('http' or 'http') used is determined in the following way: 
- 
-    * If 'base_url_filter.base_url' is used, use the scheme from this URL. 
-    * If there is a 'X-Use-SSL' request header, use 'https'. 
-    * Else, if the config setting 'tg.url_scheme' is set, use its value. 
-    * Else, use the value of 'cherrypy.request.scheme'. 
- 
-    """ 
-    get = config.get 
-    use_xfh = get('base_url_filter.use_x_forwarded_host', False) 
-    if request.headers.get('X-Use-SSL'): 
-        scheme = 'https' 
-    else: 
-        scheme = get('tg.url_scheme') 
-    if not scheme: 
-        scheme = request.scheme 
-    base_url = '%s://%s' % (scheme, get_server_name()) 
-    if get('base_url_filter.on', False) and not use_xfh: 
-        base_url = get('base_url_filter.base_url').rstrip('/') 
-    return '%s%s' % (base_url, url(tgpath, params, **kw)) 
+def get_server_name():
+    """Return name of the server this application runs on.
+
+    Respects 'Host' and 'X-Forwarded-Host' header.
+
+    See the docstring of the 'absolute_url' function for more information.
+
+    """
+    get = config.get
+    h = request.headers
+    host = get('tg.url_domain') or h.get('X-Forwarded-Host', h.get('Host'))
+    if not host:
+        host = '%s:%s' % (get('server.socket_host', 'localhost'),
+            get('server.socket_port', 8080))
+    return host
+
+
+def absolute_url(tgpath='/', params=None, **kw):
+    """Return absolute URL (including schema and host to this server).
+
+    Tries to account for 'Host' header and reverse proxying
+    ('X-Forwarded-Host').
+
+    The host name is determined this way:
+
+    * If the config setting 'tg.url_domain' is set and non-null, use this value.
+    * Else, if the 'base_url_filter.use_x_forwarded_host' config setting is
+      True, use the value from the 'Host' or 'X-Forwarded-Host' request header.
+    * Else, if config setting 'base_url_filter.on' is True and
+      'base_url_filter.base_url' is non-null, use its value for the host AND
+      scheme part of the URL.
+    * As a last fallback, use the value of 'server.socket_host' and
+      'server.socket_port' config settings (defaults to 'localhost:8080').
+
+    The URL scheme ('http' or 'http') used is determined in the following way:
+
+    * If 'base_url_filter.base_url' is used, use the scheme from this URL.
+    * If there is a 'X-Use-SSL' request header, use 'https'.
+    * Else, if the config setting 'tg.url_scheme' is set, use its value.
+    * Else, use the value of 'cherrypy.request.scheme'.
+
+    """
+    get = config.get
+    use_xfh = get('base_url_filter.use_x_forwarded_host', False)
+    if request.headers.get('X-Use-SSL'):
+        scheme = 'https'
+    else:
+        scheme = get('tg.url_scheme')
+    if not scheme:
+        scheme = request.scheme
+    base_url = '%s://%s' % (scheme, get_server_name())
+    if get('base_url_filter.on', False) and not use_xfh:
+        base_url = get('base_url_filter.base_url').rstrip('/')
+    return '%s%s' % (base_url, url(tgpath, params, **kw))
+
+
+class InvalidRedirectException(Exception):
+    """
+    An invalid redirect url was provided.  Redirects cannot
+    include a carriage return (\r) or new line (\n) character.
+    """
 
 
 def redirect(redirect_path, redirect_params=None, **kw):
@@ -673,6 +680,8 @@ def redirect(redirect_path, redirect_params=None, **kw):
         if path.startswith(request.app_root):
             path = path[len(request.app_root):]
         redirect_path = urlparse.urljoin(path, redirect_path)
+    if set(redirect_path).intersection({'\r', '\n'}):
+        raise InvalidRedirectException('Invalid redirect: {}'.format(redirect_path))
     raise cherrypy.HTTPRedirect(url(tgpath=redirect_path,
         tgparams=redirect_params, **kw))
 

programming_language: Python
filename: release.py
change_type: ModificationType.MODIFY
diff: @@ -28,7 +28,7 @@ problem.
     http://svn.turbogears.org/trunk#egg=turbogears-dev
 """
 
-version = "1.0.11.8"
+version = "1.0.11.9"
 description = "Front-to-back, open-source, rapid web development framework"
 long_description = __doc__
 author = "Kevin Dangoor"

programming_language: Python
filename: test_controllers.py
change_type: ModificationType.MODIFY
diff: @@ -257,6 +257,10 @@ class MyRoot(controllers.RootController):
     def redirect_to_path_tuple(self, path):
         raise redirect((path, 'index'))
 
+    [expose()]
+    def redirect_error(self):
+        raise redirect('/blah\r\n')
+
 
 class TestRoot(unittest.TestCase):
 
@@ -712,6 +716,11 @@ class TestURLs(unittest.TestCase):
                 assert (cherrypy.response.headers['Location']
                     == 'http://localhost/subthing/index'), url
 
+    def test_redirect_error(self):
+        url = "/redirect_error"
+        testutil.create_request(url)
+        assert cherrypy.response.status.startswith("500")
+
     def test_multi_values(self):
         testutil.create_request("/")
         assert url("/foo", bar=[1, 2]) in \

programming_language: Python