【Java代码审计 | 第十八篇】未授权漏洞代码审计及鉴权方式说明

未经许可,不得转载。

在这里插入图片描述

Actuator未授权访问(CVE-2019-3778)

Spring Boot Actuator 是 Spring Boot 提供的一个强大的工具,用于帮助监控和管理 Spring Boot 应用程序。它提供了一系列的端点(Endpoints),通过这些端点可以获取应用程序的各种运行时信息,比如健康状况、性能指标、Bean 列表、环境变量等,还能对应用进行一些动态操作,如刷新配置等。

在基于 Maven 构建的项目里,若要引入 Spring Boot Actuator 功能,可在 pom.xml 文件中添加 spring-boot-starter-actuator 依赖,如下方截图所示:

在这里插入图片描述

同时需要在 application.yml 配置文件中,通过 “include” 设置 Actuator 端点:

在这里插入图片描述

如上图所示,exposure(译为 “暴露”)中开放了多个 Actuator 端点,即 info(应用信息)、health(健康检查)、beans(Bean 信息)、env(环境变量)、metrics(指标)、heapdump(堆转储)。这意味着外部用户可以直接访问这些端点获取敏感信息,存在较大的安全风险。

为了解决上述未授权访问敏感信息的问题,我们可以对 application.yml 进行安全配置,示例如下:

server:
  port: 8088
management:
  server:
    port: 8089  # 单独为管理端点设置端口,也可不单独设置
  endpoints:
    web:
      exposure:
        include: info, health  # 在网站上开放这两个端点
  security:
    enabled: true  # 启用安全控制
  endpoint:
    health:
      show-details: when-authorized  # 健康检查端点,仅授权用户可见详细信息
    info:
      show-details: when-authorized  # 应用信息端点,仅授权用户可见详细信息

这里需要配合 Spring Security 进行用户认证和角色分配。可以在 pom.xml 中添加 Spring Security 依赖,并创建相应的配置类来实现用户认证逻辑。

综上,审计思路为:在 pom.xml 中搜索 spring-boot-starter-actuator。

Swagger未授权访问(CNVD-2021-30167)

Swagger 是一种广泛使用的 API 文档生成工具,它能够自动化地生成并展示 REST API 的文档界面。

Spring Boot 项目中,通常通过 springfox-swagger2 或 springdoc-openapi 来集成 Swagger。通过 Swagger 提供的 UI,开发人员能够方便地查看和测试 API。

在基于 Maven 构建的项目中,如果想要集成 Swagger UI,可以通过添加相应的依赖来实现。例如,使用 springfox-swagger2 的方式如下:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

然而,如果没有做适当的安全配置,外部用户可以直接访问应用程序 API 接口,导致敏感信息泄露等安全隐患。

我们可以通过创建一个安全配置类,来限制对 Swagger UI 的访问。通常可以配置只允许特定角色的用户访问 Swagger UI 页面和 API 文档。

示例代码如下:

@Configuration
public class SwaggerSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/swagger-ui/**", "/v2/api-docs/**").hasRole("ADMIN")  // 只允许 ADMIN 角色访问
            .anyRequest().authenticated()
            .and()
            .httpBasic();  // 配置基本认证
    }
}

综上,审计思路为:在 pom.xml 中搜索 springfox-swagger-ui。

Druid未授权访问(CNVD-2019-20041)

Druid 是阿里巴巴开源的数据库连接池,它不仅提供了高性能的数据库连接管理,还集成了监控功能,可以通过 Web 界面查看 SQL 执行情况、监控数据库连接、查看慢查询等信息。

在 Spring Boot 项目中,通常通过引入 druid-spring-boot-starter 依赖来集成 Druid 连接池:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.18</version>
</dependency>

默认情况下,Druid 会提供一个管理页面,访问地址为:

http://localhost:8080/druid

如果未做访问限制,任何人都可以直接访问该页面,查看数据库连接池信息、SQL 执行情况,甚至可能会被利用执行 SQL 注入。

解决方案:

1、在 application.yml 配置文件中,设置 Druid 监控页面的用户名和密码:

spring:
  datasource:
    druid:
      stat-view-servlet:
        enabled: true  # 启用Druid监控页面
        login-username: aduTsrxun  # 设置登录用户名
        login-password: aY5is/1R@2n13  # 设置登录密码

同时在该文件中配置 allow 和 deny 参数,限制只有特定 IP 可以访问:

spring:
  datasource:
    druid:
      stat-view-servlet:
        allow: 192.168.1.100,127.0.0.1  # 仅允许这些IP访问
        deny: 0.0.0.0/0  # 拒绝所有其他IP

除了 Druid 自带的用户认证机制,还可以使用 Spring Security 限制对 /druid/** 相关路径的访问:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/druid/**").hasRole("ADMIN")  // 只有ADMIN角色可以访问Druid管理页面
            .anyRequest().authenticated()
            .and()
            .httpBasic();  // 启用基本认证
    }
}

综上,审计思路为:在 pom.xml 中搜索 druid-spring-boot-starter。

鉴权流程

在 Web 应用中,权限控制(鉴权)是确保系统安全性的关键环节。Spring Boot 提供了多种鉴权方式。

1. 过滤器 (Filter) 进行鉴权

javax.servlet.Filter 接口中的 doFilter 方法用于拦截请求,执行鉴权逻辑。如果请求路径匹配 Filter 配置的拦截规则,则会执行 doFilter 方法,并根据用户权限决定是否放行。

整体流程如下:
1、在过滤器中获取 HttpServletRequest(拦截客户端请求),并解析请求的 Session 或 Token。
2、检查 Session 是否有效,或者解析 Token 以获取用户身份信息。
3、判断用户是否具有访问该资源的权限。
4、放行请求,允许访问目标资源。
5、重定向到登录页或返回 403 Forbidden 响应。

示例代码如下:

@WebFilter(urlPatterns = "/*")  // 拦截所有请求
public class AuthFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        
        // 获取 Session
        HttpSession session = req.getSession(false); // 不创建新 Session
        if (session == null || session.getAttribute("user") == null) {
            // 未登录或 Session 失效,重定向到登录页
            res.sendRedirect("/login");
            return;
        }
        
        // Session 存在,用户已登录,继续请求
        chain.doFilter(request, response);
    }
}

2. 基于拦截器 (HandlerInterceptor) 进行鉴权

除了 Filter,Spring 还提供了 HandlerInterceptor 进行请求拦截。拦截器适用于 Spring MVC 处理流程,它可以在请求到达 Controller 之前进行身份校验。

示例代码如下:

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute("user") == null) {
            response.sendRedirect("/login");
            return false;  // 拦截请求
        }
        return true; // 继续执行
    }
}

3. JWT认证

相比 Session,使用 JWT(JSON Web Token) 可以减少服务器存储负担。

具体流程为:
1、用户登录后,服务器生成 JWT,并返回给客户端。
2、之后的每个请求,客户端都在 Authorization 头部携带 JWT。
3、服务器解析 JWT,判断用户身份和权限。

以下图为例,左侧为客户端在 Authorization 头部携带的 JWT,JWT发送到服务器端后,服务器端对其进行解密,得到右侧结果:

在这里插入图片描述

鉴权的示例代码如下:

String token = request.getHeader("Authorization");
if (token == null || !JwtUtil.verifyToken(token)) {
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    return;
}

4. 案例(1)

如下图所示,代码通过判断请求 URI 是否包含"/admin/login" 或 “/admin/account” 来决定是否直接放行,因此可以实现未授权访问或越权等操作:

在这里插入图片描述

5. 案例(2)

在讲解本节案例之前,我们需要先了解一个关键点:在进行权限判断时,禁止使用 request.getRequestURI() 直接获取路径,否则可能会引发权限绕过

为什么?
getRequestURI() 方法返回的是未经服务器端处理的原始路径,原始路径中可能包含特殊字符、路径跳转或其他恶意构造的请求,从而绕过服务器端的安全控制,导致未授权访问。

从下图可以看出,第 23 行代码使用了 getRequestURI() 方法来获取请求路径,而第 24 行则通过 uri.startsWith("/admin") 来判断路径是否以 /admin 开头。同时还检查了 Session 中的 loginUser 属性是否为空,并将这两个条件通过 && 进行逻辑判断。如果两个条件都为 true,则触发权限控制逻辑,提示用户需要登录,并重定向到后台登录页面。

在这里插入图片描述

如果能够让其中一个条件变为 false,则可以规避权限检查,继续访问受保护的资源。

在这两个条件中,Session 的部分通常不可控,但 uri.startsWith("/admin") 的判断逻辑可利用的。由于 getRequestURI() 获取的是未经服务器处理的原始路径,我们可以利用特殊字符(如分号 ;、额外的斜杠 /)来绕过路径匹配规则,构造特殊路径:

/;/admin/test

///admin/test

这些路径在 getRequestURI() 解析时不会被识别为以 /admin 开头,从而使 uri.startsWith("/admin") 返回 false,然而,在服务器实际解析请求时,特殊路径被解析为 /admin/test ,从而造成权限绕过问题。

修复建议:
使用 getRequestURL() 方法处理原始路径,它返回的是完整且经过服务器处理后的 URL,可以有效减少因特殊字符、路径跳转等问题导致的安全风险,确保权限判断更加准确和可靠。

shiro鉴权方式

Shiro是Java的一个安全框架,提供了身份验证、授权、加密和会话管理等功能。

在鉴权方面,Shiro主要有以下两种方式:

1、通过ShiroConfig文件进行配置,可在配置文件中定义角色、权限以及它们与用户的关联关系等,从而实现鉴权逻辑。这种方式适用于配置相对稳定的场景,通过配置文件集中管理权限信息,便于维护和查看整体的权限架构。

2、借助注解实现鉴权。注解方式更加灵活,可直接在代码中针对方法进行权限控制,增强了代码的可读性和维护性 。

例如,@RequiresRoles注解用于角色验证,当指定logical = Logical.AND时,如@RequiresRoles(value = {"admin", "editor"}, logical = Logical.AND),表示用户必须同时具备admineditor两个角色才能访问相关资源;

@RequiresPermissions注解用于权限验证,当指定logical = Logical.OR时,如@RequiresPermissions(value = {"user.add", "user.del"}, logical = Logical.OR),表示用户拥有user.adduser.del其中一个权限即可访问对应资源。

Spring Security鉴权方式

Spring Security 可以通过 http.authorizeRequests() 方法来对 Web 请求进行授权保护。http.authorizeRequests() 是一个配置方法,用来启用基于 URL 的访问控制策略。它的作用是告诉 Spring Security,你需要对 HTTP 请求进行授权保护,并为不同的 URL 设置不同的访问权限。这个方法通常用于链式配置,后续可以通过不同的匹配器来设置具体的授权规则。

antMatchers() 方法用于匹配 URL 路径并进行授权控制。

示例配置:

http
    .authorizeRequests()
    .antMatchers("/qiushuo/**").hasRole("admin")  // 访问 /qiushuo/ 路径必须具备 admin 角色
    .antMatchers("/security/**").hasRole("user")  // 访问 /security/ 路径必须具备 user 角色
    .antMatchers("/permitAll").permitAll()  // 访问 /permitAll 路径不需要认证
    .anyRequest().authenticated()  // 除前面定义的 URL 外,其他路径需要认证才能访问
    .and()
    .formLogin();  // 启用表单登录功能,用户可以通过登录界面进行身份验证。

在使用 antMatchers() 时,配置顺序会直接影响到授权的效果。较为具体的路径配置应放在前面,较为笼统的配置应放在后面。Spring Security 会按照配置的顺序进行匹配,并执行第一个匹配成功的规则。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋说

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值