URL跳转学习总结

URL跳转漏洞学习总结

相关背景

现在web应用越来越多的需要和其他第三方应用交互,以及在自身应用内部根据不同的逻辑引向不同的页面。例如一个典型的场景就是:web站点可以使用第三方账号(如qq,微博等)进行登录,在登录时就会发生跳转,如果在这个过程中没有做好安全策略就会产生安全漏洞。例如可以利用恶意的URL跳转进行钓鱼等。

URL重定向(301/302)

  1. 301重定向:当域名或者网址永久弃用后,为了方便可以将此域名或网址重定向到新的域名或URL
  2. 302重定向:当域名或网址临时弃用时,为了方便可以将此域名或网址重定向到新的域名或URL
  3. 302URL劫持与302任意URL跳转的不同点
    • 302——URL劫持是由于搜素引擎的算法导致的问题。例如A网站暂时弃用,要302重定向到B网站,但是由于B网站URL太过复杂,而搜索引擎的算法判定A网址更加适合,所以爬取的还是A网址,这个时候访问网站是显示的URL是A网站的,而显示的内容是B网站的,造成了302重定向——URL劫持。
    • 302——任意URL跳转是用户可以直接控制URL跳转的参数或者通过一些绕过方式控制URL跳转的位置造成的问题。

成因

web站点或者第三方的服务端没有对用户输入的参数进行合法性校验,或者校验不严格,在URL跳转时用户可控,导致恶意参数的传入以及执行,将应用程序引导到恶意的第三方区域产生的安全问题(短链接更加难以防范)。

漏洞出现的详细原因

  • 写代码时没有考虑过任意URL跳转漏洞,或者根本不知道/不认为这是个漏洞
  • 写代码时考虑不周,用取子串、取后缀等方法简单判断,代码逻辑可被绕过
  • 对传入参数做一些奇葩的操作(域名剪切/拼接/重组)和判断,适得其反,反被绕过
  • 原始语言自带的解析URL、判断域名的函数库出现逻辑漏洞或者意外特性,可被绕过
  • 原始语言、服务器/容器特性、浏览器等对标准URL协议解析处理等差异性导致被绕过

WEB站点中URL跳转漏洞的发生点

  • 用户登录、统一身份认证处、认证以后发生跳转
  • 用户分享、收藏内容后会发生跳转
  • 跨站点认证、在授权后会认证
  • 站内对其他网站的链接,点击后会跳转

URL跳转的实现方式

  • 通过META标签内跳转

    • 实例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <html>
      <head>
      <title></title>
      <?php
      header("Content-Type:text/html;charset=utf-8");
      if(isset($_REQUEST["url"]))
      {
      $url = $_REQUEST["url"];
      }else{
      $url = "url_meta.php";
      }
      ?>
      <meta http-equiv="Refresh" content="5; url=<?php echo $url?>" />
      </head>
      <body>
      </body>
      </html>

  • 通过JavaScript跳转

    • 实例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <?php
      if (isset($_GET['url'])) {
      $target = $_GET['url'];
      echo "<script>window.location.href=\"$target\"</script>";
      exit;
      } else {
      echo "Please input the URL";
      }
      ?>

  • 通过header头跳转

    • 实例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <?php
      if (isset($_GET['url'])) {
      $target = $_GET['url'];
      header("Location: $target");
      exit;
      } else {
      echo "Please input the URL";
      }
      ?>
    • 如果jump参数没有任何限制,用户就可以构造恶意链接进行提交造成恶意URL跳转 http://www.lsowl.xyz/aaa.php?url=http://www.eval.com ,通过恶意链接造成不可信的第三方跳转可以进一步钓鱼等(直接跳转)。

    • 同时由于一些网站的安全策略,白名单中有 http://www.lsowl.xyz/aaa.php 而导致一些安全策略被绕过,导致用户最终访问的时恶意链接。(过滤不严格的白名单)

通过GET或POST的方式接收将要跳转的URL,然后通过上面的几种方式中的一种来跳转到目标URL。由于用户的输入会进入META,JavaScript,http头,所以都可能发生相应的上下文漏洞,如XSS等,同时URL跳转功能的特点,会在浏览器将用户从可信的站点跳转到不可信的站点,如果用户的输入带有敏感信息会泄露给不可信的第三方。

常见的发生URL跳转的参数名

  • redirect
  • redirect_to
  • redirect_url
  • url
  • jump
  • jump_to
  • target
  • to
  • link
  • linkto
  • domain

几种语句和框架版本常见的URL跳转代码(可以作为白盒测试的参考)

  • Java

    1
    response.sendRedirect(request.getParameter("url"));
  • PHP

    1
    2
    $redirect_url = $_GET['url'];
    header("Location: " . $redirect_url);
  • .NET

    1
    2
    string redirect_url = request.QueryString["url"];
    Response.Redirect(redirect_url);
  • Django

    1
    2
    redirect_url = request.GET.get("url")
    HttpResponseRedirect(redirect_url)
  • Flask

    1
    2
    redirect_url = request.form['url']
    redirect(redirect_url)
  • Rails

    1
    redirect_to params[:url]

网站后端对白名单限制(以PHP为例)

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    <?php

    // $allowedDomains 表示允许跳转的url白名单
    $allowedDomains = array(
    "aaaa.com"
    "bbbb.com"
    .......
    );
    function encodeUrl($urlInfo)
    {/*{{{*/
    $path = isset($urlInfo['path']) ? $urlInfo['path'] : '';
    if(!empty($path))
    {
    $t = explode("/", $path);

    for($i = 0; $i < count($t); $i++)
    {
    $t[$i] = rawurlencode($t[$i]);
    }
    $path = implode("/", $t);
    }
    $query = isset($urlInfo['query']) ? $urlInfo['query'] : '';
    if(!empty($query))
    {
    $t = explode("&", $query);

    for($i = 0; $i < count($t); $i++)
    {
    $tt = explode("=", $t[$i]);
    $tt[1] = rawurlencode($tt[1]);
    $t[$i] = implode("=", $tt);
    }
    $query = implode("&", $t);
    }
    if(!isset($urlInfo['host']) || empty($urlInfo['host']))
    {
    return $path. "?". $query;
    }
    $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : 'http';
    $port = isset($urlInfo['port']) ? $urlInfo['port'] : 80;


    $request = $scheme . '://'. $urlInfo['host'];
    $request .= ($port == 80) ? '' : ':'.$port;
    $request .= $path;
    $request .= (empty($query)) ? '' : '?'.$query;
    return $request;
    }/*}}}*/

    function checkUrl($url,$domainArr=array())
    {/*{{{*/
    $res = array('isTrustedDomain' => false,'url' => '','domain' => '');
    if(empty($url)) return $res;
    $domainArr = empty($domainArr) || !is_array($domainArr) ? $allowedDomains : $domainArr;
    $url = filterUrl($url);//先过滤特殊字符
    $p = parse_url($url);
    $scheme = $p['scheme'];
    if(!in_array(strtolower($scheme),array('http','https'))){
    return $res;
    }

    $host = $p['host'];
    if(!isValidHost($host)){
    return $res;
    }
    $hostLen = strlen($host);
    foreach($domainArr as $domain){
    $firstPos = strpos($host, $domain);
    if($firstPos !== false && ($firstPos + strlen($domain)) == $hostLen){

    if($firstPos == 0 || $domain[0] == '.' || $host[$firstPos-1] == '.'){
    $res['isTrustedDomain'] = true;
    $res['url'] = $url;
    $res['domain'] = $domain;
    break;
    }
    }
    }
    return $res;
    }/*}}}*/

    function filterUrl( $url )
    {/*{{{*/
    if(empty($url)) return $url;
    // Strip all of the Javascript in script tags out...
    $url = preg_replace('/<SCRIPT.*?<\/SCRIPT>/ims',"",$url);
    // Strip all blank character
    $url = preg_replace('/[\s\v\0]+/',"",$url);
    //Strip special characters(',",<,>,\)
    $url = str_replace(array("'","\"","<",">","\\"),'',$url);
    return $url;
    }/*}}}*/

    function isValidHost($host)
    {/*{{{*/
    $p = "/^[0-9a-zA-Z\-\.]+$/";
    return preg_match($p,$host) ? true : false;
    }/*}}}*/

    $url = "https://www.baidu.com";
    $call_back_url = trim($url);
    $call_back_url = encodeUrl(parse_url(urldecode($call_back_url)));
    $res = checkUrl($call_back_url, $domainArr);

    var_dump($res);

攻击方式及危害性

  • 恶意用户借助URL跳转构造钓鱼页面欺骗其他用户,以及获取敏感信息等,在有在线业务的站点危害较大。
  • 借助URL跳转突破一些基于白名单的安全机制。如:传统的IM对URL的传播进行安全校验,但对于大站点的域名及URL直接允许通过并显示可信的URL,如果该URL中包含恶意跳转可能会导致安全限制被绕过。
  • 基于白名单引用的资源,这种方式与上面的类似。比如:引入youku.com的视频,白名单中检测的时youku.com,如果包含恶意链接还是可能突破限制。
  • 在带referer传输的站点中,就不只会产生任意URL跳转这个问题,同时可能会造成所有基于referer的安全策略失效(比较少见)

模拟跨域请求

  • 在header头中跳转:(并没有带referer请求,所以这里只会产生任意URL及钓鱼诈骗等)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    url.php
    <?php
    if (isset($_GET['url'])) {
    $target = $_GET['url'];
    // 使服务端进行302跳转
    header("Location: $target");

    //在JavaScript中跳转
    // echo "<script>window.location.href=\"$target\"</script>";
    exit;
    } else {
    echo "Please input the URL";
    }
    ?>

    referer.php
    <?php
    if (isset($_SERVER['HTTP_REFERER'])) {
    $referer = $_SERVER['HTTP_REFERER'];
    if (strpos($referer, "127.0.0.1:81")) {
    echo "referer is true";
    } else {
    echo "Wrong referer";
    }
    } else {
    echo "referer is null";
    }
    ?>

    访问:http://127.0.0.1:81/url.php?url=http://127.0.0.1:82/referer.php

  • 在js中跳转(带referer传输,除了任意URL跳转漏洞之外还会绕过基于referer的所有安全策略)

测试方法

  • 抓取数据包中状态码为302的URL
  • 修改目标地址
  • 查看是否正常跳转(如果不存在直接返回到它自己的域名,如果存在则跳转)
  • 尝试绕过
  • 注意点:协议一致性问题。有些网站校验跳转后的协议必须为HTTPS,有时候无法跳转并不会提示。

常见的绕过方式(利用浏览器对URL特性的支持)

  1. “@” (在Firefox中使用会有弹窗提示,其他浏览器没有)

    http://www.target.com/redirecturl=http://whitelist.com@evil.com

  2. “.”

    http://www.target.com/redirecturl=.eval.com

  3. “/”

    http://www.target.com/redirecturl=http://evil.com/whitelist.com

    http://www.target.com/redirecturl=/http://evil.com

  4. “\”

    http://www.target.com/redirecturl=http://evil.com\a.whitelist.com

  5. “\\”

    http://www.target.com/redirecturl=http://evil.com\\a.whitelist.com

  6. “\.”

    http://www.target.com/redirecturl=http://evil.com\.a.whitelist.com

  7. “?”

    http://www.target.com/redirecturl=http://evil.com?a.whitelist.com

    1
    2
    利用问号,这是一个特性,利用问号可以成功绕过URL限制
    比如:http://www.aaa.com/acb?Url=http://login.aaa.com 这是一个跳转链接,跳转到它的二级域名下,那么这个问号放哪里可以绕过呢?其实就是放到它自身的域名前面也就是你添加的想要跳转的域名的后面,如:http://www.aaa.com/acb?Url=http://test.com?login.aaa.com 那么,它其实是会跳转到这个test.com域名下,这个域名是我想要跳转的任意域名,而后面的它自身域名一定要带上,不带上就无法辅助用问号?这个特性来跳转到指定域名了,而跳转后,问号和问号后面的内容会变为这样:http://www.test.com/?login.aaa.com
  8. “#”

    http://www.target.com/redirecturl=http://evil.com#a.whitelist.com

  9. 利用白名单缺陷绕过限制

    1
    2
    3
    4
    5
    6
    有些域名白名单限制不严格。
    1. 检测是否是当前域名
    http://www.aaa.com/acb?Url=http://login.aaa.com在访问后会跳转到login.aaa.com,如果它的白名单显示不严格,将login.aaa.com改成aaa.com同样可以跳转,到这里都是合法的跳转。这时只要构造一个第三方的域名中包含aaa.com的url就可以成功跳转。如果恰好有这样一个域名,恭喜你;如果没有,就需要买一个域名。emmm..在国内这样很亏

    2.域名字符串检测欺骗
    http://www.aaa.com/acb?Url=http://login.aaa.com,检测login.aaa.com,直接修改为:http://www.aaa.com/acb?Url=http://login.aaa.com.www.eval.com

  10. 利用多重验证&跳转绕过限制

    1
    2
    现在的许多网站都有多重验证。例如:在登录账户以后会出现另一个验证,输入手机号进行验证,这时可能存在任意URL跳转的问题。具体如下:
    http://www.aaa.com/acb?Url=http:...http://login.aaa.com,这里只需要修改最后的URL来绕过限制
  11. 点击触发达到绕过URL跳转的限制

    1
    2
    在许多的登录的位置,URL是一个跳转的地址。比如:http://www.aaa.com/acb?Url=http://login.aaa.com;这个时候只要将login.aaa.com修改为任意URL;这时界面没有发生变化,当用户输入账号密码点击登录按钮时会触发跳转。
    需要注意的是:这里不一定要输入正确的账号密码进行跳转,有些需要,视具体的站点而定
  12. 利用xip.io绕过限制(还可以借鉴其余的SSRF绕过方法)

    http://www.aaa.com/acb?Url=http://login.aaa.com.eval.com.xip.io

    1
    在SSRF读取内网地址的时候会有许多限制,有时候可以利用xip.io直接绕过限制读取,这里可以引用这个方法,直接将可以跳转的位置的地址后添加为任意url+xip.io,如果可以存在则将跳转的地址解析到后面的恶意url进行跳转。
  13. 利用超链接绕过可信站点的限制(可信站多次重定向)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    例如一个存在URL跳转漏洞的位置,在测试的时候一般都喜欢使用www.baidu.com等知名站点去做第三方跳转,但是现在像baidu.com,qq.com这样的可信站点被许多站点默认加在白名单中,所以以这样可信的站点去跳转它并不能触发URL跳转漏洞(这里是网站的正常逻辑);而别的方式又绕不过,就可以利用下面这个思路:

    1. baidu.com这个域名已经被加在测试站点的白名单中
    2. 任意跳转的URL已经被baidu收录
    3. site:xxx(任意跳转的URL)
    4. 在百度点击相关的站点的时候回利用百度自身的一个302跳转
    5. 将百度自身的这个302跳转作为测试站点热任意URL的参数,绕过白名单baidu.com的限制进行任意URL跳转(注意:百度自身的302跳转太长了,可以使用单向压缩函数进行压缩,前提是测试站点能够解析)

    第二种场景:测试站点的白名单限制的比较严格,只允许加在自己域的地址,这时就需要一个测试站点其他域存在任意URL跳转漏洞,才可能利用成功:
    https://www.aaa.com/redirect.php?url=https://auth.aaa.com/jump.do?url=eval.com
    总结:其实是利用了两次URL跳转
  14. POST方式的URL跳转

    1
    2
    3
    1. post方式下的影响较小,比如在站点的个人信息处存在头像上传,上传正常的头像,抓包将头像地址修改为恶意第三方地址,放行以后图片是不能正常显示的,这个时候如果有后台管理员的审核安全意识不高,去查看这个不显示的图片,触发恶意URL跳转,可以打到后台管理员信息。利用条件比较苛刻。在XSS也有类似的利用场景。

    2.如果在POST方式中数据只URL跳转参数,那就可以尝试将POST请求转换为GET请求,配合上面的绕过方式利用。(前提是测试网站支持GET传参的方式)
  15. HPP参数污染绕过

    1
    2
    3
    https://www.aaa.com/redirect.php?url=https://auth.aaa.com/jump.do?url=l@lsowl.xyz

    跳转会变成: location:http://https://www.aaa.com/redirect.php?url=https://auth.aaa.com/jump.do?url=@lsowl.xyz

畸形地址绕过

畸形地址由于web站点采用各种各样的语言以及框架,所以绕过的方式显得很诡异。。。

  1. 通过添加多余的“/”(%2F),然后对%2F二次URL编码绕过网站对.com的分割

    https://www.aaa.com/%2Fevil/%252Ecom

  2. 通过添加4个“/”前缀和“/..”后缀,突破限制(重复特殊字符绕过)

    https://www.aaa.com/redirect.php?url=////www.eval.com/..

  3. 特殊字符:

    1
    ";", "/", "\", "?", ":", "@", "=", "&", "."
  4. 协议型网站的实例

    http://user:pass@testweb.com/path/;help.php?q=abc#lastpage

其他绕过思路

  1. 跳转参数是IP,而不是域名
  2. 跳转到IPV6地址,不是IPV4地址
  3. 将要跳转的IP地址用10进制,8进制,16进制表示
  4. 更换协议,使用FTP,gopher
  5. CRLF注入不能XSS时,转向利用任意URL跳转漏洞

IP地址计算补充(以本地ping百度返回的IP为例)

第一种计算方法(将点分十进制转换为32位二进制)

第二种计算方法

111 256 256 256 + 13 256 256 + 100 256 +92*1 = 1863148636(十进制)

第三种计算方法(利用mysql中的inet_aton函数)

半自动化测试

修复方案

  1. referer限制
    • 确定传递URL参数的引入来源,保证URL的有效性,避免恶意用户自己生成的链接(这里要注意的是,在有些特殊的环境下,URL跳转会带着HTTP referer头,这样就会使得依赖referer头验证的方式失效)
  2. 进行token验证
    • 保证所有的链接是可信域中的,加入用户不可控的token在服务端进行验证,防止恶意跳转
  3. 服务端做好域名白名单或跳转白名单,只对合法的URL进行跳转(常用)
  4. 对请求参数做加密和签名,防止参数被篡改,服务端要能合法正确的解析URL(不常用,多应用在跳转的URL是由后台生产,不是用户在前台输入)

参考链接

https://www.anquanke.com/post/id/94377

https://blog.csdn.net/change518/article/details/54286473

https://blog.csdn.net/xyx107/article/details/82941842

https://blog.csdn.net/change518/article/details/53997509

http://www.admintony.com/redict-bypass.html#more

http://www.luteam.com/?p=211#more-211 [关于IP计算]

http://byd.dropsec.xyz/2017/11/21/SSRF%E7%BB%95%E8%BF%87%E6%96%B9%E6%B3%95%E6%80%BB%E7%BB%93/ [关于SSRF绕过]