Tornado 编写安全应用

Tornado Web服务器从设计之初就在安全方面有了很多考虑,使其能够更容易地防范那些常见的漏洞。安全 cookies 防止用户的本地状态被其浏览器中的恶意代码暗中修改。此外,浏览器cookies 可以与 HTTP 请求参数值作比较来防范跨站请求伪造攻击。

Cookie 漏洞:

许多网站使用浏览器 cookies 来存储浏览器会话间的用户标识。这是一个简单而又被广
泛兼容的方式来存储跨浏览器会话的持久状态。不幸的是,浏览器 cookies 容易受到一
些常见的攻击。

Cookie 伪造:有很多方式可以在浏览器中截获 cookies。JavaScript 和 Flash 对于它们所执行的页面
的域有读写 cookies 的权限。浏览器插件也可由编程方法访问这些数据。跨站脚本攻击
可以利用这些访问来修改访客浏览器中 cookies 的值。

安全 Cookies:Tornado 的安全 cookies 使用加密签名来验证 cookies 的值没有被服务器软件以外的任
何人修改过。因为一个恶意脚本并不知道安全密钥,所以它不能在应用不知情时修改
cookies。

使用安全 Cookies:Tornado 的 set_secure_cookie()和 get_secure_cookie()函数发送和取得浏览器
的 cookies,以防范浏览器中的恶意修改。为了使用这些函数,你必须在应用的构造函数(settings)中指定 cookie_secret (cookie的安全密钥)参数。


调用set_secure_cookie()写cookie时,会用settings中设置的cookie_secret安全密钥来对cookie进行签
名,当调用get_secure_cookie()时就会用cookie_secret安全密钥对取出的cookie值进行验证,若cookie时间戳汰
旧,或者签名与期望值不匹配则认为cookie已遭篡改,则get_secure_cookie()返回None,否则则返回cookie的值。

传递给 Application 构造函数的 cookie_secret 值应该是唯一的随机字符串。在 Python shell
下执行下面的代码片段将产生一个你自己的值:
>>> import base64, uuid
>>> base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
‘bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=‘
然而,Tornado 的安全 cookies 仍然容易被窃听。攻击者可能会通过脚本或浏览器插件
截获 cookies,或者干脆窃听未加密的网络数据。记住 cookie 值是签名的而不是加密的。
恶意程序能够读取已存储的 cookies,并且可以传输他们的数据到任意服务器,或者通
过发送没有修改的数据给应用伪造请求。因此,避免在浏览器 cookie 中存储敏感的用
户数据是非常重要的。

我们还需要注意用户可能修改他自己的 cookies 的可能性,这会导致提权攻击。比如,
如果我们在 cookie 中存储了用户已付费的文章剩余的浏览数,我们希望防止用户自己
更新其中的数值来获取免费的内容。httponly 和 secure 属性可以帮助我们防范这种
攻击。

HTTP-Only 和 SSL Cookies

Tornado 的 cookie 功能依附于 Python 内建的 Cookie 模块。因此,我们可以利用它所
提供的一些安全功能。这些安全属性是 HTTP cookie 规范的一部分,并在它可能是如
何暴露其值给它连接的服务器和它运行的脚本方面给予浏览器指导。比如,我们可以通
过只允许 SSL 连接的方式减少 cookie 值在网络中被截获的可能性。我们也可以让浏览
器对 JavaScript 隐藏 cookie 值。
为 cookie 设置 secure 属性来指示浏览器只通过 SSL 连接传递 cookie。(这可能会产
生一些困扰,但这不是 Tornado 的安全 cookies,更精确的说那种方法应该被称为签名
cookies。)从 Python 2.6 版本开始,Cookie 对象还提供了一个 httponly 属性。包括
这个属性指示浏览器对于 JavaScript 不可访问 cookie,这可以防范来自读取 cookie 值
的跨站脚本攻击。
为了开启这些功能,你可以向 set_cookie 和 set_secure_cookie 方法传递关键字参
数。比如,一个安全的 HTTP-only cookie(不是 Tornado 的签名 cookie)可以调用
self.set_cookie(‘foo‘, ‘bar‘, httponly=True, secure=True)发送。

请求漏洞

任何 Web 应用所面临的一个主要安全漏洞是跨站请求伪造,通常被简写为 CSRF 或
XSRF,发音为"sea surf"。这个漏洞利用了浏览器的一个允许恶意攻击者在受害者网站
注入脚本使未授权请求代表一个已登录用户的安全漏洞。让我们看一个例子。

 

防范请求伪造:

有很多预防措施可以防止这种类型的攻击。首先你在开发应用时需要深谋远虑。任何会
产生副作用的 HTTP 请求,比如点击购买按钮、编辑账户设置、改变密码或删除文档,
都应该使用 HTTP POST 方法。无论如何,这是良好的 RESTful 做法,但它也有额外的
优势用于防范像我们刚才看到的恶意图像那样琐碎的 XSRF 攻击。但是,这并不足够:
一个恶意站点可能会通过其他手段, HTML 表单或 XMLHTTPRequest API 来向你的应用发送 POST 请求。保护 POST 请求需要额外的策略。

为了防范伪造 POST 请求,我们会要求每个请求包括一个参数值作为令牌来匹配存储在
cookie 中的对应值。我们的应用将通过一个 cookie 头和一个隐藏的 HTML 表单元素向
页面提供令牌。当一个合法页面的表单被提交时,它将包括表单值和已存储的 cookie。
如果两者匹配,我们的应用认定请求有效。
由于第三方站点没有访问 cookie 数据的权限,他们将不能在请求中包含令牌 cookie。
这有效地防止了不可信网站发送未授权的请求。正如我们看到的,Tornado 同样会让这
个实现变得简单。

使用 Tornado 的 XSRF 保护
你可以通过在应用的构造函数中包含 xsrf_cookies 参数来开启 XSRF 保护:
settings = {
"cookie_secret": "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=",
"xsrf_cookies": True
}
application = tornado.web.Application([
(r‘/‘, MainHandler),
(r‘/purchase‘, PurchaseHandler),
], **settings)
当这个应用标识被设置时,
Tornado 将拒绝请求参数中不包含正确的_xsrf 值的 POST、
PUT 和 DELETE 请求。Tornado 将会在幕后处理_xsrf cookies,但你必须在你的 HTML
表单中包含 XSRF 令牌以确保授权合法请求。要做到这一点,只需要在你的模板中包含
一个 xsrf_form_html 调用即可:
<form action="/purchase" method="POST">
{% raw xsrf_form_html() %}
<input type="text" name="title" />
<input type="text" name="quantity" />
<input type="submit" value="Check Out" />
</form>


启了XSRF防护后,Tornado对所有的POST请求默认是不信任的,所以当提交post表单是必须包含一个token,{% raw
xsrf_form_html()
%}这个标签会获取cookie中的_xsrf的cookie值,并且在html标签中包含cookie的头信息,及用hidden表单提交token值
到服务器,服务器用这个token值与cookie中存储的对应值做比较,若匹配则说明post请求是可信任的,否则则不可信任。

XSRF 令牌和 AJAX 请求

AJAX 请求也需要一个_xsrf 参数,
但不是必须显式地在渲染页面时包含一个_xsrf 值,
而是通过脚本在客户端查询浏览器获得 cookie 值。下面的两个函数透明地添加令牌值
给 AJAX POST 请求。
第一个函数通过名字获取 cookie,
而第二个函数是一个添加_xsrf
参数到传递给 postJSON 函数数据对象的便捷函数。
function getCookie(name) {
var c = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return c ? c[1] : undefined;
}
jQuery.postJSON = function(url, data, callback) {
data._xsrf = getCookie("_xsrf");
jQuery.ajax({
url: url,
data: jQuery.param(data),
dataType: "json",
type: "POST",
success: callback
});
}
这些预防措施需要思考很多,而 Tornado 的安全 cookies 支持和 XSRF 保护减轻了应
用开发者的一些负担。可以肯定的是,内建的安全功能也非常有用,但在思考你应用的
安全性方面需要时刻保持警惕。有很多在线 Web 应用安全文献,其中一个更全面的实
践对策集合是 Mozilla 的安全编程指南。

用户验证:


需要认证用户才允许访问的处理函数上使用@tornado.web.authenticated可以在调用之前先验证用户是否已登陆认证,若未登陆,则跳
转到settings中设置的login_url(渲染登陆页面),并且会附加上一个next参数,值为我们当前想要访问的url

示例:欢迎回来

在这个例子中,我们将只通过存储在安全cookie里的用户名标识一个人。当某人首次在某个浏览器(或cookie过期后)访问我们的页面时,我们展示一个登录表单页面。表单作为到LoginHandler路由的POST请求被提交。post方法的主体调用set_secure_cookie()来存储username请求参数中提交的值。

代码清单6-2中的Tornado应用展示了我们本节要讨论的验证函数。LoginHandler类渲染登录表单并设置cookie,而LogoutHandler类删除cookie。

import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.options
import os.path

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        return self.get_secure_cookie("username")

class LoginHandler(BaseHandler):
    def get(self):
        self.render(‘login.html‘)

    def post(self):
        self.set_secure_cookie("username", self.get_argument("username"))
        self.redirect("/")

class WelcomeHandler(BaseHandler):
    @tornado.web.authenticated
    def get(self):
        self.render(‘index.html‘, user=self.current_user)

class LogoutHandler(BaseHandler):
    def get(self):
    if (self.get_argument("logout", None)):
        self.clear_cookie("username")
        self.redirect("/")

if __name__ == "__main__":
    tornado.options.parse_command_line()

    settings = {
        "template_path": os.path.join(os.path.dirname(__file__), "templates"),
        "cookie_secret": "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=",
        "xsrf_cookies": True,
        "login_url": "/login"
    }

    application = tornado.web.Application([
        (r‘/‘, WelcomeHandler),
        (r‘/login‘, LoginHandler),
        (r‘/logout‘, LogoutHandler)
    ], **settings)

    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

代码清单6-3和6-4是应用templates/目录下的文件。

代码清单6-3 登录表单:login.html

<html>
    <head>
        <title>Please Log In</title>
    </head>

    <body>
        <form action="/login" method="POST">
            {% raw xsrf_form_html() %}
            Username: <input type="text" name="username" />
            <input type="submit" value="Log In" />
        </form>
    </body>
</html>

代码清单6-4 欢迎回客:index.html

<html>
    <head>
        <title>Welcome Back!</title>
    </head>
    <body>
        <h1>Welcome back, {{ user }}</h1>
    </body>
</html>
时间: 2024-10-19 18:33:25

Tornado 编写安全应用的相关文章

根据 HTTP header 收集客户端相关信息

[课程] web2.0程序设计 [作业要求] 用 tornado 编写一段小程序,根据 HTTP header 收集客户端相关信息:是否手机.操作系统.浏览器等信息*. [作业提示] 主要解析 Request header[“User-Agent”] .该字符串格式可通过 wiki 链接 查 到 ; 通 过 tornado 英 文 网 站 文 档 , 知 道 用 self.request 获 取 tornado.httputil.HTTPServerRequest 对象实例,该实例有 heade

分布式爬虫架构设计与实现

由于scrapy框架需要更多的学习成本,还有分布式爬虫也需要redis来实现,调度方式也不是很符合业务要求,于是就自己设计了个分布式爬虫架构.架构图如下: 爬虫的客户端为tornado编写的服务,爬虫管理器也是tornado编写的后台管理服务,主要功能:获取客户端的状态信息,爬虫进程数量,启动指定数量的爬虫进程,中断.重启爬虫,爬虫异常通知等. 爬虫进程与调度器间的请求非常频繁,所以使用socket长连,获取优先级高的队列,调度器的优先级算法,根据业务需求来编写. 消息队列使用rabbitmq,

Tornado学习之异步原理

本文和大家分享的主要是Tornado 异步原理相关内容,一起来看看吧,希望对大家学习Tornado有所帮助. Tornado是什么? Tornado是一个用Python编写的异步HTTP服务器,同时也是一个web开发框架. Tornado 优秀的大并发处理能力得益于它的 web server 从底层开始就自己实现了一整套基于 epoll 的单线程异步架构. 同步.异步编程差异 .对于同步阻塞型Web服务器,我们来打个比方,将它比作一间饭馆,而Web请求就是来这家饭馆里吃饭的客人.假设饭馆店里只有

tornado学习精要

最简单的应用在程序的最顶部,我们导入了一些Tornado模块.虽然Tornado还有另外一些有用的模块,但在这个例子中我们必须至少包含这四个模块. 12341234包括了一个有用的模块(tornado.options)来从命令行中读取设置.我们在这里使用这个模块指定我们的应用监听HTTP请求的端口. 1212工作流程:如果一个与define语句中同名的设置在命令行中被给出,那么它将成为全局options的一个属性. 如果用户运行程序时使用了–help选项,程序将打印出所有你定义的选项以及你在de

第二篇:白话tornado源码之待请求阶段

上篇<白话tornado源码之一个脚本引发的血案>用上帝视角多整个框架做了一个概述,同时也看清了web框架的的本质,下面我们从tornado程序的起始来分析其源码. 概述 上图是tornado程序启动以及接收到客户端请求后的整个过程,对于整个过程可以分为两大部分: 启动程序阶段,又称为待请求阶段(上图1.2所有系列和3.0) 接收并处理客户端请求阶段(上图3系列) 简而言之: 1.在启 动程序阶段,第一步,获取配置文件然后生成url映射(即:一个url对应一个XXRequestHandler,

第一篇:白话tornado源码之一个脚本引发的血案

本系列博文计划: 1.剖析基于Python的Web框架Tornado的源码 2.为Python开发一个完善的MVC框架 首先将带着大家一起来剖析基于python编写的Web框架 tornado ,本着易读易懂的目标来写这一系列,寄希让小白也能zeng明白其中的道理,与其说剖析还不如说是白话,因为本系列都会用通俗的语言去描述Web框架中的各个知识点. 一个脚本引发的一场“血案”.... 运行脚本并在浏览器上访问http://127.0.0.1:8080 + 注意:对于上述的demo来说,我们没有对

【go】脑补框架 Express beego tornado Flux reFlux React jsx jpg-ios出品

http://goexpresstravel.com/ 今天 Express 的作者 TJ Holowaychuk 发了一篇文章,正式宣告和 Node.js 拜拜了,转向 Go 语言. Go verses Node 如果你在做分布式工作,你会发现 Go 语言丰富的并发原语非常有帮助.虽然我们用 Node 的 generator 也可以做类似的事,但在我看来,generator 永远只能做一半.没有独立的栈错误处理和报告,充其量是中等.我也不想再等(Node)社区花3 年去整理(改善),尤其是我们

使用Tornado实现Ajax请求

Ajax,指的是网页异步刷新,一般的实现均为js代码向server发POST请求,然后将收到的结果返回在页面上.   这里我编写一个简单的页面,ajax.html <html> <head> <title>测试Ajax</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script src=&q

Tornado 协程

同步异步I/O客户端 from tornado.httpclient import HTTPClient,AsyncHTTPClient def ssync_visit(): http_client = HTTPClient() response = http_client.fetch('www.baidu.com') # 阻塞,直到网站请求完成 print(response.body) def hendle_response(response): print(response.body) de