Skip to content

Latest commit

 

History

History
531 lines (433 loc) · 15.5 KB

File metadata and controls

531 lines (433 loc) · 15.5 KB

OWASP Top 10 2025 - Comprehensive Tutorial

A hands-on guide to understanding web security vulnerabilities and their mitigations

This e-commerce demo application demonstrates all OWASP Top 10 2025 vulnerabilities with both vulnerable and secure implementations side-by-side.


🚀 Quick Start

# Backend (Spring Boot)
cd backend && ./mvnw spring-boot:run

# Frontend (React + Vite)
cd frontend && npm run dev

URLs:

Test Accounts:

Role Email Password
Admin admin@owasp.demo Admin123!
Customer john@example.com Customer123!
Customer jane@example.com Customer123!

A01:2025 - Broken Access Control

🔴 The Vulnerability

Users can access resources they shouldn't - like viewing another user's orders.

Attack Scenario

  1. Login as john@example.com and note your order ID
  2. Logout and login as jane@example.com
  3. Access /api/owasp/vulnerable/orders/{johnsOrderId} - you can see John's order!

❌ Vulnerable Code

// VulnerableController.java
@GetMapping("/orders/{orderId}")
public OrderResponse getOrderInsecure(@PathVariable Long orderId) {
    // No ownership check - any user can access any order
    return orderService.getOrderByIdVulnerable(orderId);
}

✅ Secure Implementation

// SecureController.java
@GetMapping("/orders/{orderId}")
public OrderResponse getOrderSecure(
        @PathVariable Long orderId,
        @AuthenticationPrincipal User user) {
    // Ownership verification in service layer
    return orderService.getOrderById(orderId, user);
}

// OrderService.java
public OrderResponse getOrderById(Long orderId, User user) {
    Order order = orderRepository.findById(orderId)
        .orElseThrow(() -> new ResourceNotFoundException("Order not found"));
    
    // SECURE: Verify ownership
    if (!order.getUser().getId().equals(user.getId())) {
        auditLogService.logAccessControlViolation(
            "Attempted access to order " + orderId, request);
        throw new AccessDeniedException("You don't have access to this order");
    }
    return mapToResponse(order);
}

🛡️ Mitigations

  1. Always verify resource ownership before access
  2. Use role-based access control (RBAC)
  3. Deny by default - require explicit permission
  4. Log access control violations

A02:2025 - Security Misconfiguration

🔴 The Vulnerability

Exposing debug endpoints, default credentials, verbose errors, or unnecessary features.

Attack Scenario

Access /api/owasp/vulnerable/config to see:

  • Database connection strings
  • Environment variables
  • System paths
  • JVM version info

❌ Vulnerable Code

// VulnerableController.java
@GetMapping("/config")
public Map<String, Object> getConfigInsecure() {
    Map<String, Object> config = new HashMap<>();
    config.put("database.url", "jdbc:h2:mem:owaspdb");
    config.put("environment_variables", System.getenv()); // Dangerous!
    config.put("user.home", System.getProperty("user.home"));
    return config;
}

✅ Secure Implementation

# application.yml
server:
  error:
    include-stacktrace: never
    include-message: never

management:
  endpoints:
    web:
      exposure:
        include: health,info  # Only safe endpoints
// SecureController.java
@GetMapping("/config")
public Map<String, Object> getConfigSecure() {
    Map<String, Object> safeConfig = new HashMap<>();
    safeConfig.put("application.name", "OWASP E-Commerce Demo");
    safeConfig.put("api.version", "v1");
    // No database URLs, no env vars, no system properties
    return safeConfig;
}

🛡️ Mitigations

  1. Disable debug endpoints in production
  2. Remove default credentials
  3. Configure proper error handling
  4. Use security headers (CSP, X-Frame-Options)
  5. Disable directory listing

A03:2025 - Software Supply Chain Failures

🔴 The Vulnerability

Using components with known vulnerabilities or from untrusted sources.

Secure Practices

<!-- pom.xml - Pin specific versions, use reputable sources -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.3</version> <!-- Explicit, latest secure version -->
</dependency>

🛡️ Mitigations

  1. Audit dependencies regularly: ./mvnw dependency:tree
  2. Use OWASP Dependency-Check plugin
  3. Subscribe to CVE notifications
  4. Use lock files (package-lock.json, pom.xml versions)
  5. Verify checksums of downloaded packages

A04:2025 - Cryptographic Failures

🔴 The Vulnerability

Exposing sensitive data, weak encryption, storing passwords in plaintext.

Attack Scenario

Access /api/owasp/vulnerable/users/1 to see:

  • Password hash exposed
  • Failed login attempts
  • Account lock status
  • All user roles

❌ Vulnerable Code

// VulnerableController.java
@GetMapping("/users/{userId}")
public Map<String, Object> getUserInsecure(@PathVariable Long userId) {
    return userRepository.findById(userId).map(user -> {
        Map<String, Object> sensitiveData = new HashMap<>();
        sensitiveData.put("email", user.getEmail());
        sensitiveData.put("password_hash", user.getPassword()); // NEVER!
        sensitiveData.put("failedLoginAttempts", user.getFailedLoginAttempts());
        return sensitiveData;
    }).orElse(null);
}

✅ Secure Implementation

// SecureController.java
@GetMapping("/users/me")
public UserProfileResponse getUserSecure(@AuthenticationPrincipal User user) {
    // DTO controls exactly what's exposed
    return UserProfileResponse.builder()
        .id(user.getId())
        .email(user.getEmail())
        .firstName(user.getFirstName())
        .lastName(user.getLastName())
        // password, roles, failedAttempts are NEVER exposed
        .build();
}

// SecurityConfig.java
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12); // Strong cost factor
}

🛡️ Mitigations

  1. Use BCrypt/Argon2 for password hashing
  2. Use DTOs to control data exposure
  3. Encrypt sensitive data at rest
  4. Use TLS for data in transit
  5. Never log sensitive information

A05:2025 - Injection

🔴 The Vulnerability

User input directly concatenated into SQL, OS commands, or other interpreters.

Attack Scenario

  1. Go to /api/owasp/vulnerable/products/search?name=' OR '1'='1
  2. All products are returned (SQL injection worked!)
  3. Try /api/owasp/vulnerable/products/search?name='; DROP TABLE products; --

❌ Vulnerable Code

// VulnerableController.java
@GetMapping("/products/search")
public List<?> searchProductsInsecure(@RequestParam String name) {
    // VULNERABLE: String concatenation in SQL
    String sql = "SELECT * FROM Product p WHERE p.name LIKE '%" + name + "%'";
    Query query = entityManager.createQuery(sql);
    return query.getResultList();
}

✅ Secure Implementation

// ProductRepository.java - Parameterized query
@Query("SELECT p FROM Product p WHERE LOWER(p.name) LIKE LOWER(CONCAT('%', :search, '%'))")
Page<Product> searchProducts(@Param("search") String search, Pageable pageable);

// SecureController.java - Input validation
@GetMapping("/products/search")
public Page<?> searchProductsSecure(
    @RequestParam 
    @Size(min = 1, max = 100) 
    @Pattern(regexp = "^[a-zA-Z0-9\\s\\-]+$") 
    String name,
    Pageable pageable) {
    return productRepository.searchProducts(name, pageable);
}

🛡️ Mitigations

  1. Use parameterized queries (PreparedStatement, JPA @Query)
  2. Validate and sanitize all input
  3. Use ORM frameworks properly
  4. Apply least privilege to database accounts
  5. Use allowlists for input validation

A06:2025 - Insecure Design

🔴 The Vulnerability

Missing security controls in the design phase - no rate limiting, no abuse prevention.

✅ Secure Implementation

// AuthService.java - Account lockout after failed attempts
private static final int MAX_FAILED_ATTEMPTS = 5;
private static final int LOCK_DURATION_MINUTES = 30;

private void handleFailedLogin(User user, HttpServletRequest request) {
    int newFailedAttempts = user.getFailedLoginAttempts() + 1;
    userRepository.updateFailedLoginAttempts(user.getEmail(), newFailedAttempts);
    
    if (newFailedAttempts >= MAX_FAILED_ATTEMPTS) {
        userRepository.lockUser(user.getEmail(), false, LocalDateTime.now());
        auditLogService.logAccountLocked(user.getEmail(), request);
    }
}

🛡️ Mitigations

  1. Threat modeling during design
  2. Rate limiting on sensitive endpoints
  3. Account lockout mechanisms
  4. CAPTCHA for public forms
  5. Principle of least privilege

A07:2025 - Authentication Failures

🔴 The Vulnerability

Weak passwords, no account lockout, credential stuffing vulnerabilities.

✅ Secure Implementation

// AuthDto.java - Strong password policy
public class RegisterRequest {
    @NotBlank
    @Email(message = "Invalid email format")
    private String email;

    @NotBlank
    @Pattern(
        regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$",
        message = "Password must have 8+ chars, uppercase, lowercase, digit, special char"
    )
    private String password;
}

// AuthService.java - Login with lockout
public AuthResponse login(LoginRequest request, HttpServletRequest httpRequest) {
    User user = userRepository.findByEmail(request.getEmail())
        .orElseThrow(() -> new BadCredentialsException("Invalid credentials"));

    // Check if account is locked
    if (!user.isAccountNonLocked()) {
        if (isLockExpired(user)) {
            unlockAccount(user);
        } else {
            throw new LockedException("Account locked. Try again later.");
        }
    }
    // ... authentication logic
}

🛡️ Mitigations

  1. Strong password policies
  2. Account lockout mechanisms
  3. Multi-factor authentication (MFA)
  4. Secure password storage (BCrypt)
  5. Session timeout and management

A08:2025 - Software and Data Integrity Failures

🔴 The Vulnerability

Unsigned tokens, insecure deserialization, untrusted CI/CD pipelines.

✅ Secure Implementation

// JwtTokenProvider.java - Signed JWT tokens
public String generateToken(String username) {
    return Jwts.builder()
        .subject(username)
        .issuedAt(new Date())
        .expiration(new Date(System.currentTimeMillis() + jwtExpiration))
        .signWith(getSigningKey()) // HS256 with secure key
        .compact();
}

// SecurityConfig.java - CSRF protection for session auth
.csrf(csrf -> csrf
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))

🛡️ Mitigations

  1. Sign all tokens and verify signatures
  2. Use CSRF tokens for session-based auth
  3. Secure CI/CD pipelines
  4. Verify software integrity (checksums, signatures)
  5. Avoid native deserialization

A09:2025 - Security Logging & Alerting Failures

🔴 The Vulnerability

Insufficient logging, no monitoring, unable to detect or respond to breaches.

✅ Secure Implementation

// AuditLogService.java - Comprehensive security logging
@Service
public class AuditLogService {
    
    public void logLoginSuccess(String username, HttpServletRequest request) {
        createAuditLog(AuditEventType.LOGIN_SUCCESS,
            "Successful login", username, request, AuditSeverity.INFO);
    }

    public void logLoginFailure(String username, HttpServletRequest request, String reason) {
        createAuditLog(AuditEventType.LOGIN_FAILURE,
            "Failed login: " + reason, username, request, AuditSeverity.WARNING);
    }

    public void logAccessControlViolation(String description, HttpServletRequest request) {
        createAuditLog(AuditEventType.ACCESS_CONTROL_VIOLATION,
            description, getCurrentUsername(), request, AuditSeverity.CRITICAL);
    }

    public void logInjectionAttempt(String description, HttpServletRequest request) {
        createAuditLog(AuditEventType.INJECTION_ATTEMPT,
            description, getCurrentUsername(), request, AuditSeverity.CRITICAL);
    }
}

View Security Logs: Admin Panel → Security Logs

🛡️ Mitigations

  1. Log all security events (login, access, failures)
  2. Include context (IP, user agent, timestamp)
  3. Use severity levels for alerting
  4. Protect logs from tampering
  5. Set up monitoring and alerting

A10:2025 - Mishandling of Exceptional Conditions

🔴 The Vulnerability

Exposing stack traces, failing open, improper error handling.

Attack Scenario

Access /api/owasp/vulnerable/error-demo?action=divide to see:

  • Full stack trace
  • Class names and methods
  • Line numbers
  • Internal implementation details

❌ Vulnerable Code

// VulnerableController.java
@GetMapping("/error-demo")
public Map<String, Object> errorDemoInsecure(@RequestParam String action) {
    try {
        // some operation that throws
    } catch (Exception e) {
        response.put("error", e.getClass().getName());
        response.put("message", e.getMessage());
        response.put("stackTrace", getStackTraceAsString(e)); // DANGEROUS!
        response.put("cause", e.getCause().toString());
    }
    return response;
}

✅ Secure Implementation

// GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<Void>> handleAllExceptions(Exception ex) {
        // Log full details internally
        log.error("Unexpected error", ex);
        
        // Return generic message to user
        return ResponseEntity.status(500)
            .body(ApiResponse.error("An unexpected error occurred"));
    }
}

// SecureController.java
@GetMapping("/error-demo")
public ResponseEntity<?> errorDemoSecure(@RequestParam String action) {
    try {
        // some operation
    } catch (Exception e) {
        log.error("Error in demo: {} - {}", e.getClass().getSimpleName(), e.getMessage(), e);
        return ResponseEntity.badRequest()
            .body(ApiResponse.error("An error occurred processing your request"));
    }
}

🛡️ Mitigations

  1. Never expose stack traces to users
  2. Log detailed errors internally
  3. Return generic error messages
  4. Use global exception handlers
  5. Fail securely (deny access on error)

📊 Testing Checklist

Vulnerability Vulnerable Endpoint Secure Endpoint
A01: Broken Access Control GET /api/owasp/vulnerable/orders/{id} GET /api/orders/{id}
A02: Security Misconfiguration GET /api/owasp/vulnerable/config GET /api/owasp/secure/config
A04: Cryptographic Failures GET /api/owasp/vulnerable/users/{id} GET /api/owasp/secure/users/me
A05: Injection GET /api/owasp/vulnerable/products/search?name=... GET /api/products/search?query=...
A10: Exception Handling GET /api/owasp/vulnerable/error-demo?action=divide GET /api/owasp/secure/error-demo?action=divide

🔐 Security Headers

The application implements these security headers:

// SecurityConfig.java
.headers(headers -> headers
    .contentSecurityPolicy(csp -> csp
        .policyDirectives("default-src 'self'"))
    .frameOptions(frame -> frame.deny()))

📚 Additional Resources