开放重定向与路径遍历
概述
开放重定向(Open Redirect) 和 路径遍历(Path Traversal) 是两类经典但极易被忽视的 Web 安全漏洞。开放重定向允许攻击者将用户重定向到任意外部域名,常被用于钓鱼攻击和社会工程学;路径遍历则允许攻击者突破目录限制,读取服务器上的任意文件,导致敏感信息泄露。二者虽漏洞类型不同,但根源往往相似——缺乏对用户输入的充分校验。
本文将系统性地阐述这两种漏洞的原理、攻击方式、绕过技巧以及防御方案,并辅以 Java、Python、PHP 三种语言的代码示例,帮助开发者和安全工程师在日常编码与代码审计中准确识别和修复这两类风险。
一、开放重定向
1.1 原理与危害
开放重定向漏洞的产生条件是:应用程序中存在接受用户输入 URL 参数并执行 301/302 跳转的逻辑,且未对该 URL 进行充分校验。
典型的攻击场景如下:
// Java —— 存在开放重定向漏洞的代码
@GetMapping("/redirect")
public String redirect(@RequestParam("url") String url) {
return "redirect:" + url;
}攻击者可以构造如下链接,诱导用户点击:
https://example.com/redirect?url=https://malicious.com/fake-login用户看到的域名是 example.com(可信站点),实际却被跳转到了攻击者控制的钓鱼页面。
主要危害
| 危害类型 | 描述 |
|---|---|
| 钓鱼攻击 | 利用可信域名的跳转链接,诱导用户访问仿冒登录页面,窃取凭证 |
| 社会工程学 | 结合短链接服务隐藏恶意 URL,提高用户信任度 |
| 绕过 URL 校验 | 许多安全系统只检查请求的 Referer 或原始域名,跳转后绕过检查 |
| 声誉损害 | 第三方可利用合法域名的开放重定向作为跳板实施恶意行为,影响品牌声誉 |
钓鱼链接构造示例
攻击者可以将恶意 URL 伪装成 OAuth 授权回调或密码重置跳转:
https://trusted-bank.com/login?redirect_uri=https://trusted-bank.com.malicious.com/普通用户只会在地址栏看到 trusted-bank.com 开头,容易放松警惕。
1.2 常见绕过方式
即使开发者对重定向 URL 做了简单校验,攻击者仍然有多种绕过手段。
1.2.1 @ 符号绕过
URL 中的 @ 符号表示登录信息分隔符,浏览器会忽略 @ 之前的内容。
https://trusted.com@evil.com/path解析后实际访问的是 evil.com/path。如果后端使用 startsWith("https://trusted.com") 校验,则会被绕过。
1.2.2 // 协议跳转
某些浏览器和库会将 // 开头的 URL 解释为当前协议下的绝对路径。
https://example.com/redirect?url=//evil.com等价于 https://evil.com,绕过仅检查 http 或 https 协议前缀的校验。
1.2.3 URL 编码双写
攻击者对关键字符进行双重 URL 编码,绕过简单解码逻辑。
https://example.com/redirect?url=https%3A%2F%2Fevil.com // 单次编码
https://example.com/redirect?url=https%253A%252F%252Fevil.com // 双重编码如果后端只解码一次,双重编码的字符会逃过检查,到达浏览器时被二次解码生效。
1.2.4 CRLF 注入
通过注入回车换行符(%0d%0a)将恶意 URL 拆分成 HTTP 响应头,实现 HTTP 响应头分割(CRLF Injection),进而控制重定向目标。
https://example.com/redirect?url=%0d%0aLocation:%20https://evil.com1.2.5 javascript: 伪协议
在不需要 HTTP 跳转、仅由前端 window.location 控制的场景下,攻击者可注入 javascript: 伪协议:
https://example.com/redirect?url=javascript:alert('XSS')若后端校验仅判断字符串长度或包含特定字符,则 javascript: 伪协议可绕过限制。
1.3 开放重定向防御方案
1.3.1 白名单校验(推荐)
构建允许跳转的域名白名单,仅放行白名单内的 URL。
// Java —— 白名单校验
@GetMapping("/redirect")
public String redirect(@RequestParam("url") String url) {
List<String> whitelist = Arrays.asList("trusted.com", "sub.trusted.com");
try {
URI uri = new URI(url);
String host = uri.getHost();
if (host != null && whitelist.contains(host)) {
return "redirect:" + url;
}
} catch (URISyntaxException e) {
// URL 格式异常,拒绝跳转
return "redirect:/error";
}
return "redirect:/error";
}1.3.2 以 / 开头的相对路径判断
如果业务只允许站内跳转,可以校验 URL 是否以 / 开头,拒绝所有包含 :// 的输入。
# Python —— 仅允许相对路径跳转
def safe_redirect(url: str) -> str:
if url.startswith("/") and not url.startswith("//"):
return url
return "/"但需注意,//evil.com 也以 // 开头,需额外排除双斜杠。
1.3.3 前端 window.location 安全设置
在前端使用 window.location 进行跳转时,应避免将用户输入直接拼接:
// 不安全
window.location.href = userInput;
// 安全:使用 URL 构造函数校验
function safeRedirect(input) {
try {
const url = new URL(input, window.location.origin);
// 如果 url.origin 不在白名单中,拒绝跳转
if (allowedOrigins.includes(url.origin)) {
window.location.href = url.href;
}
} catch (e) {
// URL 格式无效
}
}通过 URL 构造函数解析后,浏览器会自动处理编码和 @ 符号等绕过手段,得到的 origin 是可信的。
1.3.4 其他防御措施
- URL 规范化:使用标准库(如 Java
java.net.URI、Pythonurllib.parse)对输入做规范化处理,解码后再做校验 - 提示用户:在跳转前显示目标域名,要求用户确认
- 使用跳转中间页:在可信域名下显示"即将离开本站"的中间页面,明确告知用户目标地址
- 不直接使用用户输入作为重定向参数:改用内部映射 ID,如
redirect_to=login_page,而非直接传递 URL
二、路径遍历
2.1 原理与危害
路径遍历(Path Traversal),也称目录遍历(Directory Traversal),是指攻击者利用 ../ 等路径符号从应用程序的受限制目录逃逸,访问服务器文件系统上任意文件的漏洞。
典型漏洞代码
<!-- PHP —— 存在路径遍历漏洞的代码 -->
<?php
$file = $_GET['file'];
include("pages/" . $file);
?>攻击者可传入如下参数读取系统敏感文件:
https://example.com/index.php?file=../../etc/passwd最终拼接后实际读取的是 pages/../../etc/passwd,等价于 /etc/passwd。
主要危害
| 危害类型 | 描述 |
|---|---|
| 敏感信息泄露 | 读取配置文件、数据库凭据、源码文件、系统密码文件等 |
| 源码泄露 | 获取应用程序源码,为后续挖洞提供基础 |
| 任意文件读取 | 结合文件包含漏洞,可进一步发展为 RCE(远程命令执行) |
| 权限提升 | 通过读取 /etc/shadow、.ssh/id_rsa 等获得更高级别访问权限 |
2.2 路径遍历绕过技巧
开发者常用黑名单过滤 ../,但攻击者有多种绕过方案。
2.2.1 URL 编码双写 ../
对 ../ 中的点号或斜杠进行 URL 编码:
%2e%2e%2f → ../
%2e%2e/ → ../
..%252f → 双重编码后的 ../
%2e%2e%5c → ..\(Windows)如果后端只做一次 URL 解码,编码字符可绕过简单字符串匹配。
2.2.2 / 分隔符绕过
在 Windows 环境下,可使用反斜杠 \ 替代正斜杠 /:
..\..\..\windows\system32\config\sam某些解析器对 \ 与 / 的包容性不同,可导致绕过。
2.2.3 绝对路径注入
如果应用程序仅过滤 ../ 而不检查绝对路径,可直接传入:
file=/etc/passwd绕过所有相对路径检查,直接读取目标文件。
2.2.4 截断攻击(Null Byte Injection)
在 PHP 5.3 之前的版本中,可使用 %00(空字节)截断字符串:
file=../../../etc/passwd%00.html空字节会截断后续的 .html 后缀拼接,使得实际读取的文件为 /etc/passwd(在 PHP 5.3+ 及后续版本中已修复)。
2.2.5 长路径和 Unicode 规范化绕过
某些系统在路径规范化时会做不同的处理,攻击者可以利用 Unicode 字符:
․․/ → 特殊 Unicode 点字符(U+2024)
../ → 全角斜杠(U+FF0F)不规范的路径处理函数可能将这些字符视为路径分隔符,从而实现绕过。
2.3 路径遍历防御方案
2.3.1 规范化路径(推荐)
使用标准库函数将路径规范化(如 Java 的 getCanonicalPath()、Python 的 os.path.realpath()),然后检查结果是否在允许的目录范围内。
// Java —— 使用规范化路径防御
public boolean isValidPath(String fileName) throws IOException {
String baseDir = "/var/www/uploads/";
File file = new File(baseDir + fileName);
String canonicalPath = file.getCanonicalPath();
// 判断规范化后的路径是否以允许的基目录开头
return canonicalPath.startsWith(baseDir);
}# Python —— 使用 os.path.realpath 规范化
import os
def is_safe_path(base_dir: str, user_path: str) -> bool:
# 拼接路径并规范化
full_path = os.path.realpath(os.path.join(base_dir, user_path))
# 检查是否以基目录开头
return full_path.startswith(os.path.realpath(base_dir))2.3.2 黑名单与白名单
- 黑名单:过滤
../、..\\、%00、~等危险字符。但黑名单容易遗漏,不建议作为唯一防御手段。 - 白名单:如果读取的文件是有限的,使用白名单强制限定文件名,杜绝用户自由输入。
<!-- PHP —— 白名单方案 -->
<?php
$allowed_files = [
'report1' => 'reports/report_2024_q1.pdf',
'report2' => 'reports/report_2024_q2.pdf',
];
$key = $_GET['file'];
if (isset($allowed_files[$key])) {
readfile($allowed_files[$key]);
} else {
echo "文件不存在";
}
?>2.3.3 最小权限原则
- Web 应用进程不应以 root/Administrator 权限运行
- 仅授予 Web 应用读取所需目录的最小权限
- 数据库配置文件、密钥文件等敏感资源放在 Web 根目录之外
2.3.4 chroot / 沙箱隔离
在 Linux 环境下,可以将文件读取进程运行在 chroot 沙箱中,限制其对文件系统的可见范围。现代方案推荐使用 Docker 容器或类似 sandbox 技术,从根本上限制文件系统访问边界。
2.3.5 使用安全的文件访问 API
许多现代框架提供了安全的文件访问方法,内置了路径校验:
# Python Flask —— 使用 send_from_directory
from flask import send_from_directory
@app.route('/download/<filename>')
def download_file(filename):
# send_from_directory 自动防止路径遍历
return send_from_directory('/var/www/uploads', filename)Java 中可以使用 Spring 的 Resource 抽象来安全地处理文件下载。
三、综合防御 Checklist
下表总结了针对开放重定向和路径遍历的核心防御措施:
| 类别 | 防御要点 | 优先级 |
|---|---|---|
| 开放重定向 | 使用域名白名单,拒绝纯用户输入 | 高 |
| 开放重定向 | 限制 // 协议跳转和 @ 符号 | 高 |
| 开放重定向 | 使用标准 URL 解析库做规范化校验 | 中 |
| 开放重定向 | 前端跳转使用 URL 构造函数校验 origin | 中 |
| 路径遍历 | 使用规范化路径并校验前缀 | 高 |
| 路径遍历 | 避免使用黑名单作为唯一防御 | 高 |
| 路径遍历 | 使用框架内置安全文件访问 API | 中 |
| 路径遍历 | 最小权限原则,限制 Web 进程的文件系统访问范围 | 高 |
四、总结
开放重定向和路径遍历虽然属于不同的漏洞类型,但其核心成因是一致的——信任了用户输入的参数,未做充分的校验和规范化处理。
- 对于 开放重定向,核心防御思路是:不要直接使用用户输入作为跳转 URL,要么用白名单校验域名,要么用内部映射 ID 替代 URL。
- 对于 路径遍历,核心防御思路是:将用户输入与基目录拼接后做路径规范化,校验结果是否落入预期目录,同时配合最小权限原则降低影响面。
这两类漏洞在 OWASP 自动化代码审计和人工渗透测试中都是高频检查项。开发者在编写涉及重定向逻辑或文件操作的功能时,应时刻保持安全意识,将输入校验作为默认行为而非可选操作。安全是一个持续的过程,纵深防御、层层设限,才能构建健壮的应用系统。