Django logout function Denial-of-service

26 Aug 2015 - evi1m0

Django 官方在八月十八号发布多个版本更新,修复几个安全问题,其中便包括一个由编码不当导致的 DoS 漏洞,测试一些网站均存在此问题。

Detail

django.contrib.auth.views.logout 视图用于开发者实现用户注销退出功能,正常情况下对于 logout 视图应使用官方提供的 django.contrib.auth.decorators.login_required 修饰器方法来判断用户是否已经登录。由于不少开发人员忽略使用修饰器进行判断,导致攻击者可以匿名访问视图,不断创建会话阻塞导致拒绝服务攻击。

django/contrib/sessions/middleware.py:

    try:
        accessed = request.session.accessed
        modified = request.session.modified
    except AttributeError:
        pass
    else:
        if accessed:
            patch_vary_headers(response, ('Cookie',))
        if modified or settings.SESSION_SAVE_EVERY_REQUEST:
            if request.session.get_expire_at_browser_close():
                max_age = None
                expires = None
            else:
                max_age = request.session.get_expiry_age()
                expires_time = time.time() + max_age
                expires = cookie_date(expires_time)
            # Save the session data and refresh the client cookie.
            # Skip session save for 500 responses, refs #3881.
            if response.status_code != 500:
                request.session.save()
                response.set_cookie(.....

在对 settings.SESSION_SAVE_EVERY_REQUEST 的判断条件中,middleware 中间件未对 session 的状态进行判断,导致可能绕过进入判断体创建空会话。

http://ww2.sinaimg.cn/large/c334041bgw1evg3rysahaj20h40bajv7.jpg

这意味着我们能够发送大量的请求对目标网站进行会话阻塞从而达到拒绝服务攻击:

http://ww1.sinaimg.cn/large/c334041bgw1evg3xircexj20i2036aab.jpg

Fix

django/contrib/sessions/middleware.py:

    try:
        accessed = request.session.accessed
        modified = request.session.modified
        empty = request.session.is_empty()
    except AttributeError:
        pass
    else:
        # First check if we need to delete this cookie.
        # The session should be deleted only if the session is entirely empty
        if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
            response.delete_cookie(settings.SESSION_COOKIE_NAME,
                domain=settings.SESSION_COOKIE_DOMAIN)
        else:
            if accessed:
                patch_vary_headers(response, ('Cookie',))
            if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:

sessions/backends/base.py & sessions/backends/cached_db.py:

def is_empty(self):
    "Returns True when there is no session_key and the session is empty"
    try:
        return not bool(self._session_key) and not self._session_cache
    except AttributeError:
        return True
        
def flush(self):
    """
    Removes the current session data from the database and regenerates the
    key.
    """
    self.clear()
    self.delete()
    self._session_key = None

虽然严格意义上来讲它是个 DoS 漏洞,但另一方面它完全是由开发者不严谨导致的问题(官方背锅),修复版本将 flush() 的 create() 方法修改以避免创建新的空会话,增加 is_empty() 以用来进行会话判断。

如果你实在不想升级版本,那么记得在必要的视图层增加 @login_required(‘/’) 修饰器。

评论插件使用 Disqus ,需翻墙才能查看及留言。