29

任意URL跳转漏洞修复与JDK中getHost()方法之间的坑

 5 years ago
source link: https://www.freebuf.com/articles/web/196165.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

*本文作者:ChangeS,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。

任意URL跳转漏洞

服务端未对传入的跳转url变量进行检查和控制,导致可恶意构造任意一个恶意地址,诱导用户跳转到恶意网站。由于是从可信的站点跳转出去的,用户会比较信任,所以跳转漏洞一般用于钓鱼攻击,通过转到恶意网站欺骗用户输入用户名和密码盗取用户信息,或欺骗用户进行金钱交易。

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

String url = request.getParameter("returnUrl");
String host = "";
try {
    host = new URL(url).getHost();
} catch (MalformedURLException e) {
    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](http://www.aaa.com)\[www.bbb.com](http://www.bbb.com) 会被代码认为是将要跳转到 bbb.com ,而实际在浏览器中反斜线被纠正为正斜线,跳转到 [www.aaa.com/www.bbb.com](http://www.aaa.com/www.bbb.com) ,最终还是跳到 [www.aaa.com](http://www.aaa.com) 的服务器。

使用下列代码进行测试:

public class Main {

    public static void main(String[] args) {
        String url = "https://www.aaa.com\\www.bbb.com?x=123";
        String host = "";
        try {
            host = new URL(url).getHost();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        System.out.println("host---"+host);
        System.out.println("url---"+url);
    }
}

url参数的域名getHost()之后是 [www.aaa.com](http://www.aaa.com) 还是 [www.bbb.com](http://www.bbb.com) 呢?打印结果如下:

RfUZNj7.jpg!web

该结果会被endsWith(“.bbb.com”)方法判断为真,从而成功执行跳转,但实际在浏览器中跳转到了 [www.aaa.com](http://www.aaa.com) 网站。

getHost()方法的坑之二

getHost()方法的结果在不同JDK版本中对井号#的处理结果不同,通常井号被用作页面锚点,对于 [https://www.aaa.com#www.bbb.com?x=123](https://www.aaa.com#www.bbb.com?x=123) 这个url,较高版本的JDK中,取出结果为 [www.aaa.com](http://www.aaa.com) ,低版本中为 [www.aaa.com#www.bbb.com](http://www.aaa.com#www.bbb.com) ,从而低版本又可绕过endsWith(“.bbb.com”)方法,成功跳转。

这里所说的高版本指的是 java version 1.8.0_181 或者 java version 1.7.0_161 中的181和161,与JDK7还是8无关。可能java在某个时间集中修复了JDK6/7/8中的URL库。

测试过程中发现 1.6.0_451.7.0_711.8.0_25 均可被#绕过,即不同的JDK中低版本均存在问题。

通过对比 rt.jar---java---net--URLStreamHandler.java 代码(低版本为左边,高版本为右边)找到问题所在如下图所示,代码中的start为url中冒号位置,limit为url中井号位置:

VN7fUzM.jpg!web

从代码中可以发现,低版本中未考虑到一个完整url中斜线/或者问号?之前会出现井号#的情况,如果url中有斜线/或者问号?,取host就以斜线或者问号为终止,即使中间包含井号也不处理;而高版本中进行了井号位置的判断,排除了使用井号绕过的可能。但是线上生成环境的JDK版本又不是敢随便乱升级的,只能从代码里提前预防。

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

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

rIN7raY.jpg!web

MRjiIjM.jpg!web

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

String url = request.getParameter("returnUrl");
String host = "";
try {
    url = url.replaceAll("[\\\\#],"/"); //替换掉反斜线和井号
    host = new URL(url).getHost();  
} catch (MalformedURLException e) {
    e.printStackTrace();
}
if host.endsWith(".bbb.com"){
    //跳转
}else{
    //不跳转,报错
}

附送一个真实例子

该站可使用井号配合斜线或者问号来绕过域名检测,即将target设置为URL编码后的 [https://www.baidu.com#www.bbb.com?x=123](https://www.baidu.com#www.bbb.com?x=123) ,该站即可302跳转到百度。

BZvYben.jpg!web

*本文作者:ChangeS,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK