CVE-2019-25101 (Python, published 2023-02-04T08:15Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-436 \u2014 Interpretation Conflict
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.
Repository: https://github.com/OnShift/turbogears
Files changed:
turbogears/controllers.py
turbogears/release.py
turbogears/tests/test_controllers.py
Commit message: 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).


Vulnerable code:
    @keyparam exclude_from_memory_profiling allows to exclude individual end points from memory profiling. Can be 
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)) 
version = "1.0.11.8"

Fixed code:
    @keyparam exclude_from_memory_profiling allows to exclude individual end points from memory profiling. Can be
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.
    """
    if set(redirect_path).intersection({'\r', '\n'}):
        raise InvalidRedirectException('Invalid redirect: {}'.format(redirect_path))
version = "1.0.11.9"
    [expose()]
    def redirect_error(self):
        raise redirect('/blah\r\n')

    def test_redirect_error(self):
        url = "/redirect_error"
        testutil.create_request(url)
        assert cherrypy.response.status.startswith("500")