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 "rn" 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 ' ' in value:
raise ValueError("Header value must not contain control characters: %r" % value)
return value
一旦发现n、r、 就抛出异常。那么我们怎么复现这个漏洞呢?
直接使用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</pre>
其中使用了一个urljoin,将当前url和我传入的path进行了一次"join",经过这个操作事情就变得很微妙了:Location
头一定有一个值。这种情况下,浏览器就不会渲染页面,会直接跳转到Location头指向的地址。也就是说,如果我要利用CRLF构造XSS的话,这里是不会触发的。
回想上面提到过的新浪的那个CRLF,那个漏洞的Location
是可以为空的,如果浏览器发现Location
为空就不会进行跳转,进而渲染了后面注入的HTML,造成XSS。
那么本文这里怎么处理?
两种阻止浏览器跳转的方式
之前 @ Mramydnei 就有跟我们一起研究过这个问题,后来他整理了一篇文章: http://zone.drops.wiki/topic/103
当时我提出了使用 来阻止PHP返回Location
头的方法。因为PHP的header函数一旦遇到 、r、n这三个字符,就会抛出一个错误,此时Location
头便不会返回,浏览器也就不会跳转了。
其实当时我还想出来一个方法:在PHP没有关闭display_errors
的情况下,只要在header位置的前面某处构造一个错误,一旦有错误信息在header前被输出,header函数也就不会执行了——原因是我们不能在HTTP体已经输出的情况下再输出HTTP头。
但今天这个context是Python的环境,而且似乎并不能找到一个方法让bottle不返回Location头,这就麻烦了。但上文中后两种方法在Firefox确实是可行的。
法1: 将跳转的url端口设为<80
法2:使用CSP禁止iframe的跳转
其中的法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下仍然能够直接触发:
而chrome最新版依旧无法触发,这次是为什么呢?
如上图,我估计是这个Content-Length: 0
,导致Chrome认为这个返回包没有Body,所以并没有解析。
又是一个难题,设置chunk也没有解决,明天再看看吧。
第二天
今天在两个Linux上搭了同样的环境,却发现Content-Length
的位置其实不是固定的,有时候会在下面:
但有时又会在上面,和系统是没有关系的。
这个情况下,Chrome是可以触发的:
再深入分析一下,我注入一个Content-Length头进去,你就会发现,Chrome会根据这个头的数值来截取body,如果我注入Content-Length: 5
,此时显示的body如下:
这也就是昨天为什么Chrome下总是触发不了的原因,因为昨天Content-Length头我们无法控制,其值总是为0,导致Chrome不会输出任何内容,也就无法进行XSS。
- go http 服务器编程(1)
- Linux系统内存监控、性能诊断工具vmstat命令详解
- go http 服务器编程(2)
- 利用placeholder属性来添加输入框默认文字提示,提高用户体验
- Linux系统监控、诊断工具之top命令详解
- 【Dev Club分享】iOS黑客技术大揭秘
- Linux终端:用cat命令查看不可见字符
- golang 函数定义及其接口实例
- 分享两种圣诞节雪花特效JS代码(网站下雪效果)
- React 移动 web 极致优化
- golang 高效字符串拼接
- Linux+Nginx/Apache/Tomcat新增SSL证书,开启https访问教程
- golang 使用时间通过md5生成token
- golang中对map操作类
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android打包篇:Android Studio将代码打包成jar包教程
- Android系统制作自定义签名的例子
- 抖音短视频系统开发,日期加减
- Android开发之InetAddress基础入门简介与源码实例
- Android实现通讯录功能
- 教你用CentOS7下使用mktorrent制作PT种子
- 让 Python 的高阶函数支持链式调用[实用库/轮子]
- 解决了一个 Python Type Hints 的问题,分享一下
- Elasticsearch:flattened 数据类型 (7.3 发行版新功能)
- Android开发准确获取手机IP地址的两种方式
- Android网络请求-sign参数的设置方式
- Android使用自定义View实现横行时间轴效果
- Android Studio debug.keystore位置介绍
- Android 实现长按弹出PopupMenu 菜单栏
- Android开发获取手机Mac地址适配所有Android版本