A security mindset is one that always takes security implications into account. It’s assuming the worst and then ratcheting back just enough to have a useful application.
- NEVER trust data. Assume it’s always bogus and maliciously crafted. “Data” is an all-encompassing word that includes anything you haven’t hard-coded. For instance, function arguments, GET/POST data, data files, deserialized objects and network packets.
- ASSUME whatever data you are storing is being hacked 24/7 and broadcast live to the entire world. Corollary: be smart about what you actually store and how you store it.
- ASSUME every user of your system is the victim of a man-in-the-middle attack. Assume any data being sent in the clear is once again being broadcast 24/7 to the entire world.
- Authentication mechanisms should ALWAYS be rate limited.
- NEVER reveal privileged information. Corollary: only reveal what you must.
- ASSUME your code and data is always known verbatim with 100% comprehension by every human being on this Earth. Now how strong is your security? Corollary: if you think you’re being sneaky or clever, stop doing it.
When you’re a steward of someone else’s information, it’s your duty to be careful with it. The easiest way to do this is to not store it at all. When you do have to store it, store it right:
- Password storage should be one-way. Once a password is stored, the only thing you should ever be capable of is determining if another value matches the password. To do this, you hash the original password and only store the hash. When a user attempts to login, you hash the password they enter (preferably before transmitting it) and compare it to what you have stored. Proper hashing requires that you:
- Use a different, random salt value of sufficient size for each password. Using a large enough salt frustrates rainbow tables. Using a different salt for each password frustrates birthday attacks. Using random salt values frustrates precomputation if the source is available. A salt of 8 bytes or more is sufficient
- Use a cryptographically sound hashing algorithm. SHA-2 (SHA-256, SHA-384 and SHA-512) satisfies this requirement. Technically, MD5 is safe to use for password stretching, but you’ll face no end of flak from peers who don’t understand the math behind this. Stick with SHA-2
- Apply the hash a sufficient number of times to frustrate brute force attacks. This is a moving target that is always increasing (Moore’s law AND software optimizations). Practically, you have to base it on how fast the system is that handles authentication, because your users will only tolerate waiting so long to be logged in. Right now, as of 2011, 1000 iterations should be considered a minimum if using SHA-256
- Sensitive information that needs to be retrievable has to be encrypted. Encrypting something and storing the key in an easily retrievable way somewhere on the same machine is dumb. This means don’t hide the key in your code or database. Remember the golden rule? One way to do this is to have your program require the key to be entered remotely on startup and stored in RAM before sensitive information in the database can be worked with. This will frustrate attacks that only gain access to the filesystem.
Choose your preferred social status after a successful hack on your system:
|If you reveal a person’s…
||Then you are
|Real name & physical address
|Credit card #
|Credit card #, billing zip code and card security number
||A Gaping Version of Above
|Social security # or password
||A Menace to Society
Not properly vetting or using GET/POST data is the #1 source of security flaws today.
- NEVER put user supplied data in HTML pages without escaping the dangerous XML control characters. Implement a routine to do this once, test it and then always use it.
- NEVER pass important data using GET/POST instead of storing it in a server side session. Some bad ideas that illustrate this:
- Embedding an “isLoggedOn” type parameter in a hidden form element
- Passing the price or discount percentage of a shopping cart item in a hidden form field and then using the posted value when processing an order
- NEVER construct SQL statements by concatenating strings and user data. Always use prepared statements. In Java with JDBC this is done by using PreparedStatement and the setXXX() methods. With PHP you would use prepare() and bindParam().
Also known as defensive programming, this is verifying that a parameter/argument is safe to use before actually using it. Preferably, every argument is checked before the function does anything else. Examples of defensive programming are:
- Checking C/C++ pointers for NULL
- Checking Java object references for null
- Verifying array indexes
What if an argument fails inspection? That depends. What you should never do is completely ignore it. Possibilities:
- Use a sane default value and log a warning (or print one to stdout)
- Demand that whoever is calling your code calls it correctly by throwing an exception. In Java this is standard practice, in C++ it’s a little riskier because exceptions can cause a program to terminate if they aren’t caught.
- Force whoever is calling your code to call it correctly. Assert out. Nothing says “Good job, Ace” like dumping core.