今天在刷RSS的时候看到了Django发了个新版本,修了个CVE,然后简单的看了下。

先放上release note和github的commit。

https://www.djangoproject.com/weblog/2018/aug/01/security-releases/
https://github.com/django/django/commit/a656a681272f8f3734b6eb38e9a88aa0d91806f1

看描述是修复了一个CommonMiddleware+APPEND_SLASH选项的任意URL跳转,根据github上修改的几个文件,可以看出来,主要变动是在django/middleware/common.py这个文件中,对获取到的path进行了一次编码。

众所周知,漏洞中提到的这个APPEND_SLASH的选项,默认为True,且默认没有在settings.py中显示的设置。这个设置的作用也比较简单,当请求的URL在路由表中无法匹配时,并且请求的URL末尾不是以/结尾的,那么Django会自动在请求的URL末尾添加一个/,并且再次匹配路由表,然后进行正常的处理流程;

也就是说,在这个过程中,会发生一次跳转,那么这个任意URL跳转漏洞应当也是在这块出现的,我们新建一个Django项目,使用2.0.7版本。

urls.py

from django.urls import path
from vuln.views import VulnView

urlpatterns = [
    path("<path:jump_url>/", VulnView.as_view(), name="vuln_view"),
]

views.py

from django.views import View
from django.http import HttpResponse


class VulnView(View):
    def get(self, request, jump_url):
        return HttpResponse(jump_url)

然后我们在CommonMiddleware这个类中的process_request方法下断:
image.png-128.1kB

然后请求http://localhost:8000//lightless.me,注意末尾不能加/,调试的时候建议将浏览器的cache关掉,否则会影响我们调试。

向下走会遇到一个should_redirect_with_slash方法,这个方法是用来判断上面提到过的逻辑:是否需要添加/后重新匹配路由表,如果需要添加,会继续调用self.get_full_path_with_slash方法,实际上就是对request.get_full_path的一层封装。

然后我们会拿到处理过后的path//lightless.me/,接着就会将这个path传给self.response_redirect_class方法,这个实际上就是用来处理重定向的一个类。

跟进之后,会看到在这里设置了Response的Location属性为前面得到的path,并且设置了状态码为301
image.png-101.9kB

虽然这里会检查跳转目标URL的协议,但是并没有什么用,这里的scheme不存在,自然是可以绕过这个限制,后面就是正常跳转流程。

让程序继续跑下去,回过来看浏览器,页面已经跳走了。

整个流程还是比较简单的,但是利用条件也比较苛刻,可能会在一些大型的CMS或其他的系统中出现吧,例如某些CMS会接管Django的路由并且使用自己的路由系统,感觉可能也不太常见。通常情况下我们的路由很有可能是写成这个样子:

path('vuln/<path:jump_url>/', VulnView.as_view(), name="vuln_view"),

很有可能中间还会有个路径,而不是直接就接收参数,这种情况下我目前还没有想到好的利用方法。