前言
前段时间,涛哥安排了个Confluence站点的测试,网上搜了下存在模板注入可导致命令执行,编号CVE-2020-4027。网上没有复现的文章,直到要交报告了还是没成功执行命令,只能交个模板注入导致文件读取悻悻而归。这件事如鲠在喉,于是抽空搭个环境复现了下。
前期准备
复现环境本文使用的是Confluence 7.4.4的版本,官网有部署包可下,考虑到要下断点调试,没有使用Docker部署,数据库可以使用Docker起一个postgres,配置步骤网上有很多教程,本文就不具叙。
前期准备主要是IDEA怎么去调试的问题。开头就遇到坑,Xloggc的日志文件不支持中文,环境直接都启动不起来,最后排查下在service.bat里有Xloggc文件名参数,系统是中文的文件名也会是中文,删除掉百分号的内容即可。部署包自带一个tomcat,包里的confluence目录是项目的Web目录,刚开始想着从IDEA里启动tomcat,但不过是自带的tomcat,还是自己的tomcat启动后访问都是404。最后学着远程调试的方法,在自带tomcat的catalina.bat脚本开头加入以下一行调试命令,接着运行启动文件start-confluence.bat,并开启调试即可。
复现步骤
虽然网上没有找到具体的漏洞点,但根据CVE的描述和@Xiao_C师傅的复现截图可得知漏洞点是出在用户宏(User macros)里,再查阅下文档和根据上一个模板注入漏洞CVE-2019-3396可知,用户宏可通过管理员的一般设置的用户宏处设置,然后在编辑文章的其他宏里预览触发。
用CVE-2019-3396的payload如下,试了下无法执行命令直接输出了模板的内容,根据CVE的描述得知是因为使用了沙箱导致的。
1 | #set($e="exp") |
那么先来看一个简单的velocity demo如下,最基本的会实例化模板引擎VelocityEngine
和模板上下文VelocityContext
。
1 | public static void main(String[] args) throws Exception { |
调试可以先从VelocityEngine
的init
方法下手。在init
下断点,然后重新运行网站的启动脚本,用于初始化的Properties
对象有很多的配置,其中runtime.introspector.uberspect
是与沙箱相关的一个配置。其使用ConfluenceAnnotationBoxingUberspect
类进行配置。
跟进ConfluenceAnnotationBoxingUberspect
,最终找到它的父类SecureUberspector
,是Volecity的默认沙箱。
用前面的Demo加上runtime.introspector.uberspect
属性,使用之前模板注入的payload进行测试,会抛出一句告警。
搜寻一番,发现是在SecureIntrospectorImpl
的getMethod
方法输出的日志。跟进到同一个类中的checkObjectExecutePermission
方法,验证对象是否合法。常规模板注入获取Class实例,一般通过String
类型的Class实例调用forName
方法,也就是java.lang.Class
已经在黑名单类内,所以这种方式在这里就无法使用了。
再回去看看CVE的描述,说是通过上下文绕过沙箱。接下来找找有哪些上下文属性,我这里在模板中执行String的codePointBefore
方法并下断点。顺着调用栈往上找,在GenericVelocityMacro
的execute
方法里调用了MacroUtils
的defaultVelocityContext
方法。
跟进defaultVelocityContext
方法可以看到默认设置了req
、res
、action
和webwork
四个上下文属性。
1 | public static Context createDefaultVelocityContext() { |
其中req
做最终是实现ServletRequest
接口的,具有getServletContext
方法,可获得Servlet的上下文。而上下文中有attributes属性,其中包括org.apache.tomcat.InstanceManager
的键值对,这个类名在Struts2的Poc中看过,可以通过newInstance
方法new一个给定类名的实例。
然后可以通过ScriptEngineManager
或各种表达式语言执行命令。其实这个模板注入本质上是漏洞作者@pwntester的另一个CVE漏洞Remote Code Execution in Apache Velocity,而且作者也给出了POC如下。
1 | ${req.getServletContext().getAttribute('org.apache.tomcat.InstanceManager').newInstance('javax.script.ScriptEngineManager').getEngineByName('js').eval("java.lang.Runtime.getRuntime().exec('touch /tmp/pwned')")} |
在这之前遇到大坑了,官方说受影响版本包括7.5.0,我自己搭了个7.5.0版本,包括使用测试站点,用上面的payload是会直接原样输出的,也就是被沙箱拦截了。这一度让我以为方向错了,后来换个7.4.4版本就成功了,这就很无语了。
后记
水这篇文章的时间跨度好大,从九月底到现场,过了个国庆人都懈怠了。感觉写得有点乱,文中若有错误的地方,望各位师傅不吝斧正。
参考
https://securitylab.github.com/advisories/GHSL-2020-045-atlassian_confluence/
https://securitylab.github.com/advisories/GHSL-2020-048-apache-velocity/
https://twitter.com/XiaoC75068775/status/1309673425984610306