一顷链、CSRF介紹
CSRF(cross-site request forgery橄抹,跨站域請求偽造),也被稱為 one link attack/session riding濒蒋,縮寫(XSRF/CSRF)盐碱。
二、 瀏覽器同源策略
同源策略(Same origin policy)是一種約定沪伙,它是瀏覽器最核心也最基本的安全功能瓮顽,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響围橡∨欤可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現(xiàn)翁授。
同源是指:協(xié)議拣播、域名、端口 同時相同才認為是同源收擦。在同源檢測時贮配,將使用document.domain作為檢測的依據(jù)。
Compared URL | Outcome | Reason |
---|---|---|
http://www.example.com/dir/page2.html | Success | Same protocol, host and port |
http://www.example.com/dir2/other.html | Success | Same protocol, host and port |
http://username:password@www.example.com/dir2/other.html | Success | Same protocol, host and port |
下表為反例炬守。
Compared URL | Outcome | Reason |
---|---|---|
http://www.example.com:81/dir/other.html | Failure | Same protocol and host but different port |
https://www.example.com/dir/other.html | Failure | Different protocol |
http://en.example.com/dir/other.html | Failure | Different host |
http://example.com/dir/other.html | Failure | Different host (exact match required) |
http://v2.www.example.com/dir/other.html | Failure | Different host (exact match required) |
下表依賴于瀏覽器的實現(xiàn)
Compared URL | Outcome | Reason |
---|---|---|
http://www.example.com:80/dir/other.html | Depends | Port explicit. Depends on implementation in browser. |
一般瀏覽器默認80端口赠堵,當端口顯示制定為80時,根據(jù)瀏覽器策略而定叮称。
受限制內容
對JavaScript代碼能夠操作哪些Web內容的一條完整的安全限制,主要針對js讀取某些內容或讀寫某些屬性曹洽,例如:
- DOM無法獲得:禁止對不同源頁面DOM進行操作。這里主要場景是iframe跨域的情況辽剧,不同域名的iframe是限制互相訪問的送淆。
- ajax請求不能發(fā)送: 禁止使用XHR對象向不同源的服務器地址發(fā)起HTTP請求。
- Cookie怕轿、LocalStorage 和 IndexDB 無法讀取偷崩。
不受限制內容
允許嵌入資源,例如img鏈接撞羽、<script src="..."> </script> 阐斜、<img> 、<link> 等诀紊。
三谒出、 Cookie作用域
Cookie 是服務器寫入瀏覽器的一小段信息,只有同源的網(wǎng)頁才能共享邻奠。
Cookie有兩個很重要的屬性:Domain和Path笤喳,用來指示此Cookie的作用域:Domain告訴瀏覽器當前要添加的Cookie的域名歸屬;Path告訴瀏覽器當前要添加的Cookie的路徑歸屬碌宴,如果沒有明確指明則默認為當前路徑杀狡。瀏覽器提交的Cookie需要滿足以下兩點:
當前域名或者父域名下的Cookie。
當前路徑或父路徑下的Cookie贰镣,要滿足這兩個條件的Cookie才會被提交呜象。
四、 CSRF攻擊原理
攻擊者(attacker)利用存儲在本地的有效cookie及cookie的作用域來偽造用戶的某種行為(只是利用cookie騙取服務器信任八孝,并不能拿到cookie董朝,也看不到cookie內容)。
- 用戶(victim)訪問信任網(wǎng)站A干跛,輸入用戶名和密碼并登錄成功子姜,產生網(wǎng)站A的生成的cookie。
- 在此cookie的有效期內楼入,用戶恰好訪問惡意網(wǎng)站B哥捕,網(wǎng)站B上有某個隱藏的鏈接或者圖片標簽會自動請求網(wǎng)站A的URL地址,例如表單提交,傳指定的參數(shù)嘉熊, 此時瀏覽器會自動攜帶網(wǎng)站A的cookie遥赚。
- 網(wǎng)站A收到這個請求后,誤認為是用戶(victim)的正常操作阐肤,偽造成功凫佛。
五讲坎、CSRF攻擊防御
大多數(shù)防御方法是在請求中嵌入額外的驗證數(shù)據(jù)來檢測是否是真實用戶的操作。
Synchronizer token pattern
在form表單中嵌入驗證信息愧薛,并在server端校驗晨炕。攻擊者無法使用最新的csrf值進行操作,代碼如下:
import tornado
import os
import binascii
from tornado import web
from tornado import ioloop
from tornado.web import HTTPError
csrfs = b""
class MainHandler(tornado.web.RequestHandler):
def make_crsf(self):
global csrf
csrf = binascii.b2a_hex(os.urandom(16)).decode()
return csrf
def get(self):
self.write("""<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form action="/" method="POST" enctype="multipart/form-data">
<input type="file" name="filename">
<input type="hidden" name="csrf" value="%s">
<input type="submit" name="submit">
</form>
</body>
</html>"""%(self.make_crsf()))
def post(self):
if csrf != self.get_argument('csrf',""):
raise HTTPError(403)
for filename,files in self.request.files.items():
for file in files:
print(self.request.headers.get('Content-Type'))
print(file["filename"],"len %s bytes"%(len(file["body"])))
self.write(b'upload ok')
class Application(tornado.web.Application):
def __init__(self):
settings = dict(
debug=True,
)
handlers = [
(r'/',MainHandler),
]
super(Application,self).__init__(handlers,settings)
def make_app():
return Application()
if __name__ == "__main__":
app = make_app()
app.listen(8888)
ioloop.IOLoop.current().start()
弊端:
- 服務端需要存儲每個用戶的最新的csrf值毫炉。
- 同一個用戶打開多個tab頁面時瓮栗,其它某些csrf值可能失效,返回403錯誤瞄勾。
Cookie-to-header token
此方法是基于瀏覽器的同源策略费奸,同源才能讀取cookie 值。惡意網(wǎng)站受同源限制不能讀取cookie值进陡。
用戶訪問表單頁面時愿阐,server端設置xsrf_cookie,頁面嵌入js代碼四濒,自定義header頭换况。
index.html
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript" src="https://cdn.staticfile.org/jquery/2.0.0/jquery.min.js"></script>
<script type="text/javascript" src="http://malsup.github.com/jquery.form.js"></script>
</head>
<body>
<form id="myform" action="/" method="POST" enctype="multipart/form-data">
<input id="file" type="file" name="filename">
</form>
<input id="submit" type="button" value="提交" onclick="showoption()">
</body>
<script>
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
function showoption(){
var formData = new FormData();
formData.append("filename",document.getElementById("file").files[0]);
var option = {
url : "/",
type : 'POST',
data: formData,
headers : {"X-XSRFToken":getCookie("_xsrf")}, //添加請求頭部
contentType : false,
processData : false,
success : function(data) {
console.log('success');
document.write(data);
},
error: function(data) {
console.log('error');
document.write('upload failed');
}
}
$.ajax(option)
}
</script>
</html>
handler
import os
import binascii
import tornado
from tornado import web
from tornado import ioloop
class MainHandler(tornado.web.RequestHandler):
def get(self):
xsrf = binascii.b2a_hex(os.urandom(16))
self.set_cookie('_xsrf',xsrf)
self.render("index.html")
def check_xsrf_cookie(self):
if self.request.headers.get('X-XSRFToken') != self.get_cookie("_xsrf"):
raise tornado.web.HTTPError(403)
def post(self):
self.check_xsrf_cookie()
for filename,files in self.request.files.items():
for file in files:
print(self.request.headers.get('Content-Type'))
print(file["filename"],"len %s bytes"%(len(file["body"])))
self.write(b'upload ok')
class Application(tornado.web.Application):
def __init__(self):
settings = dict(
debug=True,
xsrf_cookies=True
)
handlers = [
(r'/',MainHandler)
]
super(Application,self).__init__(handlers,settings)
def make_app():
return Application()
if __name__ == "__main__":
app = make_app()
app.listen(8888)
ioloop.IOLoop.current().start()
Double Submit Cookie
當form表單提交時,服務端設置_xsrf值到cookie中盗蟆,同時也設置到form表單中一份,server端收到請求時舒裤,從cookie中_xsrf字段取出csrf_token與form提交的_xsrf中取出的csrf_token值是否一致喳资。
下面用tornado中的xsrf_form_html來實現(xiàn)。
index.html
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form action="/" method="POST" enctype="multipart/form-data">
<input type="file" name="filename">
{% module xsrf_form_html() %}
<input type="submit" name="submit">
</form>
</body>
</html>
handlers.py
import tornado
from tornado import web
from tornado import ioloop
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
def post(self):
self.check_xsrf_cookie()
for filename,files in self.request.files.items():
for file in files:
print(self.request.headers.get('Content-Type'))
print(file["filename"],"len %s bytes"%(len(file["body"])))
self.write(b'upload ok')
class Application(tornado.web.Application):
def __init__(self):
settings = dict(
debug=True,
xsrf_cookies=True
)
handlers = [
(r'/',MainHandler),
]
super(Application,self).__init__(handlers,settings)
def make_app():
return Application()
if __name__ == "__main__":
app = make_app()
app.listen(8888)
ioloop.IOLoop.current().start()
受同源策略限制腾供,惡意網(wǎng)站不能根據(jù)form提交的_xsrf值設置別的源下的cookie值仆邓。
Cookie: _xsrf=2|f6cb1468|2debdc878192abd5577e27000d0daf91|1547196447
表單中的值為
<html><head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form action="/" method="POST" enctype="multipart/form-data">
<input type="file" name="filename">
<input type="hidden" name="_xsrf" value="2|221a566b|f93a9e845543e9d683af6503d9dced92|1547196447">
<input type="submit" name="submit">
</form>
</body></html>
cookie失效之前,cookie值不會改變伴鳖。cookie中解出token节值,form表單每次刷新時value值會改變,因為form表單中使用隨機值生成masked_token榜聂。
有疑問搞疗?
Client-side safeguards
待補充
Other techniques
檢驗 HTTP Referer 字段
攻擊者不能篡改Referer值,但是某些瀏覽器會隱藏Referer值须肆,并且依賴瀏覽器匿乃。
?