J.D. Meier, Alex Mackman, Blaine Wastell, Prashant Bansode, Jason Taylor, Rudolph Araujo
Microsoft Corporation
October 2005
This How To shows you how to perform security code reviews. This module presents the steps involved in the activity, and techniques for analyzing your results. Use this How To with Security Question List: Managed Code (.NET Framework 2.0) and Security Question List: ASP.NET 2.0. These companion question lists help you ask the right questions when performing a security code review.
Objectives
Overview
How To Use this Module
Input
Output
Steps
Step 1. Identify Security Code Review Objectives
Step 2. Perform a Preliminary Scan
Step 3. Review Code for Security Issues
Step 4. Review for Security Issues Unique to the Architecture
What To Do Next
Security Question Lists
Code Review Scenarios
Team Roles
EEG Considerations
Additional Resources
This How To module will help you to:
A properly conducted code review can do more for the security of your code than nearly any other activity. A code review allows you to find and fix a large number of security issues before the code is tested or shipped. In addition, the code review process allows the development team to share security best practices and experience, which can prevent future security issues.
Before you conduct your code review, you should understand the patterns of bad code that you want to eliminate. You can then review your code with a clear idea of what you are looking for. If the application you are reviewing has stated security objectives, make sure that you are familiar with them. Some vulnerability types can have elevated priority and others can be out of scope based on your security objectives.
Rather than waiting until the end of a project and reviewing everything at one time, review your code each time there is a meaningful change. This allows you to focus on what has changed rather than trying to find all of the security issues at once.
If you find that you are spending too much time on any one piece of code or functional area, especially if it is not a high priority, flag it for later review and move on.
Use this module to conduct an effective code review for security. When you use this module, keep the following in mind:
At a minimum, the code must be available when you conduct the code review. Additionally, the following can help:
The output of the code review activity is a set of identified vulnerabilities ready to be prioritized for repair.
This How To presents a four-step code review activity, as shown in Figure 1.
Figure 1. Security code review steps
The security code review activity includes the following steps:
The purpose of this step is to set goals and constraints for your code review. While you should not spend too much time setting objectives, do not begin reviewing a large body of code without knowing what you are looking for. While it is possible to conduct a code review without setting goals, this increases the chances of getting overwhelmed by the code and decreases the chances of finding security problems.
When you set objectives, you should know both the types of security issues that are common for the application you are reviewing as well as any specific code changes that should be reviewed. For example, when you review a source file for the first time, you may be interested in a subset of the following categories (depending on the functionality of the code under review):
During a later review, you will most likely be interested in an even smaller subset of these categories based on the changes made since the last review.
For an effective security code review, set goals and constraints for the following:
To determine the objectives for your review, consider the following questions:
The following are examples of security code review objectives:
It is better to conduct multiple short reviews on small pieces of code, for example at the time of check-in. If you have a large backlog of code to review, it is even more important to set a time limit on the review. Code reviewing is detailed, tiring work, and it is easy to start making mistakes after many hours of review. Also, without a time limit, you may become engrossed too deeply in the details of a particular implementation. By setting a time limit, you can force yourself to move on to find high-value issues elsewhere. Another useful trick is to perform a code review with a partner. The resulting discussion and extra set of eyes can keep you focused for much longer than you can manage on your own.
Focused code reviews are effective code reviews. You should look at the code with specific goals, time limits, and knowledge of the issues you want to uncover. Not only will this substantially increase your chances for success, it will also reduce the amount of time you spend reviewing.
In this step, you perform a scan of the code to find an initial set of security issues and to discover hot spots where additional security issues are likely to be discovered in later steps. You may need to perform the following two types of scans:
You can combine these scans or you can perform just one of them, depending on your time limits and review objectives.
The purpose of an automatic scan is to find security issues that could be missed during a manual review. However, an automatic scan can result in a large number of false positives, and will not find every security issue that you might find in a manual review.
You can use a static analysis tool to perform an automatic scan of your code. Use the static analyzer to find a first set of security issues and to improve your understanding of where issues are likely to be discovered manually. Theoretically, anything a static analysis tool finds can be found by manual review as well. However, static analysis tools are unique in that they test the code without knowing or requiring any external states to be set. Because a static analyzer tool does not know what the application or function is intended to do, it will not make assumptions that a developer or code reviewer might make. Static analyzers tend to be good at finding careless code practices, such as missing error handlers, empty catch blocks, integer overflows, and scoping problems.
If you do not have access to a static analysis tool, you can perform text searches on your code base: for example, by using the Findstr command-line tool.
Note Security issues tend to cluster. If your scan finds a large number of security issues in a particular component or function, then you should carefully examine that area to discover security issues that may have been missed on the first pass.
Managed code takes care of many of the security issues that scanners have been good at finding. In native code, you could scan reasonably accurately for buffer overruns, format string problems, use of potentially dangerous Win32 APIs, memory leaks, and so on. While managed code eliminates some types of security issues, there are still numerous problems that can occur, such as scoping problems, integer overflows, lack of cloning, exception handling, data truncation, lack of null checks, and unchecked values used for memory allocation or buffer access.
Do not expect an automatic scan to do more than locate surface errors. While automatic scanning can supplement a manual review; it cannot replace it. Even the best scanners have contextual problems. They are good at finding security issues that are caused by single lines of code, reasonable at finding security issues that span multiple lines of code in a single function, and generally bad at finding security issues whose scope spans multiple functions.
Due to its programmatically rigorous nature, a static analysis scan may find problems that a manual review will miss. However, these analysis tools frequently find false positives. While these can be frustrating, you can gain a better understanding of the code you are reviewing by reviewing the automated scan results. The review forces you to understand why a false positive is false, which can give you a deeper understanding of the code, including control and dataflow. On the other hand, be careful not to develop a false sense of security if an automated scan shows no security issues in your code. This does not mean that your code is free of security vulnerabilities.
You should complete a manual scan of your code to better understand the code and to recognize patterns that will assist you in Step 3. This should be a quick scan that takes no more than 10 percent of your total code review time. In particular, you should review with the following questions in mind:
The result of this scan is a set of areas that deserve further analysis in Step 3.
In this step, you manually review the code to find security vulnerabilities. You should look for common security vulnerabilities that are not unique to your application's architecture. You do this by tracing those paths through the code that are most likely to reveal security issues. You can use a question-driven approach in conjunction with techniques such as control flow and dataflow analysis.
Note These techniques are most effective when used in combination.
Using a question-driven approach can help with the review activity. This How To includes a set of questions that address the most common coding vulnerabilities and are effective to use during code review. Ask these questions while you are using control flow and dataflow analysis. Keep in mind that some vulnerabilities require contextual knowledge of control and data flow, while others are context-free and can be found with simple pattern matching.
Refer to the following: "Security Question List: Managed Code (.NET Framework 2.0)" and "Security Question List: ASP.NET 2.0."
Combine the following techniques when you review the code.
While performing dataflow analysis, review the list of inputs and outputs, and then match this to the code that you need to review. Some common sources and sinks are:
Note Prioritize any areas where the code crosses trust boundaries; for example, where the code changes trust levels.
It can be difficult to determine how much you trust each input source. Your code should not trust input that comes from outside your component, and your code should fully validate all data. For performance and maintainability reasons, however, this may not always be practical. In general, you can trust code that you are most familiar with or that comes from within your enterprise, and give less trust to code that you are less familiar with. The following is an example of how to think about trust boundaries.
As you conduct your traces, carefully examine the code to make sure that input validation is performed rigorously on low-trust input and performed adequately on medium-trust input. You should have a set of common validation routines that your application can call as soon as it receives any untrusted data. This gives your application a central validation area that can be updated as new information is discovered.
When you perform dataflow analysis, pay attention to areas where the data is parsed and may go to multiple output locations. Also pay attention to intermediary output locations. For example, input can go to a database and then later be placed in Web page content. Trace data back to its source, and assign trust based on the weakest link.
The question list provided in the companion modules "Security Question List: Managed Code (.NET Framework 2.0)" and "Security Question List: ASP.NET 2.0" are organized into a set of key areas, or hotspots, that are based on the implementation mistakes that result in the most common application vulnerabilities. These hotspots are summarized in Table 1.
Table 1: Hotspots
SQL injection | A SQL injection attack occurs when untrusted input can modify the semantics of a SQL query in unexpected ways. As you review the code, make sure that the SQL queries are parameterized and that any input used in a SQL query is validated. |
Cross-site scripting | Cross-site scripting occurs when an attacker manages to inject script code into an application so that script code is echoed back and executed in the security context of the application. This can allow an attacker to steal user information, including forms data and cookies. This vulnerability can be present whenever a Web application echoes unfiltered user input back to Web content. |
Data access | Look for improper storage of database connection strings and proper use of authentication to the database. |
Input/data validation | Look for client-side validation that is not backed by server-side validation, poor validation techniques, and reliance on file names or other insecure mechanisms to make security decisions. |
Authentication | Look for weak passwords, clear-text credentials, overly long sessions, and other common authentication problems. |
Authorization | Look for failure to limit database access, inadequate separation of privileges, and other common authorization problems. |
Sensitive data | Look for mismanagement of sensitive data by disclosing secrets in error messages, code, memory, files, or the network. |
Unsafe code | Pay particularly close attention to any code compiled with the /unsafe switch. This code does not have all of the protection that normal managed code has. Look for potential buffer overflows, array out of bound errors, integer underflow and overflow, as well as data truncation errors. |
Unmanaged code | In addition to the checks performed for unsafe code, also scan unmanaged code for the use of potentially dangerous APIs such as strcpy and strcat. For a list of potentially dangerous APIs, see the section "Potentially Dangerous Unmanaged APIs" in "Security Question List: Managed Code (.NET Framework 2.0)." Be sure to review any interop calls as well as the unmanaged code itself to make sure that bad assumptions are not made as execution control passes from managed to unmanaged code. |
Hard-coded secrets | Look for hard-coded secrets in code by looking for variable names such as "key", "password", "pwd", "secret", "hash", and "salt". |
Poor error handling | Look for functions with missing error handlers or empty catch blocks. |
Web.config | Examine your configuration management settings in the Web.config file to make sure that forms authentication tickets are protected adequately, that the correct algorithms are specified in the machineKey element, and so on. |
Code access security | Search for the use of asserts, link demands, and allowPartiallyTrustedCallersAttribute (APTCA). |
Code that uses cryptography | Check for failure to clear secrets as well as improper use of the cryptography APIs themselves. |
Undocumented public interfaces | Most undocumented interfaces should not be in your application', and they are almost never given the same level of design and test scrutiny as the rest of the code. |
Threading problems | Check for race conditions and deadlocks, especially in static methods and constructors. |
In this step, you look at the list of code review objectives, and examine anything that has not yet been reviewed. This step is especially important if your application uses a custom security mechanism or has features to mitigate known security threats. Use this final code review pass to verify the security features that are unique to your application architecture. A question-driven approach produces the best results. Consider the following questions:
A custom security implementation is a great place to look for security issues for these reasons:
Code that mitigates known threats needs to be carefully reviewed for problems that could be used to circumvent the mitigation.
The use of roles assumes that there are some users with lower privileges than others. Make sure that there are no problems in the code that could allow one role to assume the privileges of another.
When you review code that supports multiple user roles, you must understand what each role should be allowed to do. Prepare a roles matrix that specifies privileges in rows and roles in columns. Mark each cell that corresponds to a privilege allowed by a role. (See Table 2 for an example.)
After you have completed the matrix, review the code for exceptions to this matrix. Even a well-designed system with clearly drawn roles can be broken by a bad assumption or a logical mistake in the roles implementation.
Table 2: Role Matrix Example
Subjects | User creation | Permission modification | Object creation | Object removal | Object read |
Admin | √ | √ | |||
Content creator | √ | √ | √ | ||
Reader | √ | ||||
Anonymous | √ |
After you have completed your code review, do the following:
Use the following questions to help you perform code reviews.
There are several strategies for conducting code reviews, including:
In either strategy, you can select a reviewer who is familiar or unfamiliar with the code.
The advantage of using a reviewer who does not have prior knowledge of the code is that he or she will examine the code with fresh eyes and will be less likely to make assumptions than someone who is more familiar might be.
The advantage of using a reviewer with knowledge of the code is that he or she will be able to find subtle errors that require expert familiarity with the application under review.
During a code review, there are a several distinct tasks:
Typically, the following roles are involved:
The Microsoft Engineering Excellence Group (EEG) is tasked with improving the methodology used to engineer software at Microsoft. The group is responsible for delivering a common baseline of prescriptive and authoritative guidance with focus on people, process, and tools. After reviewing lessons learned from across the company for successful code review, EEG promotes the following across Microsoft:
Provide feedback by using either a Wiki or e-mail:
We are particularly interested in feedback regarding the following:
Technical support for the Microsoft products and technologies referenced in this guidance is provided by Microsoft Support Services. For product support information, please visit the Microsoft Support Web site at http://support.microsoft.com.
Community support is provided in the forums and newsgroups:
To get the most benefit, find the newsgroup that corresponds to your technology or problem. For example, if you have a problem with ASP.NET security features, you would use the ASP.NET Security forum.