Thursday, February 01, 2007

10 Guidelines for Becoming a More Secure Coder

The number of software developers that know nothing about security is staggering. In the coming years software security is going to become more and more important. I wrote these guidelines as a starting point for any developer that wants to learn how to write secure code. There are many types of software as there are many different programming languages. I tried to keep these guidelines as technology and language agnostic as possible.

1. Use a Safe Language / Development Framework

By using a type-safe language (like Java or any of the .NET languages) you can avoid a number of the classic security bugs (most notably buffer overflows). Non type-safe languages, like C and C++, allow the developer to decide how memory should be managed, accessed and interpreted. This is a complex task and is quite error-prone. If you insist on using a non type-safe language and want to do it securely, you will have a lot more research to do. Additionally, make use of sandboxed environments whenever possible to make sure that a security bug doesn’t have full access to the system that it is running on. For you .NET developers, this means not running with full-trust whenever possible. More on type-safety: http://en.wikipedia.org/wiki/Type_safety

2. Trust Nothing and Make No Assumptions

Data should not be trusted if it comes from an untrusted source (like a user). This goes for all input from web applications (all GET/POST parameters, HTTP headers, cookies, etc…), input from thick-clients, and files that get processed by your system (XML) to name a few sources. This type of data must be normalized and sanitized before using it in your system. Normalization (and we’re not talking about relational databases here) is the process of getting input into an expected form. Many software systems allow different character sets (Unicode, ASCII, UTF-8, etc…) and different encodings (hex encoding, URL encoding, HTML encoding, etc…). The data needs to be in normal form for the sanitation to be effective. Sanitation means cleaning the data of any potential unsafe characters. If you’re going to issue the data into an HTTP response stream you will need to HTML encode the data (Server.HtmlEncode in ASP, System.Web.HttpUtility.HtmlEncode in .NET) which will properly escape things like < and > with &lt; and &gt;

Make no assumptions about how your software will be used as it is an attacker’s job to find these assumptions and exploit them. Continuously ask yourself if you’ve made any assumptions about the code you’re writing. Don’t assume that because you haven’t provided a link on your website to moneyTransfer.aspx that an attacker won’t request it.

3. Embrace Least-Privilege

The concept of least-privilege means that any accounts should have the least amount of access necessary for the software to properly function. For example, if your software uses a database account to access data, that account should only have access to the data required by the application. The database account should not have access to entire databases or servers and if it only needs to read data it should only be able to read the data. This is important because if your software system does have a security vulnerability (perhaps one that allows access to data) you will have limited the surface area of any potential attack. The best an attacker could do is access or tamper with the data of that application, not all of the data on the server.

4. Don’t Store Passwords in a Recoverable Format

Passwords for things like web applications should not be stored anywhere (including a database) in readable text. There is no reason to store a readable password. The password should be hashed and the hash should be stored. A hash is a cryptographic function which is not reversible. At login time you would simply apply the hash function to the entered password and compare it to the hash in the database. If the password was correct the hashes should match. Historically, hashes like MD5 and SHA-1 have been used for this purpose. These hashes have been shown to have some security problems and while those problems don’t apply to this password hashing mechanism it is easier to use a newer, more-secure hashing algorithm than to explain to others why the vulnerabilities don’t apply. Select something like SHA-256, SHA-384 or SHA-512. More info on hash functions can be found here: http://en.wikipedia.org/wiki/Cryptographic_hash_function

5. Create and Enforce a Password Policy

It doesn’t make much sense to build a secure application if it is going to have hundreds or thousands of users with a password of “password.” Enforcing simple strength requirements on passwords can go a long way to protecting your application. You will have to determine what level of strength your application's passwords require. Some systems may only need to restrict dictionary words while other may wish to force users to select passwords with upper-case letters, lower-case letters, numbers and symbols. Alternatives to implementing strength requirements on passwords include locking out accounts after a small number of consecutive failed login attempts and expiring passwords after a certain length of time (say, 180 days).

6. Parameterize Your Data Access

SQL injection plagues many different types of software. With SQL injection an attacker provides input to your application that manipulates your intended SQL query. When a SQL injection is present an attacker can usually get information on your database schema and retrieve or edit data in the database. The easiest and most effective way to defend against SQL injection is to use a parameterized query (combined with stored procedures if they’re supported by your database engine). Never build SQL statements by concatenating strings together with user input. Check the documentation for the database access technology you’re using. Here is a link for ASP.NET developers with some examples: http://aspnet101.com/aspnet101/tutorials.aspx?id=1

7. Hide Your Errors

Error messages will not be understood by the common user but to an attacker they are a goldmine. Error messages can be used to gain information about the system and in some cases can be used to enumerate the contents of entire databases. Never display error information to the user.

8. Don’t Write Your Own Crypto

There are plenty of good choices available for cryptography. There is no need to spend time trying to write your own algorithm. The accepted algorithms have been tested and analyzed for years and it is likely that the folks that invented and tested the algorithms are smarter than you. Good choices include triple-DES or AES for symmetric encryption (with a password) and PGP or SSL/TLS for asymmetric encryption (public/private key).

9. Have Someone Else Test Your Code as an Attacker

Consider this: You have written some code and made it as secure as you know how to. You cannot possibly test for the problems you didn’t foresee. Having someone else do the security testing is much more beneficial than doing it yourself. Also, the tester should be testing the software like an attacker might. For example, they should try to access or tamper with data that they shouldn’t have access to.

10. Get Secure Coding Training

If you’re serious about becoming a secure software developer consider getting some formal training. The guidelines I’ve outlined here are a great starting point and by following them you will have an edge on most developers. As you progress in your career and the systems that you work on increase in size and importance it will become necessary to relate your security decisions to the business. More training can give you the details you need to effectively perform a business risk analysis and create an effective threat model.

Where to go for more information:

OWASP - A ton of information for web developers: http://www.owasp.org/

Great books:

Writing Secure Code, Second Edition
Hacking the Code