XSRF
跨站請求僞造
先建立一個網站127.0.0.1:8000,使用上一節中的Cookie計數器:
class IndexHandler(RequestHandler):
def get(self):
cookie = self.get_secure_cookie("count")
count = int(cookie) + 1 if cookie else 1
self.set_secure_cookie("count", str(count))
self.write(
'<html><head><title>Cookie計數器</title></head>'
'<body><h1>您已訪問本頁%d次。</h1>' % count +
'</body></html>'
)
再建立一個網站127.0.0.1:9000,
class IndexHandler(RequestHandler):
def get(self):
self.write('<html><head><title>被攻擊的網站</title></head>'
'<body><h1>此網站的圖片鏈接被修改了</h1>'
'<img alt="這應該是圖片" src="http://127.0.0.1:8000/?f=9000/">'
'</body></html>'
)
在9000網站我們模擬攻擊者修改了我們的圖片源地址爲8000網站的Cookie計數器頁面網址。當我們訪問9000網站的時候,在我們不知道、未授權的情況下8000網站的Cookie被使用了,以至於讓8000網址認爲是我們自己調用了8000網站的邏輯。這就是CSRF(Cross-site request forgery)跨站請求僞造(跨站攻擊或跨域攻擊的一種),通常縮寫爲CSRF或者XSRF。
我們剛剛使用的是GET方式模擬的攻擊,爲了防範這種方式的攻擊,任何會產生副作用的HTTP請求,比如點擊購買按鈕、編輯賬戶設置、改變密碼或刪除文檔,都應該使用HTTP POST方法(或PUT、DELETE)。但是,這並不足夠:一個惡意站點可能會通過其他手段來模擬發送POST請求,保護POST請求需要額外的策略。
XSRF保護
瀏覽器有一個很重要的概念——同源策略(Same-Origin Policy)。 所謂同源是指,域名,協議,端口相同。 不同源的客戶端腳本(javascript、ActionScript)在沒明確授權的情況下,不能讀寫對方的資源。
由於第三方站點沒有訪問cookie數據的權限(同源策略),所以我們可以要求每個請求包括一個特定的參數值作爲令牌來匹配存儲在cookie中的對應值,如果兩者匹配,我們的應用認定請求有效。而第三方站點無法在請求中包含令牌cookie值,這就有效地防止了不可信網站發送未授權的請求。
開啓XSRF保護
要開啓XSRF保護,需要在Application的構造函數中添加xsrf_cookies參數:
app = tornado.web.Application(
[(r"/", IndexHandler),],
cookie_secret = "2hcicVu+TqShDpfsjMWQLZ0Mkq5NPEWSk9fi0zsSt3A=",
xsrf_cookies = True
)
當這個參數被設置時,Tornado將拒絕請求參數中不包含正確的_xsrf值的POST、PUT和DELETE請求。
class IndexHandler(RequestHandler):
def post(self):
self.write("hello itcast")
用不帶_xsrf的post請求時,報出了HTTP 403: Forbidden ('_xsrf' argument missing from POST)
的錯誤。
模板應用
在模板中使用XSRF保護,只需在模板中添加
{% module xsrf_form_html() %}
如新建一個模板index.html
<!DOCTYPE html>
<html>
<head>
<title>測試XSRF</title>
</head>
<body>
<form method="post">
{% module xsrf_form_html() %}
<input type="text" name="message"/>
<input type="submit" value="Post"/>
</form>
</body>
</html>
後端
class IndexHandler(RequestHandler):
def get(self):
self.render("index.html")
def post(self):
self.write("hello ")
模板中添加的語句幫我們做了兩件事:
- 爲瀏覽器設置了_xsrf的Cookie(注意此Cookie瀏覽器關閉時就會失效)
- 爲模板的表單中添加了一個隱藏的輸入名爲_xsrf,其值爲_xsrf的Cookie值
渲染後的頁面原碼如下:
<!DOCTYPE html>
<html>
<head>
<title>測試XSRF</title>
</head>
<body>
<form method="post">
<input type="hidden" name="_xsrf" value="2|543c2206|a056ff9e49df23eaffde0a694cde2b02|1476443353"/>
<input type="text" name="message"/>
<input type="submit" value="Post"/>
</form>
</body>
</html>
非模板應用
對於不使用模板的應用來說,首先要設置_xsrf的Cookie值,可以在任意的Handler中通過獲取self.xsrf_token的值來生成_xsrf並設置Cookie。
下面兩種方式都可以起到設置_xsrf Cookie的作用。
class XSRFTokenHandler(RequestHandler):
"""專門用來設置_xsrf Cookie的接口"""
def get(self):
self.xsrf_token
self.write("Ok")
class StaticFileHandler(tornado.web.StaticFileHandler):
"""重寫StaticFileHandler,構造時觸發設置_xsrf Cookie"""
def __init__(self, *args, **kwargs):
super(StaticFileHandler, self).__init__(*args, **kwargs)
self.xsrf_token
對於請求攜帶_xsrf參數,有兩種方式:
- 若請求體是表單編碼格式的,可以在請求體中添加_xsrf參數
- 若請求體是其他格式的(如json或xml等),可以通過設置HTTP頭X-XSRFToken來傳遞_xsrf值
1. 請求體攜帶_xsrf參數
新建一個頁面xsrf.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>測試XSRF</title>
</head>
<body>
<a href="javascript:;" οnclick="xsrfPost()">發送POST請求</a>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
//獲取指定Cookie的函數
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
//AJAX發送post請求,表單格式數據
function xsrfPost() {
var xsrf = getCookie("_xsrf");
$.post("/new", "_xsrf="+xsrf+"&key1=value1", function(data) {
alert("OK");
});
}
</script>
</body>
</html>
2. HTTP頭X-XSRFToken
新建一個頁面json.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>測試XSRF</title>
</head>
<body>
<a href="javascript:;" οnclick="xsrfPost()">發送POST請求</a>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
//獲取指定Cookie的函數
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
//AJAX發送post請求,json格式數據
function xsrfPost() {
var xsrf = getCookie("_xsrf");
var data = {
key1:1,
key1:2
};
var json_data = JSON.stringify(data);
$.ajax({
url: "/new",
method: "POST",
headers: {
"X-XSRFToken":xsrf,
},
data:json_data,
success:function(data) {
alert("OK");
}
})
}
</script>
</body>
</html>