The .NET Framework, the Microsoft platform for building Web services, has a great deal of security built in. Applications written using it are inherently more secure than those written for Win32, the standalone Windows programming environment.
That doesn't mean .NET applications are without security vulnerabilities. Programmers still need to follow best practices to protect their apps from attacks from hackers and others with malicious intent.
While the .NET Framework protects applications from a condition known as "buffer overflow" frequently exploited by attackers, they can still be vulnerable to many other common attacks. These include techniques with technical names such as canonicalization, SQL injection and cross-site scripting that can trick a program into allowing access to system files, deleting data or maliciously gathering data from an unsuspecting user.
The .NET Framework provides tools to help prevent such attacks. Here are some tips and techniques to help .NET programmers minimize the vulnerabilities of the applications they build.
The single best way to improve the security of .NET applications is to use the security principle of least privilege, i.e., users and applications should have only the minimal privileges they require to function. For example, Notepad should have privileges to write to the file system, but it should not have privileges to contact Web sites. Internet Explorer should be able to contact Web sites, but should only be allowed to write to the temporary files folder on the file system.
The concept of least privilege for application developers is twofold:
Role-based security. Role-based security involves granting users minimal privileges to minimize the damage they, or a process they run, can do to the system. An application should be written so that users can run it without administrative privileges while logged on as a standard user.
Code access security. Code-access security (CAS) is a relatively new concept that involves restricting what individual applications can do. With the .NET Framework, applications can have fewer privileges than the user, reducing the damage if the application attempts a malicious act such as modifying system settings. Programmers should use the flexibility of the .NET Framework to grant an application only the minimum privileges required.
Programmers should follow these guidelines to minimize an application's privilege requirements:
• Debug and test applications as a standard user. Specifically, applications should only store files in the user portions of the file system and registry. Do not attempt to make system-wide changes that require administrative privileges.
• Use declarative CAS to demand only the privileges an application and individual methods require. Use SecurityAction.RequestMinimum and SecurityAction.RequestOptional to specify the exact privileges an application needs, and refuse all other privileges. Limit privileges imperatively using System.Security.CodeAccessPermission.PermitOnly when you must determine privileges during runtime.
• Reduce the risk of external assemblies introducing a security vulnerability by creating an application domain with limited privileges, and using the application domain to launch the assembly.
Validate and Sanitize Input
Canonicalization is the process of converting data that has more than one possible representation into a standard "canonical" representation. To prevent canonicalization attacks — which bypass such filters by encoding a path using special characters and can trick an application into providing unauthorized access to a system file — programmers should use the .NET Framework to perform canonicalization before they validate paths.
They should also test all inputs and arguments for valid characters, values and lengths. A common mistake is to test for invalid characters, such as searching for "" in HTML input. Attackers can defeat such filtering algorithms by sending "" or "< script >". A program should not accept input if it cannot validate every part of that input against a list of acceptable values.
For example, phone numbers should only have numbers and dashes, and should fit a specific pattern. If a phone number has any other character or the wrong number of digits, it should not be accepted.
Sometimes, attackers can use valid characters with malicious intent. For example, attackers often use apostrophes during Structured Query Language injection attacks because they are the most common delimiter in SQL commands. However, a program cannot simply reject all input containing an apostrophe because the character can appear in names, locations and sentences. In these circumstances, the input must be sanitized.
The easiest way to sanitize input is to use special characters to replace characters that might be legitimate but might also be part of an attack. For example, SQL handles two apostrophes together as a single, nondelimiting apostrophe. Therefore, replacing a single apostrophe with two apostrophes allows a name to be inserted into a dynamically generated SQL query without allowing the apostrophe to act as a delimiter. Alternatively, you can use parameterized SQL commands instead of strings to construct dynamic SQL queries. When sanitizing HTML input, replace "<" with "<" and ">" with ">".
Programmers should always use constructs known as "strongly typed classes" built into the .NET Framework, because the .NET Framework automatically validates input for these. For example, a date entered by a user should be stored as a DateTime object, not a text string, so the input will be validated automatically at runtime. For information that doesn't fit into an existing class, programmers can create custom classes.
Finally, programmers must swallow their pride and acknowledge that they're only human. A minor mistake by a developer can have huge consequences. Every security breach that's made headlines is the result of a very good programmer overlooking a single vulnerability. There are two ways to protect code from human error:
• Unit testing. Create automated tests for each method used by a program. Verify that the method behaves as expected when passed valid input. Also verify that the method sanitizes input or reacts accordingly when passed invalid data. Unit testing can prevent developers from accidentally reducing the security of their code during future updates.
• Peer review. Have other developers study secure development best practices and review code for vulnerabilities, and do the same for them. Not only will one programmer catch another's mistakes, but the review process will make everyone better programmers. Nobody on the team will take a security shortcut when they know their coworkers will catch it.
It's impossible to prevent all security vulnerabilities, but the vast majority could be prevented by using these techniques. Once mastered, they shouldn't slow down the development process. And in the long run, these best practices can save time by exposing security vulnerabilities that would otherwise be discovered by users and require subsequent patching.
Programmers can't rely on the .NET Framwork's built-in security features alone. They still need to follow these basic principles to keep their .NET applications secure: