作者:绿盟科技伏影实验室
本文为作者投稿,Seebug Paper 期待你的分享,凡经采用即有礼品相送! 投稿邮箱:[email protected]
环境搭建
使用atlas-debug
调试
- 下载安装
Atlassian SDK
,地址; atlas-create-jira-plugin
创建一个插件,参考;atlas-debug
开启调试,http端口2990,调试端口5005;IDEA
打开MyPlugin
,把WEB-INF/classes
和WEB-INF/lib
加入library;- 新建
Remote
调试;
其他:
- 如果没有设置
%JAVA_HOME%
可以通过SET JAVA_HOME=d:\jdk1.8
设置; - 默认不开启电子邮件发送,通过
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();
通过EmailBuilder
的withSubject()
方法,创建一个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
随后重写了抽象类StringRepresentation
的with()
方法,在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
,随后还会进行header
、footer
等正常加载流程,最终渲染出整个页面。而发送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
,成功达到命令执行的目的,漏洞利用十分精巧。
参考
- https://confluence.atlassian.com/jira/jira-security-advisory-2019-07-10-973486595.html
- https://developer.atlassian.com/server/framework/atlassian-sdk/atlas-debug/
- https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/
- http://ifeve.com/velocity%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6/
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/982/