CVE-2019-11581 Atlassian Jira未授权模板注入漏洞分析
2019-07-17 11:57:00 Author: paper.seebug.org(查看原文) 阅读量:219 收藏

作者:绿盟科技伏影实验室
本文为作者投稿,Seebug Paper 期待你的分享,凡经采用即有礼品相送! 投稿邮箱:[email protected]

环境搭建

使用atlas-debug调试

  1. 下载安装Atlassian SDK地址
  2. atlas-create-jira-plugin创建一个插件,参考
  3. atlas-debug开启调试,http端口2990,调试端口5005;
  4. IDEA打开MyPlugin,把WEB-INF/classesWEB-INF/lib加入library;
  5. 新建Remote调试;

其他:

  1. 如果没有设置%JAVA_HOME%可以通过SET JAVA_HOME=d:\jdk1.8设置;
  2. 默认不开启电子邮件发送,通过atlas-debug --jvmargs -Datlassian.mail.senddisabled=false开启;

第一部分:注入代码并生成邮件

post的数据通过JiraSafeActionParameterSetter->setActionProperty()方法

通过反射调用到ContactAdministrators.setSubject()方法,把ContactAdministrators对象的subject属性设置为传入的subject

随后通过ContactAdministrators.doExecute()调用send()方法,在这个方法中会查找系统中已激活的管理员,通过this.sendTo(administrator)将邮件发送给该管理员

sendTo()流程中,Jira需要通过EmailBuilder()方法创建一个邮件队列对象,随后将该对象放入邮件发送队列中。由于队列等待原因,所以触发payload可能需要等待一段时间,并且当邮件发送失败时系统会继续尝试发送邮件,所以payload可能会触发多次。

创建队列的方法有点长,精简一下就是这个样子

MailQueueItem item = (new EmailBuilder()).withSubject(this.subject).withBodyFromFile().addParameters().renderLater();

通过EmailBuilderwithSubject()方法,创建一个TemplateSources$fragment对象,参数即是我们传入的payload,随后调用renderLater()方法创建出EmailBuilder对象,再将该对象作为参数传递给RenderingMailQueueItem类,RenderingMailQueueItem的继承关系是如下图,于是最终创建出一个MailQueueItem对象,并将该对象放入邮件发送队列。

第二部分:发送邮件

当我们把payload注入到模板中之后,邮件进入待发送队列,Jira中处理邮件队列的具体流程如下:

通过模板引擎(getTemplatingEngine)生成一个Velocity模板,通过applying()方法生成RenderRequest对象,之后根据该对象成员变量source的类型,调用不同的方法解析模板,漏洞的产生正是由于这个差异造成的,下面详细分析一下。

首先进入RenderingMailQueueItem().send()方法,调用this.emailRenderer.render(),随后调用

this.getTemplatingEngine().render(this.subjectTemplate).applying(contextParams).asPlainText();

这个过程中前面是为了获取模板解析引擎(VelocityTemplatingEngine)并传入主题模板(此处为payload数据),通过applying()方法创建VelocityContext对象并把payload赋值给成员变量source

随后重写了抽象类StringRepresentationwith()方法,在with()方法中调用了asPlainText()方法

DefaultRenderRequest.this.asPlainText(sw)

asPlainText()的作用是通过Velocity模板引擎解析模板,其中的调用链是

toWriterImpl()->writeEncodedBodyForContent()->evaluate()

而在evaluate()方法中生成了AST结构,随后通过反射调用传入的payload,完成代码执行。

asPlainText()之后的调用栈如下

在处理完Object模板后会调用父类SingleMailQueueItem的send()方法,通过smtpMailServer.sendWithMessageId()发送邮件,由于没有正确配置SMTP服务会抛出异常,但在连接SMTP服务之前漏洞已经触发了,控制台也能看到MailQueue执行的过程。

思考

上述漏洞流程走完了,但还有一个关键问题没有解决:为什么邮件主题Subject会被解析成AST结构并被执行呢?按照正常发送反馈的逻辑,一封邮件的主题(字符串)似乎没有必要解析成AST,导致差异的原因是什么?

发送一封正常的“联系管理员”邮件,走一遍流程

对比一下两个处理流程,当发送正常反馈时,writeEncodedBody()中调用的是this.getVe().mergeTemplate,通过Velocity引擎的ClasspathResourceLoader()类的getResourceStream()方法加载模板文件,此处的模板是templates/email/html/contactadministrator.vm,随后还会进行headerfooter等正常加载流程,最终渲染出整个页面。而发送payload时,通过asPlainText()创建出TemplateSource$Fragment对象,再通过DefaultRenderRequest构造方法把source成员变量赋值为这个Fragment对象,于是进入第一个分支,调用的是this.getVe().evaluate(),最终调用ASTMethod.execute(),这正是前面说的差异性导致的两个不同处理逻辑。

回过头看一下Velocity渲染的大致流程:

Velocity渲染引擎首先磁盘加载模板文件到内存,然后解析模板模板文件为AST结构,并对AST中每个节点进行初始化,第二次加载同一个模板文件时候如果开启了缓存则直接返回模板资源,通过使用资源缓存节省了从磁盘加载并重新解析为AST的开销。

ASTMethod.execute()方法设计之初是在Velocity parse解析模板的过程中,通过反射调用相关方法完成正常模板渲染动作,例如获取背景颜色、获取text内容、获取页面编码等,但当此处攻击者传入精心构造的数据后,利用反射执行了java.lang.Runtime.getRuntime,成功达到命令执行的目的,漏洞利用十分精巧。

参考


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/982/



文章来源: https://paper.seebug.org/982/
如有侵权请联系:admin#unsafe.sh