Skip to content

Application Security


OWASP Top 10 (2021)

OWASP Top 10 - 2021


A01: Broken Access Control

Broken Access Control

Prevention

// BAD: Direct object reference without authorization
@GetMapping("/api/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId) {
    return orderRepository.findById(orderId);  // Anyone can access any order!
}

// GOOD: Verify ownership before returning data
@GetMapping("/api/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId, Principal principal) {
    Order order = orderRepository.findById(orderId)
        .orElseThrow(() -> new NotFoundException("Order not found"));

    if (!order.getUserId().equals(principal.getName())) {
        throw new ForbiddenException("Access denied");
    }
    return order;
}

// BETTER: Use authorization at repository level
@GetMapping("/api/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId, Principal principal) {
    return orderRepository.findByIdAndUserId(orderId, principal.getName())
        .orElseThrow(() -> new NotFoundException("Order not found"));
}

A02: Cryptographic Failures

Cryptographic Failures

Password Hashing

// BAD: Plain text or weak hash
String hashedPassword = DigestUtils.md5Hex(password);  // NEVER DO THIS

// GOOD: Use bcrypt with appropriate cost factor
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);  // cost factor
String hashedPassword = encoder.encode(rawPassword);

// Verify password
boolean matches = encoder.matches(rawPassword, hashedPassword);

Data Encryption

// AES-256-GCM encryption
public class EncryptionService {
    private static final int GCM_IV_LENGTH = 12;
    private static final int GCM_TAG_LENGTH = 128;

    public byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception {
        byte[] iv = new byte[GCM_IV_LENGTH];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);

        byte[] ciphertext = cipher.doFinal(plaintext);

        // Prepend IV to ciphertext
        byte[] result = new byte[iv.length + ciphertext.length];
        System.arraycopy(iv, 0, result, 0, iv.length);
        System.arraycopy(ciphertext, 0, result, iv.length, ciphertext.length);
        return result;
    }
}

A03: Injection

Injection

SQL Injection Prevention

// BAD: String concatenation
String query = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);

// GOOD: Parameterized queries
String query = "SELECT * FROM users WHERE username = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();

// GOOD: JPA Named Parameters
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsername(@Param("username") String username);

// GOOD: Criteria API
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
query.where(cb.equal(root.get("username"), username));

Command Injection Prevention

// BAD: Direct command execution
Runtime.getRuntime().exec("ping " + userInput);

// GOOD: Use ProcessBuilder with array (no shell interpretation)
ProcessBuilder pb = new ProcessBuilder("ping", "-c", "4", validatedHost);
pb.redirectErrorStream(true);
Process process = pb.start();

// BETTER: Avoid shell commands entirely, use libraries
InetAddress address = InetAddress.getByName(validatedHost);
boolean reachable = address.isReachable(5000);

A04: Insecure Design

Insecure Design

Secure Design Patterns

// Rate limiting
@RateLimiter(name = "loginAttempts", fallbackMethod = "loginRateLimited")
public AuthResponse login(LoginRequest request) {
    // Login logic
}

// Account lockout
public class LoginService {
    private static final int MAX_ATTEMPTS = 5;
    private static final Duration LOCKOUT_DURATION = Duration.ofMinutes(15);

    public AuthResponse login(LoginRequest request) {
        User user = userRepository.findByEmail(request.getEmail());

        if (user.isLocked() && user.getLockoutExpiry().isAfter(Instant.now())) {
            throw new AccountLockedException("Account locked. Try again later.");
        }

        if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
            user.incrementFailedAttempts();
            if (user.getFailedAttempts() >= MAX_ATTEMPTS) {
                user.lock(LOCKOUT_DURATION);
            }
            userRepository.save(user);
            throw new BadCredentialsException("Invalid credentials");
        }

        user.resetFailedAttempts();
        userRepository.save(user);
        return generateToken(user);
    }
}

A05: Security Misconfiguration

Security Misconfiguration

Security Headers Configuration

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .headers(headers -> headers
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives("default-src 'self'; script-src 'self'"))
                .frameOptions(frame -> frame.deny())
                .xssProtection(xss -> xss.block(true))
                .contentTypeOptions(Customizer.withDefaults())
                .httpStrictTransportSecurity(hsts -> hsts
                    .includeSubDomains(true)
                    .maxAgeInSeconds(31536000))
            );
        return http.build();
    }
}

Error Handling

// BAD: Exposing stack traces
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
    return ResponseEntity.status(500).body(e.getMessage());  // Leaks info!
}

// GOOD: Generic error messages
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
    log.error("Internal error", e);  // Log details internally
    return ResponseEntity.status(500)
        .body(new ErrorResponse("An unexpected error occurred",
                                UUID.randomUUID().toString()));  // Correlation ID
}

A06: Vulnerable Components

Vulnerable and Outdated Components

Dependency Scanning

<!-- Maven: OWASP Dependency Check -->
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>8.4.0</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <failBuildOnCVSS>7</failBuildOnCVSS>
    </configuration>
</plugin>
# Run dependency check
mvn dependency-check:check

# Gradle
./gradlew dependencyCheckAnalyze

# NPM
npm audit
npm audit fix

# Snyk
snyk test
snyk monitor

Cross-Site Scripting (XSS)

XSS Types

XSS Prevention

// Server-side: Use templating engine with auto-escaping
// Thymeleaf automatically escapes by default
<p th:text="${userInput}">User input here</p>  // Safe
<p th:utext="${userInput}">DANGER</p>  // Unescaped - avoid!

// Manual encoding
import org.owasp.encoder.Encode;

String safe = Encode.forHtml(userInput);
String safeJs = Encode.forJavaScript(userInput);
String safeUrl = Encode.forUriComponent(userInput);
// Client-side: Use textContent, not innerHTML
// BAD
element.innerHTML = userInput;

// GOOD
element.textContent = userInput;

// For HTML, use DOMPurify
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);

Content Security Policy

Content-Security-Policy:
    default-src 'self';
    script-src 'self' 'nonce-abc123';
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https:;
    font-src 'self';
    connect-src 'self' https://api.example.com;
    frame-ancestors 'none';

Cross-Site Request Forgery (CSRF)

CSRF Attack

CSRF Prevention

// Spring Security CSRF (enabled by default)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            );
        return http.build();
    }
}

// For APIs using JWT (stateless), CSRF can be disabled
http.csrf(csrf -> csrf.disable());  // Only if using token auth

SameSite Cookies

// Set SameSite attribute
@Bean
public CookieSerializer cookieSerializer() {
    DefaultCookieSerializer serializer = new DefaultCookieSerializer();
    serializer.setSameSite("Strict");  // or "Lax"
    serializer.setUseSecureCookie(true);
    serializer.setUseHttpOnlyCookie(true);
    return serializer;
}

Server-Side Request Forgery (SSRF)

SSRF Attack

SSRF Prevention

public class UrlValidator {
    private static final Set<String> BLOCKED_HOSTS = Set.of(
        "localhost", "127.0.0.1", "0.0.0.0", "169.254.169.254"
    );

    private static final Set<String> ALLOWED_SCHEMES = Set.of("http", "https");

    public boolean isUrlSafe(String urlString) {
        try {
            URL url = new URL(urlString);

            // Check scheme
            if (!ALLOWED_SCHEMES.contains(url.getProtocol())) {
                return false;
            }

            // Check for blocked hosts
            String host = url.getHost().toLowerCase();
            if (BLOCKED_HOSTS.contains(host)) {
                return false;
            }

            // Resolve and check IP
            InetAddress address = InetAddress.getByName(host);
            if (address.isLoopbackAddress() ||
                address.isLinkLocalAddress() ||
                address.isSiteLocalAddress()) {
                return false;
            }

            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

Input Validation

Input Validation Strategy

Validation Example

@RestController
public class UserController {

    @PostMapping("/api/users")
    public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserRequest request) {
        // Request already validated by Bean Validation
        return ResponseEntity.ok(userService.create(request));
    }
}

public class CreateUserRequest {

    @NotBlank(message = "Email is required")
    @Email(message = "Invalid email format")
    @Size(max = 255)
    private String email;

    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 100)
    @Pattern(regexp = "^[a-zA-Z\\s-']+$", message = "Invalid characters in name")
    private String name;

    @NotBlank
    @Size(min = 8, max = 128)
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&]).+$",
             message = "Password must contain uppercase, lowercase, number, and special char")
    private String password;

    @Past
    private LocalDate birthDate;
}

Security Testing

Security Testing Types


Common Interview Questions

  1. What is OWASP Top 10?
  2. List of most critical web security risks
  3. Updated periodically (latest 2021)
  4. Covers injection, broken auth, XSS, etc.

  5. How to prevent SQL injection?

  6. Parameterized queries / prepared statements
  7. Use ORM frameworks properly
  8. Input validation and escaping

  9. XSS vs CSRF?

  10. XSS: Inject malicious scripts into pages
  11. CSRF: Trick user into unwanted actions
  12. Different attack vectors, different mitigations

  13. How to store passwords securely?

  14. Never store plain text
  15. Use bcrypt, Argon2, or scrypt
  16. Use unique salt per password

  17. What is defense in depth?

  18. Multiple layers of security controls
  19. If one fails, others still protect
  20. Example: WAF + input validation + parameterized queries

  • *