SRQL injection via unescaped string concatenation in GenericFilterBuilder.BuildFilters #688
Labels
No labels
1week
2weeks
Failed compliance check
IP cameras
NATS
Possible security concern
Review effort 1/5
Review effort 2/5
Review effort 3/5
Review effort 4/5
Review effort 5/5
UI
aardvark
accessibility
amd64
api
arm64
auth
back-end
bgp
blog
bug
build
checkers
ci-cd
cleanup
cnpg
codex
core
dependencies
device-management
documentation
duplicate
dusk
ebpf
enhancement
eta 1d
eta 1hr
eta 3d
eta 3hr
feature
fieldsurvey
github_actions
go
good first issue
help wanted
invalid
javascript
k8s
log-collector
mapper
mtr
needs-triage
netflow
network-sweep
observability
oracle
otel
plug-in
proton
python
question
reddit
redhat
research
rperf
rperf-checker
rust
sdk
security
serviceradar-agent
serviceradar-agent-gateway
serviceradar-web
serviceradar-web-ng
siem
snmp
sysmon
topology
ubiquiti
wasm
wontfix
zen-engine
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
carverauto/serviceradar#688
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Imported from GitHub.
Original GitHub issue: #2150
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/issues/2150
Original created: 2025-12-16T05:18:14Z
Summary
GenericFilterBuilder.BuildFiltersfunction inpkg/mcp/builder.goconstructs SRQL query filter strings by directly concatenating user input without escaping special characters, enabling SRQL injection attacks.fmt.Sprintf("%s = '%s'", sqlField, strValue)without escaping single quotes or other SQL metacharacters, whereas they should be properly escaped or use parameterized queries.Code with bug
This vulnerable pattern appears throughout the MCP package in multiple locations:
pkg/mcp/builder.go:169- GenericFilterBuilder (primary vulnerability)pkg/mcp/builder.go:68- BuildTimeRangeFilter with time.Format (less critical but still vulnerable)pkg/mcp/tools_devices.go:84- Device ID filter constructionpkg/mcp/tools_events.go:101- Poller ID filter constructionpkg/mcp/tools_logs.go:105- Poller ID filter constructionpkg/mcp/tools_sweeps.go:98- Poller ID filter constructionpkg/mcp/tools_sweeps.go:155- Poller ID filter constructionpkg/mcp/server.go:809- Device ID filter constructionEvidence
Example
Consider a legitimate use case where an LLM queries events from a specific poller:
Benign request:
This produces the filter:
event_type = 'network_alert'Attack scenario:
An attacker crafts a malicious request:
This produces the injected filter:
event_type = 'test' OR '1'='1'The resulting SRQL query becomes:
The
'1'='1'condition is always true, causing the query to bypass the intended filter and return all events regardless of type.More severe attack with comment injection:
This produces:
poller_id = 'test' --'The
--comments out the rest of the query, potentially removing security checks or LIMIT clauses.Failing test
Test script
Test output
The test passes, demonstrating that the code successfully produces the vulnerable query strings. This is proof that the injection vulnerability exists.
Inconsistency with own spec
Reference spec
From
pkg/mcp/README.md:Current code
Contradiction
The README explicitly claims "SRQL injection protection through parameterized queries", but the implementation uses string concatenation with
fmt.Sprintfto build queries. There are no parameterized queries being used anywhere in thepkg/mcp/builder.gofile or related tool files. This is a direct contradiction between the documented security guarantee and the actual implementation.Full context
The MCP server is a component that exposes ServiceRadar's data through the Model Context Protocol, enabling LLMs and AI agents to query the system. It provides tools like
events.getEvents,devices.getDevices,logs.getLogs, andsweeps.getResultsthat accept JSON parameters and convert them into SRQL (ServiceRadar Query Language) queries.The vulnerable code path works as follows:
Entry Point: An MCP client (typically an LLM) calls a tool like
events.getEventswith JSON arguments containing user-controlled parameters likeevent_type,poller_id, ornetwork.Builder Invocation: The tool handler (e.g.,
registerGetEventsToolinpkg/mcp/tools_events.go) creates aGenericFilterBuilderwith field mappings and callsBuildGenericFilterToolWithBuilder.Query Construction: The
BuildGenericFilterToolfunction calls the builder'sBuildFiltersmethod, which usesfmt.Sprintfto directly embed user input into SRQL filter strings without escaping.Query Execution: The constructed filter is combined with other filters using
CombineFiltersand passed toBuildFilteredQuery, which produces a complete SRQL query string likeSHOW events WHERE (event_type = 'test' OR '1'='1') ORDER BY _tp_time DESC.Database Execution: The query is sent to
m.executeSRQLQuery, which callsm.queryExecutor.ExecuteSRQLQuery. The query executor (the database service inpkg/db) receives the already-constructed malicious query string and executes it.The vulnerability affects all entity-specific MCP tools that use the
GenericFilterBuilder:events.getEvents- Filters events by type, severity, and poller IDsweeps.getResults- Filters network sweep results by poller ID and network rangeSince the MCP server is designed to be called by LLMs processing user queries, an attacker could craft prompts that cause the LLM to generate malicious MCP tool calls. Even with authentication, a legitimate authenticated user or compromised LLM could exploit this vulnerability to access unauthorized data or perform unauthorized queries.
External documentation
SRQL Language Reference
From
docs/docs/srql-language-reference.md:The SRQL documentation shows that the modern SRQL syntax uses
field:valueformat, but the MCP builder constructs old-style SQL WHERE clauses likefield = 'value'. This suggests the builder may be using a deprecated or parallel query construction method. Regardless, both formats are vulnerable to injection if values aren't properly escaped.Why has this bug gone undetected?
This SRQL injection vulnerability has likely gone undetected for several reasons:
Recent Feature: The MCP server appears to be a relatively new addition to the codebase (commit
6049244fwith message "new mcp server -- untested"), meaning it hasn't been through extensive security review or penetration testing yet.Trusted Input Assumption: The MCP server is designed to be called by LLMs, which might be considered "trusted" components in the architecture. Developers may have assumed that LLM-generated queries would not contain malicious payloads, overlooking the fact that LLMs process untrusted user input.
Authentication as Primary Defense: The system has authentication (
authService) which may have given developers a false sense of security. Authentication prevents unauthorized access but doesn't prevent authenticated users (or compromised systems) from exploiting injection vulnerabilities.Complex Attack Chain: Exploiting this vulnerability requires:
This complexity makes it less likely to be discovered through casual testing or normal operation.
No Input Validation: The code performs type checking (
if strValue, ok := value.(string)) but no content validation or sanitization. The vulnerable patternfmt.Sprintf("%s = '%s'", field, value)looks innocuous at first glance and might pass code review without careful security analysis.Testing Gap: The existing test suite (
pkg/mcp/server_test.go) only tests basic functionality with benign inputs. There are no security-focused tests that attempt injection attacks or validate input sanitization.Documentation Misleading: The README claims "SRQL injection protection through parameterized queries" which may have given both developers and security reviewers false confidence that the issue was already addressed.
Query Language Abstraction: Since SRQL is a custom query language (not standard SQL), developers might not have immediately recognized the same SQL injection patterns that would be obvious in traditional SQL contexts.
Recommended fix
The recommended fix is to properly escape single quotes in user input before embedding them in SRQL query strings. At minimum, implement SQL-style quote escaping:
However, a more comprehensive solution would be:
Use Parameterized Queries: If the SRQL execution engine supports parameterized queries (placeholders for values), refactor the query construction to use them instead of string concatenation.
Input Validation: Add validation to reject values containing suspicious patterns or restrict them to expected character sets (e.g., alphanumeric + specific allowed characters).
Apply Escaping Throughout: The same escaping function should be applied in all locations where user input is embedded in SRQL queries (
pkg/mcp/tools_devices.go,tools_events.go,tools_logs.go,tools_sweeps.go, etc.).Update Documentation: Correct the security claims in
pkg/mcp/README.mdto accurately reflect the implemented protections.Security Testing: Add comprehensive security tests that attempt various injection patterns to ensure the fix is effective.
Related bugs
All occurrences of the vulnerable pattern throughout the
pkg/mcppackage should be fixed together:pkg/mcp/builder.go:68-72- BuildTimeRangeFilter (time format injection, lower severity)pkg/mcp/tools_devices.go:84- Device ID filterpkg/mcp/tools_events.go:101- Poller ID filter in alertspkg/mcp/tools_logs.go:105- Poller ID filter in recent logspkg/mcp/tools_sweeps.go:98- Poller ID filter in recent sweepspkg/mcp/tools_sweeps.go:155- Poller ID filter in sweep summarypkg/mcp/server.go:809- Device ID filter in query devicespkg/mcp/query_utils.go- Multiple instances of similar vulnerable patternsAll of these should be audited and fixed as part of a comprehensive remediation effort.
Imported GitHub comment.
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/issues/2150#issuecomment-3663454878
Original created: 2025-12-17T03:20:20Z
we are replacing the golang based MCP system with elixir/hermes_mcp, closing this as will not be implemented.