Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【漏洞分析】Django的两个url跳转漏洞分析:CVE-2017-7233&7234

0
0
【漏洞分析】Django的两个url跳转漏洞分析:CVE-2017-7233&7234

2017-04-13 16:02:36
来源: Nearg1e@同程安全应急响应中心 作者:YSRC

阅读:576次
点赞(0)
收藏





【漏洞分析】Django的两个url跳转漏洞分析:CVE-2017-7233&7234

Django官方News&Event在4月4日发布了一个安全更新,修复了两个URL跳转的漏洞,一个是urlparse的锅,另一个由长亭科技的安全研究员phithon报告,都非常漂亮。因为有复现Django漏洞的习惯,晚上抽了点时间复现了一下。有趣的点还挺多。把两个漏洞的分析整合在一起,凑了篇文章。


CVE-2017-7233分析 — Django is_safe_url() URL跳转过滤函数Bypass

国外安全研究员roks0n提供给Django官方的一个漏洞。

关于is_safe_url函数

Django自带一个函数:django.utils.http.is_safe_url(url, host=None, allowed_hosts=None, require_https=False),用于过滤需要进行跳转的url。如果url安全则返回ture,不安全则返回false。文档如下:

print(is_safe_url.__doc__) Return``True``iftheurlisasaferedirection(i.e.itdoesn'tpointto adifferenthostandusesasafescheme). Alwaysreturns``False``onanemptyurl. If``require_https``is``True``,only'https'willbeconsideredavalid scheme,asopposedto'http'and'https'withthedefault,``False``.

让我们来看看常规的几个用法:

fromdjango.utils.httpimportis_safe_url In[2]:is_safe_url('http://baidu.com') Out[2]:False In[3]:is_safe_url('baidu.com') Out[3]:True In[5]:is_safe_url('aaaaa') Out[5]:True In[8]:is_safe_url('//blog.neargle.com') Out[8]:False In[7]:is_safe_url('http://google.com/adadadadad','blog.neargle.com') Out[7]:False In[13]:is_safe_url('http://blog.neargle.com/aaaa/bbb','blog.neargle.com') Out[13]:True

可见在没有指定第二个参数host的情况下,url如果非相对路径,即HttpResponseRedirect函数会跳往别的站点的情况,is_safe_url就判断其为不安全的url,如果指定了host为blog.neargle.com,则is_safe_url会判断url是否属于’blog.neargle.com’,如果url是’blog.neargle.com’或相对路径的url,则判断其url是安全的。

urllib.parse.urlparse的特殊情况

问题就出在该函数对域名和方法的判断,是基于urllib.parse.urlparse的,源码如下(django/utils/http.py):

def_is_safe_url(url,host): ifurl.startswith('///'): returnFalse url_info=urlparse(url) ifnoturl_info.netlocandurl_info.scheme: returnFalse ifunicodedata.category(url[0])[0]=='C': returnFalse return((noturl_info.netlocorurl_info.netloc==host)and (noturl_info.schemeorurl_info.schemein['http','https']))

我们来看一下urlparse的常规用法及几种urlparse无法处理的特殊情况。

>>>urlparse('http://blog.neargle.com/2017/01/09/chrome-ext-spider-for-probe/') ParseResult(scheme='http',netloc='blog.neargle.com',path='/2017/01/09/chrome-ext-spider-for-probe/',params='',query='',fragment='') >>>urlparse('ftp:99999999') ParseResult(scheme='',netloc='',path='ftp:99999999',params='',query='',fragment='') >>>urlparse('http:99999999') ParseResult(scheme='http',netloc='',path='99999999',params='',query='',fragment='') >>>urlparse('https:99999999') ParseResult(scheme='',netloc='',path='https:99999999',params='',query='',fragment='') >>>urlparse('javascript:222222') ParseResult(scheme='',netloc='',path='javascript:222222',params='',query='',fragment='') >>>urlparse('ftp:aaaaaaa') ParseResult(scheme='ftp',netloc='',path='aaaaaaa',params='',query='',fragment='') >>>urlparse('ftp:127.0.0.1') ParseResult(scheme='ftp',netloc='',path='127.0.0.1',params='',query='',fragment='') >>>urlparse('ftp:127.0.0.1') ParseResult(scheme='ftp',netloc='',path='127.0.0.1',params='',query='',fragment='') 可以发现当scheme不等于http,且path为纯数字的时候,urlparse处理例如aaaa: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。(这里顺便提一下,django官方News&Event中提到的poc:”http:99999999”是无法bypass的,在前面的判断if not url_info.netloc and url_info.scheme:都过不了。)例如下面几种情况: >>>is_safe_url('http:555555555') False >>>is_safe_url('ftp:23333333333') True >>>is_safe_url('https:2333333333') True

使用IP Decimal Bypass is_safe_url

但是既然是url跳转漏洞,我们就需要让其跳转到指定的url里,https:2333333333这样的url明显是无法访问的,而冒号之后必须纯数字,http:127.0.0.1是无法pypass的。有什么方法呢?其实ip不仅只有常见的点分十进制表示法,纯十进制数字也可以表示一个ip地址,浏览器也同样支持。例如: 127.0.0.1 == 2130706433, 8.8.8.8 == 134744072(转换器:http://www.ipaddressguide.com/ip),而'http:2130706433'是在浏览器上是可以访问到对应的ip及服务的,即'http:2130706433 = http://127.0.0.1/'。

这里我们选用https:1029415385作为poc,这是一个google的ip,这个url可以bypassis_safe_url并跳转到google.com。


【漏洞分析】Django的两个url跳转漏洞分析:CVE-2017-7233&7234

漏洞验证与影响

我们来写一个简单的环境:

fromdjango.httpimportHttpResponseRedirect fromdjango.utils.httpimportis_safe_url defBypassIsUrlSafeCheck(request): url=request.GET.get("url",'') ifis_safe_url(url,host="blog.neargle.com"): returnHttpResponseRedirect(url) else: returnHttpResponseRedirect('/')

然后访问:http://127.0.0.1:8000/bypassIsUrlSafeCheck?url=https:1029415385, 如图,url被重定向到了google.com。

并非只有开发者自己使用is_safe_url会受到影响,Django默认自带的admin也使用了这个函数来处理next GET | POST参数,当用户访问/admin/login/?next=https:1029415385进行登录时,登录后同样会跳转到google.com,退出登录时同样使用到了该函数。

def_get_login_redirect_url(request,redirect_to): ###Ensuretheuser-originatingredirectionURLissafe. ifnotis_safe_url(url=redirect_to,host=request.get_host()): returnresolve_url(settings.LOGIN_REDIRECT_URL) returnredirect_to @never_cache deflogin(request,template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME, authentication_form=AuthenticationForm, extra_context=None,redirect_authenticated_user=False): ...... returnHttpResponseRedirect(_get_login_redirect_url(request,redirect_to)) ......
【漏洞分析】Django的两个url跳转漏洞分析:CVE-2017-7233&7234

修复

django修复了代码,自己重构了一下urlparse函数,修复了urlparse函数的这个漏洞。

###Copiedfromurllib.parse.urlparse()butusesfixedurlsplit()function. def_urlparse(url,scheme='',allow_fragments=True): """ParseaURLinto6components: <scheme>://<netloc>/<path>;<params>?<query>#<fragment> Returna6-tuple:(scheme,netloc,path,params,query,fragment). Notethatwedon'tbreakthecomponentsupinsmallerbits (e.g.netlocisasinglestring)andwedon'texpand%escapes.""" url,scheme,_coerce_result=_coerce_args(url,scheme) splitresult=_urlsplit(url,scheme,allow_fragments) scheme,netloc,url,query,fragment=splitresult ifschemeinuses_paramsand';'inurl: url,params=_splitparams(url) else: params='' result=ParseResult(scheme,netloc,url,params,query,fragment) return_coerce_result(result)

关于官方提到的 possible XSS attack

django官方News&Event中提到的这个漏洞可能会产生XSS,我认为除非程序员把接受跳转的url插入的到<script type="text/javascript" src="{{ url }}"></script>等特殊情况之外,直接使用产生XSS的场景还是比较少的。如果你想到了其他的场景还请赐教,祝好。


CVE-2017-7234 django.views.static.serve url跳转漏洞

漏洞详情

来自 @Phithon 的一个漏洞。

问题出现在:django.views.static.serve()函数上。该函数可以用来指定web站点的静态文件目录。如:

urlpatterns=[ url(r'^admin/',admin.site.urls), url(r'^staticp/(?P<path>.*)$',serve,{'document_root':os.path.join(settings.BASE_DIR,'staticpath')}) ]

这样django项目根目录下staticpath中的所有文件,就可以在staticp/目录中访问。e.g. http://127.0.0.1:8000/staticp/test.css

这种方法是不被django官方推荐在生成环境使用的,对安全性和性能都有一定影响。

问题代码如下 (django/views/static.py):

path=posixpath.normpath(unquote(path)) path=path.lstrip('/') newpath='' forpartinpath.split('/'): ifnotpart: ###Stripemptypathcomponents. continue drive,part=os.path.splitdrive(part) head,part=os.path.split(part) ifpartin(os.curdir,os.pardir): ###Strip'.'and'..'inpath. continue newpath=os.path.join(newpath,part).replace('\\','/') ifnewpathandpath!=newpath: returnHttpResponseRedirect(newpath)

path既我们传入的路径,如果传入的路径为staticp/path.css,则path=path.css。跟踪代码可知,path经过了unquote进行url解码,后来又replace('\\', '/'),进入HttpResponseRedirect,很诡异的逻辑看起来很有问题。一般遇到这类型的函数我们会先试着找看看,任意文件读漏洞,但是这个对’.’和’..’进行了过滤,所以这边这个HttpResponseRedirect函数就成了帅的人的目标。

我们的最终目的是HttpResponseRedirect('//evil.neargle.com')或者HttpResponseRedirect('http://evil.neargle.com'),那么就要使path != newpath,那么path里面就必须带有’\‘,好的现在的我们传入’/staticp/%5C%5Cblog.neargle.com’,则path=’\\blog.neargle.com’,newpath=’//blog.neargle.com’,HttpResponseRedirect就会跳转到’blog.neargle.com’造成跳转漏洞。

修复


【漏洞分析】Django的两个url跳转漏洞分析:CVE-2017-7233&7234

嗯,官方表示自己也不知道为什么要写这串代码,删了这一串代码然后用safe_url函数代替。


urls

https://github.com/django/django/commit/5ea48a70afac5e5684b504f09286e7defdd1a81a

https://www.djangoproject.com/weblog/2017/apr/04/security-releases/

https://docs.python.org/3/library/urllib.parse.html


PS

浏览器不仅仅支持十进制来代替点分十进制的IP,也可以使用十六进制和8进制来代替。http://点分十进制 == http://十进制 == http://0x十六进制 == http://0八进制(例如:http://127.0.0.1 == http://2130706433 == http://0x7F000001 == http://017700000001),十六进制非纯数字所以不可用来bypass urlparse,但是八进制还是可以的。


【漏洞分析】Django的两个url跳转漏洞分析:CVE-2017-7233&7234
【漏洞分析】Django的两个url跳转漏洞分析:CVE-2017-7233&7234
本文转载自 Nearg1e@同程安全应急响应中心
原文链接:https://mp.weixin.qq.com/s/8fyAXej2qIQAHqM67MoOUA

Viewing all articles
Browse latest Browse all 12749