仅在 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 框架进行身份验证