2014-07-12
Tornado是一个基于python的非阻塞式web框架,具有很好的性能。
XSRF又叫做CSRF,全称为Cross-site request forgery,即跨站请求伪造。关于XSRF,浅谈CSRF攻击方式给出了三个极好的示例。下面是地一个示例:
银行网站A,它以GET请求来完成银行转账的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危险网站B,它里面有一段HTML的代码如下:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
首先,你登录了银行网站A,然后访问危险网站B,噢,这时你会发现你的银行账户少了1000块......
为什么会这样呢?原因是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前,
你已经登录了银行网站A,而B中的<img>以GET的方式请求第三方资源(这里的第三方就是指银行网站了,
原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的Cookie
发出Get请求,去获取资源“http://www.mybank.com/Transfer.php?toBankId=11&money=1000”,
结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作。
值得注意的是,即使使用POST更新数据,仍然可以进行XSRF攻击,比如可以利用iframe。浅谈CSRF攻击方式中的示例3给出了一个使用iframe的XSRF攻击。下面,循序渐进地讲一下如何使用Tornado预防xsrf攻击。
Tornado安装
sudo pip install unittest2
sudo pip install futures
sudo pip install pycurl
sudo pip install twisted
sudo pip install pycares
sudo pip install monotime
sudo pip install tornado #笔者安装的版本是3.2.2
简单的Hello World
编写server.py:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
运行server.py,浏览器访问http://127.0.0.1:8888/
,浏览器会显示Hello, world
。
使用模板
将server.py改写为:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
items = ["Item 1", "Item 2", "Item 3"]
self.render("template.html", title="My title", items=items)
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
在同样的目录中创建模板文件template.html:
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<ul>
{% for item in items %}
<li>{{ escape(item) }}</li>
{% end %}
</ul>
</body>
</html>
访问效果如下:
如何防止XSRF攻击
将server.py改写为:
import tornado.ioloop
import tornado.web
settings = {
"cookie_secret": "afalihqifacn7q!@as",
"xsrf_cookies": True,
}
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class LoginHandler(tornado.web.RequestHandler):
def post(self):
self.write("hello")
router = [(r"/", MainHandler),
(r"/login", LoginHandler)]
application = tornado.web.Application(router, **settings)
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
在同一目录下创建模板文件index.html:
<html>
<head>
<title></title>
</head>
<body>
<form action="/login" method="post">
{% raw xsrf_form_html() %}
<input type="text" name="message"/>
<input type="submit" value="Post1"/>
</form>
<form action="/login" method="post">
<input type="text" name="message"/>
<input type="submit" value="Post2"/>
</form>
</body>
</html>
运行server.py,现在使用浏览器打开http://127.0.0.1:8888/
,会看到:
我们通过"xsrf_cookies": True
启用了预防xsrf攻击的功能。第一个表单可以正常跳转到/login
。下面根据访问过程论述一下预防的原理:
首先,访问http://127.0.0.1:8888/
时,http请求头类似于下面的内容:
GET / HTTP/1.1
Host: 127.0.0.1:8888
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64)
Accept-Encoding: gzip,deflate,sdch
服务器server.py响应头如下:
HTTP/1.1 200 OK
Date: Sat, 12 Jul 2014 05:48:21 GMT
Content-Length: 395
Etag: "c8a35073b1b6af47460c7318c8e9069ef998615d"
Content-Type: text/html; charset=UTF-8
Server: TornadoServer/3.2.2
Set-Cookie: _xsrf=2|fbe9aed0|553cc0b9bb7f3d7de9271add4199c185|1405144101; Path=/
在最后一行可以看到,服务器需要对客户端设置键为_xsrf
的cookie,值得注意的是每次请求得到的_xsrf
的值是变化的。
响应主体是html:
<html>
<head>
<title></title>
</head>
<body>
<form action="/login" method="post">
<input type="hidden" name="_xsrf" value="2|fbe9aed0|553cc0b9bb7f3d7de9271add4199c185|1405144101"/>
<input type="text" name="message"/>
<input type="submit" value="Post1"/>
</form>
<form action="/login" method="post">
<input type="text" name="message"/>
<input type="submit" value="Post2"/>
</form>
</body>
</html>
注意到第一个表单的的第一个input是隐藏的,且其value和响应头cookie中设置的_xsrf
的值是相同的。
点击表单1的按钮后,表单中_xsrf
和message
的值会以post的方式传到http://127.0.0.1:8888/login
,http请求头中也包含了之前设置的cookie(即_xsrf
)。server.py会对这两个_xsrf
进行比较,相同的话则意味着不是XSRF攻击,浏览器会显示hello
。
如果点击表格2的按钮Post2
,由于两个_xsrf
不相同(其实表单中就没有_xsrf
),得到的响应会是:
403: Forbidden
如果在其他的域下设法向http://127.0.0.1:8888/login
POST数据,由于cookie中没有_xsrf
或者没有正确的_xsrf
,POST将无法成功。这也就防范了XSRF攻击。
一些资料
http://www.tornadoweb.cn/
Tornado概览
Tornado Documentation
Introduction to Tornado——中文版