危害等级高,攻击者利用此漏洞,可实现远程代码执行,PoC已公开。
漏洞描述
近日,Jira官方公开了服务器模板注入漏洞,该漏洞存在于Jira Server和Jira Data Center中,服务器满足以下任意一个条件,即可导致远程代码执行:
- 服务器配置了SMTP服务,并且开启了Contact Administrators Form功能;
- 服务器配置了SMTP服务,并且攻击者具有Jira管理员访问权限。
Jira是Atlassian公司出品的项目与事务跟踪工具,被广泛应用于缺陷跟踪、客户服务、需求收集、流程审批、任务跟踪、项目跟踪和敏捷管理等工作领域。Jira配置灵活、功能全面、部署简单、扩展丰富,因此具有较广泛的应用范围,请相关用户及时进行排查,第一时间封堵风险。
参考链接:
https://confluence.atlassian.com/jira/jira-security-advisory-2019-07-10-973486595.html
影响范围
受影响版本
- Jira 4.4.0 < 7.6.14
- Jira 7.7.0 < 7.13.5
- Jira 8.0.0 < 8.0.3
- Jira 8.1.0 < 8.1.2
- Jira 8.2.0 < 8.2.3
不受影响版本
- Jira 7.6.14
- Jira 7.13.5
- Jira 8.0.3
- Jira 8.1.2
- Jira 8.2.3
漏洞防护
官方升级
Jira官方已经发布了最新版本修复了该漏洞,各版本补丁下载链接如下:
临时防护
对于不能立即升级的用户,可采用临时防护方案进行防护,具体操作步骤如下:
1.禁用Contact Administrator Form功能:
- 点击图标 ,选择System
- 选择General Configuration
- 点击Edit Settings
- 找到Contact Administrator Form,并设置为关闭
2.按照上述操作关闭Contact Administrator Form功能后,禁止对路径/secure/admin/SendBulkMail!default.jspa的访问。
技术分析
首先通过注入代码生成邮件。
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,成功达到命令执行的目的,漏洞利用十分精巧。
声明
本安全公告仅用来描述可能存在的安全问题,绿盟科技不为此安全公告提供任何保证或承诺。由于传播、利用此安全公告所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,绿盟科技以及安全公告作者不为此承担任何责任。
绿盟科技拥有对此安全公告的修改和解释权。如欲转载或传播此安全公告,必须保证此安全公告的完整性,包括版权声明等全部内容。未经绿盟科技允许,不得任意修改或者增减此安全公告内容,不得以任何方式将其用于商业目的。
关于绿盟科技
北京神州绿盟信息安全科技股份有限公司(简称绿盟科技)成立于2000年4月,总部位于北京。在国内外设有30多个分支机构,为政府、运营商、金融、能源、互联网以及教育、医疗等行业用户,提供具有核心竞争力的安全产品及解决方案,帮助客户实现业务的安全顺畅运行。
基于多年的安全攻防研究,绿盟科技在网络及终端安全、互联网基础安全、合规及安全管理等领域,为客户提供入侵检测/防护、抗拒绝服务攻击、远程安全评估以及Web安全防护等产品以及专业安全服务。
北京神州绿盟信息安全科技股份有限公司于2014年1月29日起在深圳证券交易所创业板上市,股票简称:绿盟科技,股票代码:300369。