Secure Development: Input Validation

A key approach in safe programming is input validation, which entails confirming that user input data is reliable, accurate, and satisfies the application’s specifications. Making sure that the program does not process data that could result in security flaws like code injection, buffer overflows, or denial of service attacks is crucial. With people relying more and more on mobile devices, particularly Android smartphones and tablets, the significance of input validation has significantly increased.

We will try to cover different input validation techniques applicable to Android development, from low-level to high-level programming with languages such as C, C++, Java, and Kotlin. Also, by providing some examples, we illustrate the use of these techniques.

Input Range Validation

The range of input data sometimes plays a key role in stability and security. If the code is not protected against the out-of-range input, it can lead to a crash in the simplest situation, leaking data or creating a vulnerability. For instance, if there is some data out of range that the user should not access, it should be protected by comparing the data to a strict condition. Moreover, the range can cause an overflow or even be out of the index, etc.

const int MAX_USER = 1000;

List<UserInfo> userInfoList;
void getUserInfoByIf(int userId)
{
    return  userInfoList[userId];
}

It was a straightforward illustration of what might occur if the userId is larger than the list’s size. The minus index, on the other hand, can result in a similar issue. It can get worse in languages like C and C++ by triggering overflow or writing to or reading from a nearby, forbidden memory address.

Input Length Validation

One of the simplest yet essential input validations is checking the length and range of the user input, especially when it comes to languages such as C and C++. This technique helps prevent buffer overflow attacks, which occur when an attacker provides input exceeding the capacity of a variable, causing it to leak into adjacent memory locations. Using a strict approach leads to preventing exceptions in all languages. For instance, in other languages like Java and Kotlin attempting to write/read out of bounds will raise an exception that can lead to an application crash if it’s not managed properly.

For instance, a C++ example of input length validation can be:

Safe approach

void getUserName(char *userInput)
{
    char userName[20];
    strncpy(userName, userInput, 20);
}

Unsafe approach

void getUserName(char *userInput)
{
    char userName[20];
    for(int i = 0; userInput[i]!= null; i++)
      userName[i] = userInput [i];
}

The second approach is not safe, leading to writing to memory addresses beyond the userName boundary, which can make an application crash or corrupt other data.

When you’re coding for Android, keeping your app secure is super important, and one way to keep things tight is by putting a cap on how much input you allow. It’s like bouncers at a club—you want to make sure you don’t let in so much data that it causes chaos, like crashes or worse, gives hackers a way in. By setting up these input limits, you’re creating a safety net that makes sure your app works smoothly without opening the door to cyber threats.

Importance of Input Length Restrictions

Length restrictions on user inputs demonstrate various benefits, including:

Prevention of Buffer Overflow Attacks

Buffer overflow is pretty much when a program messes up and writes more data than it’s supposed to in a storage spot called a buffer. This can cause all sorts of chaos, like crashing the application or—even worse—letting some sneaky code run when it shouldn’t. The trick to stopping this from happening is for programmers to be strict about checking the size of the input they’re getting, making sure it won’t try to cram too much data into the buffer.

Protection Against Denial of Service (DoS) Attacks

Input length restrictions can also protect applications from being consumed by excessive amounts of data, resulting in a denial of service (DoS) attack. A DoS attack’s primary goal is to overwhelm the target system, rendering it unavailable to users. Restricting the length of input data prevents this by limiting the processing power and resources required to handle input.

Data Integrity

Keeping a check on how much data gets shoved into our apps is super important. Think about it—if you try to pack too much stuff into too small a space, something’s going to get cut off or messed up, right? That’s exactly what can happen with data. If we let it get too bulky and overflows the space we’ve got for it, we can end up with only half of what we need or a total data disaster in our hands. By being strict with how long the input can be, we developers can dodge these headaches and make sure everything runs smoothly.

Usability

Applying input length restrictions can also improve an application’s usability by guiding users to provide inputs that the system can handle properly. An application might require that its users enter specific information, such as their postal code or phone number, to complete a process. Creating length restrictions helps guide users towards entering valid and acceptable information, which leads to a more satisfying user experience.

Implementation Techniques

There are various ways to enforce input length restrictions in different programming languages. Here are some practical examples using C, C++, Java, and Kotlin.

C

In C, the developer can use the fgets function to read input up to a specific limit, preventing buffer overflow vulnerabilities. The following code demonstrates reading input restricted to a set length:

#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 1024

int main() {
    char buffer[BUFFER_SIZE];
    printf("Enter some text: ");
    fgets(buffer, BUFFER_SIZE, stdin);
    printf("You entered: %s", buffer);
    return 0;
}

In this example, fgets reads the user input with a maximum limit of BUFFER_SIZE - 1 characters, preventing buffer overflow issues.

C++

In C++, one approach to restricting the input length is using the std::getline function from the string library. For example:

#include <iostream>
#include <string>

constexpr unsigned int MAX_LENGTH = 1024;

int main() {
    std::string input;
    std::cout << "Enter some text: ";
    std::getline(std::cin, input);

    if (input.length() > MAX_LENGTH) {
        input.erase(MAX_LENGTH);
        std::cout << "The input was too long, trimming to " << MAX_LENGTH << " characters.\n";
    }

    std::cout << "You entered: " << input << std::endl;
    return 0;
}

Here, std::getline reads user input as a string, and the input length is compared to the defined maximum limit. If it exceeds the limit, it trims the string to the desired length.

Java

Java provides various techniques for restricting input length. A standard approach is to use the substring method. Here’s an example:

import java.util.Scanner;

public class InputLengthExample {
    public static void main(String[] args) {
        final int maxLength = 1024;
        Scanner scanner = new Scanner(System.in);

        System.out.print("Enter some text: ");
        String input = scanner.nextLine();

        if (input.length() > maxLength) {
            input = input.substring(0, maxLength);
            System.out.println("The input was too long, trimming to " + maxLength + " characters.");
        }

        System.out.println("You entered: " + input);
        scanner.close();
    }
}

In this snippet, the input string’s length is checked to ensure it does not exceed the specified maximum limit. If it does, the substring method trims it to the desired length.

const int MAX_INDEX = 1000;
const int MIN_INDEX = 0;
UserInfo userInfoList[MAX_INDEX];

void getUserInfoByIndex(int index)
{
    return userInfoList[i];
}

In the previous example sending an index number less than zero or bigger than 1000 causes ArrayIndexOutOfBoundsException.

Kotlin

In Kotlin, the developer can leverage extension functions to create a custom inputLimit function. For example:

import java.util.Scanner

const val maxLength = 1024

fun String.inputLimit(length: Int): String {
    return if (this.length > length) {
        this.substring(0, length).also {
            println("The input was too long, trimming to $maxLength characters.")
        }
    } else {
        this
    }
}

fun main() {
    val scanner = Scanner(System.`in`)

    print("Enter some text: ")
    val input = scanner.nextLine()
    val limitedInput = input.inputLimit(maxLength)

    println("You entered: $limitedInput")
    scanner.close()
}

The inputLimit extension function limits the input string’s length. If the input exceeds the maximum limit, the function trims the string to the desired length.

Whitelisting and Blacklisting

Whitelisting is a technique that permits only specific or predefined input values, while blacklisting rejects specific or harmful input patterns. It is generally advisable to favour whitelisting, as it is a more restrictive approach. Because anything missed by blacklisting could result in a problem. We can easily compare user input against the white list depending on the kind of data the user provides.

A Java example of utilizing whitelisting is as follows:

public boolean isAllowedCharacter(char c) {
  String allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  return allowedCharacters.indexOf(c) != -1;
}

In this example, the code defines a set of allowed characters and checks if the input character exists within the set.

Input format

Sometimes we need to obtain a specific data format from a user or through another source, after which we process the data to separate or extract different parts. In this situation, extracting or slicing data without first verifying its format may result in an unexpected error, such as type casting, being outside of the range, being outside of the index, etc. To receive the appropriate information, we cannot rely on assumptions.

Regular Expressions

Regular expressions (regex) provide a robust mechanism to match patterns in input strings. They can be employed effectively to validate and sanitize user input.

A Kotlin example demonstrating the use of regular expressions for input validation is as follows:
fun main() {
    val emailPattern = Regex("""^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""")
    print("Enter your email address: ")
    val input = readLine() ?: ""

    if (emailPattern.matches(input)) {
        println("The email address is valid")
    } else {
        println("The email address is invalid")
    }
}

I just wanted to give you a heads-up on how you can check if an email address is legit using a fancy thing called a regular expression.

Type-checking and Casting

Type checking and casting ensure that the variables are of the correct data type before using them in application logic. Type casting can lead to runtime exceptions if not performed correctly, making it essential to verify the input data types. It’s important to protect the application from type-casting exceptions. As you can see in the Java code below uses type checking and casting surrounded by try-catch:

public static Integer toInteger(String input) {
  try {
    return Integer.parseInt(input);
  } catch (NumberFormatException e) {
    return null;
  }
}

Application Framework and Library Input Validation

In Android development, using app frameworks and libraries that have built-in input validation capabilities can significantly enhance security measures. For example, the Android framework contains numerous classes and methods that allow for secure input validation and handling, such as EditText and InputFilter.

Here is an example of using InputFilter in an Android app (Java):

EditText editText = new EditText(this);
InputFilter[] filters = new InputFilter[1];
filters[0] = new InputFilter.LengthFilter(10); // Maximum input length
editText.setFilters(filters);

Some libraries and methods for input validation:

  • Using inputType, maxLength and android:pattern attributes to restrict the user input
  • TextInputLayout & TextWatcher
  • AwesomeValidation (supports regex)
  • Saripaar v2 (needs to be excluded from ProGuard)

Note!

Please remember that when we discuss data, it encompasses a wide range of elements, including metadata, raw data, user input, XML, JSON, and more.

Real-life Incidents

Imposing limits on input length is a crucial aspect of application security. Neglecting to enforce these restrictions has precipitated a series of prominent security breaches.

  • The Morris Worm (1988): One of the first computer worms distributed via the internet. It used a buffer overflow attack to exploit a vulnerability in UNIX’s fingerd program. The lack of input length restrictions in the application allowed the worm to overwrite the stack and gain unauthorized access to the system.
  • The infamous Heartbleed bug that affected OpenSSL (2014): This security vulnerability enabled attackers to read sensitive data from a server’s memory by abusing the lack of proper input validation and length restrictions, resulting in unauthorized access to private information.

These examples illustrate the significant impact on security when adequate input length restrictions are not implemented. Developers must consider this vital security measure to prevent similar incidents from occurring in their applications.

Link to Book: Secure Android Development: Best Practices for Robust Apps

Leave a Comment

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