ClickCease The Bugs Behind the Vulnerabilities Part 3

Content Table

Join Our Popular Newsletter

Join 4,500+ Linux & Open Source Professionals!

2x a month. No spam.

The Bugs Behind the Vulnerabilities Part 3

Joao Correia

January 3, 2023 - Technical Evangelist

This is part three of our five-part blog series exploring the code bugs that lead to the vulnerabilities showing up every day. In this part, we’ll cover bugs #15 to #11 of the Mitre CWE Top 25 list for 2022, including everyone’s favorite, “NULL pointer dereference”, at #11.

You can find part 1 here and part 2 here.

15 – Use of Hard-Coded Credentials

Issues classified under this vulnerability class originate from including credentials in a compiled package, or including them in the distribution media or storage device that reaches the end user. This means that, if discovered, those credentials would work on all the same devices. Rather than a coding bug per-se, this is more of an issue with packaging and releasing.

There are many ways this can happen – like the use of hard-coded test credentials during CI/CD that reach the deliverable at the end of the process (as opposed to using dedicated authentication tokens, for example), or including some form of backdoor to a production device that makes it to market.

Once found, this type of issue can be very difficult to solve. When it happens in a hardware device, the only way to remove a hard-coded credential is to update the firmware. How difficult that operation is will depend on the specific device.

But this can, and does, happen on software packages too – not just hardware. In this case, since the credential is baked into the actual software package, an updated version will usually be necessary to solve the problem.

It is also relatively easy to spot such types of credentials when reverse engineering software or hardware, so malicious actors really enjoy discovering them during their operations.

The real problem with hard-coded credentials is that they will overrule any access restrictions that you can set up in a given system. If a hard-coded admin credential exists, it doesn’t matter if you have no other administrative accounts created – the hard-coded one will still be able to enter.

At an architectural level, hard-coded credentials also appear when configuring connections between systems, where a connection string or password are stored in a configuration file that is read to establish the connection. An attacker finding that file would be able to impersonate the connection.

14 – Improper Authentication

Improper authentication describes a situation where, instead of validating a set of credentials, tokens, or other authentication mechanism, an application trusts a client that claims to have a given identity.

This is very common when dealing with website connections that validate the presence of a cookie rather than validating the authentication to the site. A cookie can be forged to claim it has already been authenticated, and the website will trust it.

Another common scenario is when the authentication is validated client-side only – thus enabling an attacker to modify or interfere with the client software and gain access to server-side resources by claiming to already be authenticated.

13 – Integer Overflow or Wraparound

This is one of the earliest forms of software bugs. In a given computer architecture (32/64 bit, ARM, mips, etc), any variable will occupy a given amount of memory. The larger the amount of memory, the larger the value that can be stored in that variable. However, when doing arithmetic operations, it is easy to miss doing a check to see if the result of such an operation will still fit in the memory size of the variable – like, for example, multiplying two integers together and storing the result might lead to a value so large that it no longer fits the maximum variable size. When this happens, the integer will “overflow”. 

Different computer architectures deal with this event in different ways: some will discard the extra value, resulting in storing the wrong value; others will write the value in the next available memory location, thus potentially overwriting something else already stored there; and others will wraparound the value – like the mileage counter in a car that returns to zero when it reaches the maximum value, and you continue to drive.

Imagining the variable controls the number of iterations in a loop, it can lead to an infinite loop. In other scenarios, it can cause a crash, an unresponsive application, or an unresponsive service, and it could also exhaust available memory or overall system instability. It can also directly lead to buffer overflow scenarios (covered in a different bug on this list).

As many computer languages do not include bounds checks by default – and have to explicitly be enabled either through compiler flags or by using specific libraries – it is important to make an informed decision on the language to use for an application that is being planned. 

If this is found to happen on an already developed application, then proper bounds checks need to be added to validate that the calculations will never fall outside available variable memory sizes, always remembering that, if an application is a cross-platform one, the different platforms/architectures will have different allowable sizes.

Tools like fuzzers usually trigger them and, as such, can be of great value to developers.

Finding situations in the flow of an application that are vulnerable to this type of bug is one of the most basic tasks that is done by an attacker. 

12 – Deserialization of Untrusted Data

Serialization (or marshaling) is the process of converting a piece of data stored in memory into a format that can be stored or otherwise sent (through a network connection) to a different system/application/component. Deserialization (or unmarshaling) is the reverse process, where a stream of data gets converted into a memory representation of an application’s internal object (“object” meaning variable or grouping of variables).

When an application blindly trusts that the data that it is receiving – either read from a file, accepted from a form submission on a website, or through a network connection – is inherently valid and then attempts to deserialize it into a given memory location means that the application is exposing itself to risk. Specially crafted data streams can trick an application into overwriting existing memory locations outside the intended location or not providing enough data to fill all the variables, potentially leading to uninitialized variables or even delivering data outside expected boundaries – which can all lead to unforeseen events.

As a development rule of thumb, no input should ever be trusted, with no exceptions. Even if the developer is writing the client and the server application themselves, it is always possible to subvert a connection, or attempt to tamper with the communication between them, or impersonate one side of the conversation, thus leading to a scenario where the data being accepted is no longer trustworthy, and should therefore not be deserialized.

The following Java code looks benign enough:

try {
File file = new File("object.obj");
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
javax.swing.JButton button = (javax.swing.JButton) in.readObject();
in.close();
}


[source: https://cwe.mitre.org/data/definitions/502.html]

 

But, upon closer inspection, the file “object.obj” is not validated at all, and could contain something other than a button definition, as the application expects.

11 – NULL Pointer Dereference

This is one of the most common sources of vulnerabilities to be found in any given application written in a pointer-friendly language, like C. It consists of trying to get the value referenced by a pointer that has been invalidated previously or hasn’t been initialized yet.

The usual result, barring architectural differences, is that the application will crash, as the operating system will detect the attempt to read a memory location outside the allowed memory space of that application.

The following C# sample illustrates the issue:

 

string name;
int k=0;
if(k==1)
{
  name=Console.ReadLine();
}
Console.Writeline(name.Length);

When execution reaches the last call, the “Length” method will be called on a null string, crashing the application.

It is interesting to note that many languages have introduced changes (nullable types, compiler flags, new warning types) specifically targeted at catching this type of error early in the development process. Others forgo the explicit use of pointers altogether to guard against it, but other than cautious programming practices, there is no silver bullet that is 100% effective in finding and preventing NULL pointer dereferences.

Situations like the one described in bug #12, can lead to unexpected values being introduced in the flow of the application, triggering NULL pointer dereferences.

A relatively simple fix to this bug is to always prepend any suitable variable use by a check to validate it is not NULL, but concurrent/parallel programming can make that a less optimal solution.

 

Summary
The Bugs Behind the Vulnerabilities  Part 3
Article Name
The Bugs Behind the Vulnerabilities Part 3
Description
This is part three of our five-part blog series exploring the code bugs that lead to the vulnerabilities showing up every day.
Author
Publisher Name
TuxCare
Publisher Logo

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

Learn About Live Patching with TuxCare

Become a TuxCare Guest Writer

Get started

Mail

Join

4,500

Linux & Open Source
Professionals!

Subscribe to
our newsletter