任意URL跳转漏洞:未校验致恶意跳转,Java中的修复方法

任意 URL 跳转漏洞

任意URL跳转漏洞:未校验致恶意跳转,Java中的修复方法

服务端未对传入的跳转 url 变量展开检查与控制,这使得能够恶意构造出任意一个恶意地址,进而诱导用户跳转至恶意网站。因为是从可信站点跳转出去的,所以用户会较为信任。基于此,跳转漏洞通常被用于钓鱼攻击,其方式是通过转到恶意网站来欺骗用户输入用户名和密码,以盗取用户信息,或者欺骗用户进行金钱交易。

修复该漏洞的有效方法之一是对传入的跳转 url 参数值进行校验,以判断其是否为预期域名。在 java 中可以使用以下这些方法:

获取请求中的参数“returnUrl”,并将其赋值给字符串变量 url。
String host = "";
try {
创建一个新的 URL 对象,将给定的 url 作为参数传入,然后通过该 URL 对象获取其主机名,将获取到的主机名赋值给 host 变量。
如果捕获到 MalformedURLException 异常,那么就会执行这里的代码块。
e.printStackTrace();
}
if host.endsWith(".bbb.com"){
//跳转
}else{
//不跳转,报错
}

上述代码主要对客户端传来的 returnUrl 参数值进行了校验。通过使用 java.net.URL 包中的 getHost() 方法,获取了将要跳转的 url 的 host。接着判断该 host 是否为目标域。并且上述代码限制了必须跳转到 xxx.bbb.com 的域名,这样就排除了跳转到不可信域名的可能性。

但是,getHost() 方法真的靠谱吗??

getHost() 方法的坑之一

反斜线可以被绕过,也就是说 returnUrl=http://www.aaa.com\ 会被代码当作要跳转到 bbb.com 。然而,在浏览器中,反斜线会被纠正为正斜线,最终跳转到的还是原来的服务器。

使用下列代码进行测试:

public class Main {

在公共静态的主函数中,该函数接收一个字符串数组类型的参数 args。
String 这个是一个字符串,它的值为 "https://www.aaa.com\\www.bbb.com?x=123" 。
String host = "";
try {
创建一个新的 URL 对象,将传入的 url 作为参数传递给构造函数,然后通过调用该对象的 getHost 方法来获取主机名,并将其赋值给 host 变量。
如果捕获到 MalformedURLException 异常,就会执行这里的代码。
e.printStackTrace();
}
System.out.println("host 为:" + host);
System 输出了 "url---" 与 url 进行拼接后的内容。
}
}

url 参数的域名在使用 getHost() 方法之后,其结果是怎样的呢?打印出的结果如下:

该结果会被一种名为 endsWith(".bbb.com") 的方法判定为真,如此一来就能成功地执行跳转动作,然而实际上在浏览器中跳转至了那个网站。

getHost() 方法的坑之二

getHost()方法的结果在不同 JDK 版本下对井号#的处理情况不一样。井号通常被用作页面锚点。对于#www.bbb.com?x=123这个 url ,在较高版本的 JDK 中,取出的结果是某种情况;在低版本中,取出的结果是#www.bbb.com。正因如此,低版本能够绕过 endsWith(".bbb.com")方法,进而实现成功跳转。

这里所说的高版本,指的是 java version 1.8.0181 或者 java version 1.7.0161 里的 181 和 161,它与 JDK7 或者 JDK8 没有关系。有可能在某个时间,java 集中对 JDK6、JDK7、JDK8 中的 URL 库进行了修复。

测试过程中发现,1.6.045 可被 # 绕过,1.7.071 可被 # 绕过,1.8.0_25 也可被 # 绕过,这意味着在不同的 JDK 中,低版本均存在问题。

对比 rt.jar---java---net--URLStreamHandler.java 代码,左边是低版本,右边是高版本,从而找到问题所在,如下图所示。在代码中,start 指的是 url 中的冒号位置,limit 指的是 url 中的井号位置。

从代码中能够得知,在低版本里,没有考虑到在一个完整的 url 中,在斜线/或者问号?之前会出现井号#这种情况。倘若 url 中存在斜线/或者问号?,那么获取 host 就会以斜线或者问号作为终止,即便中间包含井号也不会进行处理。而在高版本中,对井号的位置进行了判断,排除了利用井号进行绕过的可能性。但是线上生成环境的 JDK 版本不能随意进行升级,只能通过在代码中提前做好预防措施。

下图为使用不同版本 JDK 测试的结果:

同一段代码在不同的 JDK 版本下打印出的 host 值存在差异。在低版本的 JDK 中,打印出的 host 值包含了井号以及井号后边的部分。

综合上述两个坑,要想用 getHost() 来修复任意 URL 跳转漏洞,就得考虑到反斜线和井号会绕过。可以使用如下代码:

String url = request.getParameter("returnUrl");
String host = "";
try {
url 进行替换操作,将其中的 "[\\#]" 替换为 "/" ,即替换掉反斜线和井号
创建一个新的 URL 对象,将传入的 url 作为参数传递给构造函数,然后通过该对象调用 getHost 方法,获取到的主机名赋值给 host 变量。
} catch (MalformedURLException e) {
e.printStackTrace();
}
if host.endsWith(".bbb.com"){
//跳转
}else{
//不跳转,报错
}

附送一个真实例子

该站能够通过使用井号并配合斜线或者问号的方式来绕过域名检测。具体来说,就是将 target 设置为 URL 编码后的#www.bbb.com?x=123,这样该站就可以进行 302 跳转并到达百度。

本文的作者是 ChangeS,本文属于 FreeBuf 的原创奖励计划,若未经许可,是禁止进行转载的。

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

24阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

27阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

23阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

25阅读