The OWASP Top 10 (Open Web Application Security Project) is a standard awareness document for web applications security. It is globally recognized by developers as the first step towards more secure coding.

In this article, we’ll explore the top 10 risks, understand why they’re dangerous, and where applicable look at practical code examples to illustrate both vulnerable and secure coding practices.

The code examples are in Python and java, two of the most used and expressive languages around. In case you use Typescript, you easily cater for some minor changes in the language and used frameworks.

OWASP Top 10 2021

In this article, we consider the OWASP Top 10 released in 2021, which is current until a new Top 10 will be released in the first half of 2025.

Mapping

Broken Access Control

Broken access control occurs when restriction on what authenticated users are allowed to do are not properly enforced.

  • Users shall not act outside of their intended permissions.

Java Example (Spring)

Consider a simple API endpoint that returns user data:

Vulnerable version: any authenticated user can access any other user’s data by simply changing the ID in the URL.

@GetMapping("/api/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userRepository.findById(id).orElseThrow();
    return ResponseEntity.ok(user);
}
Java

Secure version: check if the requesting user has the right to access the requested user data

@GetMapping("/api/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id, Authentication authentication) {
    UserDetails userDetails = (UserDetails) authentication.getPrincipal();
    if (!userDetails.getUsername().equals(id.toString()) && 
        !userDetails.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
      return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
    }
    User user = userRepository.findById(id).orElseThrow();
    return ResponseEntity.ok(user);
}
Java

Python Example (Flask)

Vulnerable version:

@app.route('/api/user/<id>')
def get_usr(id):
   user = database.get_user(id)
   return jsonify(user)
Python

Secure version:

@app.route('/api/user/<id>')
@login_required
def get_usr(id):
   if current_user.id != id and not current_user.is_admin:
      return jsonify({"error":"Access denied"}), 403
   user = database.get_user(id)
   return jsonify(user)
Python

Cryptographic Failures

This risk refers to failures related to lack of cryptography which leads to exposure of sensitive data.

  • Determine the protection needs of data in transit and at rest.

Java Example

Vulnerable version: Storing passwords in plain text:

public void registerUser(String username, String password) {
    User user = new User();
    user.setUsername(username);
    user.setPassword(password); // INSECURE: Password stored in plain text
    userRepository.save(user);
}
Java

Secure version: hash the password before storing it.

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

public void registerUser(String username, String password) {
    User user = new User();
    user.setUsername(username);
    user.setPassword(passwordEncoder.encode(password)); // Secure: Password is hashed before storage
    userRepository.save(user);
}
Java

Python Example

Vulnerable version:

def register_user(username, password):
   user = {
      "username" : username,
      "password" : password # INSECURE: Password stored in plain text
   }
   dataabse.save_user(user)
Python

Secure version:

import bcrypt

def register_user(username, password):
   hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
   user = {
      "username" : username,
      "password" : hashed # Secure: Password hashed before storage
   }
   dataabse.save_user(user)
Python

Injection

The most common injection flaw is SQL injection, which occurs when untrusted data is sent to an interpreter as part of a command or query. Similarly, this can happen for NoSQL, ORM, LDAP, and EL or OGNL injection.

  • User-supplied data should be validated, filtered, or sanitized by the application

Java Example

Consider a backend function returning user details

Vulnerable version: An attacker can input admin' -- as the username, effectively changing the query to SELECT * FROM users WHERE username = 'admin' --' , which would return the admin user’s details.

public User getUser(String username) {
    String query = "SELECT * FROM users WHERE username = '" + username + "'";
    return jdbcTemplate.queryForObject(query, new UserRowMapper());
}
Java

Secure version: use parametrized queries to prevent SQL injection

public User getUser(String username) {
    String query = "SELECT * FROM users WHERE username = ?";
    return jdbcTemplate.queryForObject(query, new Object[]{username}, new UserRowMapper());
}
Java

Python Example

Vulnerable version:

def get_user(username)
   query = f"SELECT * FROM users WHERE username = '{username}'"
   return database.execute(query)
Python

Secure version:

def get_user(username)
   query = f"SELECT * FROM users WHERE username = ?"
   return database.execute(query, (username,))
Python

Insecure Design

This is a broad category that refers to various design and architectural flaws in the application.

  • shift-left in the coding space to pre-coding activities, e.g. more use of threat modeling, secure design patterns, and reference architectures.

Java Example

Consider a password reset functionality.

Vulnerable version: generate and email passwords in plain text

public void resetPassword(String email) {
    String newPassword = generateRandomPassword();
    User user = userRepository.findByEmail(email);
    user.setPassword(newPassword);
    userRepository.save(user);
    emailService.sendEmail(email, "Password Reset", "Your new password is: " + newPassword);
}
Java

Secure version: send a time-limited reset link instead of a new password, delegating the reset to a direct user operation.

public void resetPassword(String email) {
    String resetToken = generateResetToken();
    User user = userRepository.findByEmail(email);
    user.setResetToken(resetToken);
    user.setResetTokenExpiry(LocalDateTime.now().plusHours(1));
    userRepository.save(user);
    String resetLink = "https://example.com/reset_password?token=" + resetToken;
    emailService.sendEmail(email, "Password Reset", "Click here to reset your password: " + resetLink);
}
Java

Python Example

Vulnerable version:

def reset_password(email):
   new_password = generate_random_password()
   user = database.get_user_by_email(email)
   user.password = new_password
   database.update_user(user)
   send_email(email, f"Your new password is: {new_password}")
Python

Secure version:

def reset_password(email):
   reset_token = generate_reset_token()
   user = database.get_user_by_email(email)
   user.reset_token = reset_token
   user.reset_token_expiry = datatime.now() + timedelta(hours=1)
   database.update_user(user)
   reset_link = f"https://example.com/reset_password?token={reset_token}"
   send_email(email, f"Click here to reset your password: {reset_link}")
Python

Security Misconfiguration

Security misconfiguration happens as a result of insecure default configurations, incomplete or ad hoc configurations, open cloud storage, misconfigured HTTP headers and verbose error messages containing sensitive information.

  • Harden security of configuration, defaults, logging, security headers.
  • Remove unneeded modules, libraries, default users, default databases

Java Example (Spring)

Vulnerable version: Returning detailed error messages in a production environment could expose sensitive details about your application structure and data.

@RestController
public class ErrorController {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body("An error occurred: " + e.getMessage());
    }
}
Java

Secure version: log the full error for debugging, but only return a generic message to the user.

@RestController
public class ErrorController {
    private static final Logger logger = LoggerFactory.getLogger(ErrorController.class);

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        logger.error("An error occurred", e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body("An internal server error occurred. Please contact support.");
    }
}
Java

This version logs the full error for debugging, but only returns a generic message to the user.

Python Example (Flask)

Vulnerable version: Leaving debug mode on in a production application

from flask import Flask, jsonify

app = Flask(__name__)

@app.errorhandler(Exception)
def handle_exception(e):
    return jsonify(error=str(e)), 500

if __name__ == '__main__':
    app.run(debug=True)  # Running in debug mode
Python

Secure version: ensure debug is off in production environments

import logging
from flask import Flask, jsonify

app = Flask(__name__)
logger = logging.getLogger(__name__)

@app.errorhandler(Exception)
def handle_exception(e):
    logger.error(f"An error occurred: {str(e)}")
    return jsonify(error="An internal server error occurred"), 500

if __name__ == '__main__':
    app.run(debug=False)  # Not running in debug mode in production
Python

Vulnerable and Outdated Components

This risk occurrs when you use components (e.g. libraries, frameworks, and software modules) with know vulnerabilities or which are out of date.

Java Example (dependencies)

The package com.fasterxml.jackson.core:jackson-databind@2.9.8 is vulnerable to Deserialization of Untrusted Data. See this report.

You can solve the issue by using a more recent version of the library. The general rule is to keep your dependencies up to date and regularly check for known vulnerabilities. For this purpose, you can use one of the many tools like Dependabot, Snyk, Renovate, OWASP Dependency-Check, etc.

Identification and Authentication Failures

This category includes weaknesses in session management, password policies, and other aspects of user authentication.

Java Example

Vulnerable version: implement a weak password policy, by only checking the password lenght.

public boolean isPasswordStrong(String password) {
    return password.length() >= 8;
}
Java

Secure version: check for length, and presence of uppercase and lowercase letters, numbers, and special characters.

public boolean isPasswordStrong(String password) {
    String pattern = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,}$";
    return password.matches(pattern);
}
Java

Python Example

Vulnerable version;

def is_password_strong(password):
    return len(password) >= 8
Python

Secure version:

import re

def is_password_strong(password):
    if len(password) < 12:
        return False
    if not re.search(r'[A-Z]', password):
        return False
    if not re.search(r'[a-z]', password):
        return False
    if not re.search(r'[0-9]', password):
        return False
    if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        return False
    return True
Python

Software and Data Integrity Failures

This risk relates to code and infrastructure that does not protect against integrity violation. This can happen when using software from untrusted sources or not verifying software updates.

Java Example

Vulnerable version: Deserializing data without proper checks can lead to remote code execution, if the serialized data is malicious.

public Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
    ByteArrayInputStream in = new ByteArrayInputStream(data);
    ObjectInputStream is = new ObjectInputStream(in);
    return is.readObject();
}
Java

Secure versions: We use a custom ObjectInputStream to restrict deserialization to classes from a specific package. This is a form or whitelist-based deserialization, which is common in Java.

public Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
    ByteArrayInputStream in = new ByteArrayInputStream(data);
    ObjectInputStream is = new ObjectInputStream(in) {
        @Override
        protected Class<?> resolveClass(ObjectStreamClass desc) 
              throws IOException, ClassNotFoundException {
          if (!desc.getName().startsWith("com.yourcompany.")) {
              throw new InvalidClassException("Unauthorized deserialization attempt", 
                desc.getName());
          }
          return super.resolveClass(desc);
        }
    };
    return is.readObject();
}
Java

Python Example

Vulnerable version:

import pickle

def deserialize(data):
    return pickle.loads(data)
Python

Secure version: the idiomatic Python approach would be using a cryptographic signature to ensure the integrity of the serialized data. This approach verifies the data has not tampered with since it was serialized. While it doesn’t prevent deserialization of arbitrary objects like in the Java example, it ensures that you de-serialize data which was serialized by authorized parties only (who have access to the secret key)

import pickle
import hmac
import hashlib

SECRET_KEY = b'your-secret-key'

def serialize(data):
    pickled = pickle.dumps(data)
    digest = hmac.new(SECRET_KEY, pickled, hashlib.sha256).hexdigest()
    return f"{digest}:{pickled.hex()}"

def deserialize(signed_data):
    digest, data = signed_data.split(':', 1)
    pickled = bytes.fromhex(data)
    if hmac.new(SECRET_KEY, pickled, hashlib.sha256).hexdigest() != digest:
        raise ValueError("Invalid signature")
    return pickle.loads(pickled)
Python

Security Logging and Monitoring Failures

This risk occurs when system lacks proper logging, monitoring or active response to detected attacks.

Java Example:

Vulnerable version: skip logging failed login attempts, makes it difficult to detect brute-force attacks.

public boolean login(String username, String password) {
    User user = userRepository.findByUsername(username);
    if (user != null && user.getPassword().equals(password)) {
        return true;
    }
    return false;
}
Java

Secure version: log both successful and failed login attempts. So doing you can detect attacks or misbehaviours.

private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class);

public boolean login(String username, String password) {
    User user = userRepository.findByUsername(username);
    if (user != null && passwordEncoder.matches(password, user.getPassword())) {
        logger.info("Successful login for user: {}", username);
        return true;
    }
    logger.warn("Failed login attempt for user: {}", username);
    return false;
}
Java

Note: in case of web services it is useful logging also IP-addresses, which gives the option to filter / ban specific addresses.

Python Example

Vulnerable version:

def login(username, password):
    user = get_user(username)
    if user and user.password == password:
        return create_session(user)
    return None
Python

Secure version:

import logging

logger = logging.getLogger(__name__)

def login(username, password):
    user = get_user(username)
    if user and user.check_password(password):
        logger.info(f"Successful login for user: {username}")
        return create_session(user)
    else:
        logger.warning(f"Failed login attempt for user: {username}")
        return None
Python

Server-Side Request Forgery

SSRF flaws occur when a web application is fetching a remote resource without validating the user-supplied URL.

Java Example:

Vulnerable image fetcher: This function blindly fetches any URL provided, which could be used to access internal resources.

public byte[] fetchImage(String url) throws IOException {
    URL obj = new URL(url);
    try (InputStream in = obj.openStream()) {
        return in.readAllBytes();
    }
}
Java

Secure version: check the URL’s host is allowed before fetching it, preventing access to internal resources.

public byte[] fetchImage(String url) throws IOException {
    URL obj = new URL(url);
    if (!isAllowedHost(obj.getHost())) {
        throw new SecurityException("Access to the specified host is not allowed");
    }
    try (InputStream in = obj.openStream()) {
        return in.readAllBytes();
    }
}

private boolean isAllowedHost(String host) {
    return !host.equals("localhost") && !host.equals("127.0.0.1") && !host.startsWith("192.168.");
}
Java

Python Version

Vulnerable version:

import requests

def fetch_image(url):
    response = requests.get(url)
    return response.content
Python

Secure version:

import requests
from urllib.parse import urlparse

def is_safe_url(url):
    parsed = urlparse(url)
    return (parsed.scheme in ('http', 'https') and
            not parsed.netloc.startswith('127.0.0.1') and
            not parsed.netloc.startswith('localhost'))

def fetch_image(url):
    if not is_safe_url(url):
        raise ValueError("Invalid URL")
    response = requests.get(url)
    return response.content
Python

Conclusion

The OWASP Top 10 is a great starting point for understanding and addressing the most critical web application security risks. We can improve the security of our web application by being aware of these risks and implement secure coding practices.

References

Here is a list of meaningful references:


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *