A01:2021 - Broken Access Control

A01:2021 - Broken Access Control

Broken access control vulnerabilities occur when applications fail to properly enforce permissions, allowing users to access resources or perform actions beyond their authorized privileges. In both Python and JavaScript applications, these vulnerabilities often arise from missing or inadequate authorization checks, particularly in API endpoints and route handlers.

# Python - Vulnerable Flask Application
from flask import Flask, request, jsonify
from functools import wraps

app = Flask(__name__)

# VULNERABLE: No authorization check
@app.route('/api/admin/users/<int:user_id>', methods=['DELETE'])
def delete_user_vulnerable(user_id):
    # Anyone can delete any user!
    db.delete_user(user_id)
    return jsonify({'status': 'deleted'})

# VULNERABLE: Client-side role check only
@app.route('/api/user/<int:user_id>')
def get_user_vulnerable(user_id):
    # Checking user_id from request, not from session
    requested_id = request.args.get('check_id', type=int)
    if requested_id == user_id:
        return jsonify(db.get_user(user_id))
    return jsonify({'error': 'Unauthorized'}), 403

# SECURE: Proper authorization implementation
def require_role(allowed_roles):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            # Get user from secure session
            current_user = get_current_user_from_session()
            
            if not current_user:
                return jsonify({'error': 'Authentication required'}), 401
            
            if current_user.role not in allowed_roles:
                return jsonify({'error': 'Insufficient privileges'}), 403
            
            # Additional check for resource ownership
            if 'user_id' in kwargs:
                if not can_access_user(current_user, kwargs['user_id']):
                    return jsonify({'error': 'Access denied'}), 403
            
            return f(*args, **kwargs)
        return decorated_function
    return decorator

@app.route('/api/admin/users/<int:user_id>', methods=['DELETE'])
@require_role(['admin'])
def delete_user_secure(user_id):
    # Verify admin role and audit the action
    audit_log('user_deletion', {
        'deleted_user_id': user_id,
        'deleted_by': get_current_user_from_session().id,
        'timestamp': datetime.utcnow()
    })
    
    db.delete_user(user_id)
    return jsonify({'status': 'deleted'})

# SECURE: Implement object-level authorization
@app.route('/api/user/<int:user_id>')
@require_authentication
def get_user_secure(user_id):
    current_user = get_current_user_from_session()
    
    # Users can only access their own profile unless admin
    if current_user.id != user_id and current_user.role != 'admin':
        return jsonify({'error': 'Access denied'}), 403
    
    user_data = db.get_user(user_id)
    
    # Filter sensitive fields based on viewer's privileges
    if current_user.id != user_id:
        user_data = filter_sensitive_fields(user_data)
    
    return jsonify(user_data)
// JavaScript - Node.js/Express Broken Access Control

// VULNERABLE: Missing authorization middleware
app.delete('/api/posts/:postId', async (req, res) => {
    // No check if user owns the post!
    await Post.findByIdAndDelete(req.params.postId);
    res.json({ message: 'Post deleted' });
});

// VULNERABLE: Trusting client-side data
app.get('/api/orders', async (req, res) => {
    // Getting user ID from request instead of session
    const userId = req.query.userId;  // User can modify this!
    const orders = await Order.find({ userId });
    res.json(orders);
});

// SECURE: Proper authorization implementation
const checkResourceOwnership = (resourceType) => {
    return async (req, res, next) => {
        try {
            const user = req.user;  // From authentication middleware
            const resourceId = req.params[`${resourceType}Id`];
            
            // Fetch resource to check ownership
            const Model = getModel(resourceType);
            const resource = await Model.findById(resourceId);
            
            if (!resource) {
                return res.status(404).json({ error: 'Resource not found' });
            }
            
            // Check ownership or admin role
            if (resource.userId.toString() !== user.id && user.role !== 'admin') {
                return res.status(403).json({ error: 'Access denied' });
            }
            
            req.resource = resource;  // Attach for use in route handler
            next();
        } catch (error) {
            res.status(500).json({ error: 'Authorization check failed' });
        }
    };
};

// SECURE: Protected routes with proper checks
app.delete('/api/posts/:postId', 
    requireAuth,  // Authentication check
    checkResourceOwnership('post'),  // Authorization check
    async (req, res) => {
        // req.resource is the verified post
        await req.resource.remove();
        
        // Audit log
        await createAuditLog({
            action: 'DELETE_POST',
            userId: req.user.id,
            resourceId: req.params.postId,
            timestamp: new Date()
        });
        
        res.json({ message: 'Post deleted successfully' });
    }
);

// SECURE: Role-based access control
const requireRole = (roles) => {
    return (req, res, next) => {
        if (!req.user) {
            return res.status(401).json({ error: 'Authentication required' });
        }
        
        if (!roles.includes(req.user.role)) {
            return res.status(403).json({ error: 'Insufficient privileges' });
        }
        
        next();
    };
};

app.get('/api/admin/users', 
    requireAuth,
    requireRole(['admin', 'moderator']),
    async (req, res) => {
        const users = await User.find({}).select('-password -sensitiveData');
        res.json(users);
    }
);