CVE-2020-11078 (published 2020-05-20T16:15Z)

In httplib2 before version 0.18.0, an attacker controlling unescaped part of uri for `httplib2.Http.request()` could change request headers and body, send additional hidden requests to same server. This vulnerability impacts software that uses httplib2 with uri constructed by string concatenation, as opposed to proper urllib building with escaping. This has been fixed in 0.18.0.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 6.8, MEDIUM (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-93
cwe_name: Improper Neutralization of CRLF Sequences ('CRLF Injection')
description: The product uses CRLF (carriage return line feeds) as a special element, e.g. to separate lines or records, but it does not neutralize or incorrectly neutralizes CRLF sequences from inputs.
extended_description: 
            
            
         
         

Fix information:
repo_url: https://github.com/httplib2/httplib2
commit_details: repo_url: https://github.com/httplib2/httplib2
author: Sergey Shepelev
author_date: 2020-05-20 14:56:12+03:00
msg: IMPORTANT security vulnerability CWE-93 CRLF injection

Force %xx quote of space, CR, LF characters in uri.

Special thanks to Recar https://github.com/Ciyfly for discrete notification.

https://cwe.mitre.org/data/definitions/93.html
repository: repo_url: https://github.com/httplib2/httplib2
repo_name: httplib2/httplib2
description: Small, fast HTTP client library for Python. Features persistent connections, cache, and Google App Engine support. Originally written by Joe Gregorio, now supported by community.
repo_language: Python
owner: httplib2
file_changes: filename: __init__.py
change_type: ModificationType.MODIFY
diff: @@ -1985,6 +1985,9 @@ class Http(object):
                 headers["user-agent"] = "Python-httplib2/%s (gzip)" % __version__
 
             uri = iri2uri(uri)
+            # Prevent CWE-75 space injection to manipulate request via part of uri.
+            # Prevent CWE-93 CRLF injection to modify headers via part of uri.
+            uri = uri.replace(" ", "%20").replace("\r", "%0D").replace("\n", "%0A")
 
             (scheme, authority, request_uri, defrag_uri) = urlnorm(uri)
 

programming_language: Python
filename: __init__.py
change_type: ModificationType.MODIFY
diff: @@ -1790,6 +1790,9 @@ a string that contains the response entity body.
                 headers["user-agent"] = "Python-httplib2/%s (gzip)" % __version__
 
             uri = iri2uri(uri)
+            # Prevent CWE-75 space injection to manipulate request via part of uri.
+            # Prevent CWE-93 CRLF injection to modify headers via part of uri.
+            uri = uri.replace(" ", "%20").replace("\r", "%0D").replace("\n", "%0A")
 
             (scheme, authority, request_uri, defrag_uri) = urlnorm(uri)
 

programming_language: Python
filename: __init__.py
change_type: ModificationType.MODIFY
diff: @@ -75,7 +75,7 @@ class BufferedReader(object):
                 chunk = b""
             else:
                 chunk = self._sock.recv(8 << 10)
-            # print('!!! recv', chunk)
+            # print("!!! recv", chunk)
             if not chunk:
                 self._end = True
                 if untilend:

programming_language: Python
filename: test_http.py
change_type: ModificationType.MODIFY
diff: @@ -703,3 +703,33 @@ def test_custom_redirect_codes():
         response, content = http.request(uri, "GET")
         assert response.status == 301
         assert response.previous is None
+
+
+def test_cwe93_inject_crlf():
+    # https://cwe.mitre.org/data/definitions/93.html
+    # GET /?q= HTTP/1.1      <- injected "HTTP/1.1" from attacker
+    # injected: attack
+    # ignore-http: HTTP/1.1  <- nominal "HTTP/1.1" from library
+    # Host: localhost:57285
+    http = httplib2.Http()
+    with tests.server_reflect() as uri:
+        danger_url = urllib.parse.urljoin(
+            uri, "?q= HTTP/1.1\r\ninjected: attack\r\nignore-http:"
+        )
+        response, content = http.request(danger_url, "GET")
+        assert response.status == 200
+        req = tests.HttpRequest.from_bytes(content)
+        assert req.headers.get("injected") is None
+
+
+def test_inject_space():
+    # Injecting space into request line is precursor to CWE-93 and possibly other injections
+    http = httplib2.Http()
+    with tests.server_reflect() as uri:
+        # "\r\nignore-http:" suffix is nuance for current server implementation
+        # please only pay attention to space after "?q="
+        danger_url = urllib.parse.urljoin(uri, "?q= HTTP/1.1\r\nignore-http:")
+        response, content = http.request(danger_url, "GET")
+        assert response.status == 200
+        req = tests.HttpRequest.from_bytes(content)
+        assert req.uri == "/?q=%20HTTP/1.1%0D%0Aignore-http:"

programming_language: Python