使用 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).