• 宝鸡市陈仓区党员干部助力夏收帮扶困难群体 2019-04-13
  • 滚滚而来的温暖!山坡上滚下万斤爱心榨菜头 2019-04-08
  • 中国科研团队刷新暗物质探测灵敏度 2019-04-08
  • 定陶战役:示弱于敌重点围歼的范例 2019-03-20
  • 里约奥运变化与机遇并存 充分准备迎接挑战 2019-03-20
  • 我国居民人均预期寿命又提高了 2017年提高至76.7岁 2019-03-17
  • 【三年决战奔小康】一封来自甘南精准扶贫户的感谢信 2019-03-17
  • 新时代新平台新机遇“一带一路”大型网络主题活动 2019-03-15
  • 学生睡觉摔骨折 法院主动出击促调解 2019-03-07
  • 总览传奇般的——波尔多八大酒庄柏图斯波尔多 2019-03-07
  • 它不会比如何做好一餐饭的难度大,或者道理更多。 2019-03-02
  • 高清:中国男篮抵达洛杉矶 长途飞行队员略显疲惫 2019-02-27
  • 新疆非法转基因玉米案:4被告获刑 近三千亩涉案玉米被铲 2018-11-22
  • 湖北治理违规提取公积金 防止用公积金炒房 2018-11-21
  • 罗品禧的专栏作者中国国家地理网 2018-11-20
  • |
    |
    51CTO旗下网站
    |
    |
    移动端

    Java代码审计之SpEL表达式注入

    Spring Expression Language 是一种功能强大的表达式语言、用于在运行时查询和操作对象图;语法上类似于 Unified EL,但提供了更多的特性,特别是方法调用和基本字符串模板函数。

    作者:Lateink来源:Freebuf|2019-03-13 08:56

    广东11选五精准计划 www.flxp.net 一、SpEL 表达式注入

    Spring Expression Language(简称 SpEL)是一种功能强大的表达式语言、用于在运行时查询和操作对象图;语法上类似于 Unified EL,但提供了更多的特性,特别是方法调用和基本字符串模板函数。SpEL 的诞生是为了给 Spring 社区提供一种能够与 Spring 生态系统所有产品无缝对接,能提供一站式支持的表达式语言。

    二、SpEL 表达式

    • 基本表达式:字面量表达式、关系,逻辑与算数运算表达式、字符串链接及截取表达式、三目运算、正则表达式以及括号优先级表达式;
    • 类相关表达式:类类型表达式、类实例化、instanceof 表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean 引用;
    • 集合相关表达式:内联 List、内联数组、集合、字典访问、列表、字典;
    • 其他表达式:模版表达式

    三、SpEL 基础

    在 pom.xml 导入 maven 或是把”org.springframework.expression-3.0.5.RELEASE.jar”添加到类路径中

    1. <properties> 
    2.     <org.springframework.version>5.0.8.RELEASE</org.springframework.version> 
    3. </properties> 
    4. <dependency> 
    5.       <groupId>org.springframework</groupId> 
    6.       <artifactId>spring-expression</artifactId> 
    7.       <version>${org.springframework.version}</version> 
    8. </dependency> 

    1. SpEL 使用方式

    SpEL 在求表达式值时一般分为四步,其中第三步可?。菏紫裙乖煲桓鼋馕銎?,其次解析器解析字符串表达式,在此构造上下文,最后根据上下文得到表达式运算后的值。

    1. ExpressionParser parser = new SpelExpressionParser(); 
    2. Expression expression = parser.parseExpression("('Hello' + ' freebuf').concat(#end)"); 
    3. EvaluationContext context = new StandardEvaluationContext(); 
    4. context.setVariable("end", "!"); 
    5. System.out.println(expression.getValue(context)); 
    • 创建解析器:SpEL 使用 ExpressionParser 接口表示解析器,提供 SpelExpressionParser 默认实现;
    • 解析表达式:使用 ExpressionParser 的 parseExpression 来解析相应的表达式为 Expression 对象。
    • 构造上下文:准备比如变量定义等等表达式需要的上下文数据。
    • 求值:通过 Expression 接口的 getValue 方法根据上下文获得表达式值。

    2. SpEL 主要接口

    1.ExpressionParser 接口:表示解析器,默认实现是 org.springframework.expression.spel.standard 包中的 SpelExpressionParser 类,使用 parseExpression 方法将字符串表达式转换为 Expression 对象,对于 ParserContext 接口用于定义字符串表达式是不是模板,及模板开始与结束字符;

    1. public interface ExpressionParser {   
    2.     Expression parseExpression(String expressionString);   
    3.     Expression parseExpression(String expressionString, ParserContext context);   

    事例 demo:

    1. ExpressionParser parser = new SpelExpressionParser(); 
    2. ParserContext parserContext = new ParserContext() { 
    3.     @Override 
    4.     public boolean isTemplate() { 
    5.     return true; 
    6.     } 
    7.     @Override 
    8.     public String getExpressionPrefix() { 
    9.     return "#{"; 
    10.     } 
    11.     @Override 
    12.     public String getExpressionSuffix() { 
    13.     return "}"; 
    14.     } 
    15. }; 
    16. String template = "#{'hello '}#{'freebuf!'}"
    17. Expression expression = parser.parseExpression(template, parserContext); 
    18. System.out.println(expression.getValue()); 

    演示的是使用 ParserContext 的情况,此处定义了 ParserContext 实现:定义表达式是???,表达式前缀为「#{」,后缀为「}」;使用 parseExpression 解析时传入的模板必须以「#{」开头,以「}」结尾。

    默认传入的字符串表达式不是模板形式,如之前演示的 Hello World。

    • EvaluationContext 接口:表示上下文环境,默认实现是 org.springframework.expression.spel.support 包中的 StandardEvaluationContext 类,使用 setRootObject 方法来设置根对象,使用 setVariable 方法来注册自定义变量,使用 registerFunction 来注册自定义函数等等。
    • Expression 接口:表示表达式对象,默认实现是 org.springframework.expression.spel.standard 包中的 SpelExpression,提供 getValue 方法用于获取表达式值,提供 setValue 方法用于设置对象值。

    3. SpEL 语法 – 类相关表达式

    类类型表达式:使用”T(Type)”来表示 java.lang.Class 实例,”Type”必须是类全限定名,”java.lang”包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段。

    具体使用方法:

    1. ExpressionParser parser = new SpelExpressionParser(); 
    2.         // java.lang 包类访问 
    3. Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class); 
    4. System.out.println(result1); 
    5.         //其他包类访问 
    6. String expression2 = "T(java.lang.Runtime).getRuntime().exec('open /Applications/Calculator.app')"
    7. Class<Object> result2 = parser.parseExpression(expression2).getValue(Class.class); 
    8. System.out.println(result2); 
    9.         //类静态字段访问 
    10. int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class); 
    11. System.out.println(result3); 
    12.         //类静态方法调用 
    13. int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class); 
    14. System.out.println(result4); 
    • 类实例化:类实例化同样使用 java 关键字「new」,类名必须是全限定名,但 java.lang 包内的类型除外,如 String、Integer。
    • instanceof 表达式:SpEL 支持 instanceof 运算符,跟 Java 内使用同义;如”‘haha’ instanceof T(String)”将返回 true。
    • 变量定义以及引用:变量定义通过 EvaluationContext 接口的 setVariable(variableName, value) 方法定义;在表达式中使用”#variableName”引用;除了引用自定义变量,SpE 还允许引用根对象及当前上下文对象,使用”#root”引用根对象,使用”#this”引用当前上下文对象;
    • 自定义函数:目前只支持类静态方法注册为自定义函数;SpEL 使用 StandardEvaluationContext 的 registerFunction 方法进行注册自定义函数,其实完全可以使用 setVariable 代替,两者其实本质是一样的

    四、审计过程

    这里拿 Spring Message 远程命令执行漏洞来作为例子

    1. 环境搭建

    1. git clone https://github.com/spring-guides/gs-messaging-stomp-websocket 
    2. git checkout 6958af0b02bf05282673826b73cd7a85e84c12d3 

    拿到项目代码,全局搜索一下 org.springframework.expression.spel.standard,发现 DefaultSubscriptionRegistry.java 文件处有导入。

    再搜索一下 SpelExpressionParser

    往下跟进发现如下关键代码,具体分析看代码注释

    1. @Override 
    2. protected void addSubscriptionInternal( 
    3. String sessionId, String subsId, String destination, Message<?> message) { 
    4. Expression expression = null
    5. MessageHeaders headers = message.getHeaders(); 
    6.         // 这里可以看出 SpEL 表达式 expression 是从 headers 中的 selector 字段中取出来 
    7. String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers); 
    8. if (selector != null) { 
    9. try { 
    10.                 //生成 expression 对象 
    11. expression = this.expressionParser.parseExpression(selector); 
    12. this.selectorHeaderInUse = true
    13. if (logger.isTraceEnabled()) { 
    14. logger.trace("Subscription selector: [" + selector + "]"); 
    15. catch (Throwable ex) { 
    16. if (logger.isDebugEnabled()) { 
    17. logger.debug("Failed to parse selector: " + selector, ex); 
    18.         // expression 传入 addSubscription 这个函数里面,即存放在 this.subscriptionRegistry 
    19. this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression); 
    20. this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId); 

    再搜索一下 this.subscriptionRegistry,看看有没有调用传进去的 expression。

    然后发现了!

    在这里调用了 this.subscriptionRegistry.getSubscriptions(sessionId) 并从中取出 info->sub-> expression。

    最关键的是,这里直接调用了 expression.getValue()!这说明如果能控制 SpEL 的表达式,就能直接命令执行!

    再来看看这个 filterSubscriptions 函数在哪里调用。从函数的调用回溯追踪调用链如下:

    1. filterSubscriptions -> findSubscriptionsInternal -> findSubscriptions -> sendMessageToSubscribers 

    2. sendMessageToSubscribers 即发送消息的功能

    回顾一下整个流程,SpEL 表达式从 headers 中 selector 获取,即发送请求时添加 selector 到请求的 header 即可传入,然后生成 expression 对象传入 this.subscriptionRegistry,然后当发送消息的时候,最终会直接从 this.subscriptionRegistry 取出并调用 expression.getValue() 执行我们传入的 SpEL 表达式。

    验证过程,在 expression.getValue() 这里打个断点,看看发送消息是否会拦截并查看调用链是否如上述分析一样。

    Bingo!

    简单总结一下 SpEL 表达式注入的分析思路,可以先全局搜索 org.springframework.expression.spel.standard, 或是 expression.getValue()、expression.setValue(),定位到具体漏洞代码,再分析传入的参数能不能利用,最后再追踪参数来源,看看是否可控。Spring Data Commons Remote Code Execution 的 SpEL 注入导致的代码执行同样可以用类似的思路分析。

    五、漏洞修复

    SimpleEvaluationContext、StandardEvaluationContext 是 SpEL 提供的两个 EvaluationContext:

    • SimpleEvaluationContext - 针对不需要 SpEL 语言语法的全部范围并且应该受到有意限制的表达式类别,公开 Spal 语言特性和配置选项的子集。
    • StandardEvaluationContext - 公开全套 SpEL 语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

    SimpleEvaluationContext 旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用,构造函数和 bean 引用;所以最直接的修复方式是使用 SimpleEvaluationContext 替换 StandardEvaluationContext。

    这是我个人学习代码审计过程中的小总结,可能逻辑性相对来说没那么严谨,但是个人觉得这是一个比较通俗易懂的分析方法,不喜勿喷。

    【编辑推荐】

    【责任编辑:赵宁宁 TEL:(010)68476606】

    点赞 0
    分享:
    大家都在看
    猜你喜欢

    订阅专栏+更多

    活学活用 Ubuntu Server

    活学活用 Ubuntu Server

    实战直通车
    共35章 | UbuntuServer

    228人订阅学习

    Java EE速成指南

    Java EE速成指南

    掌握Java核心
    共30章 | 51CTO王波

    87人订阅学习

    Mysql DBA修炼之路

    Mysql DBA修炼之路

    MySQL入门到高阶
    共24章 | 51CTO叶老师

    483人订阅学习

    读 书 +更多

    网络工程师必读——网络系统设计

    本书是一本真正意义上的网络系统设计图书,从网络系统设计角度全面介绍了整个网络系统设计的思路和方法,而不是像传统网络集成类图书那样主...

    订阅51CTO邮刊

    点击这里查看样刊

    订阅51CTO邮刊

    51CTO服务号

    51CTO播客

    广东11选五精准计划
  • 宝鸡市陈仓区党员干部助力夏收帮扶困难群体 2019-04-13
  • 滚滚而来的温暖!山坡上滚下万斤爱心榨菜头 2019-04-08
  • 中国科研团队刷新暗物质探测灵敏度 2019-04-08
  • 定陶战役:示弱于敌重点围歼的范例 2019-03-20
  • 里约奥运变化与机遇并存 充分准备迎接挑战 2019-03-20
  • 我国居民人均预期寿命又提高了 2017年提高至76.7岁 2019-03-17
  • 【三年决战奔小康】一封来自甘南精准扶贫户的感谢信 2019-03-17
  • 新时代新平台新机遇“一带一路”大型网络主题活动 2019-03-15
  • 学生睡觉摔骨折 法院主动出击促调解 2019-03-07
  • 总览传奇般的——波尔多八大酒庄柏图斯波尔多 2019-03-07
  • 它不会比如何做好一餐饭的难度大,或者道理更多。 2019-03-02
  • 高清:中国男篮抵达洛杉矶 长途飞行队员略显疲惫 2019-02-27
  • 新疆非法转基因玉米案:4被告获刑 近三千亩涉案玉米被铲 2018-11-22
  • 湖北治理违规提取公积金 防止用公积金炒房 2018-11-21
  • 罗品禧的专栏作者中国国家地理网 2018-11-20
  • 极速时时彩有猫腻吗 北京赛车软件 365体育投注 北京赛车微信投注群 腾讯彩票兑奖 双色球预测最准确 河南快赢481最近100期 北京快乐8官网 江西多乐彩新十一选五开奖结果查询 福彩幸运农场走势图表 28日新疆时时彩开奖号码 福利彩票36选7开奖结果 2013体彩排列3开奖号 代玩好运快3 新时时彩倍偷计算 生肖时时彩直一