参数名双重编码绕过
在 Spring Boot 或其他 Web 框架中,双重 URL 编码绕过参数验证 是一个常见的安全问题,主要与 解析顺序、不同组件的解码逻辑 以及 不一致的 URL 规范 有关。以下是详细分析:
1. 为什么双重编码可以绕过参数名的判断规则?
通常,Web 应用中的 URL 解析涉及多个组件(如浏览器、Web 服务器、应用服务器),它们可能在 不同阶段进行 URL 解码,导致安全检查与实际执行路径不一致。例如:
- 应用服务器(如 Tomcat、Nginx) 可能先解码一次,再把请求交给 Spring Boot 处理。
- Spring MVC 处理
@RequestParam
或@PathVariable
时会再解码一次。 - 如果某些安全检查(黑名单规则)在第一步之前执行,攻击者就可以利用双重编码绕过这些检查。
绕过方式示例
假设有一个 Web 应用,它会拦截 ../
以防止目录遍历攻击:
| @GetMapping("/files/{filename}") |
| public String getFile(@PathVariable String filename) { |
| if (filename.contains("../")) { |
| return "非法路径"; |
| } |
| return "读取文件:" + filename; |
| } |
如果攻击者直接访问:
GET /files/../etc/passwd
服务器会发现 ../
,然后拦截请求,返回 "非法路径"。
但如果攻击者使用双重编码呢?
GET /files/%252E%252E%252Fetc/passwd
解码过程:
-
Spring 解析 PathVariable 时会执行一次解码
:
%252E%252E%252F → %2E%2E%2F
-
应用逻辑执行检查(但此时
../
仍然是%2E%2E%2F
,不会被拦截)。 -
服务器读取文件时可能再解码一次
:
%2E%2E%2F → ../
-
最终导致目录遍历攻击生效!
2. 不同阶段的 URL 解码导致绕过
不同 Web 组件的 URL 解析顺序不同,会导致安全检查无效。以下是常见的 URL 解析顺序:
- 浏览器或攻击者构造 URL
- 可能使用单重或双重编码。
- Web 服务器(Nginx/Tomcat)
- 可能解码 一次(部分服务器会直接解析 URL)。
- Spring Boot 应用
@RequestParam
/@PathVariable
解析时会自动解码一次。
- 内部文件读取或 API 处理
- 可能解码第二次。
如果检查逻辑在第一层解码之前执行,而最终访问路径的解析在第二层解码之后发生,就可能导致绕过。
3. 其他常见的双重编码绕过方式
(1) /
(斜杠)绕过
某些 Web 应用可能会阻止 /
作为路径访问符号:
- 直接访问
/admin/config
被拦截。 - 但
GET /admin%2Fconfig
可能仍然有效(因为%2F
可能被后端解码为/
)。
变种:
/admin%252Fconfig → /admin%2Fconfig → /admin/config
可能绕过安全检查。
(2) SQL 注入绕过
有些 Web 应用会检查 SQL 关键字,但如果只检查一次解码后的参数,可能被绕过:
?id=%2527%2520OR%25201%253D1%2520--%2520
解码:
%2527%2520OR%25201%253D1%2520--%2520
→%27 OR 1=1 --
%27 OR 1=1 --
→' OR 1=1 --
(SQL 注入生效)
(3) 认证绕过
如果 API 通过 @RequestParam
获取 user
:
| @GetMapping("/api") |
| public String api(@RequestParam String user) { |
| if ("admin".equals(user)) { |
| return "无权访问"; |
| } |
| return "Hello " + user; |
| } |
攻击者使用:
GET /api?user=%2561%2564%256D%2569%256E
解码:
%2561%2564%256D%2569%256E
→%61%64%6D%69%6E
%61%64%6D%69%6E
→admin
如果检查发生在第一步前,就可能绕过认证。
4. 如何防止双重编码绕过?
(1) 统一解码
确保所有 输入验证 和 业务逻辑 都基于 最终解码后的值:
| import java.net.URLDecoder; |
| import java.nio.charset.StandardCharsets; |
| |
| @GetMapping("/files/{filename}") |
| public String getFile(@PathVariable String filename) { |
| // 先完整解码 |
| String decoded = URLDecoder.decode(filename, StandardCharsets.UTF_8); |
| |
| // 统一检查解码后的字符串 |
| if (decoded.contains("../")) { |
| return "非法路径"; |
| } |
| return "读取文件:" + decoded; |
| } |
🔹 这样,即使攻击者使用 %252E%252E%252F
,解码后仍然会被拦截。
(2) 服务器层面限制
-
在
Nginx / Tomcat
配置中禁止双重编码:
1
location / {
2
set $block_extensions "..";
3
if ($request_uri ~ $block_extensions) {
4
return 403;
5
}
6
}
-
Tomcat 关闭二次解码
1
server.tomcat.relaxed-query-chars=<>[]^`{|}
2
server.tomcat.relaxed-path-chars=<>[]^`{|}
(3) 使用 WAF(Web 应用防火墙)
例如:
-
ModSecurity 规则:
SecRule ARGS "@rx %25[0-9a-fA-F]{2}" "id:1001,phase:2,deny,msg:'Double URL Encoding Attack'"
阻止
%%
形式的编码。
5. 总结
攻击手法 | 原理 | 防御方案 |
---|---|---|
双重编码 | 先绕过检查,再解码后攻击 | 统一解码并检查 |
路径遍历 | %252E%252E%252F 变成 ../ | 过滤最终解码后的值 |
SQL 注入 | %2527 OR 1=1 -- 变成 ' OR 1=1 -- | 使用预编译SQL |
认证绕过 | user=%2561%2564%256D%2569%256E 变成 admin | 统一编码解析 |
双重编码绕过验证的关键在于 安全检查和实际解析的解码顺序不一致,所以最好的方法是: ✅ 先解码,再校验!
✅ 尽量避免多个组件对 URL 进行不同解析!
✅ 使用 Web 防火墙(WAF)等安全措施!