ClickCease The Bugs Behind the Vulnerabilities Part 5

Join Our Popular Newsletter

Join 4,500+ Linux & Open Source Professionals!

2x a month. No spam.

The Bugs Behind the Vulnerabilities Part 5

by Joao Correia

April 27, 2023 - Technical Evangelist

Welcome to the final installment of our five-part series looking at code bugs responsible for the vulnerabilities and exploits we try to stay safe from. In this part, we’ll go over the final five entries in the Mitre CWE Top 25, from #5 down to #1. 

 

You can find part 1 here, part 2 here, part 3 here, and part 4 here.

 

5. Out-of-Bounds Read

 

Out-of-bounds read occurs when a product reads data past the end or before the beginning of the intended buffer. This can allow attackers to read sensitive information from other memory locations or cause a crash. 

 

For example, consider the following C code snippet that reads from an array using a user-controlled input:

#include <stdio.h>




int get_element(int *array, int len, int index) {

    int result;




    if (index < len) {

        result = array[index];

    } else {

        printf("Invalid index: %d\n", index);

        result = -1;

    }




    return result;

}




int main() {

    int my_array[5] = {10, 20, 30, 40, 50};

    int index = -1; // Simulating user-controlled input

    int value = get_element(my_array, 5, index);

    printf("Value at index %d: %d\n", index, value);




    return 0;

}

 

In this example, the function get_element checks if the provided index is within the maximum bound of the array, but it does not check the minimum bound. This allows negative values to be accepted as valid array indices, resulting in an out-of-bounds read. To mitigate this issue, add a check for the minimum bound:

 

if (index >= 0 && index < len) {

    result = array[index];

}

 

4. Improper Input Validation

 

Improper input validation happens when a product receives input or data but does not validate or incorrectly validates the input, leading to altered control flow, arbitrary control of a resource, or arbitrary code execution.

 

private void buildList(int untrustedListSize) {

    if (0 > untrustedListSize) {

        die("Negative value supplied for list size, die evil hacker!");

    }

    Widget[] list = new Widget[untrustedListSize];

    list[0] = new Widget();

}


In this example, the code checks if the untrustedListSize is negative, but it does not check if it is zero. If a zero value is provided, the code will build an array of size 0 and then try to store a new widget in the first location, causing an exception to be thrown. To prevent this issue, use comprehensive input validation techniques, considering all potentially relevant properties of the input, and do not rely exclusively on looking for malicious or malformed inputs. In this case, we should check if the untrustedListSize is greater than 0 instead.

 

3. Improper Neutralization of Special Elements Used in an SQL Command (the dreaded “SQL Injection”)

 

SQL injection occurs when an application constructs all or part of an SQL command using externally-influenced input from an upstream component without neutralizing or incorrectly neutralizing special elements that could modify the intended SQL command. 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>




int main(int argc, char *argv[]) {

    char query[1024];

    char *user_input = argv[1];




    snprintf(query, sizeof(query), "SELECT * FROM users WHERE username='%s'", user_input);




    printf("Generated query: %s\n", query);




    // Execute the query...




    return 0;

}

 

In this example, the user input is directly concatenated to the SQL query, allowing an attacker to inject malicious SQL code. To prevent SQL injection, use parameterized queries, prepared statements, or stored procedures to separate user input from SQL statements. In this case, we should use prepared statements:

 

#include <stdio.h>

#include <stdlib.h>

#include <mysql/mysql.h>




(...)




MYSQL_STMT *stmt = mysql_stmt_init(con);

    if (!stmt) {

        fprintf(stderr, "mysql_stmt_init() failed\n");

        exit(1);

    }




    const char *query = "SELECT * FROM users WHERE username=?";

    if (mysql_stmt_prepare(stmt, query, strlen(query)) != 0) {

        fprintf(stderr, "mysql_stmt_prepare() failed: %s\n", mysql_stmt_error(stmt));

        exit(1);

    }



By using prepared statements, the user input is treated as data and not as part of the SQL query, effectively preventing SQL injection.

2. Improper Neutralization of Input During Web Page Generation (or “Cross-Site Scripting”)

Cross-site scripting (XSS) vulnerabilities occur when an application does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output used as a web page served to other users. 

 

Consider the following PHP code snippet:

 

$username = $_GET['username'];

echo "Welcome, " . $username . "!";

 

In this example, user input is directly embedded into the web page without proper sanitization, allowing an attacker to inject malicious HTML or JavaScript code. To prevent XSS, validate and sanitize user input, use secure output encoding techniques when displaying user input in web pages, and employ content security policies to reduce the impact of an XSS attack. In this case, we should sanitize the user input before displaying it:

 

$username = htmlspecialchars($_GET['username'], ENT_QUOTES, 'UTF-8');

echo "Welcome, " . $username . "!";

 

1. Out-of-bounds Write

 

Out-of-bounds write happens when a product writes data past the end or before the beginning of the intended buffer, which can result in data corruption, a crash, or code execution. 

 

For example, consider the following C code snippet that writes to an array:

#include <stdio.h>




void fill_array(int *array, int len) {

for (int i = 0; i <= len; i++) {

  array[i] = i * 10;

 }

}




int main() {

 int my_array[5];

 fill_array(my_array, 5);

 for (int i = 0; i < 5; i++) {

     printf("Element at index %d: %d\n", i, my_array[i]);

 }




return 0;

}


In this example, the function `fill_array` writes to the array `my_array` using the loop variable `i`. However, the loop condition is `i <= len`, which allows the loop to run one iteration beyond the intended bounds of the array, causing an out-of-bounds write.

 

To avoid this issue, ensure that you use proper boundary checks when writing to buffers. In this case, the loop condition should be `i < len`:

 

for (int i = 0; i < len; i++) {

    array[i] = i * 10;

}

 

With this modification, the code correctly writes to the array within its bounds, preventing an out-of-bounds write vulnerability.

 

Conclusion

 

As a takeaway, it is crucial for developers to be aware of the common code bugs that can lead to vulnerabilities in their applications. By understanding these issues and implementing secure coding practices, you can significantly reduce the risk of exploitation by attackers.

 

Here are some general secure coding principles to keep in mind:

 

  • Least Privilege

Ensure that your applications run with the minimum privileges necessary to complete their tasks. This limits the potential impact of a security breach by reducing the attacker’s ability to escalate their privileges or gain unauthorized access to sensitive data or system resources.

 

  • Secure Defaults

Design your applications with security settings enabled by default. Users should have to explicitly disable security features rather than enable them. This encourages a more secure user experience and reduces the likelihood of insecure configurations.

 

  • In-Depth Defense

Implement multiple layers of security in your applications, so that if one layer is breached, others can still provide protection. This can include using input validation, output encoding, and access controls in combination to provide a comprehensive defense against various attacks.

 

  • Fail Securely

Design your applications to fail securely in the event of an error or unexpected input. For example, if an application encounters an error while processing a user’s input, it should not reveal sensitive information or grant unintended access. Instead, it should handle the error gracefully and provide a useful error message to the user.

 

  • Keep it Simple

Complexity is the enemy of security. Strive for simplicity in your code and minimize the use of complex constructs that can be difficult to understand and maintain. This makes it easier to spot potential security issues and reduces the likelihood of introducing new vulnerabilities during development or maintenance.

 

  • Regularly Update Dependencies

Keep your application’s dependencies up to date, as outdated libraries and frameworks can introduce security vulnerabilities. Regularly check for updates and patches, and incorporate them into your development and deployment processes.

 

By incorporating these secure coding principles and addressing the common code bugs mentioned in this series, you will be well on your way to developing more secure applications and protecting your users from potential security threats.

Summary
The Bugs Behind the Vulnerabilities  Part 5
Article Name
The Bugs Behind the Vulnerabilities Part 5
Description
Discover the final five entries in the Mitre CWE Top 25 code bugs series, where we delve into vulnerabilities that lead to exploits.
Author
Publisher Name
TuxCare
Publisher Logo

Looking to automate vulnerability patching without kernel reboots, system downtime, or scheduled maintenance windows?

Become a TuxCare Guest Writer

Mail

Help Us Understand
the Linux Landscape!

Complete our survey on the state of Open Source and you could win one of several prizes, with the top prize valued at $500!

Your expertise is needed to shape the future of Enterprise Linux!