Skip to content

Security: SQL injection and multi-statement execution (3 critical/high findings) #37

Description

@piiiico

Note: This is responsible disclosure. A private security advisory (GHSA-fvvm-2jhg-5jgj) was filed 45 days ago. No maintainer response has been received. The repository appears unmaintained (last commit: 2025-08-27). These findings are being disclosed publicly to protect users.

Three verified vulnerabilities in mcp-database-server

All findings verified from source code on the main branch (2026-04-10).


Finding A (CRITICAL): SQL Injection via ReadResource URI — PostgreSQL and SQLite adapters

Files: src/handlers/resourceHandlers.ts, src/db/postgresql-adapter.ts, src/db/sqlite-adapter.ts

handleReadResource() extracts tableName from the URI path with no sanitization:

const tableName = pathComponents.pop();   // attacker-controlled, unvalidated
const query = getDescribeTableQuery(tableName!);

PostgreSQL interpolates directly:

WHERE c.table_name = '${tableName}'    // unsanitized

SQLite — no quoting at all:

return `PRAGMA table_info(${tableName})`;

PoC (PostgreSQL): URI db:///database/users' UNION SELECT usename,passwd,NULL,NULL,NULL FROM pg_shadow--/schema → returns password hashes.

PoC (SQLite): URI sqlite:///app.db/x);ATTACH DATABASE '/etc/passwd' AS s--/schema

Fix: Validate tableName against existing table list before constructing the query. Use parameterized queries.


Finding B (CRITICAL): MySQL multipleStatements: true enables stacked-query injection

File: src/db/mysql-adapter.ts

The constructor unconditionally sets multipleStatements: true (default in mysql2 is false). The exec() method uses connection.query() (not execute()), which executes all stacked statements. Tool validation is prefix-only:

if (!query.trim().toLowerCase().startsWith("create table")) {
  throw new Error("Only CREATE TABLE statements are allowed");
}
await dbExec(query);   // entire query string passes through

PoC: create_table with "CREATE TABLE x(id int); DROP TABLE users;" — prefix check passes, all statements execute.

Fix: Remove multipleStatements: true. It defaults to false in mysql2.


Finding C (HIGH): SQLite ATTACH DATABASE / arbitrary file read via multi-statement dbExec()

createTable and alterTable apply prefix-only checks then call dbExec() with the full input. dbExec routes to sqlite3.Database.exec(), a multi-statement executor by design.

PoC: create_table with "CREATE TABLE x(id int); ATTACH DATABASE '/etc/passwd' AS shadow;" → SQLite opens /etc/passwd. Follow-up read_query reads the file. Can escalate to RCE via load_extension.

Fix: Use db.run() (single-statement) for DDL tools. Blocklist ATTACH, DETACH, .load patterns.


Summary

Finding Severity Impact
A — URI SQL injection (PostgreSQL + SQLite) Critical Full DB read, credential exfil
B — MySQL multipleStatements Critical Arbitrary stacked SQL
C — SQLite ATTACH via dbExec High File-system read, potential RCE

All published versions are affected. No patched version exists.

If you are using this package, consider:

  1. Running it in an isolated environment with no access to sensitive data
  2. Restricting which clients can connect to the MCP server
  3. Migrating to an actively maintained alternative

Private advisory: GHSA-fvvm-2jhg-5jgj
Reporter: @piiiico

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions