Preventing Privilege Escalation

Introduction

Welcome to the fourth lesson of the "broken access control" course! In this lesson, we will explore the concept of privilege escalation, a critical aspect of broken access control vulnerabilities.

By understanding how attackers can exploit these vulnerabilities to gain unauthorized access to higher-level privileges, you'll be better equipped to secure your applications. Let's dive in and learn how to prevent privilege escalation! 🚀

Understanding Privilege Escalation

Privilege escalation occurs when an attacker gains elevated access to resources that are normally protected from an application or user. There are two main types: vertical privilege escalation and horizontal privilege escalation. Vertical privilege escalation involves gaining higher-level privileges, such as administrative rights, while horizontal privilege escalation involves accessing the resources of another user with similar privileges.

For example, if a regular user can change their role to an admin by manipulating a request, that is vertical privilege escalation. On the other hand, if a user can view another user's private data without permission, that is horizontal privilege escalation. Understanding these concepts is vital for securing web applications against unauthorized access.

Let's look at a vulnerable code example to see how these vulnerabilities can manifest in real applications.

Vulnerable Code Example

Let's examine a code snippet that demonstrates a vulnerability allowing privilege escalation. This example shows how a lack of proper validation can lead to unauthorized role changes.

@RestController
@RequestMapping("/api/user")
public class UserController {
    private final UserRepository users;
    private final JwtUtil jwt;

    public UserController(UserRepository users, JwtUtil jwt) {
        this.users = users;
        this.jwt = jwt;
    }

    @PutMapping("/profile")
    public ResponseEntity<?> updateUser(@RequestHeader(value = "authorization", required = false) String authorization,
                                        @RequestBody UpdateUserRequest request) {
        var outcome = getCurrentUser(authorization);
        if (outcome.error() != null) {
            return outcome.error();
        }
        
        User user = outcome.user();
        
        // Dangerous: allows any user to update their role!
        if (request.username() != null) {
            user.setUsername(request.username());
        }
        if (request.password() != null) {
            user.setPassword(request.password());
        }
        if (request.role() != null) {
            Role role = request.role().equals("admin") ? Role.ADMIN : Role.USER;
            user.setRole(role);  // No authorization check!
        }
        
        users.save(user);
        return ResponseEntity.ok(user);
    }

    private AuthOutcome getCurrentUser(String authorizationHeader) {
        if (authorizationHeader == null || authorizationHeader.isBlank()) {
            return new AuthOutcome(null, error(HttpStatus.UNAUTHORIZED, "Authentication required"));
        }
        String token = authorizationHeader.startsWith("Bearer ") ? authorizationHeader.substring(7) : authorizationHeader;
        try {
            DecodedJWT decoded = jwt.verify(token);
            Integer userId = decoded.getClaim("userId").asInt();
            return users.findById(userId)
                    .map(user -> new AuthOutcome(user, null))
                    .orElseGet(() -> new AuthOutcome(null, error(HttpStatus.NOT_FOUND, "User not found")));
        } catch (Exception e) {
            return new AuthOutcome(null, error(HttpStatus.UNAUTHORIZED, "Invalid token"));
        }
    }

    private ResponseEntity<ErrorResponse> error(HttpStatus status, String detail) {
        return ResponseEntity.status(status).body(new ErrorResponse(detail));
    }

    private record AuthOutcome(User user, ResponseEntity<ErrorResponse> error) {}
    public record UpdateUserRequest(String username, String password, String role) {}
}

In this code, any authenticated user can update their profile, including the role field, without any authorization checks. An attacker could send a payload to change their role to admin, leading to privilege escalation. This vulnerability arises from the absence of role validation. To understand the severity of this issue, let's see how an attacker might exploit this vulnerability.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal