.NET doesn’t care what language you write in – as long as your code compiles to CIL, it’s a .NET application. There are plenty of .NET languages out there, but the most commonly used ones are C# and Visual Basic .NET (VB.NET). Since both languages end up as CIL before being executed, there should be zero security delta, right?
It turns out that there are a few security-relevant things to consider when dealing with VB.NET versus C#. That’s right, it’s time for a security throwdown!
1) Exception Filters: Exception filters are a language feature supported in CIL, but can only be used from VB.NET. They allow you to perform logical checks when deciding whether to catch an exception. Several people have demonstrated that the exception filter mechanism introduces a potentially vulnerability where you can execute code while a class is in an inconsistent state. This happens if you have a try/finally block (no catch) that relies on code in the finally block to execute. Because the exception filter of the caller executes before the finally block, it can operate on the class when it is in an inconsistent state.
While a particularly dangerous version of this vulnerability involving Windows impersonation has been fixed in the framework, you can still make mistakes here. You could either a) perform impersonation and undo in different methods, which is not addressed by the framework, b) allow untrusted code to call your trusted code in some other (non-Windows impersonation) inconsistent state, or c) make a mistake with your own exception filters and try/finally block that creates an error condition.
For a), Shawn recommends that you look for places where you impersonate and undo in different methods and change them to work in the same method. Of course, you can always simply catch your own exceptions and avoid the try/finally construct when impersonating. Similarly for b), you should avoid using the try/finally block when you are doing something security relevant, and catch your own exception, even if you just rethrow them. For c), it’s easy – avoid using exception filters. If you do use them, look for try/finally blocks that might be called while the exception filters are in place.
While exception filters aren’t just a VB.NET problem (your C# code could be vulnerable, since it can be called by code compiled from VB.NET), they may not be something the average C# developer uses in their day-to-day programming.
Winner: C#. While both languages are vulnerable to malicious code with exception filters, VB.NET introduced the concept and you can shoot yourself in the foot if you write bad exception handlers.
2) Option Strict: Option Strict is a VB.NET compiler directive that restricts data type conversions to widening conversions. It’s Off by default, but usually it should be turned On to prevent subtle errors which may cause security issues. Without option strict on, you can do crazy stuff like:
Dim someVarsomVar = new Collectiono.IDoWhatIWant
Obviously, there is no IDoWhatIWant method in Collection, but the lack of compile-time verification means that this bug will remain hidden until it’s hit at runtime. This, like several of these issues, is a backwards-compatible holdover from early VB days.
Winner: C#. Option strict shouldn’t be an option.
3) Option Explicit: Option Explicit is a compiler directive that specifies whether variables need to be declared. This is enabled by default (good), but you can disable it manually (bad). This allows you to make mistakes like:
valueReceived = 3
//Notice misspelling
valueReceived = valueRecieved + 4
// Will print 4, rather than 7
Debug.WriteLine(valueReceived)
This is an easy way to create errors that hide until runtime. While it’s not a security bug per se, it’s something to keep an eye out for in a code review.
Winner: C#. Ditto previous issue.
4) Arithmetic overflow: Arithmetic overflow (also referred to as Integer Overflow) is the problem where arithmetical operations cause a numeric type to grow beyond its representational bounds. The overflow errors can either be checked, meaning they will thrown an exception, or unchecked, meaning that the value will simply be wrong. Obviously, unchecked arithmetic exceptions can lead to some error-prone behavior that impacts the security of the application. You can use a checked or unchecked block to specify the behavior you want:
unchecked
{
int j = Int32.MaxValue;
//No Exception thrown
j++;
//Prints out -2147483648
System.Console.WriteLine(j);
}
checked
{
int i = Int32.MaxValue;
//Throws System.OverflowException
i++;
System.Console.WriteLine(i);
}
So what’s the default .NET behavior? Consulting the .NET documentation, we see that the default behavior “depends on external factors such as compiler options”. For C#, it’s unchecked and for VB.NET it’s checked (had to do a little poking around to find this). Why? Performance. Apparently, when C# was going up against C++ and Java, it was deemed a better idea to save some cycles than prevent overflow weirdness. I guess Visual Basic developers were already on-board with MS, so there was no need to wow them with impressive benchmarking.
You can force checked or unchecked behavior for either language using the /checked compiler switch. From a security perspective, you should always enable this.
Winner: VB.NET. To think that the majority of .NET applications out there don’t check for arithmetic overflow is scary. I can’t imagine all those C# ASP.NET application really care about the few cycles spent checking for overflow conditions.
5) Unsafe code: The unsafe keyword (which is only available in C#) allows you to use pointers in your .NET application. Is this a good security idea? No. This opens up all of the C/C++ security issues like buffer overflows and memory corruption. This was also a feature introduced to lure C/C++ developers to C# and .NET, and it should almost always be removed and replaced with managed code or the P/Invoke functionality.
Winner: VB.NET. Mixing different platforms with such different security guarantees in the same assembly is playing with fire.
6) Unstructured error handling: VB.NET allows unstructured error handling. Instead of just permitting try/catch/finally blocks for exception handling, you can also use OnError, GoTo, and Resume. This looks something like:
On Error GoTo ErrorHandler
‘ Insert code that might generate an error here
Exit Sub
ErrorHandler:
‘ Insert code to handle the error here
Resume Next
There is a whole host of problems with unstructured error handling, including performance, debugging, and maintenance issues. Error handling is a critical aspect of security, so using unstructured error handling could lead to security issues in a VB.NET application.
Winner: C#. As much fun as GoTos used to be, let’s leave these constructs in the past.
Throwdown Winner: C#. While it has some problems related to it’s initial competition with other languages (C/C++ and Java), VB.NET has so much functionality for backwards-compatibility that it’s much easier to make mistakes that lead to security issues.