Incorrect Authorization
Description
Incorrect Authorization occurs when a product performs an authorization check when an actor attempts to access a resource or perform an action, but the check is implemented incorrectly. Unlike CWE-862 (Missing Authorization) where no check exists, CWE-863 involves a check that exists but doesn't work properly. Common issues include: checking the wrong user or role, using incorrect comparison logic, failing to account for edge cases, race conditions in authorization checks, caching issues that don't reflect revoked permissions, and authorization bypass through parameter manipulation.
Risk
Incorrect authorization has been consistently ranked in the CWE Top 25. CVE-2025-64707 in Frappe LMS demonstrates a caching flaw where role revocations aren't immediately enforced, allowing users to retain permissions temporarily after admin removal. Recent vulnerabilities affect GitLab CE/EE, OpenStack Keystone (Critical), JumpServer, Authlib, Adobe Commerce/Magento, and Kubernetes apiserver. These vulnerabilities are particularly dangerous because they may pass security audits that only check for the presence of authorization code, not its correctness.
Solution
Implement authorization using well-tested frameworks rather than custom code. Use centralized authorization services. Invalidate caches immediately when permissions change. Implement defense in depth with multiple authorization checks. Test authorization logic thoroughly including edge cases and race conditions. Use positive authorization (explicitly allow) rather than negative (deny specific cases). Log all authorization decisions for audit. Implement fail-secure defaults—deny access if authorization check fails.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Privilege Escalation Users gain access to resources or actions beyond their authorization level. |
| Integrity | Scope: Unauthorized Modification Attackers can modify data they shouldn't have access to due to flawed checks. |
| Confidentiality | Scope: Data Exposure Incorrect authorization exposes sensitive data to unauthorized users. |
Example Code + Solution Code
Vulnerable Code
# VULNERABLE: Wrong comparison operator
def check_admin(user):
# Should be == but using = (assignment in some languages)
# In Python this is syntax error, but logic errors are common
return user.role = 'admin' # SyntaxError, but shows concept
# VULNERABLE: Case-sensitive comparison fails
def is_authorized(user_role, required_role):
# "Admin" != "admin" - user bypasses by using different case
return user_role == required_role
# VULNERABLE: Checking wrong user object
@app.route('/api/admin/users/<user_id>')
@login_required
def admin_get_user(user_id):
target_user = User.query.get(user_id)
# BUG: Checking target_user's role instead of current_user!
if target_user.is_admin: # Should be current_user.is_admin
return jsonify(target_user.to_dict())
return 'Forbidden', 403
# VULNERABLE: Race condition in permission check
class PermissionService:
def __init__(self):
self.permission_cache = {}
def check_permission(self, user_id, resource):
# Check cache first
cache_key = f"{user_id}:{resource}"
if cache_key in self.permission_cache:
return self.permission_cache[cache_key]
# DB check
has_permission = self.db_check(user_id, resource)
self.permission_cache[cache_key] = has_permission
return has_permission
# When permission is revoked, cache isn't invalidated!
def revoke_permission(self, user_id, resource):
self.db_revoke(user_id, resource)
# Missing: del self.permission_cache[f"{user_id}:{resource}"]
// VULNERABLE: Integer overflow in age check
public boolean canAccessAdultContent(User user) {
int age = user.getAge();
// If age is negative (corrupted data), this passes!
return age >= 18;
}
// VULNERABLE: Null check bypass
public boolean hasPermission(User user, String permission) {
List<String> permissions = user.getPermissions();
// If permissions is null, this throws NPE
// Attacker might trigger null permissions to cause error
return permissions.contains(permission);
}
// VULNERABLE: Time-of-check to time-of-use (TOCTOU)
public void deleteDocument(Long docId, User user) {
Document doc = documentRepository.findById(docId);
// Check authorization
if (!doc.getOwnerId().equals(user.getId())) {
throw new UnauthorizedException();
}
// Gap between check and use - doc could be reassigned!
performExpensiveOperation();
// By now, doc owner might have changed
documentRepository.delete(doc);
}
// VULNERABLE: Type coercion in comparison
function checkRole(user, requiredRole) {
// "1" == 1 is true in JavaScript - type coercion bypass
return user.roleId == requiredRole;
}
// VULNERABLE: Prototype pollution affecting authorization
function isAdmin(user) {
// If Object.prototype.isAdmin was polluted, all objects are admins!
return user.isAdmin === true;
}
// VULNERABLE: Array includes with wrong type
function hasPermission(user, permission) {
// permissions might be strings, permission might be object
// or vice versa - comparison fails silently
return user.permissions.includes(permission);
}
// VULNERABLE: Async authorization race condition
async function accessResource(userId, resourceId) {
const hasAccess = await checkAccess(userId, resourceId);
if (hasAccess) {
// Access could be revoked between check and use!
await new Promise(r => setTimeout(r, 1000)); // Simulates delay
return await getResource(resourceId);
}
}
Fixed Code
# SAFE: Proper comparison with validation
def check_admin_safe(user):
if user is None:
return False
return user.role == 'admin'
# SAFE: Case-insensitive comparison
def is_authorized_safe(user_role, required_role):
if not user_role or not required_role:
return False
return user_role.lower() == required_role.lower()
# SAFE: Check correct user and handle edge cases
@app.route('/api/admin/users/<user_id>')
@login_required
def admin_get_user_safe(user_id):
# Check CURRENT user's admin status, not target user
if not current_user.is_admin:
audit_log.warning(f"Non-admin {current_user.id} attempted admin access")
return 'Forbidden', 403
target_user = User.query.get_or_404(user_id)
audit_log.info(f"Admin {current_user.id} accessed user {user_id}")
return jsonify(target_user.to_dict())
# SAFE: Cache invalidation on permission changes
class SecurePermissionService:
def __init__(self):
self._cache = {}
self._lock = threading.Lock()
def check_permission(self, user_id, resource):
cache_key = f"{user_id}:{resource}"
with self._lock:
if cache_key in self._cache:
cached = self._cache[cache_key]
if cached['expires'] > time.time():
return cached['value']
has_permission = self._db_check(user_id, resource)
self._cache[cache_key] = {
'value': has_permission,
'expires': time.time() + 60 # Short TTL
}
return has_permission
def revoke_permission(self, user_id, resource):
self._db_revoke(user_id, resource)
# Invalidate cache immediately
cache_key = f"{user_id}:{resource}"
with self._lock:
self._cache.pop(cache_key, None)
# Also invalidate any related caches
self._invalidate_user_cache(user_id)
// SAFE: Proper validation with defensive checks
public boolean canAccessAdultContent(User user) {
if (user == null) {
return false;
}
Integer age = user.getAge();
if (age == null || age < 0 || age > 150) {
logger.warn("Invalid age value: {} for user {}", age, user.getId());
return false; // Fail secure
}
return age >= 18;
}
// SAFE: Null-safe permission check
public boolean hasPermission(User user, String permission) {
if (user == null || permission == null) {
return false;
}
List<String> permissions = user.getPermissions();
if (permissions == null || permissions.isEmpty()) {
return false;
}
return permissions.contains(permission);
}
// SAFE: Atomic authorization check and action
@Transactional(isolation = Isolation.SERIALIZABLE)
public void deleteDocumentSafe(Long docId, User user) {
// Lock the document row
Document doc = documentRepository.findByIdWithLock(docId)
.orElseThrow(() -> new NotFoundException("Document not found"));
// Authorization check with locked row
if (!doc.getOwnerId().equals(user.getId()) && !user.isAdmin()) {
auditService.logUnauthorizedAccess(user.getId(), "document", docId);
throw new UnauthorizedException("Not authorized to delete this document");
}
// Delete while still holding lock
documentRepository.delete(doc);
auditService.logDeletion(user.getId(), "document", docId);
}
// SAFE: Strict equality comparison
function checkRole(user, requiredRole) {
if (!user || user.roleId === undefined) {
return false;
}
// Use strict equality to prevent type coercion
return user.roleId === requiredRole;
}
// SAFE: Prototype pollution resistant
function isAdmin(user) {
if (!user || typeof user !== 'object') {
return false;
}
// Check own property, not inherited
return Object.prototype.hasOwnProperty.call(user, 'isAdmin') &&
user.isAdmin === true;
}
// SAFE: Proper type checking in array search
function hasPermission(user, permission) {
if (!user || !Array.isArray(user.permissions)) {
return false;
}
if (typeof permission !== 'string') {
return false;
}
return user.permissions.some(p =>
typeof p === 'string' && p === permission
);
}
// SAFE: Re-check authorization at action time
async function accessResourceSafe(userId, resourceId) {
// Acquire lock and check authorization atomically
const lock = await acquireLock(`resource:${resourceId}`);
try {
// Check authorization while holding lock
const hasAccess = await checkAccess(userId, resourceId);
if (!hasAccess) {
throw new UnauthorizedError('Access denied');
}
// Access resource while still holding lock
const resource = await getResource(resourceId);
return resource;
} finally {
await releaseLock(lock);
}
}
Exploited in the Wild
Frappe LMS Role Caching Flaw (Frappe, 2025)
CVE-2025-64707 in Frappe LMS 2.0.0-2.41.0 allows users to retain permissions after role revocation due to caching mechanisms that delay enforcement of permission changes, fixed by implementing immediate cache invalidation.
OpenStack Keystone Authorization Bypass (OpenStack, 2025)
Critical incorrect authorization vulnerability in OpenStack Keystone allows attackers to bypass access controls through flawed authorization logic.
Kubernetes API Server Privilege Escalation (Kubernetes, 2025)
Incorrect authorization in Kubernetes apiserver allows privilege escalation through flawed authorization checks.
Tools to test/exploit
-
Autorize — Burp extension for detecting authorization issues.
-
AuthMatrix — Burp extension for authorization testing.
-
OWASP ZAP Access Control — automated access control testing.
CVE Examples
-
CVE-2025-64707 — Frappe LMS role caching authorization bypass.
-
CVE-2021-25741 — Kubernetes symlink authorization bypass.
-
CVE-2020-8945 — GPGME signature verification bypass.
References
-
MITRE. "CWE-863: Incorrect Authorization." https://cwe.mitre.org/data/definitions/863.html
-
OWASP. "Access Control Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Access_Control_Cheat_Sheet.html