OpenRemote has Authenticated SQL Injection via Datapoint Crosstab Export
The datapoint export API builds a PostgreSQL crosstab export query by concatenating asset display names into raw SQL. An authenticated user who can create or rename an asset and then request a crosstab datapoint export can inject SQL through the asset name. The injected query output is streamed back to the caller inside the normal ZIP/CSV export response.
This creates a practical database exfiltration primitive through the application API. In a multi-tenant deployment, this can expose data outside the attacker's tenant if the application database role can read shared manager tables.
COPY (SELECT ... FROM crosstab(...)) TO STDOUT statement.Impact is high. A remote authenticated attacker with asset read/write capabilities can:
SELECT results in the exported CSV contained in the ZIP response.The demonstrated impact is database data exfiltration. The proof of concept safely retrieved database execution context and an aggregate table count. A real attacker could adapt the injected SELECT to read other database tables accessible to the application database role.
This is especially sensitive in multi-tenant deployments because application tables commonly contain data for multiple realms/tenants in the same database.
The attacker needs:
No direct database access is required. No server filesystem access is required. No token forgery is required.
The export implementation derives a crosstab header from the asset name and attribute name. It then embeds that header into two SQL contexts:
"<asset name> : <attribute name>" text
crosstab(...), wrapped in a fixed dollar-quoted delimiter:$cat$ SELECT header FROM (VALUES ('<asset name> : <attribute name>')) AS t(header) $cat$
The current escaping is incomplete:
As a result, an attacker-controlled asset name can break out of the intended SQL grammar boundary and append SQL to the generated COPY ... TO STDOUT query. Because the backend streams COPY output into the export response, injected query rows are returned to the attacker as CSV.
A safe proof query demonstrated exfiltration of:
current_usercurrent_database()count(*) from an application tableThe returned CSV contained a row equivalent to:
<timestamp>,<database_user>:<database_name>:<table_count>
The root cause is manual SQL string construction using user-controlled display data as SQL syntax.
The asset name is treated as presentation data in the application model, but later reused as part of executable SQL:
These contexts require different escaping rules. Applying partial string escaping is error-prone and currently misses exploitable grammar boundaries.
Avoid embedding user-controlled asset names directly into executable SQL.
Recommended options:
Do not use asset names as SQL identifiers.
c1, c2, c3.If dynamic identifiers are unavoidable, quote them using a database-aware identifier quoting function.
" characters.Avoid fixed dollar-quote delimiters around attacker-influenced content.
Add a strict validation boundary for display names if the product can tolerate it.
Add regression tests for:
".Build the crosstab with internal, non-user-controlled category keys and column names. For example:
col_0, col_1.col_0 to the display label.This removes asset display names from SQL syntax entirely.
Suggested severity: High
Rationale:
왜 이 VPI인가 (설명가능 · 실험적)
VPI 산정 기준
| 영향도(기본값(정보 없음)) | 55.00 |
| 악용 신호(추가 악용신호 없음) | ×1.00 |
| VPI | 55.00 |
VPI 공식 vpi-v1 기준