changeset 5523:38d1c99cd000 stable

login: enhance came_from validation Drop urlparse and just validate that came_from is a RFC 3986 compliant path. This blocks an HTTP header injection vulnerability discovered by Gjoko Krstic <gjoko@zeroscience.mk> of Zero Science Lab (CVE-2015-5285)
author Søren Løvborg <sorenl@unity3d.com>
date Wed, 23 Sep 2015 16:09:14 +0200
parents 092ea4d40d60
children 1346754f1852
files kallithea/controllers/login.py kallithea/tests/functional/test_login.py
diffstat 2 files changed, 17 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/controllers/login.py	Wed Sep 30 23:19:46 2015 +0200
+++ b/kallithea/controllers/login.py	Wed Sep 23 16:09:14 2015 +0200
@@ -27,8 +27,8 @@
 
 
 import logging
+import re
 import formencode
-import urlparse
 
 from formencode import htmlfill
 from webob.exc import HTTPFound, HTTPBadRequest
@@ -56,10 +56,19 @@
     def __before__(self):
         super(LoginController, self).__before__()
 
-    def _validate_came_from(self, came_from):
-        """Return True if came_from is valid and can and should be used"""
-        url = urlparse.urlsplit(came_from)
-        return not url.scheme and not url.netloc
+    def _validate_came_from(self, came_from,
+            _re=re.compile(r"/(?!/)[-!#$%&'()*+,./:;=?@_~0-9A-Za-z]*$")):
+        """Return True if came_from is valid and can and should be used.
+
+        Determines if a URI reference is valid and relative to the origin;
+        or in RFC 3986 terms, whether it matches this production:
+
+          origin-relative-ref = path-absolute [ "?" query ] [ "#" fragment ]
+
+        with the exception that '%' escapes are not validated and '#' is
+        allowed inside the fragment part.
+        """
+        return _re.match(came_from) is not None
 
     def index(self):
         c.came_from = safe_str(request.GET.get('came_from', ''))
--- a/kallithea/tests/functional/test_login.py	Wed Sep 30 23:19:46 2015 +0200
+++ b/kallithea/tests/functional/test_login.py	Wed Sep 23 16:09:14 2015 +0200
@@ -107,6 +107,9 @@
           ('ftp://ftp.example.com',),
           ('http://other.example.com/bl%C3%A5b%C3%A6rgr%C3%B8d',),
           ('//evil.example.com/',),
+          ('/\r\nX-Header-Injection: boo',),
+          ('/invälid_url_bytes',),
+          ('non-absolute-path',),
     ])
     def test_login_bad_came_froms(self, url_came_from):
         response = self.app.post(url(controller='login', action='index',