Java代码审计实战:URL跳转漏洞深度解析
URL跳转(Open Redirect)漏洞,是指应用程序将用户提交的URL参数作为重定向目标,但没有对URL进行严格的校验,从而使得攻击者能够构造恶意链接,将用户重定向到钓鱼网站或其他恶意页面。
1. 漏洞的本质:服务器成了攻击者的帮凶
URL跳转漏洞的根本原因在于,应用程序过于信任用户输入,直接将用户提供的URL参数传递给了重定向函数,例如 response.sendRedirect()
。这使得攻击者能够利用一个可信的域名(即存在漏洞的网站)来发起钓鱼攻击,大大增加了欺骗性。
经典案例:直接使用用户提供的URL
许多Web应用在登录、支付或某些业务流程结束后,会提供一个 redirect
或 returnUrl
参数,让用户跳转回之前的页面。如果开发者直接使用这个参数,就可能导致漏洞。
Java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Controller
public class RedirectController {
@GetMapping("/redirect_vulnerable")
public void redirectVulnerable(@RequestParam("url") String url, HttpServletResponse response) throws IOException {
// 危险:直接重定向到用户提供的URL
response.sendRedirect(url);
}
}
漏洞分析:
这段代码看似无害,但如果攻击者构造一个恶意链接,例如:
http://yourdomain.com/redirect_vulnerable?url=http://phishing-site.com/login
当用户点击这个链接后,服务器会无条件地将用户重定向到 http://phishing-site.com/login
。攻击者可以利用你的可信域名来发起钓鱼攻击,欺骗用户输入敏感信息。
2. 进阶陷阱:常见的绕过技巧
许多开发者会尝试通过一些简单的校验来防御,但这些防御措施往往存在缺陷,极易被攻击者绕过。
案例一:黑名单校验的弱点
开发者可能会建立一个“黑名单”,禁止跳转到某些特定的域名。
@GetMapping("/redirect_blacklist")
public void redirectBlacklist(@RequestParam("url") String url, HttpServletResponse response) throws IOException {
// 危险:不安全的黑名单校验
if (url.startsWith("http://malicious.com")) {
// ...
return;
}
response.sendRedirect(url);
}
绕过技巧:
攻击者可以利用URL的解析特性来绕过这种简单的 startsWith 校验。
利用
@
符号:http://yourdomain.com/redirect_blacklist?url=http://www.yourdomain.com@phishing-site.com
。漏洞分析:
@
符号后面的域名是真正的跳转目标。浏览器会认为www.yourdomain.com
是用户名,而phishing-site.com
才是主机名。校验通过。
利用URL路径:
http://yourdomain.com/redirect_blacklist?url=http://www.yourdomain.com.phishing-site.com
。漏洞分析:
startsWith
校验无法识别yourdomain.com.phishing-site.com
是一个不同的域名。
案例二:白名单校验的缺陷
开发者可能会建立一个“白名单”,只允许跳转到本站或指定的几个域名。但如果校验逻辑存在缺陷,也可能被绕过。
@GetMapping("/redirect_whitelist")
public void redirectWhitelist(@RequestParam("url") String url, HttpServletResponse response) throws IOException {
// 危险:不安全的白名单校验
if (url.startsWith("http://yourdomain.com") || url.startsWith("https://yourdomain.com")) {
response.sendRedirect(url);
} else {
response.sendRedirect("/index");
}
}
绕过技巧:
攻击者可以利用URL的各种特性来绕过 startsWith 校验。
//
绕过:http://yourdomain.com/redirect_whitelist?url=//phishing-site.com
。漏洞分析:URL中双斜线
//
后面的内容会被浏览器解析为域名,从而跳转到phishing-site.com
。
#
或?
绕过:http://yourdomain.com/redirect_whitelist?url=/path/to/page#phishing-site.com
。漏洞分析:URL中的
#
或?
后面的内容通常被认为是片段标识符或查询参数,不影响主路径。但某些服务器在处理重定向时可能忽略这些符号,导致URL被解析为yourdomain.com/path/to/page#phishing-site.com
,浏览器会尝试跳转到phishing-site.com
。
3. 审计与加固:构建多层防御体系
防御URL跳转漏洞的核心在于:永远不要相信用户提供的URL,并使用白名单机制进行严格的域名校验。
审计重点:
寻找重定向入口:在代码库中搜索
response.sendRedirect()
、return "redirect:"
、header("Location: ...")
等关键字。追踪数据流:检查重定向的目标URL是否直接或间接来自用户输入(如
request.getParameter()
)。
安全修复方案:
使用白名单机制:最安全且推荐的防御措施。只允许跳转到预先定义好的、可信的域名。
@GetMapping("/redirect_safe") public void redirectSafe(@RequestParam("url") String url, HttpServletResponse response) throws IOException { String safeDomain = "yourdomain.com"; // 校验URL的域名 if (url != null && url.startsWith("http://") || url.startsWith("https://")) { // 解析URL java.net.URL parsedUrl = new java.net.URL(url); String host = parsedUrl.getHost(); if (host.equals(safeDomain) || host.endsWith("." + safeDomain)) { // 如果是本站或子域名,允许跳转 response.sendRedirect(url); } else { // 非法域名,重定向到默认首页 response.sendRedirect("/index"); } } else if (url != null && url.startsWith("/")) { // 允许相对路径跳转 response.sendRedirect(url); } else { response.sendRedirect("/index"); } }
这个示例中,我们使用了
java.net.URL
类来解析URL,获取其主机名,并与白名单进行严格比对,从而有效防止了各种绕过技巧。禁止外链跳转:如果业务需求允许,只允许用户进行站内跳转。
使用相对路径:将所有重定向目标都限定为相对路径,这样无论用户输入什么,都会被解析为站内地址。
在重定向前验证:在重定向前,手动检查目标URL是否是相对路径。
使用跳转前的中间页提示:当需要跳转到外部链接时,先显示一个中间页,明确提示用户即将离开本站,并显示目标URL,让用户自行确认。