Sep 12, 2007
Suggestions for designing and writing secure software
In this article we give some general tips for creating more secure software, which software engineers should follow at the various phases of the software development cycle. The list doesn't contain all possible advice but it does include the most important – the suggestions that are worth keeping in mind when designing and developing almost any kind of software with any programming language.
Security should be seen as part of the system from the very beginning, and not added as a layer at the end. The latter approach produces insecure code, may limit functionality, and will cost much more (in both time and money).
• Modularity: divide the program into semi-independent parts (small, well defined interfaces to each module/function).
• Isolation: each part should work correctly even if others fail (return wrong results, send requests with invalid arguments).
• Defence in depth: build multiple layers of defence instead of trusting just one protection mechanism. For example, validate user input data at entry point, and check again all values that are passed to sensitive parts of the code (like file handling etc).
• Simplicity: complex solutions are much more likely to be insecure.
• Redundancy: if possible avoid having a single point of failure.
• Make security-sensitive parts of your code small.
• Least privilege principle: don't require more privileges than you need. For example, run your code as the least privileged user necessary (don't run it as root, nor with SUID flag). Make sure that the account on which you will run your code has only the file access and execute privileges that your code really needs. Don't connect to a database with administrative privileges from your software.
• Choose safe defaults: for example, a random password that users are likely to change rather than a standard default password that many won't bother to change.
• Deny by default: for example, when validating user input accept only characters that you expect rather than trying to block known "bad" characters.
• Limit resource consumption, to limit the likelihood of a "denial of service" attack.
• Fail securely: for example, if there is a runtime error when checking user's access rights, assume the user has none.
• In distributed or web applications don't trust the client: don't expect it to validate user input, perform security checks or authenticate users – it all has to be done (again) on the server side. Remember that HTTP response header fields (cookies, user-agent, referrer etc) and HTTP query string values (from hidden fields or explicit links) may be forged or manipulated.
• Cryptography: use trusted, public algorithms, protocols and products. Do not invent your own cryptographic algorithms or protocols, nor implement existing ones.
• Read and follow guidelines for your programming language and software type.
• Think of the security implications of what your code does.
• Reuse trusted code (libraries, modules).
• Write good-quality, readable, maintainable code (bad code won't ever be secure).
• Don't trust input data – data coming from potentially malicious users is the single most common reason for security-related incidents (buffer overflow, SQL injection, Cross Site Scripting (XSS), code inside data etc). Input data includes command-line arguments, configuration files (if accessible by not-trusted users), environment variables, cookies and POST/GET arguments etc.
• Validate (sanitize) all input data: consider all input dangerous until proven valid, deny by default if not sure, validate at different levels; for example, at input data entry point and before really using that data.
• Don't make any assumptions about the environment: make sure your code doesn't break with modified/malicious PATH, CLASSPATH and other environment variables, current directory, @INC Perl variable, umask, signals, open file descriptors etc.
• Beware of the race condition: can your code run parallel? What if someone executes two instances of your program at the same time, or changes environment in the middle of its execution?
• Deal with errors and exceptions: don't assume that everything will work (especially file operations, system and network calls), catch exceptions, check result codes. Don't display internal error messages, failing SQL query, stack trace etc.
• Fail gracefully: if there is an unexpected error that you can't recover from, then log details, alert the administrator, clean the system (delete temporary files, clear memory) and inform the user.
• Protect passwords and secret information: don't hard-code passwords (it's hard to change them and easy to disclose), use external files instead (possibly encrypted) or already existing credentials (like certificates or Kerberos tickets), or simply ask the user for the password.
• Be careful when handling files: if you want to create it, report an error if it already exists; when you create it, set file permissions; if you open a file to read data, don't ask for write access; check if the file you open is not a link with the lstat() function (before and after opening the file); use absolute pathnames (for both commands and files); be extra careful when the filename (or part of it) comes from a user.
• Temporary files: don't fall for the symbolic link attack (someone guesses the name of your temporary file and creates a link from it to another file e.g. "/bin/bash", that your program overwrites). Temporary files must have unique names that are hard to guess (use tmpfile() for C/C++, mktemp shell command etc).
• Be careful with shell calls, eval functions etc: such functions evaluate the string argument as code and interpret it, or run it on the shell. If an attacker managed to inject malicious input to that argument, you're executing his code.
• Review your code and let others review it.
• When a (security) bug is found, search for similar ones.
• Use tools specific to your programming language: bounds checkers, memory testers, bug finders etc.
• Turn on compiler/interpreter warnings and read them ("perl –w", "gcc –Wall").
• Disable debugging information ("strip" command, "javac -g:none" etc).
Find useful links and language-specific tools at http://cern.ch/info-secure-software/SecurityChecklistForSoftwareDevelopers.pdf. See also http://cern.ch/SecureSoftware.
About the author
Sebastian Lopienski, IT/FIO