用户
搜索

该用户从未签到

SRC部落

Rank: 7Rank: 7Rank: 7

16

主题

21

帖子

2071

魔法币
收听
0
粉丝
1
注册时间
2016-8-31

i春秋认证SRC部落

发表于 2017-4-13 19:02:25 751500
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`` if the url is a safe redirection (i.e. it doesnt point to
a different host and uses a safe scheme).

Always returns ``False`` on an empty url.

If ``require_https`` is ``True``, only https will be considered a valid
scheme, as opposed to http and https with the default, ``False``.


让我们来看看常规的几个用法:
from django.utils.http import is_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):
    if url.startswith(///):
        return False
    url_info = urlparse(url)
    if not url_info.netloc and url_info.scheme:
        return False
    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]))我们来看一下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。
02805ff14a87d90e6d29cd8ad3643067.jpg
漏洞验证与影响

我们来写一个简单的环境:
from django.http import HttpResponseRedirect
from django.utils.http import is_safe_url

def BypassIsUrlSafeCheck(request):
    url = request.GET.get(url, )
    if is_safe_url(url, host=blog.neargle.com):
        return HttpResponseRedirect(url)
    else:
        return HttpResponseRedirect(/)然后访问: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):
    ### 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

@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):
    ......
            return HttpResponseRedirect(_get_login_redirect_url(request, redirect_to))
    ...... bd62dcce6b7fd4dfb4fb2f3fbd2df0f1.jpg
修复

django修复了代码,自己重构了一下urlparse函数,修复了urlparse函数的这个漏洞。
### Copied from urllib.parse.urlparse() but uses fixed urlsplit() function.
def _urlparse(url, scheme=, allow_fragments=True):
    Parse a URL into 6 components:
    :///;?#
    Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
    Note that we dont break the components up in smaller bits
    (e.g. netloc is a single string) and we dont expand % escapes.
    url, scheme, _coerce_result = _coerce_args(url, scheme)
    splitresult = _urlsplit(url, scheme, allow_fragments)
    scheme, netloc, url, query, fragment = splitresult
    if scheme in uses_params and ; in url:
        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插入的到等特殊情况之外,直接使用产生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.*)$, 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 =
for part in path.split(/):
    if not part:
        ### Strip empty path components.
        continue
    drive, part = os.path.splitdrive(part)
    head, part = os.path.split(part)
    if part in (os.curdir, os.pardir):
        ### Strip . and .. in path.
        continue
    newpath = os.path.join(newpath, part).replace(\\, /)
if newpath and path != newpath:
    return HttpResponseRedirect(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’造成跳转漏洞。
修复

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

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



往期文章
应急不扎心 巡风现已支持检测S2-045漏洞
同程安全研究员获谷歌、腾讯安全致谢
Android 字符串及字典混淆开源实现
我可能是用了假的隐身模式
同程旅游 Hadoop 安全实践
点我的链接就能弹你一脸计算器
点我的链接我就能知道你用了哪些chrome插件
YSRC诚意之作,巡风-企业安全漏洞快速应急、巡航系统
基于文件特征的Android模拟器检测
Android逆向与病毒分析
F-Scrack 弱口令检测脚本
unsafe 模式下的 CSP Bypass
被动扫描器 GourdScan v2.0 发布!
Android App常见逆向工具和使用技巧
XSS Trap - XSS DNS防护的简单尝试
A BLACK PATH TOWARD THE SUN - HTTP Tunnel 工具简介
初探Windows Fuzzing神器----Winafl
2b30f4088b578c2d9457c34af798f2ba.jpg

发表于 2017-4-13 22:26:31
谢谢分享,没想到Django还有这个漏洞
http://blog.163.com/sy_butian/欢迎交流
使用道具 举报 回复
发表于 2017-4-17 10:10:43
感谢分享
使用道具 举报 回复
有部分没看太懂啊
使用道具 举报 回复
发表于 2017-4-17 10:12:08
谢谢分享,拿走了
使用道具 举报 回复
urlparse无法处理的特殊情况,很详细1
使用道具 举报 回复
谢谢分享
使用道具 举报 回复
发表于 2017-4-25 23:30:30
正在学django 很有用
使用道具 举报 回复
发新帖
您需要登录后才可以回帖 登录 | 立即注册