JavaEE代码审计-鉴权逻辑 #
拦截器
Interceptor是一种拦截器,也称之为拦截器链(Interceptor Chain),主要用于拦截请求、响应或处理过程中的某些事件,比如权限认证、日志记录、性能测试等。在 Java 中,Interceptor可以用来扩展框架,增加或修改某个方法的行为,或者对应用流程做些前置处理、后置处理、环绕处理等。
过滤器
Filter被称为过滤器,过滤器实际上就是对Web资源进行拦截,做一些处理后再交给下一个过滤器或Servlet处理,通常都是用来拦截request进行处理的,也可以对返回的 response进行拦截处理。开发人员利用filter技术,可以实现对所有Web资源的管理,例如实现权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
不是在代码中
他这是网站启动就开始了
好处:
减少代码重复性:
全局统一处理:
提前拦截,提升性能:
拦截器在请求到达业务代码(如 Controller、Service)之前就拦截非法请求(如未登录、参数格式错误),避免无效的业务处理,减少服务器资源消耗。
推荐两个进行互补
通常建议 **“拦截器做全局通用校验,代码做业务专属校验”**,优势互补:
案例一:newbee-mall- #
可以发现源码中这种过滤器的代码而且写了是admin字段的过滤
preHandle是固定的写法和dofilter一样不是自己定义的 优先查看代码
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
String uri = request.getRequestURI();
if (uri.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;
}
}
preHandle:请求处理之前执行(如登录校验、权限判断);postHandle:请求处理之后、视图渲染之前执行(如修改模型数据、添加全局参数);afterCompletion:整个请求完成后执行(如资源清理、日志记录)。
绕过方法
/;/admin或//admin
//admin 匹配就为空了就饶过了逻辑 但是又正常解析admin界面
/;/admin 分号 ; 在 URI 中是 “路径参数分隔符”(RFC 规范),服务器会忽略分号及后续内容(或仅作为参数处理),因此 /;/admin 会被规范化为 /admin,实际访问的是 /admin 资源
案例二:medicine-mangement系统 #
yiyaoguanlixitong/admin/dist/index.html#/login
代码说明了
有这三个开头则放行
/dictionary/page
/file/upload
/yonghu/register
IgnoreAuth 带有这个的直接放行 比如 访问Login不需要鉴权
@IgnoreAuth
@RequestMapping(value = "/login")
public R login(String username, String password, String captcha, HttpServletRequest request) {
YuangongEntity yuangong = yuangongService.selectOne(new EntityWrapper<YuangongEntity>().eq("username", username));
if(yuangong==null || !yuangong.getPassword().equals(password))
return R.error("账号或密码不正确");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String servletPath = request.getServletPath();
if("/dictionary/page".equals(request.getServletPath()) || "/file/upload".equals(request.getServletPath()) || "/yonghu/register".equals(request.getServletPath()) ){//请求路径是字典表或者文件上传 直接放行
return true;
}
//支持跨域请求
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,request-source,Token, Origin,imgType, Content-Type, cache-control,postman-token,Cookie, Accept,authorization");
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
IgnoreAuth annotation;
if (handler instanceof HandlerMethod) {
annotation = ((HandlerMethod) handler).getMethodAnnotation(IgnoreAuth.class);
} else {
return true;
}
//从header中获取token
String token = request.getHeader(LOGIN_TOKEN_KEY);
/**
* 不需要验证权限的方法直接放过
*/
if(annotation!=null) {
return true;
}
http://192.168.56.1:8080/yiyaoguanlixitong/file/upload/../../admin/dist/index.html
比如我们找到了一个users下面的resetpass 因为它是带IgnoreAuth的 而且直接GET传入username直接越权
IgnoreAuth
@RequestMapping(value = "/resetPass")
public R resetPass(String username, HttpServletRequest request){
UsersEntity user = usersService.selectOne(new EntityWrapper<UsersEntity>().eq("username", username));
if(user==null) {
return R.error("账号不存在");
}
user.setPassword("123456");
usersService.update(user,null);
return R.ok("密码已重置为:123456");
}
案例三:华夏ERP-过滤器 #
init是初始化不用看主要看DOFILTER
requestUrl != null && (requestUrl.contains("/login.html") || requestUrl.contains("/register.html
看这个如果有register.html就可以了那么我们直接构造跳过就行了
@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();
//具体,比如:处理若用户未登录,则跳转到登录页
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;
/account/getAccount
浏览器不行换BP可以
同时他还判断请求JS CSS可以
initParams = {@WebInitParam(name = "ignoredUrl", value = ".css#.js#.jpg#.png#.gif#.ico"),
@WebInitParam(name = "filterPath",
if (verify(ignoredList, requestUrl)) {
chain.doFilter(servletRequest, response);
return;
}
案例四:天猫-过滤器 #
直接../../绕过就行了
@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);
}
}