# YesWiki: SQL Injection possible through public Bazar entry-listing APIs via numeric `query`/`queries` filters

Unauthenticated SQL injection in YesWiki Bazar numeric query filters lets attackers use public entry-listing APIs as a boolean oracle to infer database contents.

- Payout: Research
- Severity: high
- Program: YesWiki
- Found by: sondt99
- Disclosed: 2026-07-09
- CVE: CVE-2026-52770
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-qg78-vmvc-fhjw
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-qg78-vmvc-fhjw
- Tags: area:web domain:web impact:data-leak impact:pii-leak kind:research platform:ghsa sev:high tech:php technique:blind technique:source-review vuln:access-control vuln:sqli

**Target:** Bazar entry-listing API (GET query/queries filters)
**Root cause:** Numeric Bazar field values are escaped with mysqli_real_escape_string but concatenated into SQL without quotes or numeric validation, so SQL syntax stays active inside the numeric comparison.
**Steps:**
1. Identify a Bazar form with a numeric field (number/range/lat-lng), e.g. bf_age.
2. Send GET to a public route like `/api/entries/bazarlist` or `/api/forms/{formId}/entries/{output}/{selectedEntries}` with `query=bf_age>100` as control (no rows).
3. Inject `query=bf_age>100 OR (SELECT COUNT(*) FROM yeswiki_users)>0`; observe entries returned.
4. Swap in a false subquery `... WHERE 0)>0`; observe no rows, confirming boolean oracle.
5. Iterate subquery predicates to infer data accessible to the DB user.
**How found:** Code review of ApiController query handling into SearchManager::buildQueriesConditions, noticing numeric branch escapes but does not quote the value, leaving SQL syntax live.
**Exploitation:** Unauthenticated attacker hits public Bazar API endpoints with crafted `query`/`queries` GET params; injected boolean subqueries are evaluated by the DB before ACL/Guard post-processing, letting the attacker binary-search database contents (user data, hashes, private metadata) via row presence.
**Insight:** When a filter value is escaped but concatenated unquoted into a numeric CAST comparison, escaping is irrelevant—look for `_type_ == 'number'` branches and test boolean subqueries as an oracle rather than quote-breaking payloads.

## Proof of concept (copied exactly from the original report)

**Injected boolean subquery payload**

```
100 OR (SELECT COUNT(*) FROM yeswiki_users)>0
```

**Vulnerable numeric SQL sink code**

```
if ($vDescriptor['_type_'] == 'number') {
    if (isset($vValue) && trim($vValue) !== '') {
        $vValueConditions[] = 'CAST(' . mysqli_real_escape_string($this->wiki->dblink, $this->renameJSONPathVariable($vFieldName)) . ' AS DOUBLE) ' . $vComparisonOperator . ' ' . mysqli_real_escape_string($this->wiki->dblink, $vValue);
```

