CVE-2024-25128 (Python, published 2024-02-29T01:44Z)
Description: value: Flask-AppBuilder is an application development framework, built on top of Flask. When Flask-AppBuilder is set to AUTH_TYPE AUTH_OID, it allows an attacker to forge an HTTP request, that could deceive the backend into using any requested OpenID service. This vulnerability could grant an attacker unauthorised privilege access if a custom OpenID service is deployed by the attacker and accessible by the backend. This vulnerability is only exploitable when the application is using the OpenID 2.0 authorization protocol. Upgrade to Flask-AppBuilder 4.3.11 to fix the vulnerability.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/dpgaspar/Flask-AppBuilder
Files changed:
flask_appbuilder/security/manager.py
flask_appbuilder/security/views.py
flask_appbuilder/templates/appbuilder/general/security/login_oid.html
tests/config_oid.py
tests/test_mvc_oauth.py
tests/test_mvc_oid.py
tests/test_security_api.py
Commit message: fix: openID provider validation flow (#2186)

* fix: openID provider validation flow

* remove test cleanup

Vulnerable code:
                    form.openid.data,
                            </div>
                        </div>
                        <div class="control-group">
                            <div class="controls">
                                    {{ form.remember_me }} Remember Me
                    set_openid("{{ pr.url | safe }}", "{{ pr.name }}");
class APICSRFTestCase(FABTestCase):

Fixed code:
    def get_oid_identity_url(self, provider_name: str) -> Optional[str]:
        """
        Returns the OIDC identity provider URL
        """
        for provider in self.openid_providers:
            if provider.get("name") == provider_name:
                return provider.get("url")

                identity_url = self.appbuilder.sm.get_oid_identity_url(form.openid.data)
                if identity_url is None:
                    flash(as_unicode(self.invalid_login_message), "warning")
                    return redirect(self.appbuilder.get_url_for_login)
                    identity_url,
                                {{ form.remember_me }} Remember Me
                    set_openid("{{ pr.name | safe }}", "{{ pr.name }}");
import os

from flask_appbuilder.security.manager import AUTH_OID

basedir = os.path.abspath(os.path.dirname(__file__))

SQLALCHEMY_DATABASE_URI = os.environ.get(
    "SQLALCHEMY_DATABASE_URI"
) or "sqlite:///" + os.path.join(basedir, "app.db")

SECRET_KEY = "thisismyscretkey"

AUTH_TYPE = AUTH_OID

OPENID_PROVIDERS = [
    {"name": "Google", "url": "https://www.google.com/accounts/o8/id"},
    {"name": "Yahoo", "url": "https://me.yahoo.com"},
    {"name": "AOL", "url": "http://openid.aol.com/<username>"},
    {"name": "Flickr", "url": "http://www.flickr.com/<username>"},
    {"name": "OpenStack", "url": "https://openstackid.org/"},
]

WTF_CSRF_ENABLED = False

# Will allow user self registration
AUTH_USER_REGISTRATION = True

# The default user self registration role for all users
AUTH_USER_REGISTRATION_ROLE = "Admin"
class MVCOAuthTestCase(FABTestCase):
from unittest.mock import MagicMock

from flask_appbuilder import SQLA
from tests.base import FABTestCase


class MVCOIDTestCase(FABTestCase):
    def setUp(self):
        from flask import Flask
        from flask_appbuilder import AppBuilder

        self.app = Flask(__name__)
        self.app.config.from_object("tests.config_oid")
        self.db = SQLA(self.app)
        self.appbuilder = AppBuilder(self.app, self.db.session)

    def test_oid_login_get(self):
        """
        OID: Test login get
        """
        self.appbuilder.sm.oid.try_login = MagicMock(return_value="Login ok")

        with self.app.test_client() as client:
            response = client.get("/login/")
        self.assertEqual(response.status_code, 200)
        for provider in self.app.config["OPENID_PROVIDERS"]:
            self.assertIn(provider["name"], response.data.decode("utf-8"))

    def test_oid_login_post(self):
        """
        OID: Test login post with a valid provider
        """
        self.appbuilder.sm.oid.try_login = MagicMock(return_value="Login ok")

        with self.app.test_client() as client:
            response = client.post("/login/", data=dict(openid="OpenStack"))
            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.data, b"Login ok")
        self.appbuilder.sm.oid.try_login.assert_called_with(
            "https://openstackid.org/", ask_for=["email"], ask_for_optional=[]
        )

    def test_oid_login_post_invalid_provider(self):
        """
        OID: Test login post with an invalid provider
        """
        self.appbuilder.sm.oid.try_login = MagicMock(return_value="Not Ok")

        with self.app.test_client() as client:
            response = client.post("/login/", data=dict(openid="DoesNotExist"))
            self.assertEqual(response.status_code, 302)
            self.assertEqual(response.location, "/login/")
        self.appbuilder.sm.oid.try_login.assert_not_called()
        self.create_default_users(self.appbuilder)