Throughout my time spent delivering talks at security conferences and in classrooms of prominent software companies,...
I consistently hear the same question: "What can we do to secure our software completely?" This question typically comes in response to feeling completely overwhelmed by all the best practices, methodologies and techniques to which my audience has just been exposed.
Unfortunately there is no single or easy answer to that question. Application security must be dealt with and considered in every phase of the software development life cycle (SDLC). I know practitioners would rather that I discuss the silver bullet solution, but there is none. Security in the SDLC is essential -- so much so that Microsoft has bestowed upon it a completely new name: the Secure Development Lifecycle, or SDL. While these acronyms may be similar, the way we think about software differs drastically in each model.
The original software development life cycle was created to deliver applications that are feature-rich, free of bugs and shipped on time. It was designed at a time prior to security being the prevalent issue that it is today. The Secure Development Lifecycle attempts to marry the pillars of the original SDLC with fundamental secure practices throughout the lifecycle. This practice will create an application that has a secure core and can better withstand attacks.
When software development teams begin to think about security early in the SDLC, it reforms our ideas of what our software will look like and gives us flexibility to address the concerns of not only security, but performance, usability, reliability, maintainability and many other important metrics. It's only when we attempt to tack on security three weeks before the ship date that it becomes impossible.
There are five indispensible things to consider when designing software:
- Security principles must be applied to every phase of the SDLC, otherwise ship dates will slip and software will remain insecure.
- People not professionally trained in security cannot be expected to make sound security decisions.
- Software will never be 100% secure, and people will inevitably make mistakes when implementing a secure system. Therefore, proper fail-safes must be in place to mitigate these risks.
- The insider threat is real; I've worked in both small and large companies, and my distrust for those protecting data is the same.
The deck is stacked against the software development team. Attackers have to find only a single vulnerability; you have to find them all. Additionally, auditors and compliance are here to stay -- and they want proof that you are taking the necessary steps to produce a secure application.
Requirements gathering phase
- How bad would it be if this piece of personal data was lost?
- How tolerant are you to password policies?
- Would you be interested in using a biometric scanner if it meant an increased level of security and confidence?
Asking those questions will do two things for you. First it will instill confidence that you take security seriously and want to protect customers' sensitive data. It will also ensure that as features are created in the next phases, you will know how tolerant your users are to security mechanisms or data loss. Armed with this intelligence, you can now begin writing requirements.
Requirements authoring phase
Effective exercises to create security requirements are as follow:
Consider the attacker in each feature -- Consider not only what a benign user will create with your software but also what an attacker may be able to do with it.
Outline what each feature should do and what it should not do -- One requirement for a login control may be to ensure that a user with valid credentials is allowed access to his data. Another requirement for that feature should be that the login control will disallow any unauthorized user from retrieving another user's personal data.
Understand the principle of "secure by default" -- When designing the system, ensure features are off until a user enables them. This will decrease the application's attack surface. A disabled feature does not provide an avenue of attack, and it also has the added benefit of increasing performance and lowering the memory footprint.
Use threat modeling to understand the possible threats to a system -- Threat modeling and threat trees are great ways to understand the security implications of each feature. Threat modeling is an effective risk analysis technique that will be described near the end of this article.
Review and update the threat model document -- The threat model should be a living document that contains all the assets, entry points and attack vectors of the software. Ensure there are mitigations in place for each attack in the threat model.
Ensure communication channels are secured -- Use proper encryption standards such as SSL and TLS for Web traffic or AES for transmitting blocks of sensitive data.
Enable the developer to understand how the component he will be working on will be used in the application -- Consider future implementations to be sure that if a component is used in a different way than originally thought, it does not expose a latent security flaw. Ensure that each function or feature is responsible for checking all inputs or flag the function to be used only behind a secure trust layer.
There is a lot to consider when developing an application, so developers must be trained in secure development and must show a constant focus on security. This includes a careful examination of the libraries and functions they use. The moment a developer forgets to check a return value or fails to validate input, he will likely expose a serious security vulnerability.
To make things easier and to increase the overall quality of software, here are some best practices:
Train your stakeholders -- The most important thing developers can do is become knowledgeable about the system they are securing. Without the right knowledge they cannot know the security implications of their decisions. The end of this article lists helpful books and training courses to learn more.
Review the threat model -- Leveraging the threat model as a guide for development is a great way to make sure each threat and possible vulnerability has been mitigated. The threat model should be updated as new threats are discovered or mitigated.
Review code for security -- Code reviewing can be difficult to get traction on because it requires so much time. However, just like an author wouldn't dream of publishing a book before an editor had a chance to review it, a developer should always have a second pair of watchful eyes examine sensitive code blocks.
Use static analysis tools -- Static analysis tools can help create more secure code by quickly and easily checking for common security vulnerabilities. However, these tools are in their infancy and you need to understand what they can and cannot do.
Use tried-and-true security libraries -- Security libraries are very difficult to write. Leveraging the libraries that have been provided for you will reduce development time and cost while increasing the security of your application. Attempting to create a new encryption or hashing algorithm is not a good idea. There are proven algorithms and libraries that have been peer-reviewed and scrutinized for possible weaknesses. At the time of this writing the standard for encryption is the Advanced Encryption Standard (AES 128, 192, and 256). The standard for hashing is SHA-256 or greater. All other algorithms have shown signs of serious weakness and should be discontinued.
Do not implement your own security mechanisms -- As mentioned above, leveraging existing security mechanisms for cryptography, authentication and integrity will help reduce costs and development time while creating a more secure system. No security will be gained from creating a proprietary, and otherwise unknown, security algorithm.
Validate your input -- Input is the root of all evil. Without input our software would do exactly the same thing every time, which would be less glamorous, but predictable and safer. Consider all methods of input, including the UI, registry keys, the network, Remote Procedure Calls, XML, HTTP requests, UDP, return values from APIs, corrupted memory, files, and every other way your application knows what to do.
There are three main characteristics of an adept security tester:
- Imagination -- A tester must be able to think about software and software security from a completely different perspective, including the ways that the software was intended to be used and considering all other ways that the software could be used. Think like my mother, think like an attacker, think like a power user. Use your imagination to explore every corner of the software to find vulnerabilities that are elusive and likely to wreak havoc if discovered by a malicious user.
- Knowledge of system -- An effective security tester understands how every component fits together and can recognize when things are out of place. A complete knowledge of the system will alert the tester when components are used improperly or when security vulnerabilities manifest in difficult-to-find ways.
- Evil streak -- You cannot discount the benefit of being able to think like an attacker. It's important to not consider the obvious scenarios such as "Are there any temporary files being created where they shouldn't be?" Think more destructive and evil: "What could I do with these temporary files if I found one?" The opportunity to be evil without doing anything wrong is what makes security testing so exciting.
Below are a few points to help get the imagination, knowledge and evil streak going.
Education -- The single most important aspect of sound testing is a solid foundation in security. This can be provided by books, classes and conferences. They key, though, is never falling into the trap of thinking you know enough.
Books are often written by experts in the field with lots of hands-on experience (and you can always refer to them when needed).
Security classes allow you to get real hands-on testing tips and knowledge from security professionals. The techniques that you learn will stick with you and become part of your daily testing routine.
Conferences are great opportunities to communicate and knowledge share with others in the security space
Review the threat model -- The threat model can be used to hold the PM, architect and developer's feet to the fire. It's a great place to start, but great testers know that real vulnerabilities reside outside of this model. Be sure to use your imagination to isolate new threats and vulnerabilities, and be sure to update the threat model as they are discovered.
Test with a methodology – Banging your head on a keyboard has never provided a reliable or repeatable method for finding vulnerabilities that the end user cares about or the attacker may exploit. For this reason, employ a methodology to look for vulnerabilities in locations where they are likely to be found and where they are likely to cause the most damage. A methodology, however, is not a test plan; don't let it stifle your nose for great bugs. Testers are like the police dogs of the software world, if you sniff out a great SQL injection vulnerability, feel free to explore it.
Release and documentation
Before releasing a product, be sure to follow these steps to make it easy for your customers to deploy safely:
Document all security features -- Explain the benefit of leaving these features on and how to allow legacy features to communicate.
Document ways to turn on features that are not available by default -- Provide easy-to-follow instructions on how to turn on disabled features securely.
Train support staff -- Be sure to train the support staff on the new security features and the best ways to use them. Make sure the support staff does not adopt a "turn everything on and walk away" policy.
Bringing it all together
Knowledgeable development teams can make informed decisions when presented with day-to-day problems. Having each member of the team trained in security is important and will help ensure that your application is significantly more secure, but this is not sufficient. While the PMs, architects, developers and testers strive to create a secure product, they should also employ key security practitioners at each phase dedicated to reviewing their phase's deliverables. These individuals ensure rubber hits road and maintain a steady, repeatable secure development process. Typically, a developer spends the majority of his time reviewing code for security vulnerabilities, a tester focuses on testing for unmitigated threats, and an architect ensures that the application as a whole can be securely produced.
Finally, proactive organizations understand the benefit of partnering with companies with specific expertise in assessing security risk, mitigating threats and providing the knowledge their teams need to discover vulnerabilities and understand the risks they pose.
With this multilayered approach, software ships more securely, closer to the scheduled delivery date and closer to anticipated cost. Your company becomes more competitive in the market and reduces its overall risk, resulting in fewer headaches from the auditors and customers.
About the author: Joe Basirico is a senior security trainer at Security Innovation Inc. in Wilmington, Mass.