GHSA-v8vm-cqh8-q87q NocoBase: Sensitive Data Exposure via SQL Blacklist Bypass
Security Vulnerability Report: Sensitive Data Exposure via SQL Blacklist BypassSummaryThe `checkSQL()` function in `plugin-collection-sql` implements a **keyword-based blacklist** to prevent dangerous SQL queries from being executed through the SQL Collection feature. However, the blacklist is **incomplete**: it only checks for a subset of dangerous PostgreSQL system functions and **does not restrict access to sensitive system catalog tables** such as `pg_shadow`, `pg_roles`, or `pg_stat_activity`.An authenticated user with the `admin` role can exploit this to **dump PostgreSQL password hashes** (`pg_shadow`), **read all NocoBase user credentials** (hashed passwords from the `users` table), and **enumerate the full database schema** — all data that admin users should never be able to access through the application interface.Affected Component**File**: `packages/plugins/@nocobase/plugin-collection-sql/src/server/utils.ts`The `execute` action in `sql.ts` passes user-supplied SQL directly through this insufficient check:Root CauseThe blacklist approach is **fundamentally incomplete**. It attempts to enumerate every dangerous construct but misses entire categories:**PostgreSQL system catalog tables** — `pg_shadow`, `pg_authid`, `pg_roles`, `pg_stat_activity` are not restricted**Application-level sensitive tables** — `users` (containing hashed passwords) can be queried directly**`information_schema`** — full schema enumeration is possible**Schema-qualified variants** — even some blocked functions could be bypassed via `pg_catalog.` prefix (e.g. `pg_catalog.pg_read_file` may bypass checks in older versions)The correct approach is an **allowlist** (whitelist) of permitted tables/schemas, not a blacklist of forbidden keywords.Steps to Reproduce**Prerequisites**: A user account with the `admin` role (has the `pm.data-source-manager.collection-sql` ACL snippet).**Step 1**: Authenticate and obtain a token:**Step 2**: Dump PostgreSQL password hashes from `pg_shadow`:**Response**:**Step 3**: Dump NocoBase user credentials:**Response** (verified):Additional Verified Bypass Queries| Query | Blocked? | Data Exposed |
|-------|----------|--------------|
| `SELECT usename, passwd FROM pg_shadow` |**Not blocked** | PostgreSQL DB user password hashes |
| `SELECT id, email, password FROM users` |**Not blocked** | All NocoBase user credential hashes |
| `SELECT table_name FROM information_schema.tables` |**Not blocked** | Full database schema enumeration |
| `SELECT rolname, rolsuper FROM pg_roles` |**Not blocked** | All DB roles and superuser flags |
| `SELECT pid, query FROM pg_stat_activity` |**Not blocked** | Live SQL queries from all sessions |
| `SELECT pg_read_file('/etc/passwd')` |Blocked | — |
| `SELECT current_setting('app.key')` |Blocked | — |Why Admin-Required Still MattersThis vulnerability is rated **High** despite requiring admin-level authentication. The reasoning:1. Security Boundary Violation (Scope Changed → S:C)The `admin` role in NocoBase is an **application-level** role — it manages workflows, collections, and UI. It is **not** a database administrator. Accessing `pg_shadow` is a **PostgreSQL system-level** privilege that admins should never have. The `checkSQL()` function was explicitly created to enforce this boundary; bypassing it breaks the intended security model.2. Data That Admin Cannot Access Through Normal UIEven with admin privileges, NocoBase's UI and API **do not expose**:`pg_shadow` (PostgreSQL internal password store)Raw `users.password` hashes via standard API responsesFull `information_schema` enumerationVUL-2 grants access to all of the above — data the application explicitly chose not to expose.3. Enables Lateral MovementThe `pg_shadow` SCRAM-SHA-256 hashes can be subjected to offline dictionary attacks. If cracked, the attacker gains **direct PostgreSQL access** with the application's DB credentials — bypassing the NocoBase application layer entirely. This enables reading **all data** in the database (not just what NocoBase exposes), modifying records directly, and accessing data from other schemas.4. Enables Full Attack Chain When Combined with Other VulnerabilitiesImpacted API Endpoint**Authentication**: Required (`admin` role)**ACL Snippet** registered in `plugin.ts`:The `admin` role includes this snippet by default.Recommended FixesFix 1 (Immediate): Extend the blacklist with system catalog tablesFix 2 (Recommended): Replace blacklist with schema allowlistInstead of blocking dangerous keywords, only allow queries against **user-defined collection tables**:Fix 3 (Defense-in-depth): Use a read-only, restricted DB userThe application's DB connection should use a PostgreSQL user that:Does **not** have `SELECT` privilege on `pg_shadow` or `pg_authid`Only has access to the application's own schema (`nocobase` schema)This ensures that even if the blacklist is bypassed, the DB user cannot access system catalogs.Environment| Field | Value |
|-------|-------|
| NocoBase version | 2.0.59-full |
| Database | PostgreSQL 16.14 |
| Deployment | Docker (`nocobase/nocobase:2.0.59-full`) |
| Vulnerable file | `plugin-collection-sql/src/server/utils.ts` — `checkSQL()` |
| Vulnerable endpoint | `POST /api/sqlCollection:execute` |
| Auth required | Admin role (`pm.data-source-manager.collection-sql` snippet) |Timeline| Date | Event |
|------|-------|
| 2026-05-29 | Vulnerability discovered via whitebox source code audit of `utils.ts` |
| 2026-05-29 | Exploit verified on live Docker instance — `pg_shadow` and `users.password` dumped |
| 2026-05-29 | Report submitted to maintainers |Script and video PoC:[poc_vul2_sqli.py](https://github.com/user-attachments/files/28380402/poc_vul2_sqli.py)https://github.com/user-attachments/assets/6e4e7a3d-e005-4ff8-ab9a-e44ae1365732
CVSS v3.1 score: 6.8