CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析 | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析

申博_安全预警 申博 135次浏览 未收录 0个评论

0x00媒介

2016年曾爆出过python库中urllib的CRLF HTTP投注入破绽 CVE-2016-5699,近来又爆出了新的Python urllib CRLF 注入破绽CVE-2019-9740,有兴致来剖析一下

0x01CRLF

CRLF即为 “回车+换行” (\r\n)的简称,十六进制码为0x0d和0x0a。HTTP中HTTP header和http Body是用两个\n\r来区分的,浏览器依据这两个\r\n来掏出HTTP内容并显示出来。因而,当我们能掌握HTTP 音讯头中的字符,注入一些歹意的换行就能够或许诸如一些比方会话Cookie或许HTML body的代码。

当我们输入一个http://127.0.0.1的时刻,其发送的header为

GET / HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

而当我们的url变成http://127.0.0.1%0d%0a%0d%0aheaders:test时,其发送的header为

GET / HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
headers:test
Upgrade-Insecure-Requests: 1

能够看到注入进了header内里
0x02 破绽研讨
官网上的考证代码以下

import sys
import urllib
import urllib.request
import urllib.error


host = "127.0.0.1:7777?a=1 HTTP/1.1\r\nCRLF-injection: test\r\nTEST: 123"
url = "http://"+ host + ":8080/test/?test=a"

try:
    info = urllib.request.urlopen(url).info()
    print(info)

except urllib.error.URLError as e:
    print(e)

引发了CRLF破绽
CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析

抓包来看
CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析
后置的8080的后缀被自动修改,若是不加后缀:8080/test/?test=a的结果为
CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析

研讨代码,起首看到Lib/urllib文件夹下面的request.py文件,从urlopen这个函数一起跟进

def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
            *, cafile=None, capath=None, cadefault=False, context=None):
    global _opener
    if cafile or capath or cadefault:
        import warnings
        warnings.warn("cafile, cpath and cadefault are deprecated, use a "
                      "custom context instead.", DeprecationWarning, 2)
        if context is not None:
            raise ValueError(
                "You can't pass both context and any of cafile, capath, and "
                "cadefault"
            )
        if not _have_ssl:
            raise ValueError('SSL support not available')
        context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH,
                                             cafile=cafile,
                                             capath=capath)
        https_handler = HTTPSHandler(context=context)
        opener = build_opener(https_handler)
    elif context:
        https_handler = HTTPSHandler(context=context)
        opener = build_opener(https_handler)
    elif _opener is None:
        _opener = opener = build_opener()
    else:
        opener = _opener
    return opener.open(url, data, timeout)

看到代码中调用了build_opener函数,继承跟进

WinAFL 源码分析

前言 winafl 是 afl 在 windows 的移植版, winafl 使用 dynamorio 来统计代码覆盖率,并且使用共享内存的方式让 fuzzer 知道每个测试样本的覆盖率信息。本文主要介绍 winafl 不同于 afl 的部分,对于 afl 的变异策略等部分没有介绍,对于 afl 的分析可以看 https://paper.seebug.org/496/#arithmetic 源码分析 winafl 主要分为两个部分 afl-fuzz.c 和 winafl.c , 前者是 fuzzer 的主程序 ,后面的是收集程序运行时信息的 dynamorio 插件的源码。 afl-fuzz main winafl 的入口时 afl-fuzz.c , 其中的 main 函数的主要代码如下 int main(int argc, char** argv) {

// 加载变异数据修正模块
setup_post();
if (!in_bitmap) memset(virgin_bits, 255, MAP_SIZE); // MAP_SIZE –> 0x00010000
setup_shm(); // 设置共享内存
init_count_class16();

setup_dirs_fds(); // 设置模糊测试过程中的文件存放位置
read_testcases(); // 读取测试用例到队列

// 首先跑一遍所有的测试用例, 记录信息到样本队列
perform_dry_run(use_argv);

// 模糊测试主循环
while (1) {
u8 skipped_fuzz;
// 每次循环从样本队列里面取测试用例
cull_queue();

def build_opener(*handlers):
    opener = OpenerDirector()
    default_classes = [ProxyHandler, UnknownHandler, HTTPHandler,
                       HTTPDefaultErrorHandler, HTTPRedirectHandler,
                       FTPHandler, FileHandler, HTTPErrorProcessor,
                       DataHandler]
    if hasattr(http.client, "HTTPSConnection"):
        default_classes.append(HTTPSHandler)
    skip = set()
    for klass in default_classes:
        for check in handlers:
            if isinstance(check, type):
                if issubclass(check, klass):
                    skip.add(klass)
            elif isinstance(check, klass):
                skip.add(klass)
    for klass in skip:
        default_classes.remove(klass)

    for klass in default_classes:
        opener.add_handler(klass())

    for h in handlers:
        if isinstance(h, type):
            h = h()
        opener.add_handler(h)
    return opener

在build_opener函数内里依据我们的url来看使用了HTTPHandler这个类,继承跟进

class HTTPHandler(AbstractHTTPHandler):

    def http_open(self, req):
        return self.do_open(http.client.HTTPConnection, req)

    http_request = AbstractHTTPHandler.do_request_

在这个函数带有歹意payload的url经由过程Request要领举行要求,从self.open要领中也能够或许看到

def do_open(self, http_class, req, **http_conn_args):
        host = req.host
        if not host:
            raise URLError('no host given')

        # will parse host:port
        h = http_class(host, timeout=req.timeout, **http_conn_args)
        h.set_debuglevel(self._debuglevel)

        headers = dict(req.unredirected_hdrs)
        headers.update(dict((k, v) for k, v in req.headers.items()
                            if k not in headers))

        headers["Connection"] = "close"
        headers = dict((name.title(), val) for name, val in headers.items())

        if req._tunnel_host:
            tunnel_headers = {}
            proxy_auth_hdr = "Proxy-Authorization"
            if proxy_auth_hdr in headers:
                tunnel_headers[proxy_auth_hdr] = headers[proxy_auth_hdr]
                # Proxy-Authorization should not be sent to origin
                # server.
                del headers[proxy_auth_hdr]
            h.set_tunnel(req._tunnel_host, headers=tunnel_headers)

        try:
            try:
                h.request(req.get_method(), req.selector, req.data, headers,
                          encode_chunked=req.has_header('Transfer-encoding'))
            except OSError as err: # timeout error
                raise URLError(err)
            r = h.getresponse()
        except:
            h.close()
            raise
        if h.sock:
            h.sock.close()
            h.sock = None

        r.url = req.get_full_url()
        r.msg = r.reason
        return r

从新看Lib/http/client.py这个文件中的putheader要领,在之前的CVE-2016-5699破绽中它的代码以下

def putheader(self, header, *values):
        values = list(values)
        for i, one_value in enumerate(values):
            if hasattr(one_value, 'encode'):
                values[i] = one_value.encode('latin-1')
            elif isinstance(one_value, int):
                values[i] = str(one_value).encode('ascii')
        value = b'\r\n\t'.join(values)
        header = header + b': ' + value
        self._output(header)

修复后的代码以下

def putheader(self, header, *values):
        """Send a request header line to the server.

        For example: h.putheader('Accept', 'text/html')
        """
        if self.__state != _CS_REQ_STARTED:
            raise CannotSendHeader()

        if hasattr(header, 'encode'):
            header = header.encode('ascii')

        if not _is_legal_header_name(header):
            raise ValueError('Invalid header name %r' % (header,))

        values = list(values)
        for i, one_value in enumerate(values):
            if hasattr(one_value, 'encode'):
                values[i] = one_value.encode('latin-1')
            elif isinstance(one_value, int):
                values[i] = str(one_value).encode('ascii')

            if _is_illegal_header_value(values[i]):
                raise ValueError('Invalid header value %r' % (values[i],))

        value = b'\r\n\t'.join(values)
        header = header + b': ' + value
        self._output(header)

加入了_is_legal_header_name这个要领,要领为

_is_legal_header_name = re.compile(rb'[^:\s][^:\r\n]*').fullmatch

能够看到对 : 背面的内容举行婚配,婚配了一切\r\n的内容,若是婚配到\r\n,则返回报错Invalid header name <header>,然则经由过程调试发现该检测要领仅为检测返回时刻的header头而没有检测到发送撤除的url,因而发送进来的payload并没有经由正则的婚配。

0x03官方的修复要领

https://github.githistory.xyz/python/cpython/blob/96aeaec64738b730c719562125070a52ed570210/Lib/http/client.py
在putrequest要领上对url举行检查,婚配一切的ascii码在00到32的一切字符,而且同时婚配\x7f字符
CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析
CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析

有毛病的地方还请师傅们指正。


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址