一、CSRF介绍
CSRF(cross-site request forgery,跨站域请求伪造),也被称为 one link attack/session riding,缩写(XSRF/CSRF)。
二、 浏览器同源策略
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
同源是指:协议、域名、端口 同时相同才认为是同源。在同源检测时,将使用document.domain作为检测的依据。
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) |
下表依赖于浏览器的实现
Compared URL | Outcome | Reason |
---|---|---|
http://www.example.com:80/dir/other.html | Depends | Port explicit. Depends on implementation in browser. |
一般浏览器默认80端口,当端口显示制定为80时,根据浏览器策略而定。
受限制内容
对JavaScript代码能够操作哪些Web内容的一条完整的安全限制,主要针对js读取某些内容或读写某些属性,例如:
- DOM无法获得:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
- ajax请求不能发送: 禁止使用XHR对象向不同源的服务器地址发起HTTP请求。
- Cookie、LocalStorage 和 IndexDB 无法读取。
不受限制内容
允许嵌入资源,例如img链接、 、 、 等。
三、 Cookie作用域
Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。
Cookie有两个很重要的属性:Domain和Path,用来指示此Cookie的作用域:Domain告诉浏览器当前要添加的Cookie的域名归属;Path告诉浏览器当前要添加的Cookie的路径归属,如果没有明确指明则默认为当前路径。浏览器提交的Cookie需要满足以下两点:
当前域名或者父域名下的Cookie。
当前路径或父路径下的Cookie,要满足这两个条件的Cookie才会被提交。
四、 CSRF攻击原理
攻击者(attacker)利用存储在本地的有效cookie及cookie的作用域来伪造用户的某种行为(只是利用cookie骗取服务器信任,并不能拿到cookie,也看不到cookie内容)。
- 用户(victim)访问信任网站A,输入用户名和密码并登录成功,产生网站A的生成的cookie。
- 在此cookie的有效期内,用户恰好访问恶意网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数, 此时浏览器会自动携带网站A的cookie。
- 网站A收到这个请求后,误认为是用户(victim)的正常操作,伪造成功。
五、CSRF攻击防御
大多数防御方法是在请求中嵌入额外的验证数据来检测是否是真实用户的操作。
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("""
"""%(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 值。恶意网站受同源限制不能读取cookie值。
用户访问表单页面时,server端设置xsrf_cookie,页面嵌入js代码,自定义header头。
index.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来实现。
index.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()
受同源策略限制,恶意网站不能根据form提交的_xsrf值设置别的源下的cookie值。
Cookie: _xsrf=2|f6cb1468|2debdc878192abd5577e27000d0daf91|1547196447
表单中的值为
cookie失效之前,cookie值不会改变。cookie中解出token,form表单每次刷新时value值会改变,因为form表单中使用随机值生成masked_token。
有疑问?
Client-side safeguards
待补充
Other techniques
检验 HTTP Referer 字段
攻击者不能篡改Referer值,但是某些浏览器会隐藏Referer值,并且依赖浏览器。