为什么Django的CSRF不能通过HTTPS工作?

时间:2013-10-25 14:49:12

标签: django https django-csrf

我在http://example.com有一个Django网站,工作正常,包括帖子请求。我添加了HTTPS,因此我的网站也可以在https://example.com访问。

我可以在HTTPS上加载任何页面,但是当我尝试POST时,我总是遇到CSRF验证错误。 POST请求在HTTP上正常工作。

我的Django进程在nginx后面运行gunicorn,我有nginx设置X_Forwarded_For。因此,HTTPS请求具有以下标头(取自request.META):

'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.5',
'HTTP_CONNECTION': 'close',
'HTTP_COOKIE': 'redacted',
'HTTP_HOST': 'example.com:80',
'HTTP_REFERER': 'https://example.com/user/delete/49/',
'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0',
'HTTP_X_FORWARDED_FOR': '1.2.3.4, 192.168.252.22',
'HTTP_X_FORWARDED_PROTO': 'https',
'HTTP_X_REAL_IP': '1.2.3.4',

并且HTTP请求具有以下标头:

'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.5',
'HTTP_CONNECTION': 'close',
'HTTP_COOKIE': 'redacted',
'HTTP_HOST': 'example.com.com:80',
'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0',
'HTTP_X_FORWARDED_FOR': '1.2.3.4, 192.168.252.22',
'HTTP_X_FORWARDED_PROTO': 'http',
'HTTP_X_REAL_IP': '1.2.3.4',

为什么当我没有HTTP问题时,CSRF不能在HTTPS上工作?

3 个答案:

答案 0 :(得分:9)

原来这是一个nginx配置问题。我的服务器设置是:

nginx - > nginx - > gunicorn

在第二个nginx系统上,我有

proxy_set_header        Host            $host:$server_port;

但是,由于HTTPS在第一个nginx处终止,$server_port始终为80。

在HTTPS上,Django does strict referer checking(参见第4点)。看看Django来源:

good_referer = 'https://%s/' % request.get_host()
if not same_origin(referer, good_referer):
    reason = REASON_BAD_REFERER % (referer, good_referer)
    logger.warning('Forbidden (%s): %s', reason, request.path,
        extra={
            'status_code': 403,
            'request': request,
        }
    )
    return self._reject(request, reason)

CSRF验证失败,因为"https://example.com/" != "https://example.com:80/"

答案 1 :(得分:0)

就像提到的here一样,我通过在settings.py中添加以下Django常量来解决此问题,因此Django考虑使用代理标头:

# Setup support for proxy headers
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

答案 2 :(得分:-1)

如果你看一下执行情况 CsrfViewMiddleware

request.is_secure()

时,Django会检查“Referer”标头
good_referer = 'https://%s/' % request.get_host()
if not same_origin(referer, good_referer):
    reason = REASON_BAD_REFERER % (referer, good_referer)
    return self._reject(request, reason)