山石网科
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

前言
最近复现分析了CVE-2018-16621、CVE-2020-10204,以此文章做篇记录,主要是关于Nexus漏洞的一个调试分析,学习Bean Validation漏洞挖掘的思路和技巧。
Bean Validation
Bean Validation 为 JavaBean 验证定义了相应的元数据模型和 API,是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。在java应用程序中,通过使用 Bean Validation 或是你自己定义的 constraint,例如@NotNull
,@Max
,@ZipCode
, 就可以确保数据模型(JavaBean)的正确性。
而Hibernate Validator 是 Bean Validation 的参考实现,Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。参考http://www.hibernate.org/subprojects/validator.html
下面列一些常见的constraint
@NotNull
:被注释的元素必须不为null
。@NotEmpty
:被注释的元素不能为空。@NotBlank
:表示字符串字段不能是空字符串(即它必须至少有一个字符)。@Min
和@Max
:表示数值字段仅在其值高于或低于某个值时才有效。@Pattern
:说一个字符串字段只有在匹配某个正则表达式时才有效。@Email
:表示字符串字段必须是有效的电子邮件地址。
举一个类示例:
class Customer {
private String email;
@NotBlank
private String name;
@ListNotHasNull //自定义注解
private List<String> info;
// ...
}
为了验证一个对象是否有效,Bean Validation会将它传递给一个Validator来检查是否满足要求,不满足则抛出异常,例如:
Set<ConstraintViolation<Input>> violations = validator.validate(customer);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
但在spring中我们可以直接使用@Validated和
@Valid注解进行验证,@Validated是一个类级别的注解,用它来告诉Spring 验证传递给被注解类的方法的参数,
而@Valid
注解是放在方法参数和字段上,告诉 Spring 我们想要验证的对象,例如我们要对Customer对象进行验证,结合使用示例如下:
@Service
@Validated
class ValidatingService{
void validateInput(@Valid Customer customer){
// do something
}
}
而Validator可以使用内置的也可以使用自定义的验证器对Bean进行验证,例如上面举例的Customer类中的自定义注解@ListNotHasNull中需要指定由哪个类进行验证
@Target({ FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = ListNotHasNullValidatorImpl.class)
@Documented
public @interface ListNotHasNull {
String message() default "";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
@Constraint指向接口实现的注解ConstraintValidator,像这里指定了约束条件ListNotHasNullValidatorImpl类验证器进行验证,用于判断List集合中是否含有null元素,值得的注意的是验证器需要继承自ConstraintValidator
接口,第一个参数是自定义的注解,第二个参数是校验的数据类型
public class ListNotHasNullValidatorImpl implements ConstraintValidator<ListNotHasNull, List> {
private int value;
@Override
public void initialize(ListNotHasNull constraintAnnotation) {
//传入value 值,可以在校验中使用
this.value = constraintAnnotation.value();
}
public boolean isValid(List list, ConstraintValidatorContext context) {
for (Object object : list) {
if (object == null) {
//如果List集合中含有Null元素,校验失败
return false;
}
}
return true;
}
}
了解了Bean Validation的基本使用之后我们就可以开始Nexus Repository Manager漏洞的调试分析了。
CVE-2018-16621分析
环境搭建
首先是漏洞环境的搭建,该漏洞的影响版本范围为:Nexus Repository Manager OSS/Pro 3.x - 3.13
选择Nexus Repository Manager OSS 3.13为测试,先下载对应版本源码:https://github.com/sonatype/nexus-public.git
然后拉取对应版本的docker镜像进行搭建:
docker pull sonatype/nexus3:3.13.0
运行容器,因为选择以远程调试的方式,所以在运行容器时要开启JDWP调试端口映射:
docker run -d -p 8081:8081 -p 8000:8000 --name nexus -e INSTALL4J_ADD_VM_PARAMS="-Xms2g -Xmx2g -XX:MaxDirectMemorySize=3g -Djava.util.prefs.userRoot=${NEXUS_DATA}/javaprefs -Dstorage.diskCache.diskFreeSpaceLimit=1024 -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000" sonatype/nexus3:3.13.0
参数介绍:
-p 选项指定端口映射,这里8000端口为映射远程调试端口
-e 选项指定容器环境变量,INSTALL4J_ADD_VM_PARAMS
为动态调试参数
开启容器后,浏览器访问映射端口8081即可,默认密码为admin/admin123
然后导入项目源码到Idea,配置远程调试:
复现分析
参考网上给出的poc,先登录账号admin,获取管理员session,然后发送如下数据包:
POST /service/extdirect HTTP/1.1
Host: 192.168.52.128:8081
Content-Length: 336
X-Requested-With: XMLHttpRequest
X-Nexus-UI: true
Content-Type: application/json
Accept: */*
Origin: http://192.168.52.128:8081
Referer: http://192.168.52.128:8081/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: Hm_lvt_866c9be12d4a814454792b1fd0fed295=1641052730; Hm_lvt_df6f78cfc7b28956736ab98287309c75=1641052807; NXSESSIONID=3c62bb00-aef5-4390-8140-e211332f8eac
Connection: close
{
"action": "coreui_User",
"method": "create",
"data": [{
"userId": "admin",
"version": "2",
"firstName": "admin",
"lastName": "User",
"email": "admin@example.org",
"status": "active",
"roles": ["exp|${111*2}|"]
}],
"type": "rpc",
"tid": 11
}
可以从返回包中看到表达式成功执行。
那么一开始怎么入手呢?可以考虑到javax.servlet.http.HttpServlet#service方法先下断点跟
跟进service方法后先是判断了POST请求后,继续跟进到HttpServlet的子类DirectJNgineServlet的doPost方法
前面都是一些编码处理,然后调用了processRequest方法进行处理请求,跟进
接着根据Content-Type进入case JSON分支,调用了RequestRouter类的processJsonRequest方法
在JsonRequestProcessor#process方法中先是调用了getIndividualJsonRequests方法解析了请求包中的json数据,得到action为coreui_User,method为create,接着调用processIndividualRequestsInThisThread方法
通过获取到的action和method作为参数传入dispatchStandardMethod方法,开始进行调度
后面也可以可以直接找到对应的action和method,在UserComponent#create方法断点跟进,原因是UserComponent类通过@DirectAction注解的方式注入了action,也就是我们请求的coreui_User action,也通过@DirectMethod注解注入了对应的methond
同时也可以看到使用了@Valid注解对UserXO进行了验证,我们可以跟进UserXO类查看分别使用了哪些Validator,如下图所示,另外我们从poc中的漏洞位置可以判断是跟其中的roles属性有关,于是重点关注roles
这里roles属性标注了自定义的@RolesExist注解,跟进查看
可以发现最终的验证处理类是RolesExistValidator类,于是就可以直接在RolesExistValidator的isValid方法处进行断点,并直接
那RolesExistValidator是在调用栈中何时进行获取的呢?
观察调用栈发现是在ConstraintTree#validateConstraints方法中,可以看到依次获取UserXO中使用到的各种Validator,其中就包含了RolesExistValidator
回到RolesExistValidator#isValid方法中
因为验证不通过,捕获异常后missing不为空,写入自定义的错误信息,也就是运行到这个漏洞的关键代码:
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Missing roles: " + missing).addConstraintViolation();
而我们构造的表达式注入payload也作为参数传入了context.buildConstraintViolationWithTemplate,并且调用了addConstraintViolation
导致ConstraintViolation不为空,接着返回到调用栈中的ConstraintTree#validateSingleConstraint,后续return时调用了executionContext.createConstraintViolations,并且将刚刚创建的ConstraintViolation作为参数传入
然后不断跟进几个interpolate方法,来到org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator#interpolateExpression方法
其中在实例化InterpolationTerm时,如果传进来的参数以"$"开头,那么会将其中的属性type赋值“InterpolationTermType.EL”
而我们的payload就是以“$”开头的
也就导致了后面调用了interpolateExpressionLanguageTerm(context)
最后在InterpolationTerm#interpolateExpressionLanguageTerm方法中执行了表达式解析
其他漏洞点挖掘
调试完整漏洞后就可以发现,实际上是在开发者自定义验证处理时,把验证后的错误信息传入以下这行代码造成的
context.buildConstraintViolationWithTemplate("Missing roles: " + missing).addConstraintViolation();
于是我们可以再寻找其他类似的Validator,比如PrivilegesExistValidator
再全局查找RolesExistValidator是哪个注解的处理类,以及哪个地方使用了这个注解,查找完整的调用方式
可以发现在RoleXO中就使用了@PrivilegesExist注解,于是构造数据包:
POST /service/extdirect HTTP/1.1
Host: 192.168.52.128:8081
Content-Length: 205
X-Requested-With: XMLHttpRequest
X-Nexus-UI: true
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://192.168.52.128:8081
Referer: http://192.168.52.128:8081/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: Hm_lvt_866c9be12d4a814454792b1fd0fed295=1641052730; Hm_lvt_df6f78cfc7b28956736ab98287309c75=1641052807; NXSESSIONID=f0facde6-ab42-45a5-a3de-db7ad822be61
Connection: close
{"action":"coreui_Role","method":"create","data":[{"version":"","source":"default","id":"123","name":"123","description":"123","privileges":["nx-all|${111*3}"],"roles":["nx-admin"]}],"type":"rpc","tid":36}
这个版本还有其他的Validator存在类似的问题,这里就不一一列举了。
CVE-2020-10204分析
这个漏洞影响版本Nexus Repository Manager OSS/Pro 3.x -3.21.1,实际上是对上面的漏洞修复后的绕过
通过diff可以看到修复方式就是加了getEscapeHelper().stripJavaEL
对el表达式做了清除,将${
替换为了{
,但是可以用$\\A{1*333}
这样的方式绕过,关键的漏洞原理基本一致。
总结
通过调试以及参考网上文章分析,对Nexus3的漏洞有了一个大体了解,问题主要是出现在利用Bean Validation进行验证数据时,在自定义的Validator处理逻辑中将错误信息被当作EL表达式进行执行。在进行漏洞挖掘时可以通过全局搜索漏洞根源代码中的关键字buildConstraintViolationWithTemplate
定位审计点,判断是否将错误信息传入以及是否有过滤处理,再向上溯源查找bean传入点,构造请求数据包进行调试。
Reference
https://xz.aliyun.com/t/10693#toc-0
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)