使用 AngularJS 在 Tornado 中进行 csrf 检查
csrf check in Tornado with AngularJS
我是 AngularJS 和 Python Tornado 的新手,目前正在研究 CSRF/XSRF 检查。我在 header 中检查了 "WebService.py" return "set-cookie",而我第一次向 "test_c" 发送了 GET 请求,当我在浏览器中检查时确实创建了 cookie .但是当 POST 请求发送到 "test"...
时,Tornado keep 显示“POST 中缺少‘_xsrf’参数”错误
在我检查了 POST 请求的 header 之后,我发现 xsrf cookie 是在 header 中发送的,名称为 'cookie'(例如:Cookie:PHPSESSID=xxx;X-Csrftoken=xxx;csrftoken=xxx;_xsrf=xxx)。 tornado\web.py 中定义的 check_xsrf_cookie 函数无法正确获取 xsrf 令牌,因为该函数试图从 POST 的参数中获取令牌,header 名称为 'X-Xsrftoken' 或 'X-Csrftoken'。
因此,我添加了一些代码来检查 header 中的 '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('!');
});
});
});
}) ();
编辑:
我删除了添加到 Tornado\web.py 的所有代码。相反,我 return 在 "test_c" 被调用时编辑了 cookie 的值。并在 JavaScript 发出 POST 请求时设置 header。但是当 POST 请求验证令牌时,我得到 "XSRF cookie does not match POST argument" 错误。
我检查了从 GET 请求中 return 发送的令牌、从 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('!');
});
});
});
编辑 2
我深入研究 Tornado\web.py 并找到了解决我在上次编辑中提到的问题的方法,但不确定它是否是正确的方法。如果有其他更好的方法,请告诉我。
在 Tornado\web.py 中,它试图将来自 POST 参数或 header 的 CSRF 令牌与存储在 "check_xsrf_cookie" 函数中的 cookie 进行匹配。并且 Tornado 使用“_get_raw_xsrf_token”函数获取名称为“_xsrf”的 CSRF cookie 但不是 "X-Xsrftoken" 也不是 "X-Csrftoken" Tornado 用于检查 header。因此,我修改了 "test_c" 函数以生成名称为“_xsrf”的 CSRF cookie,并将其 return 编辑到前端。 "JavaScript.js" 与 POST header 中名称为 "X-Xsrftoken" 的令牌保持相同的方式,因此 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('!');
});
});
});
CSRF 保护的要点是相同的值以两种不同的方式发送:一次在 cookie 中(浏览器自动发送),一次在请求本身(body 或HTTP headers)。在 CSRF 攻击中,cookie 是 "write-only":浏览器会将它们发送到服务器,但攻击者无法分辨它们是什么。这将使攻击者充当经过身份验证的用户,因此为防止这种情况,我们要求请求中存在 CSRF 令牌(证明发出请求的页面具有读取令牌的能力)。
根据您的更改,cookie 用于 比较的双方,完全击败了检查。相反,您必须更改 javascript 端以在 X-Csrftoken
HTTP header 中发送 CSRF 令牌(或者在 POST body 中发送 form-encoded).
我是 AngularJS 和 Python Tornado 的新手,目前正在研究 CSRF/XSRF 检查。我在 header 中检查了 "WebService.py" return "set-cookie",而我第一次向 "test_c" 发送了 GET 请求,当我在浏览器中检查时确实创建了 cookie .但是当 POST 请求发送到 "test"...
时,Tornado keep 显示“POST 中缺少‘_xsrf’参数”错误在我检查了 POST 请求的 header 之后,我发现 xsrf cookie 是在 header 中发送的,名称为 'cookie'(例如:Cookie:PHPSESSID=xxx;X-Csrftoken=xxx;csrftoken=xxx;_xsrf=xxx)。 tornado\web.py 中定义的 check_xsrf_cookie 函数无法正确获取 xsrf 令牌,因为该函数试图从 POST 的参数中获取令牌,header 名称为 'X-Xsrftoken' 或 'X-Csrftoken'。
因此,我添加了一些代码来检查 header 中的 '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('!');
});
});
});
}) ();
编辑: 我删除了添加到 Tornado\web.py 的所有代码。相反,我 return 在 "test_c" 被调用时编辑了 cookie 的值。并在 JavaScript 发出 POST 请求时设置 header。但是当 POST 请求验证令牌时,我得到 "XSRF cookie does not match POST argument" 错误。
我检查了从 GET 请求中 return 发送的令牌、从 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('!');
});
});
});
编辑 2 我深入研究 Tornado\web.py 并找到了解决我在上次编辑中提到的问题的方法,但不确定它是否是正确的方法。如果有其他更好的方法,请告诉我。
在 Tornado\web.py 中,它试图将来自 POST 参数或 header 的 CSRF 令牌与存储在 "check_xsrf_cookie" 函数中的 cookie 进行匹配。并且 Tornado 使用“_get_raw_xsrf_token”函数获取名称为“_xsrf”的 CSRF cookie 但不是 "X-Xsrftoken" 也不是 "X-Csrftoken" Tornado 用于检查 header。因此,我修改了 "test_c" 函数以生成名称为“_xsrf”的 CSRF cookie,并将其 return 编辑到前端。 "JavaScript.js" 与 POST header 中名称为 "X-Xsrftoken" 的令牌保持相同的方式,因此 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('!');
});
});
});
CSRF 保护的要点是相同的值以两种不同的方式发送:一次在 cookie 中(浏览器自动发送),一次在请求本身(body 或HTTP headers)。在 CSRF 攻击中,cookie 是 "write-only":浏览器会将它们发送到服务器,但攻击者无法分辨它们是什么。这将使攻击者充当经过身份验证的用户,因此为防止这种情况,我们要求请求中存在 CSRF 令牌(证明发出请求的页面具有读取令牌的能力)。
根据您的更改,cookie 用于 比较的双方,完全击败了检查。相反,您必须更改 javascript 端以在 X-Csrftoken
HTTP header 中发送 CSRF 令牌(或者在 POST body 中发送 form-encoded).