Pete Zaitcev 8bb9a30
From 3b945a2fcbcc8df63cb9e1987741fa12b8f54a8c Mon Sep 17 00:00:00 2001
Pete Zaitcev 8bb9a30
From: John Dickinson <me@not.mn>
Pete Zaitcev 8bb9a30
Date: Fri, 6 Jun 2014 11:46:41 -0700
Pete Zaitcev 8bb9a30
Subject: [PATCH] properly quote www-authenticate header value
Pete Zaitcev 8bb9a30
Pete Zaitcev 8bb9a30
HTTP header values should be quoted. Since the WWW-Authenticate
Pete Zaitcev 8bb9a30
header value contains user-supplied strings, it's important to
Pete Zaitcev 8bb9a30
ensure it's properly quoted to ensure the integrity of the protocol.
Pete Zaitcev 8bb9a30
Pete Zaitcev 8bb9a30
Previous to this patch, the URL was unquoted and then the unquoted
Pete Zaitcev 8bb9a30
value was returned in the header. This patch re-quotes the value
Pete Zaitcev 8bb9a30
when it is set on the response.
Pete Zaitcev 8bb9a30
Pete Zaitcev 8bb9a30
This is filed as CVS-2014-3497
Pete Zaitcev 8bb9a30
Pete Zaitcev 8bb9a30
Fixes bug 1327414
Pete Zaitcev 8bb9a30
Pete Zaitcev 8bb9a30
Change-Id: If8bd8842f2ce821756e9b4461a18a8ac8d42fb8c
Pete Zaitcev 8bb9a30
(cherry picked from commit b223322ed1ef44f61490f820240aa01f1047ae2e)
Pete Zaitcev 8bb9a30
---
Pete Zaitcev 8bb9a30
 swift/common/swob.py          |  2 +-
Pete Zaitcev 8bb9a30
 test/functional/tests.py      | 13 +++++++++++++
Pete Zaitcev 8bb9a30
 test/unit/common/test_swob.py | 22 ++++++++++++++++++++++
Pete Zaitcev 8bb9a30
 3 files changed, 36 insertions(+), 1 deletion(-)
Pete Zaitcev 8bb9a30
Pete Zaitcev 8bb9a30
diff --git a/swift/common/swob.py b/swift/common/swob.py
Pete Zaitcev 8bb9a30
index 638086e..f4f38c7 100644
Pete Zaitcev 8bb9a30
--- a/swift/common/swob.py
Pete Zaitcev 8bb9a30
+++ b/swift/common/swob.py
Pete Zaitcev 8bb9a30
@@ -1203,7 +1203,7 @@ class Response(object):
Pete Zaitcev 8bb9a30
                 realm = 'unknown'
Pete Zaitcev 8bb9a30
         except (AttributeError, ValueError):
Pete Zaitcev 8bb9a30
             realm = 'unknown'
Pete Zaitcev 8bb9a30
-        return 'Swift realm="%s"' % realm
Pete Zaitcev 8bb9a30
+        return 'Swift realm="%s"' % urllib2.quote(realm)
Pete Zaitcev 8bb9a30
 
Pete Zaitcev 8bb9a30
     @property
Pete Zaitcev 8bb9a30
     def is_success(self):
Pete Zaitcev 8bb9a30
diff --git a/test/functional/tests.py b/test/functional/tests.py
Pete Zaitcev 8bb9a30
index ad8c398..7983815 100644
Pete Zaitcev 8bb9a30
--- a/test/functional/tests.py
Pete Zaitcev 8bb9a30
+++ b/test/functional/tests.py
Pete Zaitcev 8bb9a30
@@ -333,6 +333,19 @@ class TestAccount(Base):
Pete Zaitcev 8bb9a30
             self.assertEqual(sorted(containers, cmp=locale.strcoll),
Pete Zaitcev 8bb9a30
                              containers)
Pete Zaitcev 8bb9a30
 
Pete Zaitcev 8bb9a30
+    def testQuotedWWWAuthenticateHeader(self):
Pete Zaitcev 8bb9a30
+        conn = Connection(config)
Pete Zaitcev 8bb9a30
+        conn.authenticate()
Pete Zaitcev 8bb9a30
+        inserted_html = 'Hello World'
Pete Zaitcev 8bb9a30
+        hax = 'AUTH_haxx"\nContent-Length: %d\n\n%s' % (len(inserted_html),
Pete Zaitcev 8bb9a30
+                                                        inserted_html)
Pete Zaitcev 8bb9a30
+        quoted_hax = urllib.quote(hax)
Pete Zaitcev 8bb9a30
+        conn.connection.request('GET', '/v1/' + quoted_hax, None, {})
Pete Zaitcev 8bb9a30
+        resp = conn.connection.getresponse()
Pete Zaitcev 8bb9a30
+        resp_headers = resp.getheaders()
Pete Zaitcev 8bb9a30
+        expected = ('www-authenticate', 'Swift realm="%s"' % quoted_hax)
Pete Zaitcev 8bb9a30
+        self.assert_(expected in resp_headers)
Pete Zaitcev 8bb9a30
+
Pete Zaitcev 8bb9a30
 
Pete Zaitcev 8bb9a30
 class TestAccountUTF8(Base2, TestAccount):
Pete Zaitcev 8bb9a30
     set_up = False
Pete Zaitcev 8bb9a30
diff --git a/test/unit/common/test_swob.py b/test/unit/common/test_swob.py
Pete Zaitcev 8bb9a30
index 7cc5439..b0452b9 100644
Pete Zaitcev 8bb9a30
--- a/test/unit/common/test_swob.py
Pete Zaitcev 8bb9a30
+++ b/test/unit/common/test_swob.py
Pete Zaitcev 8bb9a30
@@ -601,6 +601,28 @@ class TestRequest(unittest.TestCase):
Pete Zaitcev 8bb9a30
         self.assertEquals('Me realm="whatever"',
Pete Zaitcev 8bb9a30
                           resp.headers['Www-Authenticate'])
Pete Zaitcev 8bb9a30
 
Pete Zaitcev 8bb9a30
+    def test_401_www_authenticate_is_quoted(self):
Pete Zaitcev 8bb9a30
+
Pete Zaitcev 8bb9a30
+        def test_app(environ, start_response):
Pete Zaitcev 8bb9a30
+            start_response('401 Unauthorized', [])
Pete Zaitcev 8bb9a30
+            return ['hi']
Pete Zaitcev 8bb9a30
+
Pete Zaitcev 8bb9a30
+        hacker = 'account-name\n\nfoo
' # url injection test
Pete Zaitcev 8bb9a30
+        quoted_hacker = quote(hacker)
Pete Zaitcev 8bb9a30
+        req = swift.common.swob.Request.blank('/v1/' + hacker)
Pete Zaitcev 8bb9a30
+        resp = req.get_response(test_app)
Pete Zaitcev 8bb9a30
+        self.assertEquals(resp.status_int, 401)
Pete Zaitcev 8bb9a30
+        self.assert_('Www-Authenticate' in resp.headers)
Pete Zaitcev 8bb9a30
+        self.assertEquals('Swift realm="%s"' % quoted_hacker,
Pete Zaitcev 8bb9a30
+                          resp.headers['Www-Authenticate'])
Pete Zaitcev 8bb9a30
+
Pete Zaitcev 8bb9a30
+        req = swift.common.swob.Request.blank('/v1/' + quoted_hacker)
Pete Zaitcev 8bb9a30
+        resp = req.get_response(test_app)
Pete Zaitcev 8bb9a30
+        self.assertEquals(resp.status_int, 401)
Pete Zaitcev 8bb9a30
+        self.assert_('Www-Authenticate' in resp.headers)
Pete Zaitcev 8bb9a30
+        self.assertEquals('Swift realm="%s"' % quoted_hacker,
Pete Zaitcev 8bb9a30
+                          resp.headers['Www-Authenticate'])
Pete Zaitcev 8bb9a30
+
Pete Zaitcev 8bb9a30
     def test_not_401(self):
Pete Zaitcev 8bb9a30
 
Pete Zaitcev 8bb9a30
         # Other status codes should not have WWW-Authenticate in response