【问题标题】:csrf check in Tornado with AngularJScsrf 使用 AngularJS 检查 Tornado
【发布时间】:2015-11-02 04:14:20
【问题描述】:

我是 AngularJS 和 Python Tornado 的新手,目前正在研究 CSRF/XSRF 检查。当我第一次向“test_c”发送 GET 请求时,我检查了“WebService.py”在标头中返回“set-cookie”,并且在我签入浏览器时确实创建了 cookie。但是当 POST 请求发送到“test”时,Tornado 会显示“POST 中缺少'_xsrf' 参数”错误...

在检查 POST 请求的标头后,我发现 xsrf cookie 是在标头中发送的,名称为 'cookie'(例如:Cookie:PHPSESSID=xxx; X-Csrftoken=xxx; csrftoken=xxx; _xsrf= xxx)。 tornado\web.py 中定义的 check_xsrf_cookie 函数无法正确获取 xsrf 令牌,因为该函数试图从 POST 的参数、名称为“X-Xsrftoken”或“X-Csrftoken”的标头中获取令牌。

因此,我添加了一些代码来检查标题中“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 的所有代码。相反,我在调用“test_c”时返回了 cookie 的值。并在从 JavaScript 发出 POST 请求时设置标头。但是当 POST 请求验证令牌时,出现“XSRF cookie 与 POST 参数不匹配”错误。

我检查了从 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('!');
                    });
            });
    });

编辑 2 我深入研究了 Tornado\web.py 并找到了解决我在上次编辑中提到的问题的方法,但不确定它是否正确。如果有其他更好的方法,请告诉我。

在 Tornado\web.py 中,它尝试将来自 POST 参数或标头的 CSRF 令牌与它存储在“check_xsrf_cookie”函数中的 cookie 进行匹配。 Tornado 使用 "_get_raw_xsrf_token" 函数来获取名称为 "_xsrf" 但不是 "X-Xsrftoken" 或 "X-Csrftoken" Tornado 用于检查标头的 CSRF cookie。因此,我修改了“test_c”函数以生成名为“_xsrf”的 CSRF cookie 并将其返回到前端。并且“JavaScript.js”保持相同的方式在标题中以名称“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('!');
                    });
            });
    });

【问题讨论】:

    标签: python angularjs cookies tornado csrf


    【解决方案1】:

    CSRF 保护的要点是相同的值以两种不同的方式发送:一次在 cookie 中(浏览器自动发送),一次在请求本身中(正文或 HTTP 标头)。在 CSRF 攻击中,cookie 是“只写”的:浏览器会将它们发送到服务器,但攻击者无法分辨它们是什么。这将使攻击者充当经过身份验证的用户,因此为防止这种情况发生,我们要求请求中存在 CSRF 令牌(证明发出请求的页面具有读取令牌的能力)。

    随着您的更改,cookie 被用于比较的双方,完全失败了检查。相反,您必须更改 javascript 端以在 X-Csrftoken HTTP 标头中发送 CSRF 令牌(如果是表单编码,则在 POST 正文中)。

    【讨论】:

    • 嗨,Ben,我无法将 cookie 作为 CSRF 令牌发送,因为 cookie 是“仅限 HTTP”并且我无法在 AngularJS 中使用“$cookies['xxx']”访问它。我已经尝试了下面的代码,但它不起作用。我是否以错误的方式使用它? $http.defaults.headers.post['X-CSRFToken'] = $cookies['xxx'];
    • 您的 CSRF 令牌必须可供 javascript 访问,才能允许基于 JS 的 HTTP 请求。要么使 CSRF cookie 不是httponly,要么添加一个新的 HTTP 端点,其中 javascript 可以请求 CSRF 令牌(使用 GET,因此它不受 CSRF 本身的保护)。
    • 这应该可以。但是,“httponly”不是一开始就为安全问题设置的吗?如果我将其设为“非 httponly”或可通过 GET 请求访问,这会有风险吗?
    • 我在 Javascript 中设置了 CSRF 令牌并将令牌发送到 Tornado。但是 Tornado 无法将它收到的令牌与 cookie 匹配...我已经更新了原始帖子,谢谢。 :D
    • "httponly" 是为一个安全问题而创建的,但是一个不同的问题。细节太长了,无法在评论中解释,但请在别处做一些研究,因为所有 Web 开发人员都必须理解这一点。对于您更新的答案,X-Csrftoken 用作 HTTP 标头名称,而不是 cookie;当令牌出现在 cookie 中时,它被命名为 _xsrf。此外,set_cookie 设置的 cookie 直到 next 请求才可见,因此您不能在同一请求中使用 set_cookie 和 get_cookie。
    猜你喜欢
    • 1970-01-01
    • 2017-12-28
    • 2015-10-02
    • 1970-01-01
    • 1970-01-01
    • 2020-03-13
    • 2018-08-02
    • 2015-12-04
    • 2013-08-11
    相关资源
    最近更新 更多