Skip to content

Commit a1457cc

Browse files
committed
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
1 parent 9413ffc commit a1457cc

File tree

4 files changed

+37
-1
lines changed

4 files changed

+37
-1
lines changed

Diff for: python2/httplib2/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,9 @@ def request(
19851985
headers["user-agent"] = "Python-httplib2/%s (gzip)" % __version__
19861986

19871987
uri = iri2uri(uri)
1988+
# Prevent CWE-75 space injection to manipulate request via part of uri.
1989+
# Prevent CWE-93 CRLF injection to modify headers via part of uri.
1990+
uri = uri.replace(" ", "%20").replace("\r", "%0D").replace("\n", "%0A")
19881991

19891992
(scheme, authority, request_uri, defrag_uri) = urlnorm(uri)
19901993

Diff for: python3/httplib2/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -1790,6 +1790,9 @@ def request(
17901790
headers["user-agent"] = "Python-httplib2/%s (gzip)" % __version__
17911791

17921792
uri = iri2uri(uri)
1793+
# Prevent CWE-75 space injection to manipulate request via part of uri.
1794+
# Prevent CWE-93 CRLF injection to modify headers via part of uri.
1795+
uri = uri.replace(" ", "%20").replace("\r", "%0D").replace("\n", "%0A")
17931796

17941797
(scheme, authority, request_uri, defrag_uri) = urlnorm(uri)
17951798

Diff for: tests/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def _fill(self, target=1, more=None, untilend=False):
7575
chunk = b""
7676
else:
7777
chunk = self._sock.recv(8 << 10)
78-
# print('!!! recv', chunk)
78+
# print("!!! recv", chunk)
7979
if not chunk:
8080
self._end = True
8181
if untilend:

Diff for: tests/test_http.py

+30
Original file line numberDiff line numberDiff line change
@@ -703,3 +703,33 @@ def test_custom_redirect_codes():
703703
response, content = http.request(uri, "GET")
704704
assert response.status == 301
705705
assert response.previous is None
706+
707+
708+
def test_cwe93_inject_crlf():
709+
# https://cwe.mitre.org/data/definitions/93.html
710+
# GET /?q= HTTP/1.1 <- injected "HTTP/1.1" from attacker
711+
# injected: attack
712+
# ignore-http: HTTP/1.1 <- nominal "HTTP/1.1" from library
713+
# Host: localhost:57285
714+
http = httplib2.Http()
715+
with tests.server_reflect() as uri:
716+
danger_url = urllib.parse.urljoin(
717+
uri, "?q= HTTP/1.1\r\ninjected: attack\r\nignore-http:"
718+
)
719+
response, content = http.request(danger_url, "GET")
720+
assert response.status == 200
721+
req = tests.HttpRequest.from_bytes(content)
722+
assert req.headers.get("injected") is None
723+
724+
725+
def test_inject_space():
726+
# Injecting space into request line is precursor to CWE-93 and possibly other injections
727+
http = httplib2.Http()
728+
with tests.server_reflect() as uri:
729+
# "\r\nignore-http:" suffix is nuance for current server implementation
730+
# please only pay attention to space after "?q="
731+
danger_url = urllib.parse.urljoin(uri, "?q= HTTP/1.1\r\nignore-http:")
732+
response, content = http.request(danger_url, "GET")
733+
assert response.status == 200
734+
req = tests.HttpRequest.from_bytes(content)
735+
assert req.uri == "/?q=%20HTTP/1.1%0D%0Aignore-http:"

0 commit comments

Comments
 (0)