仅在 DRF 中进行身份验证时才检查 CSRF?

Posted

技术标签:

【中文标题】仅在 DRF 中进行身份验证时才检查 CSRF?【英文标题】:CSRF is only checked when authenticated in DRF? 【发布时间】:2018-08-22 19:47:40 【问题描述】:

TLDR;如果客户端具有经过身份验证的会话,我的 POST(到 DRF 端点)似乎仅受 CSRF 保护。这是错误的,并将应用程序选项留给login CSRF 攻击。我该如何解决这个问题?

我开始为 ReactJS 前端构建一个 django rest 框架 API,我们希望通过 API 处理所有事情,包括身份验证。我们正在使用 SessionAuthentication。

如果我有一个经过身份验证的会话,那么 CSRF 将完全按预期工作(当经过身份验证时,客户端应该设置一个 CSRF cookie,并且这需要与 POST 数据中的 csrfmiddlewaretoken 配对)。

但是,当 进行身份验证时,似乎没有 POST 需要接受 CSRF 检查。包括已创建的(基本)登录 APIView。这使得该网站容易受到login CSRF 漏洞的攻击。

是否有人知道如何在未经身份验证的会话上强制执行 CSRF 检查? 和/或 DRF 似乎如何绕过 CSRF 检查以进行登录?

以下是我的粗略设置...

settings.py:

REST_FRAMEWORK = 
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],

views.py:

class Login(APIView):

    permission_classes = (permissions.AllowAny,)

    @method_decorator(csrf_protect)  # shouldn't be needed
    def post(self, request, format=None):
        user = authenticate(
            request,
            username=request.POST['username'],
            password=request.POST['password']
        )
        # ... perform login logic ...

    def get(self, request, format=None):
        """
        Client must GET the login to obtain CSRF token
        """
        # Force generation of CSRF token so that it's set in the client
        get_token(request)  
        return Response(None)

urls.py:

urlpatterns = [
    url(r'^login/$', views.Login.as_view(), name='login'),
]

预期行为:

login_url = reverse('login')
login_details = 
    'username': self.user.email,
    'password': self.password,

client = APIClient(enforce_csrf_checks=True)
# Try to just POST to a CSRF protected view with no CSRF
response = client.post(reverse('login'), login_details)
# response status should be 403 Missing or incorrect CSRF

# GET the login API first to obtain CSRF
client.get(reverse('login'))
login_details['csrfmiddlewaretoken'] = client.cookies.get('csrftoken').value
# Now POST to the login API with the CSRF cookie and CSRF token in the POST data
response = client.post(reverse('login'), login_details)
# response status should now be 200 (and a newly rotated CSRF token delivered)

实际行为:

client = APIClient(enforce_csrf_checks=True)
# Try to just to a CSRF protected view with no CSRF
response = client.post(reverse('login'), login_details)
# BROKEN: response status is 200, client is now logged in

# Post to the exact same view again, still with no CSRF
response = client.post(reverse('login'), login_details)
# response status is now 403
# BROKEN: This prooves that this view is protected against CSRF, but ONLY for authenticated sessions.

【问题讨论】:

好问题!试图解决这个问题...... CSRF 保护是否适用于对外开放的 API?在您的示例中,攻击者可以通过 GET 访问登录 API 以获取 CSRF 令牌。然后他可以使用该 CSRF 令牌进行 POST,因此没有真正的保护。您可以 CSRF 保护登录视图,而不是在 GET 上给出令牌。这是安全的,但是您不能再从“外部”登录。我想如果可以通过对外开放的 API 登录,那么任何人都可以登录。也许对于外部访问,请改用令牌身份验证?我错过了什么吗? 【参考方案1】:

当使用 SessionAuthentication 并且用户未通过身份验证时,Django REST Framework 正在禁用 CSRF 令牌要求。这是为了不弄乱不需要 CSRF 身份验证的其他身份验证方法(因为它们不是基于 cookie),您应该自己确保 CSRF 在登录请求中得到验证,并且在 @ 的最后一段中提到987654321@。建议要么使用非 API 登录流程,要么确保基于 API 的登录流程受到全面保护。

您可以查看DRFs SessionAuthentication 在您登录时如何执行 CSRF 验证,并以此为基础。

【讨论】:

那么,谢谢。我已经多次阅读该文档,不知道我怎么没有发现这一点。基于警告“在创建登录页面时始终使用 Django 的标准登录视图”,我猜测解决这个问题的“方法”就是不通过 API 完成身份验证......这有点糟糕。您是否知道以稳健的方式实现我想要的(API 身份验证)的任何示例(即我不想重新发明 CSRF ***)? 您也可以通过 API 完成,但您必须确保 CSRF 验证对特定视图处于活动状态。 是的,谢谢,我已经通过在我的 LoginView 的 dispatch 方法上使用 @method_decorator(csrf_protect) 解决了问题。一切正常并受到保护。【参考方案2】:

您可以创建一个 APIView 的子类来强制 CSRF。

from rest_framework import views

class ForceCRSFAPIView(views.APIView):
    @classmethod
    def as_view(cls, **initkwargs):
        # Force enables CSRF protection.  This is needed for unauthenticated API endpoints
        # because DjangoRestFramework relies on SessionAuthentication for CSRF validation
        view = super().as_view(**initkwargs)
        view.csrf_exempt = False
        return view

那么您需要做的就是将您的登录视图更改为从该视图下降

class Login(ForceCRSFAPIView)
    # ...

【讨论】:

以上是关于仅在 DRF 中进行身份验证时才检查 CSRF?的主要内容,如果未能解决你的问题,请参考以下文章

OpenID-Connect 身份验证服务器的登录页面中是不是需要 CSRF 令牌?

为啥 Angular Oninit 仅在离开页面时才调用服务

带有 React 的 Django DRF:如何获取 CSRF cookie?

如何使用 Postman 对 Django REST 框架进行身份验证

Django(DRF)和React - 禁止(未设置CSRF cookie)

使用 Firebase 电子邮件/密码身份验证的 CSRF 保护