CVE-2022-26134 分析学习
简介
描述:在受影响的Confluence Server和Data Center版本中存在一个OGNL注入漏洞,允许未经身份验证的攻击者在Confluence Server或Data Center实例上执行任意代码。
影响版本:
- 1.3.0 ≤ Atlassian Confluence Server/Data Center < 7.4.17
- 7.13.0 ≤ Atlassian Confluence Server/Data Center < 7.13.7
- 7.14.0 ≤ Atlassian Confluence Server/Data Center < 7.14.3
- 7.15.0 ≤ Atlassian Confluence Server/Data Center < 7.15.2
- 7.16.0 ≤ Atlassian Confluence Server/Data Center < 7.16.4
- 7.17.0 ≤ Atlassian Confluence Server/Data Center < 7.17.4
- 7.18.0 ≤ Atlassian Confluence Server/Data Center < 7.18.1
参考链接:https://jira.atlassian.com/browse/CONFSERVER-79016
分析
漏洞环境:confluence 7.13.6
根据官方公告,如果不升级的话,可以通过替换 jar 包进行临时修复,Confluence 6.0.0 - Confluence 7.14.2 之间的漏洞版本修复方式如下:
1)关闭 Confluence
2)下载新的jar包到Confluence 服务器:
- xwork-1.0.3-atlassian-10.jar
- webwork-2.1.5-atlassian-4.jar
- CachedConfigurationProvider.class
3)从Confluence 安装目录中移除旧的jar包,如:
/confluence/WEB-INF/lib/xwork-1.0.3.6.jar
/confluence/WEB-INF/lib/webwork-2.1.5-atlassian-3.jar 4)将先前下载的xwork-1.0.3-atlassian-10.jar、webwork-2.1.5-atlassian-4.jar包,复制到Confluence 安装目录:
/confluence/WEB-INF/lib/ (这里要注意新的jar包权限要和同目录中其他文件相同) 5)创建一个名为webwork的新目录
将CachedConfigurationProvider.class复制到
/confluence/WEB-INF/classes/com/atlassian/confluence/setup/webwork(确保权限和所有权和同目录文件相同) 启动 Confluence
因为官方提供的下载地址需要登录,所以我从 7.13.7 这个安全版本中将上面两个 jar 考出来,与漏洞环境中的的 jar 包进行比较
发现对com.opensymphony.xwork.ActionChainResult#execute
方法进行了修改,不再调用com.opensymphony.xwork.util.TextParseUtil#translateVariables
方法
跟进TextParseUtil#translateVariables
方法可以发现,这里先匹配${}
里面的字符串,然后调用了findValue()
方法
正好OgnlValueStack#findValue()
方法也添加了安全检测
在ActionChainResult#execute
方法和OgnlValueStack#findValue()
方法中下断点
基础
如何触发到 ActionChainResult#execute 方法呢 ?
在 struts2 中当请求逻辑走完后,会调用 DefaultActionInvocation
的 executeResult()
方法,在该方法中调用 Result
实现类里的 execute()
方法开始处理这次请求的结果,而ActionChainResult
类就是实现Result
接口的众多类之一
那么如何确定要调用哪个 Result 接口的实现类呢?
在DefaultActionInvocation#executeResult
方法中,会调用DefaultActionInvocation#createResult
方法来获得这个Result
实现类的实例,然后再执行其execute
方法
根据this.resultCode
的值确定Result
实例
从这里就可以看到当this.resultCode
为notpermitted
时,获取到的就是ActionChainResult
实例
这个 this.resultCode 从哪里定义呢?
在DefaultActionInvocation#invoke
方法中,会先递归调用拦截器栈中的拦截器,如果调用过程中被哪个拦截器所拦截,也会返回值给this.resultCode
,如果顺利通过所有拦截器,那么就会进入 else if
或 else
中,通过反射调用请求 action 中的方法,这个this.resultCode
就是执行 action 方法后返回的值
1 |
|
<result>
标签用于定义 action 的结果
name
属性的值用于匹配 action 中所执行方法的返回值,在上面的配置中 name 的属性值为 success,如果execute
方法返回字符串 success ,那么服务器就返回talk.jsp
页面
type
属性决定了如何处理 Action 的结果,不写默认为dispatcher
dispatcher
:默认值。将结果发送到服务器端的资源(通常是 JSP 页面),由服务器端进行处理和呈现。
redirect
:将结果重定向到指定的 URL 或 Action。
chain
:将结果传递给下一个 Action。
redirectAction
:重定向到另一个 Action,并将结果传递给该 Action。
json
:将结果作为 JSON 数据返回。
stream
:将结果作为流数据返回。
confluence 中相关的配置在confluence-x.x.x.jar
的xwork.xml
文件中,可以看到当<result>
标签中type
属性值为chain
时,便会调用ActionChainResult
来处理结果
result-type
标签的作用就是用来自定义结果类型
global-results
标签用来配置全局结果,当某个 action 或者拦截器返回对应结果时,便会触发相应的处理逻辑
param
标签指定下一个会被调用到的 action
以notpermitted
举例,当返回结果为notpermitted
时,将以chain
类型也就是调用ActionChainResult
来处理,下一个被调用到的 action 是notpermitted.action
(result 可以通过配置跳转到各个页面,所以也可以跳转到其他 action)
调试
下一步就是找到所有返回结果对应type="chain"
的,看如何触发能使其返回对应结果,同时又是可控的
刚开始调试的时候,便偶然触发到一个,直接未登录状态访问 confluence 网站根路径便会触发到ActionChainResult#execute
方法,而这里的this.actionName
是notpermitted
,也就是下一个被调用的 action 是notpermitted
,那么说明访问的 action 或者拦截器返回的结果是notpermitted
根据 web.xml 中配置的welcome-file-list
可知当访问网站根路径时默认会访问index.action
,其对应的类为com.atlassian.confluence.core.actions.IndexAction

而IndexAction#execute
方法根本不会返回notpermitted
,说明没有调用到 action 这一步,而是在拦截器中返回的notpermitted
index.action 对应的是defaultStack
默认拦截器栈,在所有能返回notpermitted
结果的拦截器中下断点,发现是从ConfluenceAccessInterceptor#intercept
这个拦截器中返回的
这个拦截器的作用是根据要调用的方法上存在的访问检查注释,判断当前用户是否有对该方法的访问权限,如果方法中没有注释会检查类上的注释,类上没有会检查包上的
而IndexAction
类上存在@RequiresAnyConfluenceAccess
注释,该注释的意思是允许具有 Confluence 访问权限的任何用户执行目标操作,而未登录的情况下,很明显没有访问权限,所以被ConfluenceAccessInterceptor
拦截器拦截并返回notpermitted
,因此调用ActionChainResult
处理返回结果
很明显this.actionName
是不可控的,它表示下一个要调用的 action,这个是根据xwork.xml
中的param
标签的配置得来的
所以只能控制this.namespace
,这是因为在xwork.xml
中,只指定了下一个要调用的 action,并未在param
标签中指定namespace
在ActionChainResult#execute
方法中,如果namespace
为null
,则会获取请求中的namespace
当传入请求时,会在RuntimeConfigurationImpl#getActionConfig
方法中进行处理,先根据传入的路径获取该路径下的所有 ActionConfig,然后根据传入的action
名称获取对应的ActionConfig
,当根据传入的路径获取不到actions
时,则会获取根路径下的actions
,尝试从中获取对应的ActionConfig
,所以路径是什么无所谓,只要传入的action
名称在根路径中存在即可,这样程序便能继续往下运行,否则会因为获取不到config
而抛出异常
利用
如果是低版本直接使用如下 poc 便可以,因为 poc 是放在请求路径中的,如果 poc 中包含/
则会报 400 错误,所以这里通过请求头传递命令,发送请求时记得对 poc 进行 url 编码
1 |
|
但是在 7.15.0 版本中,该 poc 则无效了,因为该版本在OgnlValueStack#findValue()
方法中添加了安全检测,检测方式与CVE-2021-26084
中相同,不过这里的检测变得更为严格,CVE-2021-26084
中的 poc 无法直接用
检查最终还是在SafeExpressionUtil#containsUnsafeExpression()
方法中进行的,通过对网上 payload 的学习,发现一个最简单的绕过方式,那就是在我们原先的 poc 前面添加一个null
,便可以绕过检测(其它绕过方式链接)
1 |
|
在SafeExpressionUtil#containsUnsafeExpression()
方法中依然会对每个节点极其子节点进行递归检查,那么这里第一个检查的子节点便是null
,null
属于ASTConst
类型
所以会进入到SafeExpressionUtil#isSafeConstantExpressionNode
方法中,节点null
执行getValue
方法获得的依然是null
,但是后面执行toString
方法会抛出java.lang.NullPointerException
异常,而下面的catch
无法捕捉该异常
异常会在SafeExpressionUtil#isSafeExpressionInternal
方法中被捕捉到,因此也就跳出了SafeExpressionUtil#containsUnsafeExpression()
方法的检测
1 |
|