CVE-2022-24857 (Python, published 2022-04-15T19:15Z)
Description: value: django-mfa3 is a library that implements multi factor authentication for the django web framework. It achieves this by modifying the regular login view. Django however has a second login view for its admin area. This second login view was not modified, so the multi factor authentication can be bypassed. Users are affected if they have activated both django-mfa3 (< 0.5.0) and django.contrib.admin and have not taken any other measures to prevent users from accessing the admin login view. The issue has been fixed in django-mfa3 0.5.0. It is possible to work around the issue by overwriting the admin login route, e.g. by adding the following URL definition *before* the admin routes: url('admin/login/', lambda request: redirect(settings.LOGIN_URL)
Severity: MEDIUM (CVSS v3 base score: 8.8)
Weakness: CWE-287 \u2014 Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
Repository: https://github.com/xi/django-mfa3
Files changed:
README.md
mfa/admin.py
mfa/apps.py
tests/tests.py
tests/urls.py
Commit message: fix bypass via admin login

inspired by django-two-factor-auth


Vulnerable code:
2.  Use `mfa.views.LoginView` instead of the regular login view

Fixed code:
2.  Use `mfa.views.LoginView` instead of the regular login view. (Be sure to
    remove any other login routes, otherwise the multi factor authentication
    can be circumvented. The admin login will automatically be patched to
    redirect to the regular login.)
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.views import redirect_to_login
from django.urls import reverse
original_login = admin.AdminSite.login


def custom_login(self, request, extra_context=None):
    next_url = (
        request.GET.get(REDIRECT_FIELD_NAME)
        or request.POST.get(REDIRECT_FIELD_NAME)
        or reverse('admin:index')
    )
    return redirect_to_login(next_url)


def patch_admin():
    setattr(admin.AdminSite, 'login', custom_login)


def unpatch_admin():
    setattr(admin.AdminSite, 'login', original_login)

from django.apps import AppConfig


class TwoFactorConfig(AppConfig):
    name = 'mfa'
    verbose_name = 'Multi Factor Authentication'

    def ready(self):
        from .admin import patch_admin
        patch_admin()
class PatchAdminTest(TestCase):
    def test_root(self):
        res = self.client.get('/admin/')
        self.assertEqual(res.status_code, 302)
        self.assertEqual(res.url, '/admin/login/?next=/admin/')

        res = self.client.get(res.url)
        self.assertEqual(res.status_code, 302)
        self.assertEqual(res.url, '/login/?next=/admin/')

    def test_app(self):
        res = self.client.get('/admin/mfa/')
        self.assertEqual(res.status_code, 302)
        self.assertEqual(res.url, '/admin/login/?next=/admin/mfa/')

        res = self.client.get(res.url)
        self.assertEqual(res.status_code, 302)
        self.assertEqual(res.url, '/login/?next=/admin/mfa/')

    def test_login(self):
        res = self.client.get('/admin/login/')
        self.assertEqual(res.status_code, 302)
        self.assertEqual(res.url, '/login/?next=/admin/')


from django.contrib import admin
    path('admin/', admin.site.urls),