漏洞描述
当setting
中配置了django.middleware.common.CommonMiddleware
且APPEND_SLASH=True
时会触发URL
跳转漏洞,而这两个配置默认存在,且APPEND_SLASH
不需显式写在setting
配置文件。CommonMiddleware
是Django
的一个通用中间件,其实质是一个位于site-packages/django/middleware/common.py
的类,用于执行一些HTTP
请求的基础操作:
- Forbid access to User-Agents in settings.DISALLOWED_USER_AGENTS
- URL rewriting: Based on the APPEND_SLASH and PREPEND_WWW settings,
append missing slashes and/or prepends missing "www."s.
- If APPEND_SLASH is set and the initial URL doesn't end with a
slash, and it is not found in urlpatterns, form a new URL by
appending a slash at the end. If this new URL is found in
urlpatterns, return an HTTP redirect to this new URL; otherwise
process the initial URL as usual.
This behavior can be customized by subclassing CommonMiddleware and
overriding the response_redirect_class attribute.
- ETags: If the USE_ETAGS setting is set, ETags will be calculated from
the entire page content and Not Modified responses will be returned
appropriately. USE_ETAGS is deprecated in favor of
ConditionalGetMiddleware.
漏洞成因与URL rewriting
有关,若设置了APPEND_SLASH=True
且初始URL
未以斜杠结尾,在urlpatterns
中无法找到,则能够在末尾添加斜杠形成新的URL
。若在urlpatterns
中找到了新的URL
,则会将HTTP
重定向到新URL
。
举例:
# 页面未以/结尾
http:<ip>:8000
# 构造新URL,页面会重定向至新URL
http:<ip>:8000//baidu.com
当发出类似上文的请求时,程序会进行设定好的跳转,首先执行process_request()
函数,进入get_full_path_with_slash()
函数:
# Check if a slash should be appended
if self.should_redirect_with_slash(request):
path = self.get_full_path_with_slash(request)
else:
path = request.get_full_path()
get_full_path_with_slash()
函数,给path
末尾加斜杠:
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
返回的new_path
就是之前构造的//baidu.com
,然后进入HTTP
跳转的HttpResponseRedirectBase
基类:
# Return a redirect if necessary
if redirect_url or path != request.get_full_path():
redirect_url += path
return self.response_redirect_class(redirect_url)
虽然类的初始化函数有对协议进行检查,但schema
不存在,所以会跳过判断:
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_to)
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)
之所以使用双斜杠是为了告知浏览器这是一个绝对路径,否则浏览器会跳转到http://<ip>:8000/baidu.com
。
影响版本
1.11.0<Django<1.11.5
。
2.0.0<Django<2.0.8
。
漏洞复现
访问http://<ip>:8000
,页面回显:
Hello, world.
访问http://<ip>:8000//baidu.com
,页面跳转到https://www.baidu.com/
。
文章许可:本文采用CC BY-NC-SA 4.0许可协议,转载请注明出处。