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請求參數中提交的值。html

代碼清單6-2中的Tornado應用展現了咱們本節要討論的驗證函數。LoginHandler類渲染登陸表單並設置cookie,而LogoutHandler類刪除cookie。python

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/目錄下的文件。web

代碼清單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>
相關文章
相關標籤/搜索