G’day guys!
Continuing with our exploration of various security vulnerabilities from a software developer’s perspective, I thought we could switch gears for a minute and look at something that affects (or has the potential to affect) .NET and other strongly typed applications.
Picking on .NET now, are we?
Not specifically - likely any strongly typed language would be susceptible to this, and since we focused on PHP in the last article, it’s only fair to mix it up.
Alright already, show us the vulnerability
Imagine a code block that looks something like this:
int a = 257;
byte b = (byte)a;
What do you think the value of b
will be? Spoiler alert: it’s actually 1. As we know, a byte has 8 bits, and holds a range from 0 to 255. With 255, all 8 bits will be turned on, so it’s binary representation will look like 1111 1111. For a value of 257 though, it’d look like 1 0000 0001. Since the byte only holds 8 bits, the leading 1 will get stripped off, leaving just the trailing 1, so we end up with a value of 1 instead of 257.
Duh. How is that a vulnerability, though?
Well, imagine that you were doing something similar to this in your application, and that an attacker was able to manipulate the value of a
. a
here could represent a Price Id, Invoice Id, User Id or any other entity. By forcing the integer overflow to produce the value 1, the attacker could set the entity id to 1, which is likely the first record added to the system in databases that use auto-incrementing IDs. Some possible results might be that they could end up with elevated priveleges or free products.
Okay, that makes sense. But surely libraries and frameworks already handle this?
Somewhat. I did actually stumble upon this in practice back in .NET Framework land many years ago, but from my quick tests earlier, it looks like model binding in .NET Core has improved. These were what I noticed:
Library | Overflow effect |
.NET Core MVC | Resets to zero |
.NET Core Web Api | Deserialization error |
Newtonsoft.Json | Deserialization error |
System.Text.Json | Deserialization error |
CsvHelper | Deserialization error |
As you can see, it looks like most packages and frameworks will raise an error (or just default to zero) when an overflow is about to take place. Having said that, it’s probably still best not to rely on this and of course you’re not protected when doing any manual mapping or manual deserialization.
Fair enough! So how do we prevent it?
There are 3 approaches you could employ.
1. Manually check that the value is in range before copying it.
Coming back to our earlier example:
int a = 257;
byte b = (byte)a;
One thing we could do is to verify that a
is within the range of a byte before performing the assignment to b
. For example:
if (a < byte.MinValue || a > byte.MaxValue)
{
throw new OverflowException($"{nameof(a)} was outside the bounds of a byte.");
}
This is a bit clunky though, especially if we’d have to repeat this logic throughout multiple parts of the codebase. Onto option 2!
2. Use a checked block
Another thing we could do is to have the compiler perform these checks for us by wrapping overflow-sensitive logic in a checked block. For example:
checked
{
int a = 257;
byte b = (byte)a;
}
Here, the runtime will produce an OverflowException
for any overflows (or underflows) that occur within the checked
block. You might be wondering though, if your codebase does this a lot, if there’s a way to make this the default for the entire project.
3. Turn on CheckForOverflowUnderflow in the project settings
In your csproj
file, add <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
to enable overflow checks for the entire codebase. There is of course the question of will this hurt application performance. I haven’t tested this myself, but I imagine if your project happens to be performing a lot of overflow/underflow checks already anyway, then this could be a wortwhile option to consider.
And that’s it for this write-up. Thanks for reading, and see you all next time.
Catch ya!