Django is_safe_url() the URL to jump to the filter function of the Bypass(CVE-2017-7233)

2017-04-13T00:00:00
ID SSV:92943
Type seebug
Reporter Root
Modified 2017-04-13T00:00:00

Description

Source: same thread safety Emergency Response Center
Author: Nearg1e@YSRC

Foreign security researcher roks0n provided to the Django official of a vulnerability.

On is_safe_url function

Django comes with a function: django. utils. http. is_safe_url(url, host=None, allowed_hosts=None, require_https=False)for filtering the need for the jump of the url. If the url is safe then return ture, insecurity, false is returned. The document is as follows:

``python print(is_safe_url.doc)

Return True if the url is a safe redirection (i.e. it doesn't 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. ``

Let's look at the conventional of the several usages:

``python 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 ``

Visible in the absence of the designated second parameter of the host case,the url if the non-relative path, i.e., the HttpResponseRedirectfunction will jump to another site, the is_safe_url it is determined that it is unsafe url,if you specify a host as blog.neargle.comthen is_safe_urlwill determine whether the url belongs to the’blog.neargle.com’if the url is’blog.neargle.com’or the relative path of the url, it is determined that the url is safe.

urllib. parse. urlparse special case

The problem lies in the function of the domain name and the method of determination is based on urllib. parse. urlparse Of,the source code is as follows(the django/utils/http.py):

python 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. the scheme in ['http', 'https']))

We take a look at the urlparse conventional usage and several urlparse unable to handle the special case.

``python

> > > 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=") ``

Can be found when the scheme is not equal to http, and the path is purely digital,urlparse processing, for example, aaaa:2222222223the case is not properly divided, will all return to the path. In this case url_info. netloc == url_info. scheme == ""then ((not url_info. netloc or url_info. netloc == host) and (not url_info. scheme or url_info. the scheme in ['http', 'https']))is true. Here, incidentally, django official News&Event mentioned in the poc:"http:99999999"is unable to bypass, in front of the judge if not url_info. netloc and url_info. scheme:it can not.) For example, the following situations:

``

> > > is_safe_url('http:555555555') False is_safe_url('ftp:23333333333') True is_safe_url('https:2333333333') True ``

The use of IP Decimal Bypass is_safe_url

But since it is a url jump vulnerability, we need to make a jump to the specified url, https:2333333333这样的url明显是无法访问的 and colon must be followed by pure digital,http:127.0.0.1是无法pypass的 the. What method? In fact, ip is not only the common dotted decimal notation,a decimal number can also represent an ip address,the browser is also supported. For example: 127.0.0.1 == 2130706433, 8.8.8.8 == 134744072(Converter:http://www. ipaddressguide. com/ip ),and'http:2130706433'is on the browser can access to the corresponding ip and services, i.e.'http:2130706433 = http://127.0.0.1/'the.

Here we use https:1029415385 as a poc, this is a google ip,this url can be bypassis_safe_url and jump to google. com.

Vulnerability validation with the impact

We have to write a simple environment:

``python 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('/') ``

Then visit: http://127.0.0.1:8000/bypassIsUrlSafeCheck?url=https:1029415385 , as shown in Figure,url被重定向到了google.com the.

Not only the developers themselves use is_safe_urlwill be affected, Django by default comes with the admin also use this function to process the next GET | POST parameters, when the user access/admin/login/? next=https:1029415385log, 登录后同样会跳转到google.com to exit the login is the same used by the function.

``python 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)) ...... ``

Repair

django fix the code yourself refactoring a bit urlparse function,fix the urlparse function of this vulnerability.

``python

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 don't break the components up in smaller bits (e.g. netloc is a single string) and we don't 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) ``

On the official mentioned possible XSS attack

django official News&Event mentioned in this vulnerability may produce a XSS, I think unless the programmer to accept a jump to the url inserted into a<script type="text/javascript" src="{{ url }}"></script>and other special circumstances, direct use to produce XSS in the scene is still relatively small. If you think of the other scenes also please enlighten me and good it.