跳转至

Django任意URL跳转漏洞(CVE 2018 14574)

Django任意URL跳转漏洞

在Django默认配置下,用户访问的URL的最后一位应该是以斜杠结尾,如果用户输入的URL没加斜杠,那么会默认自动帮你补上它并跳转到带斜杠的请求中。在path开头为//example.com的情况下,Django没做处理,导致浏览器认为目的地址是绝对路径,最终造成任意URL跳转漏洞。该漏洞利用条件是目标URLCONF中存在能匹配上//example.com的规则。

漏洞详情

影响版本:version < 2.0.8

漏洞原因:Django安装好后,当settings模块配置好了django.middleware.common.CommonMiddleware,且APPEND_SLASH为True时漏洞就会触发(这两个配置是默认存在的)。

CommonMiddleware其实是Django中的一个中间件,因为Django是用python写的,所以又可以说它是一个位于site-packages/django/middleware/common.py的一个类。Django在接受到一个请求(request)后,会先放到中间件(CommonMiddleware)中去处理,然后再通过默认的 URL 方式进行。如果设置了APPEND_SLASH=True,并且用户输入的URL没有以斜杠结尾,在urlpatterns中也匹配不到这个URL,则通过在末尾附加斜杠来形成新的URL。如果在urlpatterns中找到此新URL,则将HTTP重定向返回到此新URL。例如:

初始链接http://your-ip:8000/test
301重定向后的链接http://your-ip:8000/test/
12

但是如果URL是这样的:http://your-ip:8000//example.com,那么程序会进入site-packages/django/middleware/common.py下执行process_request()函数:

def process_ request(self, request): 
"""
Check for denied User-Agents and rewrite the URL based on
settings. APPEND_ SLASH and settings. PREPEND_ WWW
"""
# Check for denied User-Agents
if 'HTTP_ USER_ AGENT' in request.META:
for user_ agent_ regex in settings .DISALLOWED _USER_ AGENTS:
if user_ agent_ regex . search(request .META['HTTP_ USER_ AGENT']):
raise Permi ssionDenied( ' Forbidden user agent ' )
# Check for a redirect based on settings.PREPEND WWW
host = request.get host()
must_ prepend = settings.PREPEND _WWW and host and not host . startswith( ' www.' )
redirect_ url = ( '%s:/ /WWw.%s' % ( request. scheme, host)) if must_ prepend else''
# Check if a slash should be appended
if self.should_ redirect_with_slash(request):
path = self.get_fu11_path_with_slash(request)
else :
path = request.get _fu11_ _path()
# Return a redirect if necessary
if redirect_ _url or path != request.get_ full_ path():
redirect_ url += path
return self .response_ redirect_ class(redirect_ url)
1234567891011121314151617181920212223

可以看到,上面的程序会进入get_full_path_with_slash()函数:

# Check if a slash should be appended
if self.should_ redirect_with_slash(request):
path = self.get_fu11_path_with_slash(request)
123

分析一下get_full_path_with_slash()函数:

def get_full_path_with_slash(self, request):
Return the full path of the request with a trailing sLash appended.
Raise a RuntimeError if settings.DEBUG is True and request. method is
POST, PUT, or PATCH.
new path = request.get_full_path(force_append_slash=True )
if settings . DEBUG and request . method in ( 'POST', 'PUT', 'PATCH' ):
raise RuntimeError 
"You called this URL via %( method)s, but the URL doesn't end
"in a slash and you have APPEND_ SLASH set. Django can't'
"redirect to the slash URL while maintaining %( method)s data .
"Change your form to point to %(url)s (note the trailing ”
"slash),
or set APPEND_SLASH=False in your Django settings." % {
method ' : request . method,
url': request.get_host() + new_ path,
}
return new_ path
1234567891011121314151617

其实作用就是给path末尾加上斜杠,也就是返回一个//example.com/

接着进入response_redirect_class函数,它是HTTP跳转的一个基类:

# Return a redirect if necessary
if redirect_ url or path != request. get_ ful1_ path():
redirect_ _url += path
return self. response redirect_ class (redirect_ ur 1)
1234

以//开头的外部URL将由浏览器翻译为协议、绝对URL

再往后有对协议的检查,但是scheme根本就不存在,所以会跳过这个判断:

class HttpResponseRedirectBase (HttpResponse):
allowed_ schemes = [ 'http', 'https', 'ftp']
def init__ (self, redirect to, *args, **kwargs):
super( )._ init_ ( *args, **kwargs )
self['Location'] = iri_ to_ uri(redirect_ tq)
parsed = urlparse(str(redirect_ to))
if parsed. scheme and parsed. scheme not in self . allowed_ schemes:
raise DisallowedRedirect("Unsafe redirect to URL with protocol '%s'" % parsed. scheme )
url = property(lambda self: self[ 'Location'])
def_ repr_ (self):
return '<%(c1s)s status_ code=%( status_ code )d%( content_ type)s, ur1="%(ur1)s">' % {
cls': self._ class__ ._ name___ ,
status_ code' : self. status_ code ,
content_ type': self.. content_ type_ for repr,
url' : self .ur1,
}
12345678910111213141516

接着可以看到正常的301跳转

漏洞复现

进入环境

1

301重定向后尾巴加了斜杠 2 添加//www.baidu.com后实现了页面的跳转

成功跳转 3