Chapter 20. Vulnerability Discovery

After securely architected code has been designed, written and reviewed—then a pipline should be put in place to ensure that no vulnerabilities slip through the cracks.

Typically, applications with the best architecture experience the least amount of vulnerabilities and the lowest risk vulnerabilities.

After that, applications with sufficient secure code review processes in place experience a lower vulnerabilities than those without such a process—but more than those with secure by default architecture.

However, even securely architected and sufficiently reviewed applications still fall prey to the occasional vulnerability.

These vulnerabilities can slip through reviews, or come as a result of an unexpected behavior when the application is run in a different environment, or it’s intended environment is upgraded.

As a result of this, you need vulnerability discovery processes in place that target production code rather than preproduction code.

Security Automation

The initial step in discovering vulnerabilities past the architecture and review phases is that of the automation phase.

Automating vulnerability discovery is essential, but not because it will catch all vulnerabilities. Instead, automation is (usually) cheap, effective, and long-lasting.

Automated discovery techniques are fantastic at finding routine security flaws in code that may have slipped past architects and code reviewers.

Automated discovery techniques are not good at finding logical vulnerabilities specific to how your application functions, or finding vulnerabilities that require “chaining” to be effective (multiple weak vulnerabilities producing a strong vulnerability when used together).

Security automation comes in a few forms, but the most common of whom are the following:

  1. Static Analysis

  2. Dynamic Analysis

  3. Vulnerability Regression Testing

Each of these forms of automation has a separate purpose and position in the application development lifecycle—but each is essential as they pick up types of vulnerabilities the others would not.

Static Analysis

The first type of automation you should write, and possibly the most common is static analysis.

Static analyzers are scripts that look at source code, and evaluate the code for syntax errors and common mistakes.

Static analysis can take place locally during development (aka a linter) as well as on demand against a source code repository or on each commit/push to the master branch.

Many robust and powerful static analysis tools exist, for example:

  1. Checkmarx (Most Major Languages—Paid)

  2. PMD (Java—Free)

  3. Bandit (Python—Free)

  4. Brakeman (Ruby—Free)

Each of these tools can be configured to analyze the syntax of a document containing text and representing a file of code.

None of these tools actually execute code, as that would move them into the next category called “dynamic analysis” or sometimes “runtime analysis”.

Static analysis tools should be configured to look for common OWASP 10 vulnerabilities.

Many of these tools exist for major languages, in both free and paid form. Static analysis tools can also be written from scratch—but in-house built tools often do not perform well on code-bases at scale.

For example:

General XSS—Look for DOM manipulation with innerHTML. Reflected XSS—Look for variables pulled from a URL param. DOM XSS—Look for specific DOM sinks like setInterval().

SQL Injection—Look for user provided strings being used in queries.

CSRF—Look for state changing GET requests.

DoS—Look for improperly written regular expressions.

Further configuration of static analysis tooling can also help you enforce best secure coding practices.

For example, your static analysis tools should reject API endpoints that do not have the proper authorization functions imported—or functions consuming user input that do not draw from a single source of truth validations library.

Static analysis is powerful as a form of general purpose vulnerability discovery, but it may also be a source of frustration as they will report many false positives.

Additionally, static analysis suffers when dealing with dynamic languages (like JavaScript).

Statically types languages like Java or C# are much easier to perform static analysis on, as the tooling understands the expected data type and that data cannot change type as it traverses through functions and classes.

Dynamically typed languages on the other hand are much more difficult to perform accurate static analysis on. JavaScript is a fine example of this—because JavaScript variables (including functions, classes, etc.) are mutable objects—they can change at any point in time. Furthermore, with no typecasting it is difficult to understand the state of a JavaScript application at anytime without evaluating it at run-time.

To conclude, static analysis tooling is great for finding common vulnerabilities and misconfigurations—particularly in regards to statically typed programming languages.

Static analysis tooling is not effective at finding advanced vulnerabilities involving deep application knowledge, chaining of vulnerabilities, or vulnerabilities in dynamically typed languages.

Dynamic Analysis

Static analysis looks at code, typically prior to execution. On the other hand, dynamic analysis looks at code post execution.

Because dynamic analysis requires code execution—it is much more costly and significantly slower.

In a large application, dynamic analysis requires a production-like environment (servers, licenses, etc) prior to having any utility.

Dynamic analysis is fantastic at picking up actual vulnerabilities, whereas static analysis picks up many potential vulnerabilities but has limited ways of confirming them.

Dynamic analysis executes code prior to analyzing the outputs and comparing said outputs against a model which describes vulnerabilities and misconfigurations.

This makes it great for testing dynamic languages, as it can see the output of the code rather than the (vague) inputs and flow.

It is also great for finding vulnerabilities that occur as a side effect of proper application operation—for example, sensitive data improperly stored in memory or side-channel attacks.

Dynamic analysis tools also exist for many languages and frameworks. Some examples of these are:

  1. IBM AppScan (Paid)

  2. Veracode (Paid)

  3. Iroh (Free)

Due to the increase complexity of functioning in a production-like environment, the better tools are often paid or require significant upfront configuration.

Simple applications can build their own dynamic analysis tools, but for complete automation at the CI/CD level they will require a significant effort and a bit of upfront cost.

Unlike static analysis tools, dynamic analysis tooling that is properly configured should have less false positives and give deeper introspection in regards to your application.

The trade-off is in maintenance, cost and performance when compared to static analysis tooling.

Vulnerability Regression Testing

testing
Figure 20-1. Jest—you don’t need a special framework for security vulnerability tests. Any testing framework capable of reproducing the vulnerability should do. Pictured here is Jest—a fast, clean and powerful testing library for JavaScript applications. Jest can be easily modified to test for security regressions.

The final form of automation that is essential for a secure web application, is that of vulnerability regression testing nets.

Static analysis and dynamic analysis tools are cool, but compared to regression tests they are difficult to setup, configure and maintain.

A vulnerability regression testing suite is simple—it works similarly to a functional or performance testing suite, but tests previously found vulnerabilities to ensure they do not get released into the codebase once against as a result of a rollback or overwrite.

Imagine the following vulnerability:

“Steve introduced a new API endpoint in our application that allows a user to upgrade or downgrade their membership on demand from a UI component in their dashboard.”

const currentUser = require('../currentUser');
const modifySubscription = require('../../modifySubscription');

const tiers = ['individual', 'business', 'corporation'];

/*
 * Takes an HTTP GET on behalf of the currently authenticated user.
 *
 * Takes a param `newTier` and attempts to update the authenticated
 * user's subscription to that tier.
 */
app.get('/changeSubscriptionTier', function(req, res) {
 if (!currentUser.isAuthenticated) { return res.sendStatus(401); }
 if (!req.params.newTier) { return res.sendStatus(400); }
 if (!tiers.includes(req.params.newTier)) { return res.sendStatus(400); }

 modifySubscription(currentUser, req.params.newTier)
 .then(() => {
   return res.sendStatus(200);
 })
 .catch(() => {
   return res.sendStatus(400);
 });
});

Steve’s old friend Jed, who is constantly critiquing Steve’s code realizes that he can make a request like GET /api/changeSubscriptionTier with any tier as the newTier param and send it via hyperlink to Steve.

When Steve clicks this link, a request is made on his accounts behalf—changing the state of his subscription in his company’s application portal.

Jed has discovered a CSRF vulnerability in the application.

Luckily, although Steve is annoyed by Jed’s constant critiquing, Steve realizes the danger of this exploit and reports it back to his organization for triaging.

Once it is triaged, the solution is determined to be switching the request from a HTTP GET to a HTTP POST instead.

Not wanting to look bad in front of his friend Jed again, Steve writes a vulnerability regression test:

const tester = require('tester');
const requester = require('requester');

/*
 * Checks the HTTP Options of the `changeSubscriptionTier` endpoint.
 *
 * Fails if more than one verb is accepted, or the verb is not equal
 * to 'POST'.
 * Fails on timeout or unsuccessful options request.
 */
const testTierChange = function() {
 requester.options('http://app.com/api/changeSubscriptionTier')
  .on('response', function(res) {
   if (!res.headers) {
    return tester.fail();
   } else {
     const verbs = res.headers['Allow'].split(',');
     if (verbs.length > 1) { return tester.fail(); }
     if (verbs[0] !== 'POST') { return tester.fail(); }
   }
  })
  .on('error', function(err) {
    console.error(err);
    return tester.fail();
  })
};

This regression test looks similar to a functional test, and it is!

The difference between a functional test and a vulnerability test is not the framework but instead the purpose for which the test was written.

In this case, the resolution to the CSRF bug was determined to be that the endpoint should only accept HTTP POST requests.

The regression test written ensures that the endpoint changeSubscriptionTier only takes a single HTTP verb, and that verb is equal to POST.

If a change in the future introduces a non-POST version of that endpoint, or the fix is over-written—then this test will fail indicated the vulnerability has regressed.

Vulnerability regression tests are simple—sometimes they are so simple in fact that they can be written prior to a vulnerability being introduced.

This can be useful in the case of code where minor insignificant looking changes could have big impact.

Ultimately, vulnerability regression testing is a simple and effective way of preventing vulnerabilities that have already been closed from re-entering your codebase.

The tests themselves should be run on commit or push hooks when possible (reject the commit or push if the tests fail). Regular scheduled runs (aka daily) are the second best choice for more complex version control environments.

Responsible Disclosure Programs

In addition to having the appropriate automation in place to catch vulnerabilities, your organization should also have a well-defined and publicized way of disclosing vulnerabilities in your application.

It’s possible your internal testing doesn’t cover all possible use cases of your customers. Because of this, it’s very possible your customers will find vulnerabilities that would otherwise go unreported.

Unfortunately, several large organizations have taken vulnerability reports from their users and turned them into lawsuits and hush orders against the reporter.

Because the law doesn’t well define a difference between white-hat research and black-hat exploitation—it’s very possible that your application’s most tech savvy users will not report accidentally found vulnerabilities unless you explicitly define a path for responsible disclosure.

A good responsible disclosure program will include a list of ways that your users can test your application’s security without incurring any legal risk.

Beyond this, your disclosure program should define a clear method of submission and a template for a good submission.

To reduce the risk of public exposure prior to the vulnerability being patched in your application, you can include a clause in the responsible disclosure program that prevents a researcher from publicizing a recently found vulnerability.

Often a responsible disclosure program will list a period of time (weeks or months) where the reporter cannot discuss the vulnerability externally as it is fixed.

A properly implemented vulnerability disclosure program will further reduce the risk of exploitable vulnerabilities being left open—and improve public reception of your development team’s commitment to security.

Bug Bounty Programs

HackerOne
Figure 20-2. HackerOne—one of the most popular bug bounty platforms on the web. HackerOne helps small companies setup bug bounty programs and get connected with security researchers and ethical hackers.

Although responsible disclosure allows researchers and end-users to report vulnerabilities found in your web application, they do not offer incentives for actually testing your application and finding vulnerabilities.

“Bug Bounty” programs have been employed by software companies for the last decade—offering cash prizes in exchange for properly submitted and documented vulnerability reports from end-users and security researchers.

In the beginning, starting a bug bounty program was a difficult process that required extensive legal documentation, a triage team and specially configured sprint/kanban processes for detecting duplicates and resolving vulnerabilities.

Today, intermediate companies exist to facilitate the development and growth of a bug bounty program. These companies, like HackerOne and BugCrowd provide easily customizable legal templates as well as a web interface for submission and triaging.

Making use of a bug bounty program in addition to issuing a formal responsible disclosure policy will allow freelance penetration testers (aka, “bug bounty hunters) and end-users to not only report found vulnerabilities—but be incentivized to report said vulnerabilities.

Third Party Penetration Testing

In addition to creating a responsible disclosure system, and incentivizing disclosure via bug bounty—third party penetration testing can give you deeper insight into the security of your codebase that you could not otherwise get via your own development team.

Third party penetration testers are similar to bug bounty hunters as they are not directly affiliated with your organization, but provide insight to the security of your web application.

Bug bounty hunters are (mostly, minus the top 1%) freelance penetration testers. They work when they feel like it, and don’t have a particular agenda to stick to.

Penetration testing firms on the other hand, can be assigned particular parts of an application to test—and often through legal agreements can be safely provided with company source code (for more accurate testing results).

Ideally, contracted tests should target high-risk and newly written areas of your application’s codebase prior to it’s release into production.

Post-release tests are also valuable for high-risk areas of the codebase, and for testing to ensure security mechanisms remain constant across platforms.

Summary

There are many ways to find vulnerabilities in your web application’s code base, each with it’s own pros, cons and position in the application’s lifecycle.

Ideally, several of these techniques should be employed to ensure that your organization has the best possible chance of catching and resolving serious security vulnerabilities prior to them being found or exploited by a hacker outside of your organization.

By combining vulnerability discover techniques like the ones above, with proper automation and feedback into your secure software development lifecylce (SSDL) you will be able to confidently release production web applications without significant fear of serious security holes being discovered in production.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.151.141