未授权远程代码执行:Groovy 脚本注入
漏洞概述
| 项目 |
内容 |
| 漏洞类型 |
Pre-Auth Remote Code Execution (RCE) |
| 危害等级 |
🔴 严重 (Critical) |
| 影响版本 |
PowerJob v5.1.0 ~ v5.1.2 (全部 v5.1.x 版本,含 latest) |
| 前置条件 |
无需任何认证,默认配置即可利用 |
| 攻击入口 |
/openApi/addWorkflowNode + /openApi/saveWorkflow + /openApi/runWorkflow |
漏洞根因
PowerJob 存在两个独立的安全缺陷,组合利用可实现未授权远程代码执行:
缺陷 1: OpenAPI 默认无鉴权
OpenApiInterceptor.java 中 enableOpenApiAuth 默认为 false:
@Value("${oms.auth.openapi.enable:false}")
private boolean enableOpenApiAuth;
导致 /openApi/* 下所有端点(包括 addWorkflowNode、saveWorkflow、runWorkflow)默认完全不需要任何认证即可访问。
相关issue:#1128
缺陷 2: Groovy 脚本引擎无沙箱(核心漏洞)
GroovyEvaluator.java 使用 ScriptEngine.eval() 直接执行传入的表达式,无任何沙箱限制:
public class GroovyEvaluator implements Evaluator {
private static final ScriptEngine ENGINE = new ScriptEngineManager().getEngineByName("groovy");
@Override
public Object evaluate(String expression, Object input) {
Bindings bindings = ENGINE.createBindings();
bindings.put("context", input);
return ENGINE.eval(expression, bindings); // ← 直接执行,无沙箱
}
}
DecisionNodeHandler.java 中 nodeParams 来自用户输入,被直接传给 GroovyEvaluator:
public class DecisionNodeHandler implements ControlNodeHandler {
private final GroovyEvaluator groovyEvaluator = new GroovyEvaluator();
@Override
public void handle(PEWorkflowDAG.Node node, ...) {
String script = node.getNodeParams(); // ← 用户完全可控,无过滤
result = groovyEvaluator.evaluate(script, wfContext); // ← 直接执行
}
}
nodeParams 被直接拼接到 Groovy 脚本引擎中:
用户输入 (nodeParams)
→ POST /openApi/addWorkflowNode
→ SaveWorkflowNodeRequest.nodeParams
→ 存入数据库 WorkflowNodeInfoDO.nodeParams
→ POST /openApi/runWorkflow 触发工作流
→ DecisionNodeHandler.handle()
→ node.getNodeParams()
→ groovyEvaluator.evaluate(script, wfContext)
→ ScriptEngine.eval(expression) // 在 Server JVM 中执行
→ Runtime.exec() // 任意命令执行
全链路无任何过滤、校验或沙箱隔离。
攻击链
攻击者 (无需认证)
│
├── Step 1: POST /openApi/fetchAllJob → 探测有效 appId(默认 appId=1)
├── Step 2: POST /openApi/saveJob → 创建虚拟 Job(用作 DAG 下游分支)
├── Step 3: POST /openApi/addWorkflowNode → 创建 3 个节点:
│ ├─ DECISION 节点 (type=2): nodeParams = 恶意 Groovy 脚本
│ ├─ JOB 节点 (true 分支)
│ └─ JOB 节点 (false 分支)
├── Step 4: POST /openApi/saveWorkflow → 创建 DAG
│ DECISION ──true──→ JOB1
│ ──false─→ JOB2
└── Step 5: POST /openApi/runWorkflow → 触发工作流
│
▼ Server 处理 DECISION 节点
DecisionNodeHandler.handle()
│ groovyEvaluator.evaluate("Runtime.getRuntime().exec(...)")
▼ ScriptEngine.eval()
RCE ✅ (Server JVM, root 权限)
本地复现
复现截图
修复建议
- 将
oms.auth.openapi.enable 默认值由 false 改为 true(涉及 application.properties 和OpenApiInterceptor.java 中的 @Value 注解默认值)。
- 为
GroovyEvaluator 增加沙箱限制(使用 SecureASTCustomizer 禁止调用 Runtime、ProcessBuilder、System 等危险类)
- 对
nodeParams 进行危险关键字校验(拒绝包含 Runtime、exec、ProcessBuilder 等的脚本)
未授权远程代码执行:Groovy 脚本注入
漏洞概述
/openApi/addWorkflowNode+/openApi/saveWorkflow+/openApi/runWorkflow漏洞根因
PowerJob 存在两个独立的安全缺陷,组合利用可实现未授权远程代码执行:
缺陷 1: OpenAPI 默认无鉴权
OpenApiInterceptor.java中enableOpenApiAuth默认为false:导致
/openApi/*下所有端点(包括addWorkflowNode、saveWorkflow、runWorkflow)默认完全不需要任何认证即可访问。相关issue:#1128
缺陷 2: Groovy 脚本引擎无沙箱(核心漏洞)
GroovyEvaluator.java使用ScriptEngine.eval()直接执行传入的表达式,无任何沙箱限制:DecisionNodeHandler.java中nodeParams来自用户输入,被直接传给GroovyEvaluator:nodeParams被直接拼接到 Groovy 脚本引擎中:全链路无任何过滤、校验或沙箱隔离。
攻击链
本地复现
复现截图
修复建议
oms.auth.openapi.enable默认值由false改为true(涉及application.properties和OpenApiInterceptor.java中的@Value注解默认值)。GroovyEvaluator增加沙箱限制(使用SecureASTCustomizer禁止调用Runtime、ProcessBuilder、System等危险类)nodeParams进行危险关键字校验(拒绝包含Runtime、exec、ProcessBuilder等的脚本)