6. Run with Least Privileges
All code that runs on Windows NT or Windows 2000 runs in the context of a user account, therefore code runs with whichever privileges are allowed for that user account. If the process runs as a highly privileged account such as LocalSystem or an administrator then any malicious code in that process also will run with the elevated capabilities. The potential for damage is very high.
The lesson here is require only the privileges needed to get the job done. Do not require your application to run with system, administrative, or power-user accounts, because these accounts have special privileges. If you require such capabilities, support secondary logon using the CreateProcessAsUser() function. Another useful security measure is to start new threads with reduced privileges using the CreateRestrictedToken() function.
7. Don't Store Secret Information in Software
Securely storing secrets is always a difficult proposition. Simply put, it's next to impossible to secure private data in software. If you must store secret data, read my "Best Defense" article Storing Your Secret Data in Windows for further details about how.
8. Use CryptoAPI
Don't write your own cryptography code. Use the code built into the operating system. Generally speaking, the roll-your-own-crypto approach is not secure. Use the standard symmetric ciphers such as RC2, RC4, DES, and 3DES, hash functions such as MD5 and SHA-1, and asymmetric ciphers such as RSA. MSDN includes many samples and code snippets to help get you started, and I've posted some sample code for COM+ (written in C++) that you can use to do basic crypto stuff.
9. Add Security Comments to Your Code
Security comments are a guaranteed long-term time saver. If you have code that assumes some security requirements or does something security related, add a comment before the code like in this (incomplete) code snippet:
// SECURITY. Store the password using the Data protection API
// SECURITY. Password and extra entropy are passed to us from GatherDetails()
// SECURITY. pOut is a pointer passed to us from GatherDetails()
assert(pOut != NULL);
if (pOut != NULL) {
DATA_BLOB blobPwd={cbPwd,szPwd};
DATA_BLOB blobEntropy={cbEntropy,bEntropy};
BOOL fRet = CryptProtectData(blobPwd,
L"password",
blobEntrpoy,
NULL,NULL,
CRYPTPROTECT_UI_FORBIDDEN,
pOut);
}
10. Check Those Filenames
Many platforms have faced a number of file canonicalization errors in the past. For example, imagine you have a program that accepts filenames and returns the file to the user. However, you don't allow anyone to access a special file named ServerConfiguration.xml. If you see a request for this file, simply return "File not Found." Don't send "Access Denied" because an attacker will know that the file exists, he just won't have access—yet! However, the attacker could access the file by using the FAT 8.3 filename (Server~1.xml) instead, and because you don't check for this filename the attacker will know your server configuration. So make sure you check that all file requests are valid, or better yet, don't make security decisions based on the name of a file.