An application security review can be performed at any time in the application lifecycle as long as the design is feature complete and a system or a significant feature is nearing completion. You want all new applications or major changes to the existing software to undergo a review. The purpose of the review is to ensure that the proper security controls are present, that they work as intended, and that they have been invoked in all the right places.
Any appsec review should be a timeboxed exercise. The goal of timeboxing is to define and limit the amount of time dedicated to an activity. I never do reviews longer than one hour regardless of the size of the application. Research shows that engagement starts to drop off quite rapidly after about the first 30 minutes. It is vital to have the right people in the room, a lead developer or anyone who understands the architecture and can efficiently navigate through the code is essential. If one hour is not enough the review can be repeated but there is one rule: The next review must be re-scoped and focus on critical parts of the application only. Use the first review to identify the most critical parts.
The agile appsec review aims to effectively use the 80/20 rule and focus on core activities that result in an exponential risk reduction output. Here are my top 10 things check (no it doesn’t contain all pieces of advice for reviewers, but it is an excellent start especially for web apps):
1. Review the high-level architecture and understand the data flows
At the minimum, a high-level architectural diagram is required that clearly defines boundaries between the system or part of the system showing the entities it interacts with it. The components of the application must be identified and have a reason for being in the application. Data flows should be indicated. This diagram must be documented and updated on every significant change. For high-risk applications, a threat model to determine key risks is required.
2. Check whether input validation is being applied whenever input is processed
Look for all input parameters, things like POST parameters, query strings, cookies, HTTP headers, and ensure these go through validation routines. Whitelisting input is the preferred approach. Only accept data that meets specific criteria. Canonicalization of data before performing input validation must be performed. Lack of canonicalization can allow encoded attack strings to bypass the input validation functions you have implemented. When accepting file uploads from the user make sure to validate the size of the file, the file type, and the file contents as well as ensuring that it is not possible to override the destination path for the file. SQL queries should be crafted with user content passed into a bind variable. The input validation must also check minimum and maximum lengths for all data types received and processed.
3. Check that appropriate encoding has been applied to all data being output by the application
4. Verify that authentication credentials, session tokens and personal and sensitive data transmitted over secure connections
Find all routines that transmit data and ensure that secure transport encryption is used. The best practice to protect network traffic against eavesdropping attacks is to deploy TLS everywhere regardless of the sensitivity of data transmitted. Review the effective TLS configuration, especially if the app does not specify it. TLS settings may be inherited from the operating system and require hardening. Legacy protocols like SSLv3 or TLSv1 have known weaknesses and are not considered to be secure. Additionally, some ciphers are cryptographically weak and should be disabled. Finally, the encryption keys, certificates and trust stores must be secured appropriately at rest, as explained in the next section.
5. Enumerate all privileged credentials and secrets used by the application, check how these are protected at rest
Compile a list of credentials used by the application; this includes passwords, certificates, API keys, tokens, encryption keys. Check if secrets are not stored in the source code repositories or left sitting unprotected on disk. While it can be convenient to test application code with hardcoded credentials during development, this significantly increases risk and must be avoided. Secrets should be protected at rest, ideally stored in a central vault where strong access controls and complete traceability and auditability is enforced. User passwords must be stored using secure hashing techniques with robust algorithms like Argon2, PBKDF2, scrypt, or SHA-512. Simply hashing the password a single time does not sufficiently protect the password. Use iterative hashing, combined with a random salt for each user to make the hash strong.
6. Review that authentication is implemented for all pages, API calls, resources, except those specifically intended to be public
All authentication controls should be centralised and self-contained, including libraries that call external authentication services. Check that session tokens are regenerated when the user or service authenticates to the application and when the user privilege level changes. All types of interaction both H2M and M2M, internal and external, service to service, frontend to backend, all must be authenticated. A modern application must not rely on trust boundaries defined by firewalls and network segmentation - never trust, always verify.
7. Review how authorisation has been implemented and ensure that its logic gets executed for each request at the first available opportunity and based on clearly defined roles
Check that all authorisation controls are enforced on a trusted system (server-side). Make use of a Mandatory Access Control (MAC) approach. All-access decisions must be based on the principle of least privilege. If not explicitly allowed, then access should be denied. Additionally, after an account is created, rights must be explicitly added to that account to grant access to resources. Establish and utilise standard, tested, authentication and authorisation services whenever possible. Always apply the principle of complete mediation, forcing all requests through a common security “gatekeeper”. This ensures that access control checks are triggered whether or not the user is authenticated. To prevent Cross-Site Request Forgery attacks, you must embed a random value that is not known to third parties into the HTML form. This CSRF protection token must be unique to each request. This prevents a forged CSRF request from being submitted because the attacker does not know the value of the token.
8. Review the application’s logging approach to ensure relevant information is logged, allowing for a detailed investigation of the timeline when an event happens
The primary objective of error handling and logging is to provide a useful reaction by the user, administrators, and incident response teams. Check that any authentication activities, whether successful or not, are logged. Any activity where the user’s privilege level changes should be logged. Any administrative activities on the application or any of its components should be logged. Any access to sensitive data should be logged. While logging errors and auditing access is important, sensitive data should never be logged in an unencrypted form. Logs should be stored and maintained appropriately to avoid information loss or tampering by an intruder. If logs contain private or sensitive data, the definition of which varies from country to country, the logs become some of the most sensitive information held by the application and thus very attractive to attackers in their own right.
9. Review how error and exceptions are handled by the application to ensure that sensitive debugging information is not exposed
Review the exception handling mechanisms employed by the application. Does the application prevent exposure of sensitive information in error responses, including system details, session identifiers, software details? Error messages should not reveal details about the internal state of the application. For example, file system paths and stack information should not be exposed to the user through error messages. Given the languages and frameworks in use for web application development, never allow an unhandled exception to occur. The development framework or platform may generate default error messages. These should be explicitly suppressed or replaced with customised error messages as framework generated messages may reveal sensitive information back to the user.
10. Check if the application takes advantage of the security HTTP response headers
This is important for web applications and APIs consumed by browsers or HTTP clients. The security-related HTTP headers give the browser more information about how you want it to behave. It can protect clients from common client-side attacks like Clickjacking, TLS Stripping, XSS. Security headers can be used to deliver security policies, set configuration options and disable features of the browser you don’t want to be enabled for your site. Look for the X-Content-Type-Options: nosniff header as explained earlier to ensure that browsers do not try to guess the data type. Use the X-Frame-Options header to prevent content from being loaded by a foreign site in a frame. Content Security Policy (CSP) and X-XSS-Protection headers help defend against many common reflected Cross-Site Scripting (XSS) attacks. HTTP Strict-Transport-Security (HSTS) enforces secure (HTTP over SSL/TLS) connections to the server. This reduces the impact of bugs in web applications leaking session data through cookies (section 4) and defends against Man-in-the-middle attack.
The output of the review is a written record of what transpired, list of issues identified and corresponding risk levels along with a list of actions that named individuals have agreed to perform to mitigate it. This is a very crucial part of effective review that often gets overlooked.