Five tools for helping counter security threats:
The security symposium was great - loads of useful information and interesting anecdotes; I came away far better informed about the most pressing issues, but equally alarmed by how easily you can unwittingly leave a huge hole in an application.
By the way, am I allowed a teensy-weensy little criticism of this session? <rant> FOR GOODNESS SAKE - WHAT ON EARTH WERE YOU THINKING IN RUNNING STRAIGHT THROUGH FROM 8:30am to 12:30pm WITHOUT A SINGLE BREAK? SOME OF US HAVE BLADDERS, YOU KNOW! :-) </rant>
The SQL Server security lead developer demonstrated a black hat tool circulating on the Internet that utilises a SQL injection vulnerability to expose access to the full underlying database server, allowing query of any other table on that system or any linked server for which a web application has access. He demonstrated how a simple ASP.NET page query with a filter textbox could be used to reveal all the credit card details stored in another table in the database.
This kind of application demonstrates how the maturity of attacks is increasing. It's even more important than ever before to lock down the user accounts used and perform threat modelling and penetration testing against SQL injection attacks. This threat is scary and emphasises the importance of everything mentioned today.
Managed code is safer code! The following function is a C# analogue of a previous fragment:
private void CopyStuff(string data) { char[] buffer = new char[128]; data.CopyTo(0, buffer, 0, data.Length); // do other stuff }
If the output buffer is too small in the above scenario, the CLR will simply throw an exception.
Does that mean that all input can be trusted in managed code? Certainly not. For example, ASP.NET provides some built-in protection against cross-site scripting attacks: if you entered some JavaScript code into a textbox, with code in a button to display the text in a label, and ran this, ASP.NET would throw an exception. The mitigations are not absolute, however. The Page directive includes an option ValidateRequest="False" which switches this off, for instance. As ever, you should verify all input data before trusting it. Remember the defender's dilemma: you might not have thought of all the potential attack scenarios.
Another example of where you need to be careful in managed code is remoting, which has no built-in authentication or encryption support. One solution is to use HTTP channel to leverage the built-in sinks. SQL injection attacks are problematic in many applications too: using stored procedures with parameters and validating the input data are both approaches to deal with the problem.
One esoteric problem is the Turkish İ problem. Turkish has four letter Is: I, İ, ı and i. In Turkish, UC("file") == "FİLE". This introduces some subtle bugs, such as the following:
// Do not allow file:// URLs if (url.ToUpper().Left(4) == "FILE") return ERROR; getStuff(url);
This shows how hard it is to validate input data!
Error #1: Copying untrusted data Take a line of code such as the following:
while (*c != '\\') *p++ = *c++;
What's the problem here? The copy process is limited by the source data, not the destinatioin buffer size.
Copying untrusted data is one of the most prevalent causes of security issues. Buffer overruns, SQL injection and cross-site scripting are all due to input trust issues. Just because the RFC says that an integer value must be within a certain range doesn't mean for a moment that an attacker will follow the RFC!
Buffer Overruns A buffer overrun occurs when the external data is larger than the destination; overflowing the destination causes the application to change the execution flow to the attacker's code included in the data, usually by trampling on the return address in the stack. C and C++ are by far the most common victims to this kind of attack, because they allow direct access to memory.
Buffer overruns are common - approximately 25 to 30% of the security bulletins being issued across all operating systems involve buffer overruns. They are prevalent because of the quantity of C/C++ code, coupled with the fact that many data structures jump to code. For example, if a v-table can be modified, your server is "owned". The threat is constantly evolving. Stack overruns were once seen as the only threat; now heap-based buffer overruns are becoming prevalent. Even integer overflow attacks can cause buffer overruns.
Here's an example of an integer overflow:
int concatstring(char*buf1, char* buf2, size_t len1, size_t len2) { char buf[256]; if ((len1 + len2) > 256 return -1; memcpy(buf, buf1, len1); memcpy(buf + len1, buf2, len2); }
What if len1 = 0xFFFFFFFE and len2=0x00000102? Then you're going to try and copy large quantities of data into a 100-byte buffer. In this case, all that will happen is an Access Violation, but MS03-008 is an integer overflow bug that can be used to cause a heap overrun and ultimately compromise a system. This type of attack wasn't even conceived of three years ago.
Remedies One remedy is to enforce less error-prone constructs. For example, the C runtime function gets() is dangerous because there's no way to validate the size of the input. During the WIndows Server 2003 security push, the team created strsafe.h - a library of string library functions that are safer and are available on MSDN. Microsoft has now submitted a set of "safe" string functions (e.g. strcpy_s) to the C standards bodies.
The /GS switch in C++ uses a "canary in a coalmine" approach to check for buffer overruns, but whilst tools like this mitigate against overruns, it's vital to get the code right in the first place. Visual C++ .NET 2003 also swaps the buffers and other variables around, which means you have to create a buffer underrun instead - a much harder proposition.
Another remedy is to use higher-level languages such as C#. For example, in Windows Server 2003, UDDI Server is entirely written in managed code. However, the best defence is to reduce your attack surface - switching a feature off, and running with a lower level of privilege.
The moment you plug a live Internet network connection into your computer, you become part of the seediest neighbourhood in the planet. Your neighbours include thieves, con-artists, vandals, criminals and hackers. No wonder our computers are exposed to a very different environment to that of ten years ago!
It only takes one bad guy to take your system down. This is the attacker's advantage and the defender's dilemma:
Worse, there are many conflicts when building software. Choosing security often means a trade-off in other areas. Historically in the industry, software has always been very convenient: easy to use, with services switched on by default and rapid releases. The security pendulum has to a certain extent swung to the other side: reducing the attack surface has made products harder to use - we get more IIS questions now asking "how do I just get stuff done" because many things are switched off. The pendulum is now starting to swing to a more balanced perspective where the attack surface is smaller and security and first-class privacy are first-class features, without turning so much off that the product becomes unusable.
You can't build, design and test code and then check for security - you need a process that fosters secure systems. Internal Microsoft statistics show that this adds perhaps 15% to the schedule, but the net effect of not designing security is a 30% schedule slip.