Or press ESC to close.

Part 1: Automating Tests for OWASP Top 10 Vulnerabilities - A QA Engineer's Guide

May 26th 2024 15 min read
medium
security
web
python3.12.1

As cyber threats continue to evolve, ensuring our applications are secure from vulnerabilities is paramount. The OWASP Top 10 is a widely recognized standard that highlights the most critical security risks to web applications. Understanding and addressing these risks is essential for protecting our application and its users.

In this post, we will delve into automated testing methods for the first five vulnerabilities identified in the OWASP Top 10 for 2021. By incorporating these tests into our QA processes, we can catch and mitigate these vulnerabilities before they become significant issues.

We will cover the following five vulnerabilities:

Let's get started with understanding each vulnerability and how to effectively test for them.

A01:2021-Broken Access Control

Broken Access Control occurs when restrictions on what authenticated users are allowed to do are not properly enforced. Attackers can exploit these flaws to access unauthorized functionalities and data, such as viewing sensitive files, modifying user data, or changing access rights.

Common Consequences and Examples:

Testing Strategy:

A general approach for testing Broken Access Control includes:

Code Example:

Here is a sample test using Selenium to verify role-based access control in a web application:

                                     
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
                        
# Initialize the Chrome driver
driver = webdriver.Chrome()
                        
try:
    # Admin user login
    driver.get('http://example.com/login')
    driver.find_element(By.ID, 'username').send_keys('admin')
    driver.find_element(By.ID, 'password').send_keys('adminpass' + Keys.RETURN)
                        
    # Access restricted admin page
    driver.get('http://example.com/admin')
    assert "Admin Page" in driver.page_source, "Admin should have access to admin page"
                        
    # Logout
    driver.get('http://example.com/logout')
                        
    # Regular user login
    driver.get('http://example.com/login')
    driver.find_element(By.ID, 'username').send_keys('user')
    driver.find_element(By.ID, 'password').send_keys('userpass' + Keys.RETURN)
                        
    # Try accessing the restricted admin page
    driver.get('http://example.com/admin')
    assert "Access Denied" in driver.page_source, "Regular user should not have access to admin page"
                            
finally:
    driver.quit()
                    

The script first logs in as an admin and accesses a restricted admin page to verify that the admin has the necessary access. Then, it logs out, logs back in as a regular user, and attempts to access the same admin page, ensuring that access is denied for non-admin users.

This example demonstrates how to automate the testing of role-based access control using Selenium. By regularly running such tests, we can ensure that our application's access controls are correctly enforced, reducing the risk of unauthorized access.

If you'd like to see a practical example, rather than just a sample, I've added a similar script to our GitHub page. This script demonstrates the vulnerability on the OWASP Juice Shop web application.

A02:2021-Cryptographic Failures

Cryptographic Failures occur when cryptographic algorithms are implemented incorrectly or when sensitive data is not adequately protected during storage or transmission. This can lead to data breaches, unauthorized access, and other security incidents.

Common Scenarios and Impacts:

Testing Strategy:

A general approach for testing Cryptographic Failures includes:

Code Example:

Here is a sample script using Python's requests library to check for HTTPS enforcement:

                                     
import requests

# List of URLs to test
urls = [
    'http://example.com/login',
    'http://example.com/register',
    'http://example.com/profile'
]
                        
def check_https(url):
    https_url = url.replace('http://', 'https://')
    try:
        response = requests.get(https_url)
        if response.status_code == 200:
            print(f"HTTPS is enforced for: {url}")
        else:
            print(f"HTTPS not enforced for: {url}")
    except requests.exceptions.RequestException as e:
        print(f"Failed to connect to {https_url}: {e}")
                        
for url in urls:
    check_https(url)
                    

The script checks a list of URLs that should be secured by attempting to connect to each URL using HTTPS. If the connection is successful and returns a status code of 200, it confirms that HTTPS is enforced; otherwise, it flags the URL as not being secure. This example demonstrates how to automate the testing for HTTPS enforcement, ensuring that all sensitive data transmissions are properly secured. By regularly running such tests, we can identify and remediate cryptographic failures, enhancing the overall security of our application.

A03:2021-Injection

Injection flaws occur when untrusted data is sent to an interpreter as part of a command or query. The attacker's hostile data can trick the interpreter into executing unintended commands or accessing data without proper authorization.

Types of Injection Attacks and Their Effects:

These attacks can lead to data breaches, loss of data integrity, denial of service, and unauthorized access.

Testing Strategy:

A general approach for testing Injection flaws includes:

Code Example:

                                     
import requests

# URL of the web application's login endpoint
url = 'http://example.com/login'
                        
# List of payloads to test for SQL injection
payloads = [
    "' OR '1'='1",
    "' OR '1'='1' --",
    "' OR 1=1 --",
    "' OR 'a'='a",
    "' OR 'a'='a' --",
    "' OR 1=1 #",
    "' OR '1'='1' /*"
]
                        
def test_sql_injection(url, payloads):
    for payload in payloads:
        data = {
            'username': payload,
            'password': 'password'
        }
        response = requests.post(url, data=data)
        if "Welcome" in response.text or response.status_code == 200:
            print(f"Potential SQL Injection vulnerability detected with payload: {payload}")
        else:
            print(f"Payload {payload} did not succeed.")
                        
# Run the test
test_sql_injection(url, payloads)
                    

The script defines the URL of the web application's login endpoint and a list of common SQL injection payloads. The test_sql_injection function iterates over each payload, sends it to the login endpoint, and checks the response.

If the response indicates a successful login by containing "Welcome" in the response text or returning a 200 status code, it flags the payload as potentially causing an SQL injection vulnerability. This example demonstrates how QA engineers can automate the testing for SQL injection vulnerabilities by sending various malicious inputs to a web application's endpoint and analyzing the responses. By regularly performing such tests, we can identify and address injection flaws, enhancing the security of our application.

A04:2021-Insecure Design

Insecure design refers to security vulnerabilities that arise from inadequate or flawed design decisions made during the development of an application. Unlike implementation bugs, these issues stem from fundamental weaknesses in the application's architecture, design patterns, or overall security posture. Insecure design encompasses a broad range of issues, such as lack of threat modeling, poor data validation strategies, inadequate authentication mechanisms, and insufficient error handling.

Impact of Design Flaws on Security:

Design flaws can have severe consequences, including:

Testing Strategy:

A general approach for identifying and mitigating design flaws includes:

Code Example:

Since insecure design is more about the architectural and design level rather than specific code implementations, a conceptual example using a threat modeling tool can be highly effective. Below is an example of how to use the Microsoft Threat Modeling Tool to identify and mitigate design flaws:

Conceptual Example Using Microsoft Threat Modeling Tool:

The Microsoft Threat Modeling Tool provides a structured approach to identifying and mitigating design flaws by visually representing the application's architecture and potential threats. By regularly using threat modeling techniques and incorporating security considerations into the design phase, we can proactively address design-related vulnerabilities and enhance the overall security of our application.

A05:2021-Security Misconfiguration

Security misconfiguration refers to improper configuration of security settings in applications, servers, databases, or frameworks. These misconfigurations can leave systems vulnerable to attacks by providing attackers with unnecessary access or exposing sensitive information. Common misconfigurations include default credentials, unpatched systems, overly permissive permissions, and exposed debug or error messages.

Common Misconfigurations and Their Effects:

Testing Strategy:

A general approach for testing for misconfigurations includes:

Code Example:

Using Python to check for common misconfigurations such as default credentials, directory listing, and HTTP headers.

                                     
import requests

# Base URL for the web application
base_url = 'http://example.com/'
                        
# List of URLs to test for directory listing and headers
urls = [
    base_url,
    base_url + 'admin/',
    base_url + 'config/'
]
                        
# Default credentials to test
default_credentials = [
    ('admin', 'admin'),
    ('root', 'root'),
    ('admin', 'password'),
    ('user', 'user')
]
                        
# Function to check for directory listing
def check_directory_listing(url):
    response = requests.get(url)
    if 'Index of /' in response.text:
        print(f'Directory listing enabled on: {url}')
    else:
        print(f'Directory listing not enabled on: {url}')
                        
# Function to check for default credentials
def check_default_credentials(base_url, creds):
    login_url = base_url + 'login'
    for username, password in creds:
        response = requests.post(login_url, data={'username': username, 'password': password})
        if response.status_code == 200 and 'Welcome' in response.text:
            print(f'Default credentials work on {base_url} with username: {username} and password: {password}')
        else:
            print(f'Default credentials failed on {base_url} with username: {username} and password: {password}')
                        
# Function to check for overly permissive headers
def check_headers(url):
    response = requests.get(url)
    if 'X-Frame-Options' not in response.headers:
        print(f'Missing X-Frame-Options header on: {url}')
    if 'Content-Security-Policy' not in response.headers:
        print(f'Missing Content-Security-Policy header on: {url}')
    if 'X-XSS-Protection' not in response.headers:
        print(f'Missing X-XSS-Protection header on: {url}')
                        
# Run tests
print(f'\nTesting Base URL for Default Credentials: {base_url}')
check_default_credentials(base_url, default_credentials)
                        
for url in urls:
    print(f'\nTesting URL: {url}')
    check_directory_listing(url)
    check_headers(url)
                    

The script defines a base URL for the web application and a list of additional URLs to test for directory listing and security headers. It includes a function to check if directory listing is enabled by looking for the "Index of /" text in the response. Another function tests default credentials on the base URL's login endpoint, verifying if any common default credentials allow successful login. The script also checks for the presence of critical security headers like X-Frame-Options, Content-Security-Policy, and X-XSS-Protection in each URL's response.

Conclusion

In this blog post, we explored the importance of web application security and focused on automated testing strategies for the first five vulnerabilities in the OWASP Top 10. We discussed how each vulnerability can impact our application, provided general approaches for testing them, and shared code examples to help you get started with automation.

Regularly testing for these vulnerabilities will help you identify and address security issues early, ultimately enhancing the security posture of your web applications. Start by customizing the provided code examples to fit your specific needs and continuously refine your testing strategies.

If you'd like to try out the code examples, I've added modified versions targeting the OWASP Juice Shop web application to our GitHub page.

Stay tuned for the next post in this series, where we will cover the remaining five vulnerabilities in the OWASP Top 10.