Java Web应用权限绕过漏洞分析与防护指南
0x00 前言
背景概述
在国内Web项目中,开发者常使用过滤器(Filter)或拦截器(Interceptor)等自定义方法实现统一鉴权。然而,由于对请求路径获取API的错误使用或混用,导致权限校验存在安全盲点。攻击者可利用路径解析差异构造特殊请求绕过鉴权机制。
漏洞影响范围
- 影响组件:某微、某凌、某远等主流OA/CMS系统
- 技术根源:Spring框架路径解析特性与自定义鉴权逻辑不匹配
- 实战价值:攻防演练中高频出现的高危漏洞类型
0x01 常见鉴权方案分析
Java鉴权方案对比表
| 框架/方式 | 特性 | 基于什么鉴权 | 适用场景 |
|---------|------|------------|---------|
| 自定义Servlet Filter | 全局请求入口、可拦截静态资源 | Session/Token(自解析) | 自定义认证逻辑、中央鉴权 |
| Spring HandlerInterceptor | 控制器级拦截、路由粒度 | 依赖上游认证 | URL级鉴权、审计 |
| AOP自定义方法拦截 | 业务方法最靠近,支持复杂上下文 | 基于ThreadLocal/Token的主体信息 | 细粒度业务授权 |
| Spring Security | 完整过滤器链、方法级注解 | Session/JWT/OAuth2 tokens | 现代Spring Boot应用 |
| Apache Shiro | 简洁Subject/Realm/Session | Session/RememberMe/Token | 轻量应用、非Spring项目 |
自定义鉴权实现详解
1. Servlet Filter实现
配置类:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean<VulnerableAuthFilter> vulnerableAuthFilter() {
FilterRegistrationBean<VulnerableAuthFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new VulnerableAuthFilter());
registrationBean.addUrlPatterns("/admin/*");
return registrationBean;
}
}
过滤器逻辑:
@WebFilter("/*")
public class AuthFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String authHeader = httpRequest.getHeader("Authorization");
if (authHeader == null || !authHeader.equals("BypassVulnDemo")) {
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.getWriter().write("Filter: Authentication required");
return;
}
chain.doFilter(request, response);
}
}
2. Spring MVC拦截器实现
配置类:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new VulnerableInterceptor())
.addPathPatterns("/secure/**");
}
}
拦截器逻辑:
public class AuthzInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.equals("BypassVulnDemo")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Unauthorized: Authentication required");
return false;
}
return true;
}
}
3. AOP切面实现
自定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireAuth {
}
切面逻辑:
@Aspect
@Component
public class AopConfig {
@Around("@annotation(com.bypassvuln.config.RequireAuth)")
public Object checkAuth(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.equals("BypassVulnDemo")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Unauthorized: Authentication required");
return null;
}
return joinPoint.proceed();
}
}
0x02 请求路径获取方法分析
路径获取API对比表(Spring-Web 5.3.20)
| 类别 | 方法 | 描述 | 安全特性 |
|------|------|------|----------|
| HttpServletRequest | getRequestURL() | 返回完整URL | 原始输入,无处理 |
| | getRequestURI() | 返回协议名到查询字符串间部分 | 原始输入,无处理 |
| | getServletPath() | 返回servlet路径 | 删除;后内容、URL解码、解析../ |
| | getPathInfo() | 返回额外路径信息 | 一般为null |
| Spring HandlerMapping | BEST_MATCHING_PATTERN_ATTRIBUTE | 返回Controller配置路径 | 强关联匹配资源 |
| | PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE | 处理器映射内路径 | 删除;后内容,不解码 |
| Spring UrlPathHelper | getPathWithinApplication() | 应用内路径 | 删除;后内容、URL解码 |
路径解析示例
请求:http://localhost:18083/app/path-demo/show///%2e%2e/../admin;.js
解析结果:
{
"getRequestURL()": "http://localhost:18083/app/path-demo/show///%2e%2e/../admin/dashboard;.js",
"getRequestURI()": "/app/path-demo/show///%2e%2e/../admin/dashboard;.js",
"getServletPath()": "/admin/dashboard",
"HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE": "/path-demo/show/**",
"HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE": "/path-demo/show///%2e%2e/../admin/dashboard"
}
0x03 权限绕过技术深度分析
场景一:startsWith匹配绕过
漏洞代码:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestUri = request.getRequestURI();
// 漏洞:使用startsWith匹配,可被../绕过
if (requestUri.startsWith("/swagger")) {
return true;
}
// 鉴权逻辑...
}
绕过Payload:
/swagger/../app/secure/data/swagger/..;666666/app/secure/data/swagger/////%2e%2e;666666/////%61%70%70/secure/data
技术原理:
getRequestURI()返回原始未规范化路径- Tomcat的
CoyoteRequest保存原始请求对象 - Spring MVC在
HandlerMapping阶段进行路径规范化 - 上下文路径配置影响路径解析结果
场景二:endsWith匹配绕过
漏洞代码:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String requestUrl = httpRequest.getRequestURL().toString();
// 漏洞:使用endsWith匹配,可被;.js绕过
if (requestUrl.endsWith(".js")) {
chain.doFilter(request, response);
return;
}
// 鉴权逻辑...
}
绕过Payload:
/app/admin/dashboard;.js/app/admin;666666/dashboard;6666666;;;.js
技术原理:
- 分号
;标识矩阵参数,Spring会删除;至/间内容 - 无
/时删除;后所有内容 - 规范化后仍能匹配到目标Controller
场景三:contains匹配绕过
漏洞代码:
@Around("@annotation(com.bypassvuln.config.RequireAuth)")
public Object checkAuth(ProceedingJoinPoint joinPoint) throws Throwable {
String requestUri = request.getRequestURI();
// 漏洞:使用contains匹配
if (requestUri.contains("swagger")) {
return joinPoint.proceed();
}
// 鉴权逻辑...
}
绕过Payload:
/swagger/../app/api/sensitive/app/api/sensitive;swagger/app/api;swagger/sensitive
场景四:后缀匹配模式绕过(历史漏洞)
背景:
- Spring 5.3之前默认开启
useSuffixPatternMatch - 5.3开始默认关闭,需手动配置
配置开启:
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
}
绕过效果:
/upload.swagger等效于/upload
场景五:强制性白名单绕过
漏洞代码:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String requestUri = httpRequest.getRequestURI();
// 漏洞:强制性检查.do/.jsp后缀
if (requestUri.endsWith(".do") || requestUri.endsWith(".jsp")) {
// 执行鉴权
} else {
// 直接放行
chain.doFilter(request, response);
}
}
绕过Payload:
/system/dashboard.do/(结尾加/)/system/dashboard.do;(结尾加分号)
场景六:equals精确匹配绕过
漏洞代码:
String requestUri = httpRequest.getRequestURI();
if (requestUri.equals("/app/system/admin")) {
// 执行鉴权
}
绕过Payload:
- URL编码:
/app/system/%61%64%6d%69%6e
Spring 5.3.20路径解析特性总结
| 特性 | context-path | controller-path | 说明 |
|------|-------------|-----------------|------|
| ;参数处理 | 支持 | 支持 | 删除;至/间内容 |
| ../解析 | 支持 | 不支持 | 需配置context-path |
| URL解码 | 支持 | 支持 | 规范化阶段处理 |
| 多个/处理 | 不支持 | 不支持 | 保持原样 |
0x04 安全防护机制
Spring Security StrictHttpFirewall
默认防护规则
- HTTP方法检查:仅允许GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS
- URL阻止列表:
- 编码类禁止:
%25、%2e、%2E - 字符类禁止:
%、\u2028、\u2029、;、%2f、//、\、%00等
- 编码类禁止:
- 路径规范化检查:检测
..、.等非规范化路径 - 非打印字符检测:拒绝包含非打印ASCII字符的请求
防护效果示例
引入依赖即可启用:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
自定义配置(不推荐生产环境)
@Bean
public HttpFirewall relaxedFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowSemicolon(true);
firewall.setAllowUrlEncodedSlash(true);
firewall.setAllowUrlEncodedPeriod(true);
return firewall;
}
安全开发建议
1. 使用一致的路径获取API
- 鉴权阶段使用
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE - 避免混用
getRequestURI()和getServletPath()
2. 路径规范化处理
private String normalizePath(String path) {
return UriUtils.decode(path, StandardCharsets.UTF_8)
.replace("//", "/")
.replace("/../", "/")
.replace("/./", "/");
}
3. 使用Spring Security最佳实践
- 优先使用Spring Security而非自定义鉴权
- 保持StrictHttpFirewall默认配置
- 使用方法级注解
@PreAuthorize进行细粒度控制
4. 安全测试用例
@Test
public void testPathBypassVulnerabilities() {
// 测试各种绕过Payload
String[] testPaths = {
"/admin/../secure/data",
"/api/sensitive;swagger",
"/system/dashboard.do/",
"/app/system/%61%64%6d%69%6e"
};
for (String path : testPaths) {
mockMvc.perform(get(path))
.andExpect(status().isForbidden());
}
}
漏洞检测清单
-
代码审计重点:
- 查找自定义Filter/Interceptor中使用
getRequestURI()、startsWith()、endsWith()、contains()的代码 - 检查路径匹配逻辑是否进行规范化处理
- 确认是否使用Spring Security及其配置
- 查找自定义Filter/Interceptor中使用
-
黑盒测试Payload:
- 目录穿越:
/path/../target、/path/..;/target - 分号参数:
/target;bypass、/target;/bypass - URL编码:
/%74%61%72%67%65%74 - 后缀变异:
/target.js/、/target.jsp/
- 目录穿越:
总结
Java Web应用权限绕过漏洞源于路径解析差异与鉴权逻辑不匹配。深入理解Spring框架路径处理机制、严格统一路径获取方式、采用安全框架默认防护是有效防护的关键。开发人员应避免使用字符串匹配进行路径鉴权,安全人员需掌握各种绕过技术以进行全面安全测试。