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.
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);
}
JavaSecure 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);
}
JavaPython Example (Flask)
Vulnerable version:
@app.route('/api/user/<id>')
def get_usr(id):
user = database.get_user(id)
return jsonify(user)
PythonSecure 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)
PythonCryptographic 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);
}
JavaSecure 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);
}
JavaPython Example
Vulnerable version:
def register_user(username, password):
user = {
"username" : username,
"password" : password # INSECURE: Password stored in plain text
}
dataabse.save_user(user)
PythonSecure 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)
PythonInjection
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());
}
JavaSecure 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());
}
JavaPython Example
Vulnerable version:
def get_user(username)
query = f"SELECT * FROM users WHERE username = '{username}'"
return database.execute(query)
PythonSecure version:
def get_user(username)
query = f"SELECT * FROM users WHERE username = ?"
return database.execute(query, (username,))
PythonInsecure 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);
}
JavaSecure 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);
}
JavaPython 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}")
PythonSecure 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}")
PythonSecurity 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());
}
}
JavaSecure 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.");
}
}
JavaThis 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
PythonSecure 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
PythonVulnerable 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;
}
JavaSecure 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);
}
JavaPython Example
Vulnerable version;
def is_password_strong(password):
return len(password) >= 8
PythonSecure 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
PythonSoftware 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();
}
JavaSecure 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();
}
JavaPython Example
Vulnerable version:
import pickle
def deserialize(data):
return pickle.loads(data)
PythonSecure 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)
PythonSecurity 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;
}
JavaSecure 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;
}
JavaNote: 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
PythonSecure 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
PythonServer-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();
}
}
JavaSecure 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.");
}
JavaPython Version
Vulnerable version:
import requests
def fetch_image(url):
response = requests.get(url)
return response.content
PythonSecure 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
PythonConclusion
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