We use cookies to enhance your experience of our website, save your preferences and provide us with information on how you use our website. For more information please read our Privacy Policy. By using our website without changing your browser settings you consent to our use of cookies.
July 12, 2023 Exploiting Type Confusion
9 minutes read
Exploiting Type Confusion

As a security auditor, finding vulnerabilities in applications requires me to make use of any cracks that I may find, but finding the cracks can often be the most challenging portion of a test. Learning about the inherent weaknesses of weak-typed programming languages, like JavaScript or PHP, will provide a new way of finding cracks to leverage.

Types

Programming languages use variables, which can be considered as a little bucket of datum. The data can come from users, configuration files, etc., and is stored in memory for use in the program. How much memory is set aside for a particular variable is set according to its type. Variable types can be several things across languages; some examples are integer, decimal, string, or character. The type is set at the variable declaration.

type variable_name = initial value

Below are some pseudocode examples with types, an ‘integer’ and a ‘string’ used to set a couple of variables.

int counter = 0;
string sentence = “I love blogs!”;

There are two camps in programming languages, strong-typed and weak-typed. Let's discuss the differences with some examples.

Strong

Strong-typed languages do not allow commingling of different variable types without express commands to do so. Strong-typed languages include Go, C#, and Java.

The following code would throw an error, because we are attempting to assign an integer type value into a string typed variable:

int counter = 0;
string sentence = counter;

You will also get an error when attempting to compare different types for equivalency:

int apples = 0;
string oranges = “0”;
if(apples == oranges){}//throws error

It is possible to put the integer value into the string variable by type-casting, or simply “casting” the integer value into a string:

int counter = 0;
string sentence = Integer.toString(counter);

Weak

Weak-typed programming languages do not force type rules on their variables. Some examples of weak-typed languages include: JavaScript, PHP, and Ruby. Note that an integer value can be used as the initial value for sentence, and then it can immediately be assigned a string value.

You’ll also observe the “int” and “string” type declarations are no longer required, as data is treated equally.

var counter = 0;
var sentence = counter;
sentence = “I love blogs!”;

Another difference is in the comparison, a weak-typed programming language will not let types get in the way of comparing values, where this was blocked before, it is now OK. 

int apples = 0;
string oranges = “0”;
if(apples == oranges){}//returns true

The fluidity of weak-typed programming makes the programming faster and has a lot of flexibility between systems, but as you read on, you will see that this increased flexibility gives you greater responsibility over granular controls in a program.

Variable Type Protection

If you look at this simple Java function, note that it accepts a single variable named “userId” that is of the type, integer (‘int”). The variable is then concatenated directly to an SQL query to retrieve some rudimentary user data.

public static void loadUserProfile(int userId) {
    Connection conn = getConnection(DB_URL, USER, PASS);
    Statement stmt = conn.createStatement();
    String q = "SELECT id, first, last FROM users where id = " + Integer.toString(userId);
    ResultSet rs = stmt.executeQuery(q);) {
        while (rs.next()) {            // build out page
         }
      }
   }

This function is a prime example of an injection attack, but any malicious user would be hard-pressed to get an injection payload into an integer type. Therefore, any malicious payload provided to loadUserProfile() that is not an integer will fail with an “incompatible types: String cannot be converted to int” error long before it has an opportunity to be concatenated to the SQL query.

This code is an example of a strong-typed language at its best, the strictures of which protect an application from malicious payloads being passed along to their intended destination. Not all programming languages are strong-typed; most are intended to be a scripting language and have been shoehorned into server-side code.

Let’s take a look at the same code in JavaScript.

function loadUserProfile(userId) {
    var query = 
"SELECT id, first, last FROM users where id = " + userId;
    var connection = new DBBuildConnection();
    connection.fetchSQL(query)
                    .then(rows) => {
                        if(rows.length == 0){
                            response.message = "User not found!";
                            response.success = false;
                            return;
                        }
                         // build out page
}

Outside of the apparent syntax differences between Java and Javascript, you should note that Javascript function arguments do not allow types to be declared. There is no “int” type declaration before “userId” in JavaScript.

Java: loadUserProfile(int userId)

JavaScript: loadUserProfile(userId)

The effects of a weak-typed variable declaration mean that any type can be passed to the function, and will be appended to the SQL query. So, for example, “5” is just as valid as “5 or 1=1” and makes an injection attack possible. JavaScript developers have to check their variable types manually; in this example, a Number.isInteger() could be used.

When a programmer spends most of their time in strong-typed languages like Java or C and is then required to create a NodeJS application, their strong-typed habits will carry over with them, and they will more than likely fail to do the manual type checks.

Working closely with a development team that uses strong typed programming languages, my ears always perk up when they mention their new mico-service will use NodeJS, and I will instantly begin scouring their code for type confusion issues.

How can you discern if a backend application is using weak-typed languages? Make requests with differing types, of course! Here is an example of a JSON that we send to a server to perform a simple search that will return 25 results of egg-associated articles.

{
  “search_term”: “eggs”,
  “offset:”: 0,
  “limit”: 25,
  “include_ads” : true
}

We can now attempt to alter the parameter type to see how it is handled. We change ‘limit’ to have a string value instead of an integer.

{
  “search_term”: “eggs”,
  “offset:”: 0,
  “limit”: “25”,
  “include_ads” : true
}

If the response to this differently-typed request is the same as before, it is an excellent indication that the parameter types are not being strictly checked, and it provides an avenue to get a payload where it needs to be.

Type Comparison

Beyond this example, there is also a bevy of comparison bugs that can arise from type assumptions. There is a well-documented issue with PHP when comparing authentication passwords.

When a person creates an account on a web application with a username and password, their password is stored in the database to be compared to future authentication attempts to ensure the correct password is used. Therefore, when keeping a user’s password, it should be salted and hashed to be stored in a format that is not retrievable to database administrators and malicious users alike. Once hashed, the value of the password will be a random alphanumeric string.

Any time a user authenticates, the same salt and hash process will be done to the password provided at the login screen and will be compared to the stored password hash in the database. These two hash values will be string-type variables that need to be compared to allow users access to an account.

PHP, like JavaScript, is also a weak-typed language, and the flexibility in the language leads to some peccadillos that can cause stumbling. 

First, we look at an example of this password string comparison, sans salt, in C#:

string storedPasswordHash = "0e462097431906509019562988736854"; 
string providedPassword = "240610708"; 
if(storedPasswordHash == md5(providedPassword))
//allow account access

The provided password is passed into the md5 hashing algorithm and compared to the saved hash. (Do note that md5 is being used here as a simple example, and should not be used in your own program for password hashing. If you require a password hashing algorithm, please search for the current best, as it is a fluid answer.)

Logically we are checking if “0e462…” is equal to “0e462…” and then allowing access. All very straightforward in the strong-typed C family language.

Let’s take a look at the same issue in PHP:

$storedPasswordHash = 0e462097431906509019562988736854";
$providedPassword = "240610708";
if ($storedPasswordHash == md5($providedPassword))
//allow access

Same story, marginally different syntax, but what is happening under the hood is extremely different than what the syntax similarities would leave you to believe. Look at this next example; our storedPasswordHash value is the same, but the provided password differs:

$storedPasswordHash = "0e462097431906509019562988736854";
$providedPassword = "QNKCDZO";
if ($storedPasswordHash == md5($providedPassword))
//allow access

And yet this password still works! Is this a case of a hash collision? Here are their hashes:

md5(“240610708”)=0e462097431906509019562988736854
md5(“QNKCDZO”)=0e830400451993494058024219903391

“A PHP string is considered numeric if it can be interpreted as an int or a float.”

What is happening is that each of these strings is being interpreted as a number, the character ‘e’ denoting an exponential. So PHP is logically asking, does 0 == 0? And indeed, it does, meaning there are many more passwords to access this account than a user realizes.

You can test here: https://onlinephp.io/c/22a03

JavaScript and PHP are coming up on their 30th birthdays, and we still see this type of issue coming up every year, as evidenced by some recent CVEs: 

  1. PHP List, an open source email marketing application has the exact issue we discussed above with their authentication mechanism.
    Code Fix: Bypass authentication through loose comparison · Issue #668 · phpList/phplist3 · GitHub

  1. DB Authentication, makes the same type insensitive comparison when checking a user’s password.
    Code Fix: Official Moodle git projects - moodle.git/blobdiff - auth/db/auth.php

So, why does this continue to happen?

The habits of programming established early on and the day-in day-out programming in a strong-typed language are difficult to break and will often go overlooked when the people reviewing your pull requests are in the same programming rut. Static code scanners will flag incorrect type comparisons as low or informative findings and will not get in the way of a pull request merging to the production branch.

Remediation

Let’s spend some time looking at how our code examples would be properly fixed.

First, let’s tackle the JavaScript function that allows strings to be passed as an argument. To ensure that function is receiving expected data, we will add a type check with Number.isInteger():

function loadUserProfile(userId) {
    if (!Number.isInteger(userId)) throw new Error(“Expected Integer for id”);
    var query = "SELECT id, first, last FROM users where id = " + userId;
    var connection = new DBBuildConnection();
    connection.fetchSQL(query)
                    .then(rows) => {
                        if(rows.length == 0){
                            response.message = "User not found!";
                            response.success = false;
                            return;
                        }
                         // build out page
 
}

Now, any value that is not an integer will throw an error back to the caller.

Second, our PHP example of password comparison is fixed by using the triple-equal operator (===)< that properly compares variable values and types.

$storedPasswordHash = "0e462097431906509019562988736854";
$providedPassword = "QNKCDZO";
if ($storedPasswordHash === md5($providedPassword))
//allow access

Conclusion

There are some take-aways from this article and I’d like to iterate it specifically based on your role in the company.

Security Auditors

If you test the password hash vulnerability successfully using the passwords in the example above, (“240610708” and “QNKCDZO”) there are two issues to highlight. First, the loose string comparison that we spoke of, and second, the passwords that are not being salted. Using a salt when storing hashed passwords ensures a hash value is unpredictable to a malicious user.

Another interesting point is that a poor constructor scanning software will often miss coverage because of JSON construction. As an example, scan an HTTP request with this body, and it will iterate across the values as they are, meaning that you’ll be sending requests such as this:

{
  “search_term”: “eggs”,
  “offset:”: 0,
  “limit”: 20 or 1=1,
  “include_ads” : true
}
{
  “search_term”: “eggs”,
  “offset:”: 0,
  “limit”: 20 ‘or ‘1’=’1’,
  “include_ads” : true
}

The invalid JSON will likely fail before ever getting to the code that you are attempting to test, and when the results come back with a “No Vulnerabilities Found”, you have a false sense of assurance that all is well. So, make a point to check for type-checks in the JSON requests before dropping the request into a scanner to allow for greater coverage.

Certus Cybersecurity has published a Portswigger Burp extension to help with this process which can be found here: Type Confusion Scanner - PortSwigger

Developers

Developers must ensure proper type handling to confirm that the data received from users is what a program can correctly compute without unexpected results. Be sure to have a firm grasp of your languages’ properties. Take the time to review documentation surrounding your language’s type comparison and casting functionality and common language shortcomings.

DevOps

DevOps teams should ensure code build pipelines are making use of static-code analysis tools and that they are properly flagging low-rated findings to be fixed before deployment.

Project Leads/Directors

Project directors need to reflect if it is really worth bringing a development team out of their comfort zone for a new language and that when it is required, sufficient time is allotted for development teams to read and become acquainted with a language’s pitfalls.

About the Author

Jesse Clark, CISSP, is Senior Security Engineer at Certus Cybersecurity and is highly experienced in executing numerous web application and API penetration tests for Certus Cybersecurity’s Fortune 100 clients. He’s also conducted multiple security code reviews utilizing JavaScript, C++ and PHP. 

Contact Us
Ready to get started? Book a free consultation today, and we’ll write you back within 24 hours. For further inquiries, please submit the form at right. By submitting completed “Book a Free Consultation” form, your personal data will be processed by Certus Cybersecurity. Please read our Privacy Notice for more information.