10 Steps to Secure Software
In this day and age, you need secure software. This article from DZone's 2015 Guide to Application Security shows you the 10 steps you need to know to achieve secure software.
Join the DZone community and get the full member experience.Join For Free
This article is featured in the DZone Guide to Application Security. Get your free copy for more insightful articles, industry statistics, and more.
OWASP’s Top 10 Risk List is an important tool for security engineers and compliance analysts. It describes the 10 worst security problems that are found in web and mobile applications today. But, on its own, it’s not much help to developers, so OWASP has come up with a list of 10 things that you can do as a developer to make sure that your code is safe and secure.
These are practical, straightforward steps that developers can take, with code examples, links to free tools, and more information, including OWASP’s Cheat Sheets. Consider this article a sneak peek at the latest OWASP Top 10 list for developers, which should be published before the end of 2015. If you have questions or suggestions, we’d be happy to hear from you. Just send an email to email@example.com.
1. Protect Your Database From SQL Injection
One of the most dangerous (and most common) attacks on web applications is SQL Injection: attackers inserting malicious SQL into a dynamic SQL statement. SQL injection vulnerabilities are easy for an attacker to find and exploit using free tools like SQL Map or SQL Ninja, or even manually: try inserting a value like 1' or '1' = '1 into the user name, password, or any other text fields and see what happens. Once SQL injection vulnerabilities are found, they’re easy to exploit.
Luckily, SQL injection is also easy to prevent. You simply need to parameterize your SQL statements, making it clear to the SQL interpreter which parts of a SQL statement make up the command and which parts are data. OWASP has a Cheat Sheet that explains how to parameterize queries in Java (using prepared statements or Hibernate) and in other languages.
2. Encode Data Before Using It
The solution to injection attacks is simple in concept: if you can’t clearly separate code from data (which is what you do to prevent SQL injection using a parameterized API), you have to make the data safe before handing it off to an external interpreter, such as an XML parser, an OS command shell, or a browser.
To do this you need to output encode/escape data before handing it to the interpreter, so that the interpreter will not recognize executable statements in the data.
There are tools to help you do this, including the OWASP Java Encoder for XSS protection, and Microsoft’s open-source Anti-XSS Library for .NET (encoder functions for XSS protection have been taken from this library, improved, and implemented in the .NET 4.5 AntiXssEncoder class).
But even with these tools, it can be difficult to get everything right, which is why injection—especially XSS—is one of the most common major security vulnerabilities in web applications. To learn more about how XSS works and how to find it in an app, try playing Google’s XSS game.
3. Validate Input Data Before You Use It or Store It
All data from outside your program or service, especially data from remote clients, is evil and needs to be validated: files, parameters, HTTP headers, cookies, … It doesn’t matter if the client or the other system validated the data. You need to validate it again.
The basic rules of data validation are as follows:
- Don’t rely on client-side checking. Always check on the server.
- Use positive, whitelist validation rules wherever possible. Negative, blacklist checks that reject data if they contain dangerous or illegal values can be subverted through (double) encoding and other evasion tricks. Where you can, use strong whitelist rules that clearly specify the size and range of acceptable values using regular expressions. Look to libraries like the Apache Commons Validator for help in how to properly check for data types like dates, currencies, IP addresses, URLs, and credit card numbers.
You can test how solid your input validation is with different tools, such as fuzzers, which throw garbage at your code, or static analysis taint checking tools, which run through execution paths in the code and identify when you are referencing data that has not been validated.
4. Access Control—Deny by Default
Deciding who needs what access to which data and to which features—and how these rules will be enforced—should be carefully thought through upfront in design. It’s a pain to retrofit access control later without making mistakes.
- Implement access control rules in a central, server-side management library, instead of sprinkling these rules throughout the business logic. This makes it much easier to audit and update the rules. Use the access control functions of your application framework, or use a security library like Apache Shiro to do this.
- Only use server-side trusted data (data that has been properly validated) to make access control decisions.
- Deny by default—all functions should check to make sure that the user is authorized before proceeding.
5. Establish Identity Upfront
Building your own bulletproof authentication and session management scheme isn’t easy. There are lots of places to make mistakes, which is why “Broken Authentication and Session Management” is #2 on the OWASP Top 10 list of serious application security problems. If your application framework doesn't take care of this properly for you, then look at a library like Apache Shiro to provide functions for authentication and secure session management.
Try to enforce multi-factor authentication if you can. If you have to rely on just User IDs and passwords, make sure to follow rules for password length and complexity. If you are using an email address as the User ID, be careful to keep it safe: bad guys will try to harvest email addresses for other purposes.
When storing passwords, you can’t get away with just salting and hashing the value anymore. OWASP’s Password Storage Cheat Sheet explains what you need to do and what algorithms to use.
Password recovery is another important function that you need to be careful with. OWASP’s Forgot Password Cheat Sheet can help you design a secure recovery function, covering things like choosing and using good user security questions, properly verifying the answer to these questions, and using a side channel to send the reset password token.
6. Protect Data and Privacy
Protecting data and privacy is about access control (which we’ve already talked about), auditing (which we’ll cover under logging), and encryption: encrypting data in transit, at rest, and during processing.
For web apps and mobile apps, encrypting data in transit means using SSL/TLS. Using SSL isn't hard. Making sure that it is setup and used correctly takes more work. OWASP’s Transport Layer Protection Cheat Sheet explains how SSL and TLS work and rules that you should follow. The Cheat Sheet on Certificate Pinning explains how you can prevent man-in-the-middle attacks when using SSL/TLS.
The most common mistakes in encrypting data at rest are:
- Forgetting to encrypt data in the first place
- Trying to roll your own encryption algorithm
- Mishandling keys or other setup steps for standard encryption libraries
OWASP has another Cheat Sheet on Cryptographic Storage which covers the different crypto algorithms that you should use and when. Libraries like Google KeyCzar or Jasypt will take care of the implementation details for you.
The last problem to look out for is exposing sensitive data during processing. Be careful not to store this data unencrypted in temporary files and don’t include it in logs. You may even have to watch out when storing it in memory.
7. Logging and Intrusion Detection
Logging is important for more than troubleshooting and debugging. It is also critical for activity auditing, intrusion detection (telling Ops when the system is being hacked), and forensics (figuring out what happened after the system was hacked). You should take all of this into account in your logging strategy.
Always log when, who, where, and what: log timestamps (you will need to take care of syncing timestamps across systems and devices or be prepared to account for differences in time zones, accuracy, and resolution), User ID, source IP and other address information, and event details.
To make correlation and analysis easier, follow a common logging approach throughout the application and across systems. Use an extensible logging framework like SLF4J with Logback or Apache Log4j/Log4j2.
There is data that you must log (complete sequential history of specific events to meet compliance or legal requirements), data that you must not log (PII, credit card data, opt-out/do-not-track data, or intercepted communications), and other data you should not log (authentication information, other personal data, and debug tracing in production).
Review code for correct logging practices and test the logging code to make sure that it works. OWASP’s Logging Cheat Sheet provides more guidelines on how to do logging right, and what to watch out for.
Another OWASP project, the OWASP AppSensor, explains how to build on application logging to implement application-level intrusion detection. AppSensor identifies common detection points in an application so you know where to add checks that will alert you when your system is being attacked. For example, if a server-side edit catches bad data that should already have been edited at the client, or catches a change to a non-editable field, then you either have some kind of coding bug, or (more likely) somebody has bypassed client-side validation and is attacking your app. Don’t just log this case and return an error. Throw an alert or disconnect the session.
8. Don’t Roll Your Own Security Code
Know your tools and use them. Take advantage of the security capabilities of your application framework, and use security libraries like Apache Shiro or some of the other libraries that we’ve gone over earlier, to fill in blanks. There are lots of built-in security features in frameworks like Spring Security, Ruby on Rails, .NET, AngularJS, and Play, along with iOS and Android mobile platforms that will take care of common security problems for you, if you use them right. Take the time to understand and use them properly.
Be aware that there is also a downside to frameworks and libraries: they can expose your app to dangerous vulnerabilities if you don’t keep them up-to-date. This has become a common enough and serious enough problem that using software frameworks and other components with known vulnerabilities is now in the OWASP Top 10 Risk list. Tools like OWASP’s free Dependency Check will help you to find all of the dependencies in your app, and check for known vulnerabilities that need to be patched.
9. Handle Errors and Exceptions Correctly
Error Handling isn’t sexy, but it has to be done right. Mistakes in error handling and exception handling lead to different kinds of common and serious security vulnerabilities:
- Leaking information that attackers can use to penetrate your system. Stack traces and detailed error messages can disclose too much technical information about your run-time environment or architecture. For example, “invalid user” or “invalid password” instead of “invalid logon” helps bad guys as much as it helps users.
- Missing or inconsistent error handling can lead to errors going unnoticed, unpredictable behavior, or crashes. A University of Toronto study found that small mistakes in error handling can lead to catastrophic system failures in large systems.
10. Build Security Testing Into Development
With the velocity of development increasing in Agile and DevOps, it’s not possible for security auditors or penetration testers to keep up. Security checks need to be included in code reviews, and security testing needs to be automated and included in Continuous Integration and Continuous Delivery pipelines.
Make sure that you have good automated unit and integration test coverage for security features and controls (like authentication, access control, and auditing) and critical business features: code that handles money, private data, trade secrets, and admin functions. This has to include both positive and negative tests.
Other system-level security tests and checks can be automated in CI/CD using tools like Gauntlt, BDD-Security, and Zapper (a Jenkins wrapper over the OWASP Zed Attack Proxy). These tools make it easy to run security tests and provide clear pass/fail feedback.
Static analysis checking using tools like Findbugs and PMD, also needs to be part of a developer’s toolbox, integrated into your IDE and into the CI/CD pipeline to catch common security mistakes and other coding problems.
It’s your code. It’s your job to make sure that it is safe and secure.
For more insights on security tools, application vulnerabilities, and how to build secure applications, get your free copy of the DZone Guide to Application Security!
Opinions expressed by DZone contributors are their own.