Improper Authorization

Description

Improper Authorization occurs when software does not perform or incorrectly performs authorization checks, allowing users to access resources or perform actions beyond their intended privileges. This differs from authentication (verifying identity) in that authorization determines what an authenticated user is permitted to do. Common issues include missing authorization checks, incorrect role verification, reliance on client-side authorization, and privilege escalation through parameter manipulation.

Risk

Improper authorization is consistently among the most critical web application vulnerabilities. It can lead to unauthorized data access, data modification, administrative actions by regular users, or access to other users' accounts. IDOR (Insecure Direct Object Reference) attacks exploit missing authorization. Horizontal privilege escalation allows accessing other users' data. Vertical privilege escalation allows performing administrative actions. These vulnerabilities often have critical impact as they directly expose business logic flaws.

Solution

Implement centralized authorization checks that cannot be bypassed. Use role-based access control (RBAC) or attribute-based access control (ABAC). Verify authorization on every request, not just navigation. Never rely on client-side authorization. Validate that the authenticated user has permission to access the specific resource being requested. Implement proper access control matrices. Log authorization failures for security monitoring. Use framework-provided authorization mechanisms.

Common Consequences

ImpactDetails
Access ControlScope: Privilege Escalation

Users can gain access to resources and actions beyond their authorization level.
ConfidentialityScope: Data Exposure

Unauthorized access to sensitive data belonging to other users or the system.
IntegrityScope: Unauthorized Modification

Ability to modify data or configurations without proper authority.

Example Code + Solution Code

Vulnerable Code

# VULNERABLE: No authorization check
@app.route('/api/users/<user_id>/profile')
@login_required
def get_user_profile(user_id):
    # Only checks authentication, not authorization
    # Any logged-in user can access any profile!
    user = User.query.get(user_id)
    return jsonify(user.to_dict())

# VULNERABLE: Client-side authorization
@app.route('/admin/delete-user/<user_id>', methods=['DELETE'])
@login_required
def delete_user(user_id):
    # Authorization only enforced in frontend!
    # Backend doesn't verify if user is admin
    User.query.filter_by(id=user_id).delete()
    return jsonify({'status': 'deleted'})

# VULNERABLE: IDOR - Direct object reference
@app.route('/api/documents/<doc_id>')
@login_required
def get_document(doc_id):
    # Any user can access any document by guessing IDs
    document = Document.query.get(doc_id)
    return send_file(document.path)
// VULNERABLE: Missing authorization check
@RestController
public class OrderController {

    @GetMapping("/orders/{orderId}")
    public Order getOrder(@PathVariable Long orderId, Principal principal) {
        // Gets any order regardless of who owns it
        return orderRepository.findById(orderId).orElseThrow();
    }

    @PostMapping("/admin/settings")
    public void updateSettings(@RequestBody Settings settings) {
        // No check if user is actually admin!
        settingsService.update(settings);
    }
}

// VULNERABLE: Role check in wrong location
@Controller
public class AdminController {

    @GetMapping("/admin/users")
    public String listUsers(Model model) {
        // Only hiding the menu in UI - endpoint still accessible!
        // isAdmin is client-side only
        return "admin/users";
    }
}
// VULNERABLE: Authorization bypass through parameter manipulation
app.get('/api/account', (req, res) => {
    // User ID from request parameter, not session
    const userId = req.query.userId;  // Attacker changes this!
    const account = await Account.findById(userId);
    res.json(account);
});

// VULNERABLE: Role from client
app.post('/api/admin/create-user', (req, res) => {
    // Role passed from client - attacker sets role: 'admin'
    const { username, password, role } = req.body;
    await User.create({ username, password, role });
    res.json({ success: true });
});

// VULNERABLE: Missing ownership check
app.put('/api/posts/:postId', async (req, res) => {
    // Any authenticated user can edit any post
    await Post.findByIdAndUpdate(req.params.postId, req.body);
    res.json({ success: true });
});

Fixed Code

# SAFE: Proper authorization check
from functools import wraps

def authorize_resource(resource_type):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            resource_id = kwargs.get(f'{resource_type}_id')
            if not current_user.can_access(resource_type, resource_id):
                abort(403, 'Access denied')
            return f(*args, **kwargs)
        return wrapper
    return decorator

@app.route('/api/users/<user_id>/profile')
@login_required
def get_user_profile(user_id):
    # Check if current user can access this profile
    if str(current_user.id) != user_id and not current_user.is_admin:
        abort(403, 'Access denied')

    user = User.query.get_or_404(user_id)
    return jsonify(user.to_dict())

# SAFE: Server-side role verification
@app.route('/admin/delete-user/<user_id>', methods=['DELETE'])
@login_required
def delete_user(user_id):
    # Verify admin role on server
    if not current_user.has_role('admin'):
        abort(403, 'Admin access required')

    # Additional check: can't delete self or other admins
    target_user = User.query.get_or_404(user_id)
    if target_user.id == current_user.id:
        abort(400, 'Cannot delete yourself')
    if target_user.has_role('admin'):
        abort(403, 'Cannot delete admin users')

    db.session.delete(target_user)
    db.session.commit()
    return jsonify({'status': 'deleted'})

# SAFE: IDOR prevention with ownership check
@app.route('/api/documents/<doc_id>')
@login_required
def get_document(doc_id):
    document = Document.query.get_or_404(doc_id)

    # Verify ownership or permission
    if not document.is_accessible_by(current_user):
        abort(403, 'Access denied')

    return send_file(document.path)

# Using decorators for cleaner code
from flask_principal import Permission, RoleNeed

admin_permission = Permission(RoleNeed('admin'))

@app.route('/admin/settings', methods=['POST'])
@login_required
@admin_permission.require(http_exception=403)
def admin_settings():
    # Only reachable by admins
    pass
// SAFE: Proper authorization with ownership check
@RestController
public class SecureOrderController {

    @GetMapping("/orders/{orderId}")
    @PreAuthorize("isAuthenticated()")
    public Order getOrder(@PathVariable Long orderId, Principal principal) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new NotFoundException("Order not found"));

        // Verify ownership
        if (!order.getUserId().equals(principal.getName()) &&
            !hasRole(principal, "ADMIN")) {
            throw new AccessDeniedException("Not authorized to view this order");
        }

        return order;
    }

    @PostMapping("/admin/settings")
    @PreAuthorize("hasRole('ADMIN')")  // Spring Security annotation
    public ResponseEntity<?> updateSettings(@RequestBody Settings settings) {
        settingsService.update(settings);
        return ResponseEntity.ok().build();
    }
}

// SAFE: Service layer authorization
@Service
public class DocumentService {

    @PreAuthorize("hasPermission(#docId, 'Document', 'read')")
    public Document getDocument(Long docId) {
        return documentRepository.findById(docId).orElseThrow();
    }

    public boolean canAccess(User user, Document document) {
        // Check ownership
        if (document.getOwnerId().equals(user.getId())) {
            return true;
        }

        // Check shared permissions
        return documentPermissionRepository
            .existsByDocumentIdAndUserId(document.getId(), user.getId());
    }
}

// SAFE: Custom permission evaluator
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication auth, Object targetId,
                                 Object targetType, Object permission) {
        User user = (User) auth.getPrincipal();

        if ("Document".equals(targetType)) {
            Document doc = documentRepository.findById((Long) targetId).orElse(null);
            if (doc == null) return false;

            switch (permission.toString()) {
                case "read":
                    return doc.getOwnerId().equals(user.getId()) ||
                           doc.isPublic() ||
                           hasSharedAccess(user, doc);
                case "write":
                    return doc.getOwnerId().equals(user.getId());
                case "delete":
                    return doc.getOwnerId().equals(user.getId()) ||
                           user.hasRole("ADMIN");
            }
        }
        return false;
    }
}
// SAFE: Proper authorization
const authorize = (requiredRole) => {
    return (req, res, next) => {
        if (!req.user) {
            return res.status(401).json({ error: 'Authentication required' });
        }
        if (requiredRole && !req.user.roles.includes(requiredRole)) {
            return res.status(403).json({ error: 'Insufficient permissions' });
        }
        next();
    };
};

const authorizeResource = async (req, res, next) => {
    const resource = await Resource.findById(req.params.resourceId);
    if (!resource) {
        return res.status(404).json({ error: 'Not found' });
    }

    // Check ownership or admin
    if (resource.userId.toString() !== req.user.id &&
        !req.user.roles.includes('admin')) {
        return res.status(403).json({ error: 'Access denied' });
    }

    req.resource = resource;
    next();
};

// SAFE: User ID from session, not request
app.get('/api/account', authenticate, (req, res) => {
    // User ID from authenticated session
    const userId = req.user.id;  // From authentication middleware
    const account = await Account.findById(userId);
    res.json(account);
});

// SAFE: Server-determined role
app.post('/api/admin/create-user', authenticate, authorize('admin'), (req, res) => {
    const { username, password } = req.body;
    // Role determined by server, not client
    const role = 'user';  // Default role
    await User.create({ username, password, role });
    res.json({ success: true });
});

// SAFE: Ownership verification
app.put('/api/posts/:postId', authenticate, async (req, res) => {
    const post = await Post.findById(req.params.postId);

    if (!post) {
        return res.status(404).json({ error: 'Post not found' });
    }

    // Only owner or admin can edit
    if (post.authorId.toString() !== req.user.id &&
        !req.user.roles.includes('admin')) {
        return res.status(403).json({ error: 'Not authorized' });
    }

    await Post.findByIdAndUpdate(req.params.postId, req.body);
    res.json({ success: true });
});

Exploited in the Wild

Parler Data Breach (2021)

After Parler's AWS hosting was terminated, users discovered that public APIs had no authorization checks, allowing enumeration and download of millions of posts, including deleted content and user data.

Facebook Graph API Vulnerability (2018)

A bug in Facebook's "View As" feature combined with authorization flaws allowed attackers to steal access tokens of 50 million users.

Uber API Authorization Bypass (2016)

Authorization bypass in Uber's API allowed viewing any rider's personal information by manipulating user IDs.


Tools to test/exploit

  • Burp Suite — intercept and modify authorization parameters.

  • OWASP ZAP — automated authorization testing.

  • Autorize — Burp extension for authorization testing.

  • AuthMatrix — authorization testing matrix.


CVE Examples


References

  1. MITRE. "CWE-285: Improper Authorization." https://cwe.mitre.org/data/definitions/285.html

  2. OWASP. "Authorization Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html