1. 前言
跟踪调试一下命令执行的过程,其实就是表达式递归解析,有些许繁琐。
官方公告:
漏洞描述:
Confluence 存在 Velocity 模板注入漏洞,未经身份验证的攻击者可以直接访问*.vm文件传入恶意 OGNL 表达式来实现 RCE。
漏洞成因**:**
Velocity 模板渲染时需要的参数来自于上下文中,通过向上下文中传入恶意参数值,如果没有对参数进行过滤或进行了不当的调用,就会导致注入漏洞产生。
Confluence 使用了 Struts2 框架和 Velocity 模板引擎,所以可以通过 OGNL 表达式向 Velocity 模板中嵌入 Struts2 上下文的恶意数据来进行利用。
影响版本:
Confluence Data Center and Server:
8.0.x
8.1.x
8.2.x
8.3.x
8.4.x
8.5.0 ~ 8.5.3
2. 环境搭建
下载地址:https://www.atlassian.com/software/confluence/download-archives
官网下载 8.5.3 版本,下载对应系统的安装包和源码。
具体安装步骤之前有写过:
元亨-blckder02,公众号:中孚安全技术研究Confluence 数据中心和服务器中的访问控制漏洞(CVE-2023-22515)
如果出现下面的报错,就按链接里的方法解决:http://confluence.atlassian.com/x/GAtmDg;
也就是修改下confluence.cfg.xml中 jdbc 连接的内容。
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/confluence?sessionVariables=transaction_isolation='READ-COMMITTED'</property>
重新启动,搭建成功。
3. 漏洞复现
POST 请求/template/aui/text-inline.vm,传入如下回显 payload:
label=\u0027%2b#request\u005b\u0027.KEY_velocity.struts2.context\u0027\u005d.internalGet(\u0027ognl\u0027).findValue(#parameters.x,{})%2b\u0027&x=@org.apache.struts2.ServletActionContext@getResponse().setHeader('X-Cmd-Response',(new+freemarker.template.utility.Execute()).exec({"whoami"}))
label参数值解析:
#request['KEY_velocity.struts2.context']:从 request 获取 Struts2 中与 Velocity 集成的上下文对象;
.internalGet('ognl'):从上下文对象中获取 OGNL 上下文;
.findValue(#parameters.x,{}):使用 OGNL 表达式从请求参数中获取x的值。
目的就是对参数x进行表达式解析,所以通过x传入恶意表达式,就可以实现命令执行。
x参数值解析:
@org.apache.struts2.ServletActionContext:获取上下文对象;
@getResponse():调用静态方法 getResponse();
setHeader('X-Cmd-Response',(new+freemarker.template.utility.Execute()).exec({"whoami"})):调用 setHeader() 设置响应头;
(new+freemarker.template.utility.Execute()).exec({"whoami"}):生成一个 Freemarker 模板引擎的 Execute 对象,调用其 exec() 方法执行命令。
4. 漏洞分析
/template/aui/text-inline.vm文件内容如下,接收$parameters中的参数,并且$parameters.label以字符串的形式传给了$stack.findValue。
Confluence 的路由表配置在confluence/WEB-INF/classes/com/atlassian/confluence/impl/webapp/UrlPattern.class,能直接访问*.vm;
Servlet 注册在confluence/WEB-INF/classes/com/atlassian/confluence/impl/webapp/Servlets.class,访问*.vm的请求是由 ConfluenceVelocityServlet 处理的。
在 ConfluenceVelocityServlet.doPost() 下断点开始跟踪,此时 context 中已经有传入的label和x两个参数;
跟进 handleRequest(),获取到当前请求的路径,并且根据/template/aui/text-inline.vm名称返回对应的模板对象。
接着跟进 mergeTemplate() 合并模板和上下文,调用 merge() 将结果输出到writer;
merge() 中创建了一个内部上下文适配器ica,用于在 Velocity 引擎中执行模板。将模板名称和模板对象都放入了这个适配器中,然后调用 render() 渲染模板,结果写到writer;
递归渲染子节点,text-inline.vm 中的第一个节点就是#set( $labelValue = $stack.findValue("getText('$parameters.label')") );
跟进,逐步拆分节点,这里获取到参数中的label值,放入params数组中,然后获取并调用 OgnlValueStack.findValue();
跟进 findValue(),解析并执行表达式。
拆分计算表达式,到findValue(#parameters.x, { })时,result是一个 OgnlTool 对象;
继续拆分计算,获取x的值。
返回x的值;
回到 ASTMethod.getValueBody(),此时的args就是x的值,接着调用 OgnlTool.findValue() 方法。
跟进 OgnlTool.findValue() 方法,就开始对x表达式进行解析了,继续拆分计算;
获取到 Execute 对象;
调用 exec() 方法,带入whoami参数;
在这执行命令。
然后将结果返回,调用 setHeader() 方法将结果输出到响应头的X-Cmd-Response字段中。
整体看一下 AST 语法树结构,就是一层一层节点的递归解析。
5. 补丁分析
8.5.4版本中新增了一个 ConfluenceOgnlGuard 类,用于拦截和检查 OGNL 表达式。
ConfluenceOgnlGuard 继承了org.apache.struts_struts2-core-6.3.0-atlassian-8.jar!\org\apache\struts2\ognl\StrutsOgnlGuard.class;
在将 OGNL 表达式解析成 AST 语法树结构时,会调用其中的 containsExcludedNodeType() 方法检查生成的语法树是否包含禁止的节点类型;
如果包含则会将表达式解析结果设为_ognl_guard_blocked,后面就会抛出异常,解析失败,无法执行命令。
参考链接:
https://blog.projectdiscovery.io/atlassian-confluence-ssti-remote-code-execution/
https://forum.butian.net/share/2741
https://github.blog/2023-01-27-bypassing-ognl-sandboxes-for-fun-and-charities/