文章目录
预备知识
由于web应用越来越多的需要和其他的第三方应用交互,以及在自身应用内部根据不同的逻辑将用户引向到不同的页面,譬如一个典型的登录接口就经常需要在认证成功之后将用户引导到登录之前的页面,整个过程中如果实现不好就可能导致一些安全问题,特定条件下可能引起严重的安全漏洞。
1.对于URL跳转的实现一般会有几种实现方式:
META标签内跳转
javascript跳转
header头跳转
通过以GET或者POST的方式接收将要跳转的URL,然后通过上面的几种方式的其中一种来跳转到目标URL。一方面,由于用户的输入会进入Meta,javascript,http头所以都可能发生相应上下文的漏洞,如xss等等,但是同时,即使只是对于URL跳转本身功能方面就存在一个缺陷,因为会将用户浏览器从可信的站点导向到不可信的站点,同时如果跳转的时候带有敏感数据一样可能将敏感数据泄漏给不可信的第三方。
譬如一个典型的登录跳转如下:
<?php
$url=$_GET['jumpto'];
header("Location: $url");
?>
2.如果jumpto没有任何限制,那么恶意用户可以构造并提交
http://www.baidu.com/login.php?jumpto=http://www.evil.com
来生成自己的恶意链接,安全意识较低的用户很可能会以为该链接展现的内容是www.baidu.com从而可能产生欺诈行为,同时由于QQ,淘宝旺旺等在线IM都是基于URL的过滤,同时对一些站点会一白名单的方式放过,所以导致恶意URL在IM里可以传播,从而产生危害,譬如这里IM会认为www.baidu.com都是可信的,但是通过在IM里点击上述链接将导致用户最终访问evil.com。
恶意用户完全可以借用URL跳转漏洞来欺骗安全意识低的用户,从而导致“中奖”之类的欺诈,这对于一些有在线业务的企业如淘宝等,危害较大,同时借助URL跳转,也可以突破常见的基于“白名单方式”的一些安全限制,如传统IM里对于URL的传播会进行安全校验,但是对于大公司的域名及URL将直接允许通过并且显示会可信的URL,而一旦该URL里包含一些跳转漏洞将可能导致安全限制被绕过。
如果引用一些资源的限制是依赖于“白名单方式”,同样可能被绕过导致安全风险,譬如常见的一些应用允许引入可信站点如youku.com的视频,限制方式往往是检查URL是否是youku.com来实现,如果youku.com内含一个url跳转漏洞,将导致最终引入的资源属于不可信的第三方资源或者恶意站点,最终导致安全问题。
3.漏洞通常发生在以下几个地方:
用户登录、统一身份认证处,认证完后会跳转
用户分享、收藏内容过后,会跳转
跨站点认证、授权后,会跳转
站内点击其它网址链接时,会跳转
4.常见的可能产生漏洞的参数名:
redirect,redirect_to,redirect_url,url,jump,jump_to,target,to,link,linkto,domain
漏洞案例:
1、CVE-2018-11784 Tomcat URL跳转漏洞
当默认servlet返回一个重定向到一个目录时,一个精心制作的URL可用于导致重定向生成到任何攻击者选择的URI。
2、CVE-2018-14574 Django任意URL跳转漏洞
在path开头为//example.com的情况下,Django没做处理,导致浏览器认为目的地址是绝对路径,最终造成任意URL跳转漏洞。
3、利用sohu网站URL跳转漏洞欺骗邮箱密码
由于没有检查post给注册程序的链接是否是属于自己的页面,最终导致了URL欺骗的形成。
提示:以下是本篇文章正文内容,下面案例仅供参考
一、Django url跳转漏洞分析
1.漏洞还原
Django自带一个函数:
django.utils.http.is_safe_url(url,host=None,allowed_hosts=None,require_https=False)
用于过滤需要进行跳转的url。如果url安全则返回true,不安全则返回false。相关调用如下:
is_safe_url 常规的几个用法及漏洞
在没有指定第二个参数host的情况下,url如果非相对路径,即 HttpResponseRedirect 函数会跳往别的站点的情况,is_safe_url 就判断其为不安全的url,如果指定了host为 blog.neargle.com,则is_safe_url会判断url是否属于 blog.neargle.com,如果url是 blog.neargle.com 或相对路径的url,则判断其url是安全的。
2.存在问题的源码
def _is_safe_url(url, host):
# Chrome considers any URL with more than two slashes to be absolute, but
# urlparse is not so flexible. Treat any url with three slashes as unsafe.
if url.startswith('///'):
return False
url_info = urlparse(url)
# Forbid URLs like http:///example.com - with a scheme, but without a hostname.
# In that URL, example.com is not the hostname but, a path component. However,
# Chrome will still consider example.com to be the hostname, so we must not
# allow this syntax.
if not url_info.netloc and url_info.scheme:
return False
# Forbid URLs that start with control characters. Some browsers (like
# Chrome) ignore quite a few control characters at the start of a
# URL and might consider the URL as scheme relative.
if unicodedata.category(url[0])[0] == 'C':
return False
return ((not url_info.netloc or url_info.netloc == host) and
(not url_info.scheme or url_info.scheme in ['http', 'https']))
3.漏洞存在位置
问题出在该函数对域名和方法的判断,该判断是基于 urllib.parse.urlparse ,构造几种 urlparse 无法处理的特殊情况。
发现当scheme不等于http,且path为纯数字的时候,urlparse 处理例如 https:2222222223 的情况是不能正常分割开的,会全部归为path。
这时
url_info.netloc == url_info.scheme == "",
则 ((not url_info.netloc or url_info.netloc == host) and (not url_info.scheme or url_info.scheme in ['http', 'https']))
为true。
二、漏洞利用
1.钓鱼页面制作原理
利用url跳转漏洞的话那就需要构造url,使其跳转到我们想要的url链接里面,https:2333333333 这样的url明显是无法访问的,而冒号之后必须纯数字,http:127.0.0.1 是无法pypass的。有什么方法呢?其实IP不仅只有常见的点分十进制表示法,纯十进制数字也可以表示一个ip地址,浏览器也同样支持。将IP的点分十进制,逐位转换为16进制然后拼接在一起转为10进制即可。
例如:
219.146.211.115 == 3683832691
127.0.0.1 == 2130706433
8.8.8.8 == 134744072
而 ‘https:134744072’ 在浏览器上是可以访问到对应的IP及服务的,即 ‘http:134744072 = https://8.8.8.8/’
Django默认自带的admin使用了 is_safe_url 来处理 next GET | POST 参数,当用户访问 /admin/login/?next=https:134744072 进行登录时,登录后同样会跳转到 8.8.8.8,退出登录时同样使用到了该函数。
问题代码如下:
def _get_login_redirect_url(request, redirect_to):
# Ensure the user-originating redirection URL is safe.
if not is_safe_url(url=redirect_to, host=request.get_host()):
return resolve_url(settings.LOGIN_REDIRECT_URL)
return redirect_to
@deprecate_current_app
@sensitive_post_parameters()
@csrf_protect
@never_cache
def login(request, template_name='registration/login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm,
extra_context=None, redirect_authenticated_user=False):
"""
Displays the login form and handles the login action.
"""
redirect_to = request.POST.get(redirect_field_name, request.GET.get(redirect_field_name, ''))
if redirect_authenticated_user and request.user.is_authenticated:
redirect_to = _get_login_redirect_url(request, redirect_to)
启动django,python manage.py runserver 0.0.0.0:8000
2.将点分十进制转换为纯数字十进制
打开浏览器,输入地址 http://127.0.0.1:8000/admin,输入帐号admin,密码admin123,即可正常登录,退出登录接下来我们来模拟攻击者使用url跳转漏洞进行钓鱼攻击。攻击者拥有一个钓鱼页面 https://10.1.1.136,首先需要将点分10进制IP转换为纯数字10进制IP。
打开命令行,输入python进入Python的CLI,使用 hex() 函数及 int() 函数将点分十进制的IP转换为纯十进制的表示
构造漏洞url:http://127.0.0.1:8000/admin/login/?next=https:167838088
攻击者发送此链接给受害用户,当用户使用此链接登录成功之后,就会跳转到 https://10.1.1.136/index.html
钓鱼网站源码:
<?php
@file_put_contents('./shiyan.txt',$POST['username'].'|',$POST['password']."\r\n",FILE_APPEND);
header("location:http:127.0.0.1:8000/admin")
?>
钓鱼页面前端有一个登录框,如果用户安全意识不足,再次输入帐号密码,则会被钓鱼页面记录帐号密码写入到服务器的hetianlab.txt,然后再跳转回正常的登录后页面
逻辑漏洞
逻辑漏洞简介
逻辑漏洞是指攻击者利用业务/功能上的设计缺陷,获取敏感信息或破坏业务的完整性。一般出现在密码修改,确权访问,密码找回,交易支付金额等功能处。
这种漏洞一般防护手段或设备无法阻止,因为走的是合法流量也没有防御标准。
逻辑漏洞的分类
1.越权漏洞
水平越权
指攻击者尝试访问与它用户相同权限的用户资源,比如用户A和用户B属于同一角色,拥有相同的权限等级他们都能获得属于自己的私有数据,但如果系统只验证了能访问数据的角色,而没有对数据做细分或者校验,导致用户A能访问了用户B的数据,那么用户A访问用户B的这种行为就叫做视频越权访问。
垂直越权
由于后台应用没有做权限控制,或仅仅在菜单,按钮上做了权限控制,导致了恶意用户只要猜测其他管理页面的URL或者敏感的参数信息,就可以访问或者控制其他角色拥有的数据或页面,达到提权的目的。
漏洞成因分析:
账户体系上在判断权限时不严格导致存在绕过漏洞,这一类的绕过通常发生在cookie验证不严,简单判断用户提交的参数,归根截底,都是因为这些参数实在客户端提交,服务端未严格校验
1.通过隐藏URL
实现控制访问有些程序的管理员的管理页面只有管理员才显示,普通用户看不到,利用URL实现访问控制,但URL泄露或被恶意攻击者猜到后,这会导致越权攻击
2.直接对象引用
这种通过修改参数就可以产生水平越权,例如查看用户信息页面URL后加上自己的id便可查看,当修改为他人的id号时会返回他人的信息,便产生了水平越权
3.多阶段功能
多阶段功能是一个功能有多个阶段的实现,例如:修改密码,可能第一步是验证用户身份信息,号码验证码类的,当验证成功后,跳到第二部,输入新的密码,很多程序会在这一步不在验证用户身份,导致恶意用户抓包直接修改密码
静态文件
很多网站的下载功能,一些被下载的静态文件,例如pdf、word、xls等,可能只有付费用户或者会员才可以下载,但当这些文件的URL地址泄露后,导致任何人可下载,如果知道URL命名规则,则可利用服务器的收费文档进行批量下载
平台配置错误
一些程序通过控件来限制用户访问,例如后台地址,普通用户不属于管理员组,则不能访问。但当配置平台或配置控件错误时,就会出现越权访问。
2.密码需改/找回/重置
验证码爆破 某些网站发送手机验证码是四位数的验证码,这时我们可以使用字典爆破。
验证码抓取 验证码由客户端生成的,我们可以利用抓包工具,在发送验证码的时候抓取验证码。
3.验证码不更新 获取验证码后,验证码不刷新且后端程序对验证码不校验,导致用A手机号成功重置密码的验证码可以在B手机号密码重置时使用,并且成功使B用户重置了密码。只用一个验证码,然后不断替换不同的手机号,可以实现批量密码重置。
4.删除验证码 抓取数据包然后把验证码删除,可以使服务器判断为正确的操作。
5.修改返回包
修改返回包里面的判断语句(将fail改为true);修改返回的数据值(0、1、200);
替换判断性质的参数值。抓取正确的(A账号)返回包参数值,然后用来替换掉另一个抓取错误的(B账号)返回包的参数值;
删除判断语句;
3.密码找回
1.第一种,如果存在csrf漏洞我们就可以直接修改一波
2.如果存在越权漏洞就可以直接修改其他人的密码
3.点击修改后抓包测试,观察数据包有没有验证类似cookie随机数,如果没有的话可以尝试修改用户名,手机号或者uid来尝试重置其他密码 如果后台是通过向注册手机或者注册邮箱来重置密码的,关于验证码的漏洞我们都可以尝试,这种方式的前提是你已经通过某种方式进入到了对方的个人中心所以意义不大。
基于找回密码的
一般情况下当我们点击找回密码的时候都是通过验证手机号或者邮箱,这就又变成了验证码的问题
1.验证码发送后前端返回
2.验证码时效导致验证码爆破
3.验证码有规律可控
4.验证码被放回在返回包中
5.输入验证码后通过修改响应包的状态来重置密码
6.验证码为空(原理就是后台未考虑验证码为空的情况,直接就是如果存在,然后下面仅判断了存在的情况)绕过过或使用万能密码
7.拦截数据包,发送验证码时可以向多个手机号发送验证码,这个时候就可以添加个云短信,直接接受验证码完成修改
填写验证码和新密码阶段
填写验证码和新密码就意味着我们已经拿到了验证码或者重置密码的URL这里存在的问题有
1.验证凭证较简单,可以暴力破解
2.验证凭证算法简单,凭证可预测
3.验证凭证直接保存在源码里
4.支付漏洞
攻击者通过修改交易金额、交易数量等从而利用漏洞,如Burp修改交易金额、使交易数量为负数或无限大等。
1、支付过程中可直接修改数据包中的支付金额
金额后端没有做校验,传递过程中也没有做签名,导致可以随意篡改金额提交。
2、没有对购买数量进行负数限制,购买数量无限大,
无限大时则程序可能处理出错,从而实现0金额支付
3、请求重放,实现”一次购买对此收货”
4、其他参数干扰
在支付时直接修改数据包中的支付金额,实现小金额购买大金额商品
修改购买数量,使之为负数,可购买负数量商品,从而扣除负积分,即增加积分,
或使购买数量无限大,无限大时则程序可能处理出错,从而实现0金额支付
请求重放,在购买成功后重放请求,可实现"一次购买对此收货"
url逻辑漏洞的预防
-
若跳转的URL事先是可以确定的,包括url和参数的值,则可以在后台先配置好,url参数只需传对应url的索引即可,通过索引找到对应具体url再进行跳转;
-
若跳转的URL事先不确定,但其输入是由后台生成的(不是用户通过参数传入),则可以先生成好跳转链接然后进行签名,而跳转cg首先需要进行验证签名通过才能进行跳转;
-
若1和2都不满足,url事先无法确定,只能通过前端参数传入,则必须在跳转的时候对url进行按规则校验。
-
XSS漏洞的注意事项:跳转url检测中也加入了CRLF头部注入漏洞的检测逻辑, 具体就是在请求参数中加入了%0d%0a这种测试代码,需要对这些参数进行删除处理(事实上:在判断到一个参数中包含 %00 -> %1f 的控制字符时都是不合法的,需对其进行删除)。
-
开源项目及时进行升级,如Django升级 pip install django --upgrade