freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

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

BeanValidation RCE 漏洞复现分析
山石网科 2022-02-11 11:18:10 170041
所属地 广东省

前言

最近复现分析了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 {
@Email
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,配置远程调试:

1644549114_6205d3fa4ba98e2eaee7c.png!small?1644549112466

复现分析

参考网上给出的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
}

1644549138_6205d412e75354fbaa9c5.png!small?1644549137149

可以从返回包中看到表达式成功执行。

那么一开始怎么入手呢?可以考虑到javax.servlet.http.HttpServlet#service方法先下断点跟

1644549147_6205d41b895eaec667054.png!small?1644549145852

1644549154_6205d4222ca8b2b5ad75b.png!small?1644549152510

跟进service方法后先是判断了POST请求后,继续跟进到HttpServlet的子类DirectJNgineServlet的doPost方法

1644549161_6205d4292f452ebcf3635.png!small?1644549159484

前面都是一些编码处理,然后调用了processRequest方法进行处理请求,跟进

1644549167_6205d42f21202b729ab0a.png!small?1644549165614

接着根据Content-Type进入case JSON分支,调用了RequestRouter类的processJsonRequest方法

1644549173_6205d43598648c365a14d.png!small?1644549171761

1644549178_6205d43a30fdf09fd2743.png!small?1644549176776

在JsonRequestProcessor#process方法中先是调用了getIndividualJsonRequests方法解析了请求包中的json数据,得到action为coreui_User,method为create,接着调用processIndividualRequestsInThisThread方法

1644549186_6205d44228677ba158384.png!small?1644549184361

1644549193_6205d4494a197174ec5a3.png!small?1644549191500

通过获取到的action和method作为参数传入dispatchStandardMethod方法,开始进行调度

1644549217_6205d461cbaaaa29793c5.png!small?1644549215897

后面也可以可以直接找到对应的action和method,在UserComponent#create方法断点跟进,原因是UserComponent类通过@DirectAction注解的方式注入了action,也就是我们请求的coreui_User action,也通过@DirectMethod注解注入了对应的methond

1644549224_6205d468de1f811214503.png!small?1644549223166

同时也可以看到使用了@Valid注解对UserXO进行了验证,我们可以跟进UserXO类查看分别使用了哪些Validator,如下图所示,另外我们从poc中的漏洞位置可以判断是跟其中的roles属性有关,于是重点关注roles

1644549235_6205d4736fdde40927aa2.png!small?1644549233713

这里roles属性标注了自定义的@RolesExist注解,跟进查看

1644549258_6205d48aebd29f4c7bc11.png!small?1644549257472

可以发现最终的验证处理类是RolesExistValidator类,于是就可以直接在RolesExistValidator的isValid方法处进行断点,并直接

1644549266_6205d49224f7fd6d2c5c0.png!small?1644549264621

那RolesExistValidator是在调用栈中何时进行获取的呢?

观察调用栈发现是在ConstraintTree#validateConstraints方法中,可以看到依次获取UserXO中使用到的各种Validator,其中就包含了RolesExistValidator

1644549274_6205d49a3b19e87412201.png!small?1644549272730

回到RolesExistValidator#isValid方法中

1644549280_6205d4a0cc0c44c6180b9.png!small?1644549279042

因为验证不通过,捕获异常后missing不为空,写入自定义的错误信息,也就是运行到这个漏洞的关键代码:

context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Missing roles: " + missing).addConstraintViolation();

而我们构造的表达式注入payload也作为参数传入了context.buildConstraintViolationWithTemplate,并且调用了addConstraintViolation

1644549290_6205d4aa3569c18bda197.png!small?1644549288404

导致ConstraintViolation不为空,接着返回到调用栈中的ConstraintTree#validateSingleConstraint,后续return时调用了executionContext.createConstraintViolations,并且将刚刚创建的ConstraintViolation作为参数传入

1644549297_6205d4b10e79c19865716.png!small?1644549295361

然后不断跟进几个interpolate方法,来到org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator#interpolateExpression方法

1644549303_6205d4b78d2255ee04438.png!small?1644549301919

其中在实例化InterpolationTerm时,如果传进来的参数以"$"开头,那么会将其中的属性type赋值“InterpolationTermType.EL”

1644549334_6205d4d6c7df0d8745bd1.png!small?1644549333030

而我们的payload就是以“$”开头的

1644549341_6205d4dd9656f5357f77c.png!small?1644549339841

也就导致了后面调用了interpolateExpressionLanguageTerm(context)

1644549348_6205d4e482eff9908b7c5.png!small?1644549346808

最后在InterpolationTerm#interpolateExpressionLanguageTerm方法中执行了表达式解析

1644549354_6205d4ea7402c3ab53c62.png!small?1644549352773

其他漏洞点挖掘

调试完整漏洞后就可以发现,实际上是在开发者自定义验证处理时,把验证后的错误信息传入以下这行代码造成的

context.buildConstraintViolationWithTemplate("Missing roles: " + missing).addConstraintViolation();

于是我们可以再寻找其他类似的Validator,比如PrivilegesExistValidator

1644549375_6205d4ff33d0afb4b060e.png!small?1644549373584

再全局查找RolesExistValidator是哪个注解的处理类,以及哪个地方使用了这个注解,查找完整的调用方式

1644549392_6205d510171bde013f08b.png!small?1644549390480

1644549399_6205d517246a252e99265.png!small?1644549397445

可以发现在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}

1644549410_6205d522bd053e6473eee.png!small?1644549409117

这个版本还有其他的Validator存在类似的问题,这里就不一一列举了。

CVE-2020-10204分析

这个漏洞影响版本Nexus Repository Manager OSS/Pro 3.x -3.21.1,实际上是对上面的漏洞修复后的绕过

1644549418_6205d52ab4f0f882fa3f3.png!small?1644549417007

通过diff可以看到修复方式就是加了getEscapeHelper().stripJavaEL对el表达式做了清除,将${替换为了{,但是可以用$\\A{1*333}这样的方式绕过,关键的漏洞原理基本一致。

1644549425_6205d5316c3de8fd0e5fd.png!small?1644549423763

总结

通过调试以及参考网上文章分析,对Nexus3的漏洞有了一个大体了解,问题主要是出现在利用Bean Validation进行验证数据时,在自定义的Validator处理逻辑中将错误信息被当作EL表达式进行执行。在进行漏洞挖掘时可以通过全局搜索漏洞根源代码中的关键字buildConstraintViolationWithTemplate定位审计点,判断是否将错误信息传入以及是否有过滤处理,再向上溯源查找bean传入点,构造请求数据包进行调试。

Reference

https://xz.aliyun.com/t/10693#toc-0

https://support.sonatype.com/hc/en-us/articles/360044356194

https://blog.knownsec.com/2020/07/nexus-repository-manager-3-%E5%87%A0%E6%AC%A1%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%A7%A3%E6%9E%90%E6%BC%8F%E6%B4%9E/

# RCE漏洞 # BeanValidation
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 山石网科 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
山石网科 LV.7
山石网科安研院
  • 94 文章数
  • 90 关注者
Linux内核攻击面研究
2023-03-13
详解Flask SSTI 利用与绕过技巧V2
2023-03-06
Shadowsocks 重定向攻击
2023-02-09
文章目录