CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析
字数 1189 2025-08-29 08:32:24
Python urllib CRLF 注入漏洞分析 (CVE-2019-9740)
漏洞概述
CVE-2019-9740 是 Python urllib 库中的一个 CRLF 注入漏洞,允许攻击者通过精心构造的 URL 在 HTTP 请求头中注入恶意内容。该漏洞是 2016 年 CVE-2016-5699 漏洞的变种,尽管之前已经修复了部分问题,但仍存在可利用的注入点。
CRLF 注入原理
CRLF 是"回车+换行"(\r\n)的简称,十六进制码为 0x0d 和 0x0a。在 HTTP 协议中:
- HTTP 头和 HTTP 体使用两个
\r\n分隔 - 浏览器根据这些控制字符来解析和显示 HTTP 内容
- 攻击者若能控制 HTTP 消息头中的字符,就可以注入恶意换行来操纵会话 Cookie 或 HTML 体
漏洞演示
正常请求示例
import urllib.request
url = "http://127.0.0.1"
response = urllib.request.urlopen(url)
正常请求头:
GET / HTTP/1.1
Host: 127.0.0.1
User-Agent: Python-urllib/3.7
...
恶意请求示例
import urllib.request
host = "127.0.0.1%0d%0a%0d%0aheaders:test"
url = "http://" + host
response = urllib.request.urlopen(url)
注入后的请求头:
GET / HTTP/1.1
Host: 127.0.0.1
headers:test
User-Agent: Python-urllib/3.7
...
漏洞分析
漏洞代码路径
urllib.request.urlopen()是入口函数- 调用
build_opener()创建 opener - 使用
HTTPHandler处理 HTTP 请求 - 最终通过
http.client.HTTPConnection发送请求
关键问题点
在 http/client.py 中的 putheader 方法:
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)
问题在于:
- 只检查了响应头中的 CRLF,没有检查发送的 URL
- 允许在 URL 中嵌入 CRLF 控制字符
修复后的代码
修复后的 putheader 方法增加了严格的头部验证:
def putheader(self, header, *values):
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 = re.compile(rb'[^:\s][^:\r\n]*').fullmatch
官方修复方案
官方在 putrequest 方法中增加了对 URL 的严格检查:
- 匹配所有 ASCII 码在 00 到 32 的控制字符
- 同时匹配
\x7f字符
修复提交:
https://github.githistory.xyz/python/cpython/blob/96aeaec64738b730c719562125070a52ed570210/Lib/http/client.py
漏洞影响
- 允许攻击者操纵 HTTP 请求头
- 可能导致 HTTP 请求走私、缓存投毒、跨站脚本等攻击
- 影响所有使用 Python urllib 库的应用程序
防护建议
- 升级到修复后的 Python 版本
- 对用户提供的 URL 进行严格验证
- 避免直接使用用户输入构造 URL
- 使用更现代的请求库如
requests替代urllib
参考链接
- https://bugs.python.org/issue36276
- https://hg.python.org/cpython/rev/bf3e1c9b80e9
- https://bugs.python.org/issue30458#msg295067