Flask-AppBuilder incorrect authentication when using auth type OpenID
Description
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.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
Flask-AppBuilderPyPI | < 4.3.11 | 4.3.11 |
Affected products
1- Range: < 4.3.11
Patches
16336456d83f8fix: openID provider validation flow (#2186)
7 files changed · +100 −8
flask_appbuilder/security/manager.py+8 −0 modified@@ -1447,6 +1447,14 @@ def _has_view_access( # If it's not a builtin role check against database store roles return self.exist_permission_on_roles(view_name, permission_name, db_role_ids) + 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") + def get_user_roles(self, user) -> List[object]: """ Get current user roles, if user is not authenticated returns the public role
flask_appbuilder/security/views.py+5 −1 modified@@ -565,8 +565,12 @@ def login_handler(self): form = LoginForm_oid() if form.validate_on_submit(): session["remember_me"] = form.remember_me.data + 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) return self.appbuilder.sm.oid.try_login( - form.openid.data, + identity_url, ask_for=self.oid_ask_for, ask_for_optional=self.oid_ask_for_optional, )
flask_appbuilder/templates/appbuilder/general/security/login_oid.html+2 −6 modified@@ -36,13 +36,9 @@ <label class="hidden control-label" id="label-username" for="username">{{ _("Enter your OpenID Username") }}:</label> {{ form.username(size = 80, class = "hidden form-control", autofocus = true) }} - </div> - </div> - <div class="control-group"> - <div class="controls"> <label class="checkbox" for="remember_me"> - {{ form.remember_me }} Remember Me </label> + {{ form.remember_me }} Remember Me </div> </div> <input @@ -133,7 +129,7 @@ {% for pr in providers %} document.getElementById("btn-oid-provider-{{ pr.name }}") .addEventListener("click", function () { - set_openid("{{ pr.url | safe }}", "{{ pr.name }}"); + set_openid("{{ pr.name | safe }}", "{{ pr.name }}"); }); {% endfor %} document.getElementById("btn-oid-before-submit")
tests/config_oid.py+29 −0 added@@ -0,0 +1,29 @@ +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"
tests/test_mvc_oauth.py+1 −1 modified@@ -26,7 +26,7 @@ def get(self, item): return UserInfoReponseMock() -class APICSRFTestCase(FABTestCase): +class MVCOAuthTestCase(FABTestCase): def setUp(self): from flask import Flask from flask_wtf import CSRFProtect
tests/test_mvc_oid.py+53 −0 added@@ -0,0 +1,53 @@ +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()
tests/test_security_api.py+2 −0 modified@@ -444,6 +444,8 @@ def setUp(self): if hasattr(b, "datamodel") and b.datamodel.session is not None: b.datamodel.session = self.db.session + self.create_default_users(self.appbuilder) + def tearDown(self): self.appbuilder.session.close() engine = self.appbuilder.session.get_bind(mapper=None, clause=None)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-j2pw-vp55-fqqjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-25128ghsaADVISORY
- github.com/dpgaspar/Flask-AppBuilder/commit/6336456d83f8f111c842b2b53d1e89627f2502c8ghsax_refsource_MISCWEB
- github.com/dpgaspar/Flask-AppBuilder/security/advisories/GHSA-j2pw-vp55-fqqjghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.