
Atlassian Jira 未认证模板注入
4.4.x
5.x.x
6.x.x
7.0.x
7.1.x
7.2.x
7.3.x
7.4.x
7.5.x
7.6.x 至 7.6.14(7.6.x 的修复版本)
7.7.x
7.8.x
7.9.x
7.10.x
7.11.x
7.12.x
7.13.x 至 7.13.5(7.13.x 的修复版本)
8.0.x 至 8.0.3(8.0.x 的修复版本)
8.1.x 至 8.1.2(8.1.x 的修复版本)
8.2.x 至 8.2.3(8.2.x 的修复版本)
./bin/setenv.bat 文件中的 set JVM_SUPPORT_RECOMMENDED_ARGS= 值(或 Linux 下对应的 ./bin/setenv.sh),以启用远程调试set JVM_SUPPORT_RECOMMENDED_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
Remote JVM Debug,host 和 port 设置为 localhost:5005,Command line arguments for remote JVM 为:-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
运行 ./bin/config.bat(或 Linux 下的 config.sh),将 "Jira home" 设置为自己的 jira home 目录

运行 ./bin/start-jira.bat(或 Linux 下的 ./bin/start-jira.sh)。请检查 Java 版本以及 8080 端口和 5005 端口是否可用,否则无法启动!
选择 private 模式

到这一步,请记得使用能接收邮件的邮箱地址 :)

配置并测试连接,确保完整

在 http://localhost:8080/secure/admin/EditApplicationProperties!default.jspa 开启 Contact Administrators Form 功能

我只记录了一些构建时的注意事项,更多可参考 从源码构建 Jira
阅读 安全公告 后,我们知道此漏洞位于 ContactAdministrators 和 SendBulkMail(后者需要认证,故略过)。因此我开始测试 ContactAdministrators 功能并抓取请求。

我看到请求发往 /secure/ContactAdministrators.jspa,于是查看文件 ./atlassian-jira/WEB-INF/web.xml 来确定该请求被转发到哪个类


因此该请求将由 JiraWebworkActionDispatcher 处理,我在该类的 init 和 server 方法上设置断点并启动调试

程序停在了 server 方法,跟踪一会儿后程序跳入 ContactAdministrators.doExecute()

然后进入 send,在此方法中程序列出了所有活跃的管理员账户

接着程序进入 sendTo 方法。这里我们看到程序创建了一个 MailQueueItem 并将其添加到 mailQueue 中。

程序调用了 EmailBuilder.withSubject。此处邮件主题(攻击者提交)字符串被转换为 TemplateSources 并赋值给 EmailBuilder 的 subjectTemplate 参数。

在 renderLater 方法中,程序创建了一个 EmailRenderer 并用它来生成 RenderingMailQueueItem

此时,回到 ContactAdministrators.sendTo。创建完 MailQueueItem 后,程序将其加入 mailQueue 并返回 doExecute 进行重定向。如果按照这个调试流程,我们无法跳转到邮件渲染部分,也就无法到达模板注入发生的位置。那么如何跟踪邮件的处理过程呢?
我看到 EmailRenderer 中有 renderNow 方法(程序只调用了 renderLater),我猜测当队列中的邮件被调用渲染时,也会走类似 renderNow 的流程。因此我决定从 renderNow 方法开始跟踪。

从 renderNow 出发,程序调用 EmailRenderer.render()。我在此处设置断点并重新发起请求,以确认程序是否确实执行到这里。

幸运的是,程序按照我预想的方向执行了。接下来调用 renderEmailSubject

接着调用 DefaultVelocityTemplatingEngine.render(this.subjectTemplate)

进入 DefaultVelocityTemplatingEngine.applying 和 DefaultVelocityTemplatingEngine.asPlainText

继续调用 asPlainText(Writer writer)

进入 toWriterImpl,由于传入的 writer 是一个 Fragment,程序跳转到 else 分支
private void toWriterImpl(Writer writer, boolean attachCartridge) throws IOException {
if (this.source instanceof File) {
File template = (File)this.source;
if (attachCartridge) {
this.context.attachEventCartridge(DefaultVelocityTemplatingEngine.this.createDefaultCartridge());
}
DefaultVelocityTemplatingEngine.this.velocityManager.writeEncodedBody(writer, template.getPath(), "", DefaultVelocityTemplatingEngine.this.applicationProperties.getEncoding(), this.context);
} else if (this.source instanceof Fragment) {
Fragment fragment = (Fragment)this.source;
if (attachCartridge) {
this.context.attachEventCartridge(DefaultVelocityTemplatingEngine.this.createDefaultCartridge());
}
DefaultVelocityTemplatingEngine.this.velocityManager.writeEncodedBodyForContent(writer, fragment.getContent(), this.context);
}
}
DefaultVelocityManager.writeEncodedBodyForContent

VelocityEngine.evaluate -> RuntimeInstance.evaluate(Context, Writer, String, String) -> RuntimeInstance.evaluate(Context, Writer, String, Reader)。在此处,程序从 Reader 创建了一个 SimpleNode

render 方法,此处调用了 nodeTree.render(ica, writer); 来解析 Velocity 模板,因此这里可以发生模板注入!public boolean render(Context context, Writer writer, String logTag, SimpleNode nodeTree) throws IOException {
InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
ica.pushCurrentTemplateName(logTag);
try {
try {
nodeTree.init(ica, this);
} catch (TemplateInitException var13) {
throw new ParseErrorException(var13);
} catch (RuntimeException var14) {
throw var14;
} catch (Exception var15) {
String msg = "RuntimeInstance.render(): init exception for tag = " + logTag;
this.getLog().error(msg, var15);
throw new VelocityException(msg, var15);
}