转载

Bottle HTTP 头注入漏洞探究

今天看到两个头注入,一个ASP.NET的 http://seclists.org/bugtraq/2016/Dec/43 ,一个Bottle的。

漏洞分析

这几天更新的bottle,修复了一个漏洞(CVE-2016-9964),介绍是这样说的

It was discovered that bottle, a WSGI-framework for the Python
programming language, did not properly filter "/r/n" sequences when
handling redirections. This allowed an attacker to perform CRLF
attacks such as HTTP header injection.

分析一下,实际上和redirect没有太大关系,只要是能设置HTTP返回头的地方,都存在头注入的问题。先看github的fix: https://github.com/bottlepy/bottle/commit/6d7e13da0f998820800ecb3fe9ccee4189aefb54 和 https://github.com/bottlepy/bottle/commit/3f838db73f7488a108dd8eea308fcc1188303371 ,其将所有设置头的地方都使用了 _hval 方法:

def _hval(value):
    value = value if isinstance(value, unicode) else str(value)
    if '/n' in value or '/r' in value or '/0' in value:
        raise ValueError("Header value must not contain control characters: %r" % value)
    return value

一旦发现/n、/r、/0就抛出异常。那么我们怎么复现这个漏洞呢?

直接使用pip安装老版本的bottle即可: pip install https://github.com/bottlepy/bottle/archive/0.12.10.zip

其实漏洞没什么可分析的,就是设置HTTP头的时候没有处理换行,导致了头注入。

Location && XSS ?

写一个小的例子

import bottle
from bottle import route, run, template, request, response

@route('/')
def index():
    path = request.query.get('path', 'https://www.leavesongs.com')
    return bottle.redirect(path)

if __name__ == '__main__':
    bottle.debug(True)
    run(host='localhost', port=8081)

这里还是使用的redirect,但重申一下这个漏洞和redirect函数没有任何关系。因为redirect函数是向response中插入一个HTTP头,也就是 Location: xxx ,所以存在头注入。

CRLF头注入的原理、利用方法,包括如何绕过浏览器的XSS Auditor我都在这篇文章( https://www.leavesongs.com/PENETRATION/Sina-CRLF-Injection.html )里进行了介绍,本文不再赘述.

但实际测试的过程中遇到了一个有趣的问题,看看redirect函数的实现:

def redirect(url, code=None):
    """ Aborts execution and causes a 303 or 302 redirect, depending on
        the HTTP protocol version. """
    if not code:
        code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302
    res = response.copy(cls=HTTPResponse)
    res.status = code
    res.body = ""
    res.set_header('Location', urljoin(request.url, url))
    raise res

其中使用了一个urljoin,将当前url和我传入的path进行了一次"join",经过这个操作事情就变得很微妙了: Location 头一定有一个值。这种情况下,浏览器就不会渲染页面,会直接跳转到Location头指向的地址。也就是说,如果我要利用CRLF构造XSS的话,这里是不会触发的。

回想上面提到过的新浪的那个CRLF,那个漏洞的 Location 是可以为空的,如果浏览器发现 Location 为空就不会进行跳转,进而渲染了后面注入的HTML,造成XSS。

那么本文这里怎么处理?

两种阻止浏览器跳转的方式

之前 @ Mramydnei 就有跟我们一起研究过这个问题,后来他整理了一篇文章: http://zone.drops.wiki/topic/103

当时我提出了使用/0来阻止PHP返回 Location 头的方法。因为PHP的header函数一旦遇到/0、/r、/n这三个字符,就会抛出一个错误,此时 Location 头便不会返回,浏览器也就不会跳转了。

其实当时我还想出来一个方法:在PHP没有关闭 display_errors 的情况下,只要在header位置的前面某处构造一个错误,一旦有错误信息在header前被输出,header函数也就不会执行了——原因是我们不能在HTTP体已经输出的情况下再输出HTTP头。

但今天这个context是Python的环境,而且似乎并不能找到一个方法让bottle不返回Location头,这就麻烦了。但上文中后两种方法在Firefox确实是可行的。

法1: 将跳转的url端口设为<80

Bottle HTTP 头注入漏洞探究

法2:使用CSP禁止iframe的跳转

Bottle HTTP 头注入漏洞探究

其中的法2利用代码如下:

<?php
header("Content-Security-Policy: frame-src http://localhost:8081/");
?>

<iframe src="http://localhost:8081/?path=http://www.baidu.com/%0a%0dX-XSS-Protection:0%0a%0d%0a%0d<script>alert(location.href)</script>"></iframe>
最后再请大佬们支支招,我觉得应该有更好的办法,而不仅限于Firefox。

Bottle头注入的其他利用点

前面反复强调,bottle这个头注入和redirect无关。也就是说,只要Bottle中设置了HTTP头的位置,都讲存在头注入漏洞,比如试试直接增加一个HTTP头:

import bottle
from bottle import route, run, template, request, response


@route('/')
def index():
    server = request.query.get('server')
    response.add_header('Server', server)
    return response


if __name__ == '__main__':
    bottle.debug(True)
    run(host='localhost', port=8081)

Firefox下仍然能够直接触发:

Bottle HTTP 头注入漏洞探究

而chrome最新版依旧无法触发,这次是为什么呢?

Bottle HTTP 头注入漏洞探究

如上图,我估计是这个 Content-Length: 0 ,导致Chrome认为这个返回包没有Body,所以并没有解析。

又是一个难题,设置chunk也没有解决,明天再看看吧。

原文  https://www.leavesongs.com/PENETRATION/bottle-crlf-cve-2016-9964.html
正文到此结束
Loading...