这个漏洞已经出来很久了,以前简单分析过,但是由于时间关系,没能深入研究原理,网上对这个漏洞的分析也不太多,最近由于工作原因,深入分析了一下这个漏洞的原理,这里重点将漏洞调试过程,以及一些之前遇到的一些奇怪问题的原因记录下来。
首先来看一下官方对这个漏洞的描述,如下图:
可以看到,这个漏洞的形成,是因为在早于JSP2.0的版本上,由于没有EL表达式的支持,Spring标签为了兼容这部分版本,Spring的一部分标签独立于Servlet/JSP容器添加了对EL表达式的支持。
但这样做引起了一个问题,就是当这部分标签在支持EL表达式的容器中运行的时候,Spring标签的属性会被当做EL表达式执行两次,第一次是被容器当做EL执行,第二次是被Spring标签自身执行。所以,如果攻击者可以控制标签属性的内容,就可以执行自己提交的EL表达式,会造成信息泄露、代码执行等风险。
首先编写一段测试JSP代码,并且引入存在漏洞的Spring标签,我这里使用的是Tomcat 7.0.57和Spring 3.0.5
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%> <spring:message text="${param.a}"></spring:message>
这里使用message标签,text属性用el表达式从请求参数中取值,这样当访问
http://localhost/tag.jsp?a=${applicationScope}
${applicationScope}这个字符串,就会被当做EL表达式执行,如下图
下面我们就从代码中,研究一下这个漏洞的原理。
首先,${param.a}这个EL表达式要在JSP里面执行,这里有必要简单说一下JSP的执行原理,tag.jsp在执行的时候,会先通过规则变成tag_jsp.java,
这个文件可以在tomcatRoot/work/Catalina/localhost/项目名称/org/apache/jsp/路径下找到,这个类其实是一个servlet,
通过查看这个文件的代码,我们可以发现${param.a}这个EL表达式,其实是被转化成了这样的代码org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${param.a}", .......),
这行代码调用了Tomcat的一个专用方法来执行EL表达式,这段代码执行了之后,我们通过请求参数提交的${applicationScope}就被容器获取,并且传入了Spring标签,如下图:
还需要说一下关于标签的一些内容,标签都有一个tld文件,这个文件里面有标签的各种信息,包括标签实现类、属性信息等等,
这个标签对应的spring.tld文件可以在Spring的jar包中找到,打开这个文件搜索message,就可以定位到如下图的位置:
从上面可以看到org.springframework.web.servlet.tags.MessageTag就是这个标签的实现类,关于标签的规范,我就不多说了,有兴趣的朋友可以去翻翻相关的规范。
Debug程序,在doStartTagInternal方法下断点,如下图:
然后跟进resolveMessage方法,因为程序执行到这里,容器已经第一次执行了EL表达式${parm.a},将参数值${applicationScope}取了出来,可以看到text变量,这时候就是我们传入的${applicationScope},如下图:
最后跟踪到org.springframework.web.util.ExpressionEvaluationUtils类的evaluateExpression方法,在这里可以看到Spring的代码调用了getExpressionEvaluator方法获取了容器执行El表达式求值对象,并执行了EL表达式,到这里可以看到我们通过请求参数提交的字符串,被当做EL表达式执行了,如下图:
从上面的过程可以看到,容器第一次执行EL表达式${param.a}获得了我们输入的${applicationScope},然后Spring标签获取容器的EL表达式求值对象,把${applicationScope}再次执行掉,形成了漏洞。
这个漏洞既然能执行攻击者提交的EL表达式,那么获取敏感信息、XSS等攻击自然不是问题,我这里主要说一下代码执行的一些问题。
这个漏洞之所以能造成远程代码执行,是因为从JEE6开始,EL(EL2.2)表达式不仅支持获取对象属性,还加入了对方法调用的支持,而Tomcat、Resin等容器都加入了EL表达式对方法调用的支持,之前已经有大牛公布的针对Resin和GlassFish的代码执行POC,虽然由于容器对EL表达式的执行实现上有差异,每个容器POC有所差异,但是都可以成功实现方法调用,我这里就不多说了,而我在最初测试这个漏洞的时候,发现Tomcat始终没法调用方法,当时没有深入研究,通过前面的分析,其实原因已经可以看出来了,这里还是测试一下,先写一段JSP测试代码:
${"aaa".replace('a','b')}
打开这个页面,发现a都已经被b替换,说明replace方法被成功调用:
但是当我们通过之前的tag.jsp尝试提交
http://localhost/tag.jsp?a=${"aaaa".replace('a','b')}
的时候却爆出了错误,如下图:
从Tomcat 7开始已经支持EL表达式方法调用,为什么第二次会失败呢?
其实前面的分析中已经看到,在JSP中直接写EL表达式,Tomcat调用了org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate方法,这个方法已经加入了对EL表达式方法调用的支持,而通过Spring标签执行EL表达式,前面已经看到是通过pageContext的getExpressionEvaluator方法,而在EL表达式的新标准中这个方法已经“废弃”,Tomcat并没有在这个方法的实现中加入对方法调用的支持,所以造成执行失败,这也让这个漏洞在Tomcat上显得有点鸡肋了,谁能保证Tomcat哪天不会抽风,改变代码呢?说不定到时候,这个漏洞,就可以在Tomcat上造成代码执行了。
http://support.springsource.com/security/cve-2011-2730
http://drops.wooyun.org/tips/2892
【作者:京东安全中心Lupin 日期:2015-4-20】