Secure code review is a critical process in software development to identify and mitigate potential security vulnerabilities in the code. Here, I’ll provide you with a step-by-step guide on how to perform a secure code review, along with some examples of common security issues and their corresponding fixes.
Step-by-step guide for secure code review:
- Understand the Requirements: Familiarize yourself with the project’s requirements, specifications, and security guidelines.
- Choose the Right Tools: Utilize code review tools that can help identify security vulnerabilities automatically. Some popular tools include SAST (Static Application Security Testing) and code analysis tools like SonarQube, Fortify, and Checkmarx.
- Inspect Authentication and Authorization: Review how user authentication and authorization are implemented. Ensure that sensitive operations and resources are protected properly.
Example – Insecure Authentication:
// Insecure code - Storing passwords in plain text
public boolean authenticateUser(String username, String password) {
String storedPassword = database.getPasswordByUsername(username);
return password.equals(storedPassword);}
Fix – Use Salted Hashing for Passwords:
// Secure code - Using salted hashing for password storage
public boolean authenticateUser(String username, String password) {
String storedPasswordHash = database.getPasswordHashByUsername(username);
String salt = database.getSaltByUsername(username);
String hashedPassword = hashFunction(password + salt);
return hashedPassword.equals(storedPasswordHash);}
- Check Input Validation: Look for input validation issues that may lead to code injection or data manipulation attacks.
Example – SQL Injection Vulnerability:
# Insecure code - SQL Injection vulnerability
def get_user_by_id(user_id):
query = "SELECT * FROM users WHERE id='" + user_id + "';"
result = execute_sql_query(query)
return result
Fix – Use Parameterized Queries:
# Secure code - Using parameterized queries
def get_user_by_id(user_id):
query = "SELECT * FROM users WHERE id=%s;"
result = execute_sql_query(query, (user_id,))
return result
- Handle Error Conditions: Make sure error messages don’t expose sensitive information and are handled securely.
Example – Information Leakage:
// Insecure code - Exposing sensitive error information
if (!isAuthorized(user)) {
throw new SecurityException("Unauthorized access for user: " + user.getName());
}
Fix – Use Generic Error Messages:
// Secure code - Using generic error messages
if (!isAuthorized(user)) {
throw new SecurityException("Unauthorized access");
}
- Review Data Storage and Encryption: Check how sensitive data is stored and ensure proper encryption methods are used.
Example – Weak Data Encryption (hardcoded key and ECB mode):
# Insecure code - Hardcoded key and ECB mode (identical blocks leak patterns, no integrity check)
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
KEY = b"0123456789abcdef" # hardcoded in source control
def encrypt_data(padded_data):
encryptor = Cipher(algorithms.AES(KEY), modes.ECB()).encryptor()
return encryptor.update(padded_data) + encryptor.finalize()
Fix – Use Authenticated Encryption with a Managed Key:
# Secure code - AES-256-GCM with a random nonce; the 32-byte key comes from a secrets manager or KMS
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def encrypt_data(data, key):
nonce = os.urandom(12) # never reuse a nonce with the same key
return nonce + AESGCM(key).encrypt(nonce, data, None)
def decrypt_data(blob, key):
nonce, ciphertext = blob[:12], blob[12:]
return AESGCM(key).decrypt(nonce, ciphertext, None) # raises if the data was tampered with
- Check for Code Injection Vulnerabilities: Look for potential vulnerabilities like Remote Code Execution (RCE) and Command Injection.
Example – Command Injection Vulnerability:
# Insecure code - Command injection vulnerability
def run_shell_command(command):
os.system(command)
Fix – Never Pass User Input to a Shell; Use a Fixed Argument List:
# Secure code - No shell, allowlisted actions, arguments passed as a list
import subprocess
ALLOWED_ACTIONS = {
"status": ["systemctl", "status", "nginx"],
"uptime": ["uptime"],
}
def run_action(action):
if action not in ALLOWED_ACTIONS:
raise ValueError("Unsupported action")
subprocess.run(ALLOWED_ACTIONS[action], shell=False, check=True)
- Review Third-Party Libraries: Verify the security of third-party libraries used in the project. Ensure they are up to date and don’t have known vulnerabilities.
- Analyze Session Management: Check how sessions are managed and that session tokens are generated securely.
Example – Insecure Session Token Generation:
// Insecure code - Insecure session token generation
public String generateSessionToken() {
return UUID.randomUUID().toString();
}
Fix – Use Secure Random Number Generator:
// Secure code - Using secure session token generation
public String generateSessionToken() {
SecureRandom random = new SecureRandom();
byte[] tokenBytes = new byte[32];
random.nextBytes(tokenBytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes);
}
- Review Error Handling: Ensure that error handling is implemented securely and doesn’t expose sensitive information.
- Check for Cross-Site Scripting (XSS) Vulnerabilities: Review how user input is displayed in the application and ensure proper escaping or sanitization.
Example – Cross-Site Scripting Vulnerability:
<!-- Insecure code - XSS vulnerability -->
<div>Welcome, <%= user.getName() %></div>
Fix – Use Proper Escaping:
<!-- Secure code - Escaping user input to prevent XSS -->
<div>Welcome, <%= encodeHtml(user.getName()) %></div>
- Test Boundary Conditions: Verify that the code handles boundary conditions appropriately, such as maximum input lengths and array bounds.
These examples and guidelines should give you a starting point for performing secure code reviews. It’s essential to stay up-to-date with the latest security best practices and have a thorough understanding of the programming languages and frameworks being used. Regular code reviews and continuous security testing should be integral parts of the software development lifecycle to maintain a secure codebase.
Find these flaws before attackers do
Secure Steps runs web application penetration tests that uncover injection, authentication and session flaws like these, with fixes verified by retesting. See a real case in our CLI-argument injection writeup.