Skip to content

Commit 635e0f3

Browse files
committed
Detect exposed Jupyter servers and vector databases, and an AEM reflected XSS
More read only checks, aimed at the places attackers using AI tools go. An open Jupyter server answers its authenticated API with no token, which means anyone who reaches it can run code and read files on the host: arbitrary code execution. The check confirms it by the contents, kernel specs or session API answering without a login, against a control. A vector database left without authentication, which is how Chroma, Qdrant and Weaviate ship by default, exposes the embeddings that encode the organization's documents, to read and to poison. The check confirms each by a string it returns. Adobe AEM: the ContentFinder suggestions endpoint echoing a parameter back unencoded, which is a reflected cross site scripting sink, confirmed when a harmless marker comes back unencoded. The real world precedent table gained the vector store exposure class, and the AI tool class now covers notebook servers.
1 parent 93897af commit 635e0f3

4 files changed

Lines changed: 98 additions & 1 deletion

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"id": "injection-aem-contentfinder-xss",
3+
"class": "injection",
4+
"safety": "read",
5+
"severity_floor": "medium",
6+
"title": "Adobe AEM reflects a script payload (ContentFinder XSS)",
7+
"summary": "The AEM ContentFinder suggestions endpoint echoes a query parameter into its response without encoding it, so a crafted link runs script in the visitor's session. The check sends a harmless marker and confirms it comes back unencoded.",
8+
"needs": [],
9+
"candidate_paths": [
10+
"/bin/wcm/contentfinder/connector/suggestions.json?query_term=path%3a/&pre=%3Csvg/onload%3Dalert(1)%3E&post=x"
11+
],
12+
"probe": [
13+
{ "request": { "method": "GET", "path": "{route}" } }
14+
],
15+
"confirm": {
16+
"control": "A path that cannot exist, so the site's not found answer is known and a page that reflects the marker is told apart from it.",
17+
"differential": "status == 200 and body_contains(\"<svg/onload=alert(1)>\") and body_differs(control)",
18+
"retest": 2
19+
},
20+
"reproduce": "curl -sk 'https://host/bin/wcm/contentfinder/connector/suggestions.json?query_term=path:/&pre=%3Csvg/onload=alert(1)%3E&post=x' and check the response echoes <svg/onload=alert(1)> unencoded. Unencoded reflection means the parameter is a cross site scripting sink.",
21+
"remediate": "Patch AEM and block the ContentFinder endpoints at the dispatcher if they are not needed. Output encode any reflected value. Follow Adobe's AEM dispatcher security checklist.",
22+
"references": [],
23+
"mappings": { "cwe": "CWE-79", "owasp": "A03:2021" },
24+
"rulesets": ["aem"]
25+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"id": "misconfiguration-jupyter-exposed",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "high",
6+
"title": "Jupyter notebook server is exposed without a token",
7+
"summary": "A Jupyter server is answering its authenticated API without a token or password, so anyone who can reach it can open a notebook, run code and read files on the host. An open Jupyter server is arbitrary code execution on the machine it runs on.",
8+
"needs": [],
9+
"candidate_paths": [
10+
"/api/contents",
11+
"/api/contents/",
12+
"/api/kernelspecs",
13+
"/api/sessions"
14+
],
15+
"probe": [
16+
{ "request": { "method": "GET", "path": "{route}" } }
17+
],
18+
"confirm": {
19+
"control": "A path that cannot exist, so the site's not found answer is known and an open API response is told apart from a soft 404.",
20+
"differential": "status == 200 and (body_contains(\"\\\"kernelspecs\\\"\") or (body_contains(\"\\\"type\\\":\\\"directory\\\"\") and body_contains(\"\\\"writable\\\"\")) or body_contains(\"\\\"execution_state\\\"\")) and body_differs(control)",
21+
"retest": 2
22+
},
23+
"reproduce": "curl -sk 'https://host/api/contents' (or /api/kernelspecs) with no token. A token protected server answers 403; an open one answers 200 with the contents listing (type directory, writable) or the kernel specs. Either means the server runs code for anyone who can reach it.",
24+
"remediate": "Set a strong token or password, bind the server to localhost or a trusted range (c.NotebookApp.ip and c.NotebookApp.token), and firewall it off the internet. Never run a notebook server open to the network.",
25+
"references": [],
26+
"mappings": { "cwe": "CWE-306", "owasp": "A05:2021" },
27+
"rulesets": ["jupyter"]
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"id": "misconfiguration-vector-db-exposed",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "high",
6+
"title": "A vector database is exposed without authentication",
7+
"summary": "A vector database (Chroma, Qdrant or Weaviate) is answering without a login. These stores hold the embeddings that encode the organization's documents, so an outsider who reaches it can read the whole knowledge base and write poisoned vectors that steer the AI's answers. The major vector databases ship with no authentication by default.",
8+
"needs": [],
9+
"candidate_paths": [
10+
"/api/v1/heartbeat",
11+
"/api/v2/heartbeat",
12+
"/collections",
13+
"/v1/meta"
14+
],
15+
"probe": [
16+
{ "request": { "method": "GET", "path": "{route}" } }
17+
],
18+
"confirm": {
19+
"control": "A path that cannot exist, so the site's not found answer is known and the database response is told apart from a soft 404.",
20+
"differential": "status == 200 and (body_contains(\"nanosecond heartbeat\") or (body_contains(\"\\\"status\\\":\\\"ok\\\"\") and body_contains(\"\\\"collections\\\"\")) or (body_contains(\"\\\"hostname\\\"\") and body_contains(\"\\\"modules\\\"\"))) and body_differs(control)",
21+
"retest": 2
22+
},
23+
"reproduce": "curl -sk 'https://host/api/v1/heartbeat' (Chroma answers with a nanosecond heartbeat), 'https://host/collections' (Qdrant answers status ok with its collections), or 'https://host/v1/meta' (Weaviate answers with its hostname and modules). If any answers without a login, the vector store is open to anyone.",
24+
"remediate": "Turn on authentication (an API key for Qdrant, Chroma's token auth, Weaviate's API key or OIDC), bind the database to localhost or a trusted range, and firewall its port off the internet. Treat the embedded corpus as readable and poisonable until it is closed.",
25+
"references": [],
26+
"mappings": { "cwe": "CWE-306", "owasp": "A05:2021" },
27+
"rulesets": ["vector-db"]
28+
}

‎app/core/dast_precedent.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,26 @@
173173
"QueryBuilder and default GET servlet endpoints, and Adobe has issued "
174174
"critical AEM patches.",
175175
},
176+
{
177+
"key": "vector-db-exposure",
178+
"match": re.compile(r"vector (data)?base|chromadb|\bchroma\b|qdrant|weaviate|"
179+
r"embeddings|nanosecond heartbeat", re.I),
180+
"ref": "CWE-306; OWASP A05:2021",
181+
"abuse": "An attacker reads the stored embeddings, which encode the "
182+
"organization's documents, exfiltrating the knowledge base, and "
183+
"writes poisoned vectors so the AI built on it returns attacker "
184+
"chosen content.",
185+
"precedent": "Unauthenticated Chroma, Qdrant and Weaviate instances are "
186+
"routinely found exposed on the internet; the major vector "
187+
"stores default to no authentication, so the embedded corpus "
188+
"and the retrieval answers built on it are open to read and "
189+
"to poison.",
190+
},
176191
{
177192
"key": "ai-tool-exposure",
178193
"match": re.compile(r"open (self-)?registration|registrationenabled|librechat|"
179-
r"ollama|open webui|llmjack|exposed ai|model api key", re.I),
194+
r"ollama|open webui|llmjack|exposed ai|model api key|"
195+
r"jupyter|notebook server", re.I),
180196
"ref": "CWE-1188; LLMjacking (Sysdig, 2024)",
181197
"abuse": "An attacker reaches the exposed AI tool, creates an account or uses an "
182198
"open endpoint, and runs their own work on the organization's paid model "

0 commit comments

Comments
 (0)