我是AngularJS和Python Tornado的新手,目前正致力于CSRF / XSRF检查 . 我已经检查过“WebService.py”在 Headers 中返回“set-cookie”,而我第一次向“test_c”发送了一个GET请求,并且在我在浏览器中检查时创建了cookie . 但是,当POST请求发送到“test”时,Tornado会显示“POST中缺少'_xsrf'参数”错误...
检查POST请求的 Headers 后,我发现xsrf cookie在 Headers 中发送,名称为'cookie'(例如:Cookie:PHPSESSID = xxx; X-Csrftoken = xxx; csrftoken = xxx; _xsrf = xxx) . tornado \ web.py中定义的check_xsrf_cookie函数无法正确获取xsrf令牌,因为该函数试图从POST的参数,名称为“X-Xsrftoken”或“X-Csrftoken”的标头获取令牌 .
因此,我添加了一些代码来检查 Headers 中的'cookie'中的csrf标记,如下所示,它按预期工作...我想知道如果我以正确的方式解决这个问题?或者Tornado / AngularJS已经用其他函数解决了这个问题,或者我只需添加一些参数来使csrf令牌像Tornado一样被发送?
===========================================
Tornado\Web.py
===========================================
def check_xsrf_cookie(self):
###### Added by me #####
_cookies_dict = {}
_cookies_header_reformat = re.findall(r'\w+=[\w\d.]+', self.request.headers.get('Cookie'))
for _cookie in _cookies_header_reformat:
key, value = _cookie.split('=', 1)
_cookies_dict[key] = value*
#########################
token = (self.get_argument("_xsrf", None) or
self.request.headers.get("X-Xsrftoken") or
self.request.headers.get("X-Csrftoken")
###### Added by me #####
or _cookies_dict['csrftoken'])
#########################
if not token:
raise HTTPError(403, "'_xsrf' argument missing from POST")
_, token, _ = self._decode_xsrf_token(token)
_, expected_token, _ = self._get_raw_xsrf_token()
if not _time_independent_equals(utf8(token), utf8(expected_token)):
raise HTTPError(403, "XSRF cookie does not match POST argument")
===========================================
WebService.py:
===========================================
class Basic(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header('Access-Control-Allow-Origin', self.request.headers.get('Origin', '*'))
self.set_header('Access-Control-Allow-Methods', 'GET, POST, DELETE, PUT, OPTIONS')
self.set_header('Access-Control-Allow-Credentials', 'true')
class test(Basic):
def get(self):
self.write('hi')
def put(self):
self.set_status(200)
def post(self):
print('ok')
def delete(self):
self.set_status(200)
class test_c(Basic):
def get(self):
self.set_cookie('_xsrf', '12345')
settings = {
"xsrf_cookies": True,
"debug": True,
}
application = tornado.web.Application([
(r"/test", test),
(r"/test_c", test_c),
], **settings)
===========================================
JavaScript.js:
===========================================
(function() {
angular.module('ngRouteExample', ['ngCookies'])
.config(function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
})
.controller('MainController', function($http, $scope) {
$http.get('http://localhost:8889/test_c')
.success(function(headers, data) {
$http.post('http://localhost:8889/test')
.then(function() {
alert('!');
});
});
});
}) ();
EDIT: 我删除了我添加到Tornado \ web.py的所有代码 . 相反,我在调用"test_c"时返回了cookie的值 . 并在从JavaScript发出POST请求时设置标头 . 但是当POST请求验证了toke时,我收到"XSRF cookie does not match POST argument"错误 .
我检查了从GET请求返回的两个令牌,从POST请求发送并在触发“test_c”时打印的都是'6e785017a6a1c28377a7d92187806136' .
但是当我从Tornado \ web.py打印“token”和“expected_token”时,它们变成了不同的值......“token”显示为b'nxP \ x17 \ xa6 \ xa1 \ xc2 \ x83w \ xa7 \ xd9! \ x87 \ x80a6'和“expected_token”为b'\ x11 \ xc4 / \ xa9 \ xd4 \ xe3 \ x83 \ xa2 \ xd9` \ xc4 \ x12 \ xaf2 \ xfeK'......
===========================================
WebService.py
===========================================
class test_c(Basic):
def get(self):
if(self.get_cookie('X-Xsrftoken') == None):
self.set_cookie('X-Xsrftoken', hashlib.md5(str(time.localtime()).encode('utf8')).hexdigest())
print(type(self.get_cookie('X-Xsrftoken'))) # For Debug
print(self.get_cookie('X-Xsrftoken')) # For Debug
self.write(self.get_cookie('X-Xsrftoken'))
===========================================
JavaScript.js
===========================================
.controller('MainController', function($http, $scope) {
$http.get('http://localhost:8889/test_c')
.success(function(data) {
$http.post('http://localhost:8889/test', '1', {headers: {'X-Xsrftoken': data}})
.then(function() {
alert('!');
});
});
});
EDIT 2 我挖到Tornado \ web.py并找到了解决我在上次编辑中提到的问题的方法,但不确定它是否正确 . 如果有任何其他更好的方法,请告诉我 .
在Tornado \ web.py中,它尝试将POST参数或 Headers 中的CSRF令牌与存储在“check_xsrf_cookie”函数中的cookie进行匹配 . 并且Tornado使用“_get_raw_xsrf_token”函数来获取名称为“_xsrf”的CSRF cookie,但不是“X-Xsrftoken”,也不是用于检查 Headers 的“X-Csrftoken”Tornado . 因此,我修改了我的“test_c”函数以生成名为“_xsrf”的CSRF cookie并将其返回到前端 . 并且“JavaScript.js”保持以相同的方式在 Headers 中使用名称“X-Xsrftoken”POST标记,因此Tornado可以在验证时检索它 .
===========================================
WebService.py
===========================================
class test_c(Basic):
def get(self):
if(self.get_cookie('_xsrf') == None):
self.set_cookie('_xsrf', hashlib.md5(str(time.localtime()).encode('utf8')).hexdigest())
self.write(self.get_cookie('_xsrf').encode('utf8'))
===========================================
JavaScript.js
===========================================
.controller('MainController', function($http, $scope) {
$http.get('http://localhost:8889/test_c')
.success(function(data) {
$http.post('http://localhost:8889/test', '1', {headers: {'X-Xsrftoken': data}})
.then(function() {
alert('!');
});
});
});
1 回答
CSRF保护的重点是以相同的两种方式发送相同的值:一次在cookie中(浏览器自动发送),一次在请求本身(主体或HTTP头)中 . 在CSRF攻击中,cookie是“只写”的:浏览器会将它们发送到服务器,但是攻击者无法分辨它们是什么 . 这将使攻击者充当经过身份验证的用户,因此为了防止这种情况,我们要求在请求中出现CSRF令牌(证明发出请求的页面具有读取令牌的能力) .
根据您的更改,cookie将用于比较的两侧,完全取消检查 . 相反,您必须更改javascript端以在
X-Csrftoken
HTTP标头中发送CSRF令牌(如果表单编码,则在POST正文中) .