EHLO again! I had the pleasure of speaking at QCon NYC last week and I must say it was a pretty damn good conference. Unlike most of the conferences I’ve spoken at, this one was a developer conference. For anyone that likes speaking on security-related topics, I can’t recommend speaking at developer conferences strongly enough. It’s great to speak with one another in the security industry about all the problems plaguing the state of security in the world, but nine times out of 10 we are not the ones with boots on the ground responsible for fixing the myriad holes that we find. This responsibility quite often falls on the shoulders of developers, and as such we should see it as our responsibility to work closely with the software development community to equip them with the knowledge required to improve the general security posture of software.

But I digress – the talk that I gave was entitled Addressing Security Regression Through Unit Testing. This post is a write-up of the topic and a run-through of the software that I wrote to support the talk. I hope this content serves to inspire some of you to implement similar methods in your own codebases!!

The slides for the talk can be found on my Slideshare here:

The code for the talk can be found here:

https://github.com/lavalamp-/security-unit-testing

Once the talk recording is up, I’ll post a link as well.

Security Regression

Regression in codebases is a known and largely addressed subject. The problem of regression is, simply put, that there is no guarantee that the integrity of code is maintained as new code is added. In order to address this problem developers commonly rely upon unit tests – they write unit tests that check whether or not small components of code are working as intended. These tests are then run prior to deployment as new functionality is added to ensure that the new functionality has not broken the older, unit tested functionality.

Through my time in penetration testing, I’ve come to the conclusion that regression with respect to security is a similarly large (if not larger) problem. The number of times that I’ve been on an engagement, found a number of issues in software, counseled the software owners through what the problems were and how to properly fix them, verified that the proper fixes were in place, and then came back six months later to find that the problems were back is far more than I would like to admit. Sometimes the vulnerabilities were back in the same place that they had been found previously. Sometimes the same type of vulnerability was present in new functionality. In all cases though, it seemed that there was little lasting improvement to application security posture as a result of the engagement I had conducted. We can have a much longer conversation around the shortcomings of offensive security testing (which I may reserve for another blog post), but regardless of the details it appeared that even when teams were able to properly address vulnerabilities in their codebases/networks/environments there was no guarantee that those fixes would have any bearing on the continued improvement of the affected organization’s security posture.

So what can we do to start addressing this problem? Well that’s the purpose of this talk and blog post. It turns out we can use the same techniques that developers use to address integrity regression to address security regression. Furthermore, by using a technique that so much infrastructure has already been built around, we get additional improvements to the security posture of the affected codebase by leveraging that infrastructure (continuous integration/deployment infrastructure for example). Not only that but we can use introspection to dynamically generate security unit tests that will provide us with guarantees around the security posture of code that hasn’t even been written yet. Put altogether, we can use unit tests to address security regression to a significant extent.

Dynamically Generating Unit Tests

The code for this blog post is written using the Django Web Framework. The techniques discussed here will certainly work with other frameworks (and perhaps even compiled languages), but one core component that we’ll be leveraging here is the presence of an explicit mapping of URL routes to the views that handle requests to those routes. For example:

The reason that this approach requires explicit mapping is that we will use introspection to look into these URL routes and use them to dynamically generate unit tests for all of the views registered in the application.

While we can use introspection to enumerate all of these views, one thing I did not particularly want to do dynamically was figure out how to invoke all of the different HTTP verb functionality (e.g. GET, POST, PUT, DELETE, etc) for all of the registered views with valid HTTP requests. For instance, sending a POST request to the CreatePostView requires title, description, and image parameters. Dynamically figuring out how to create a valid request is certainly possible but would take a significant amount of effort. And so, the approach that I’m using requires a small amount of additional code written for every view in the application in the form of a Requestor class. The base Requestor class is shown below:

The Requestor class contains all of the functionality required for our unit tests to invoke all of the functionality for a view in the application. The class contains methods for sending requests for each of the supported HTTP verbs, as well as methods for retrieving the data that should be supplied alongside a given HTTP verb request in order to invoke the functionality successfully. The class also contains a list of the HTTP verbs that the view supports as well as whether or not the view requires authentication. Creating a Requestor for a particular view is quite simple. For example, the Requestor class for the CreatePostView view is shown below:

So now that we have the ability to enumerate all of the views found within the application, and we have the Requestor classes for invoking the functionality for all of these views, the last thing we need is to establish a mapping from the views to the requestors written for them. To accomplish this I introduce the notion of the Registry, which contains a dictionary mapping views to the Requestor classes written to invoke the relevant view functionality:

In order to establish the mapping from views to their related Requestor classes, I use the following decorator:

This decorator can then be used to decorate the view classes, and the only argument to the decorator is the import path of the Requestor related to the view. For instance, the CreatePostView class is decorated as follows:

With this approach, now every view will automatically be mapped to its related Requestor as soon as it is imported. To demonstrate this, run the following from the sectesting directory:

python manage.py shell -c "from sectesting import urls; from streetart.tests import TestRequestorRegistry; registry = TestRequestorRegistry.instance(); registry.print_mappings()"

The result of running this command is shown below:

View to requestor mapping
View to requestor mapping

With the mapping in place, we now have the ability to:

  1. Enumerate all views in the application
  2. Retrieve a requestor for every view that has the ability to invoke all of the view’s functionality

Great! With this framework we can now dynamically generate unit tests for all of the HTTP verbs for all of the views in the application. This dynamic generation is handled by the StreetArtTestRunner class found in tests/runner.py, the contents of which are shown below:

To demonstrate how we dynamically generate tests, let’s take the RegularViewRequestIsSuccessfulTestCase as an example:

This test case inherits from the BaseViewVerbTestCase class, which takes a view and a verb argument in the constructor. In order to generate instances of this test case for all of the verbs and views in the application, let’s take a look at the __get_dos_class_tests method in the StreetArtTestRunner class:

As shown above, we:

  1. Iterate over all of the views found in the application (line 9)
  2. Get the view and the Requestor associated with the view (line 10)
  3. Iterate over all of the supported verbs listed in the Requestor (line 11)
  4. For each of the supported verbs, we create an anonymous subclass of RegularViewRequestIsSuccessfulTestCase (line 13)
  5. We add an instance of the anonymous subclass instantiated with the verb and view that we are iterating over (line 19)

This may look a bit odd – why are we creating an anonymous subclass? Well the way that the Python unit testing framework works we cannot have two instances of the same test case class in a test suite (it will only run one of them). As such, we create unique classes for each of the test cases that we need to run. It’s a bit of a quirk, but nothing we can’t handle!

And with that, we have the ability to dynamically generate unit tests for all of the functionality in our application. Let’s now take a look at how we can make good use of this capability.

Testing For Adherence To The Requestor Architecture

Since we are relying on our developers to add a little bit of extra functionality for all of the views that they author, a logical first step to test is that all of the code within our codebase does, in fact, follow our architecture. This is tested by the ViewHasRequestorTestCase:

This test is rather simple – it takes the view that the test case was instantiated with and checks the Registry to make sure that a requestor for the view exists. To see the results of this test, check out the v0.1 tag:

git checkout tags/v0.1

Once checked out, modify the settings.py file so that only the Requestor check tests are enabled:

We can now run the following to verify that all of the views have a Requestor mapped to them:

python manage.py test

The result of running this command is shown below:

Requestor unit tests passing
Requestor unit tests passing

That’s great and all that all of our unit tests are passing, but let’s make sure that they’re testing what we intend them to. To do so, we remove the requested_by decorator from the MyPostsListView view as shown below:

After commenting out requested_by as shown in line 1 above, we run the tests again:

Requestor unit tests failing
Requestor unit tests failing

Sure enough, as expected one of our unit tests fails indicating that the MyPostsListView does not have a Requestor mapped to it. Great! Let’s go ahead and uncomment the requested_by decorator and continue.

Testing For Denial Of Service

Now that we know all of our views have the necessary Requestor class mapped to them, let’s test to make sure that all of the functionality within our application is working properly (ie: returns an HTTP status code indicating a successful request). Testing this is handled by the RegularViewRequestIsSuccessfulTestCase and AdminViewRequestIsSuccessfulTestCase test cases:

These test cases simply send a request to the related view using the configured verb and check to see that the HTTP status code of the response indicates that the request was successful. To run these tests, configure the application to only run the denial of service test cases in settings.py:

We now run the unit tests:

python manage.py test

The result of running these tests is shown below:

Denial of service unit tests passing
Denial of service unit tests passing

As we did before, let’s modify our code to introduce a failing test case just to make sure that our unit tests are working as intended. To do this, raise a PermissionDenied exception in the EditPostView view’s get method:

We then run the tests again:

Denial of service unit tests failing
Denial of service unit tests failing

As expected, we see two failed unit tests indicating that the EditPostView view is returning an HTTP status code indicating a request error. Great! As before, let’s modify the EditPostView back to what it was beforehand and continue.

Testing For Unknown HTTP Verbs

We’ve now got some guarantees that our code is following the Requestor framework and that all of the tested functionality is indicating success, but what if our Requestor classes aren’t testing all of the HTTP verbs supported by our views? It is incredibly common for frameworks (especially ones like Django and Rails) to have all sorts of crazy functionality under the hood, and I’ve abused functionality that developers didn’t know about to nefarious ends on more than one occasion. To ensure that we are testing all of the functionality within our application, let’s make sure that the supported verbs reported by our views match the verbs that our Requestor classes are configured to invoke. Testing this is handled by the RegularUnknownMethodsTestCase:

As shown above, we issue an HTTP OPTIONS request to the view, parse the contents of the Allow HTTP response header (which contains a comma-separated list of the verbs supported by the endpoint), and then check those verbs against the verbs listed in the related Requestor. To run these tests, configure settings.py to only enable the unknown methods test cases:

We then run the tests:

python manage.py test

The result of running these tests is shown below:

Unknown HTTP methods test cases failing
Unknown HTTP methods test cases failing

As shown above, we have multiple Requestor classes that are not testing all of the HTTP verbs associated with their views! Funnily enough – this output was exactly what I saw when I was first authoring the test codebase for this talk. I had no idea that views that subclassed DeleteView supported both the GET and DELETE HTTP verbs!

Taking a look at the DeletePostViewRequestor class, we see that the class only tests for the POST verb:

Let’s go ahead and check out the next tag:

git checkout tags/v0.2

And taking a look at the DeletePostViewRequestor class again, we see that it is now updated to support the GET and DELETE verbs:

Let’s run the tests again and check to make sure we are now testing all of the HTTP verbs supported by our application’s views:

Unknown HTTP methods test cases passing
Unknown HTTP methods test cases passing

Boom! We now know that all of our Requestor classes are properly configured to test all of the HTTP verbs associated with all of our views!

Testing For Authentication Enforcement

Darn near every application contains functionality that is only available to users that are authenticated to the application. Wouldn’t it be great to ensure that all of our post-auth functionality is properly enforcing authentication checks? Well this case is tested for by the AuthenticationEnforcementTestCase. To see this test case, first checkout the v0.3 tag:

git checkout tags/v0.3

The contents of the AuthenticationEnforcementTestCase are shown below:

When generating instances of this unit test, we generate them only for endpoints that require authentication as shown in the __get_authentication_enforcement_tests method in the StreetArtTestRunner class:

To run these tests we configure settings.py to enable only the authentication check test cases:

We then run the unit tests:

python manage.py test

The results of running these tests are shown below:

Authentication enforcement tests failing
Authentication enforcement tests failing

We see that an error is being thrown indicating that an attribute is being accessed on a user object when that user is not authenticated. The offending code can be found in the MyPostsListView view on line 16:

Let’s check out the next tag to see what has changed in the MyPostsListView:

git checkout tags/v0.4

The new contents of MyPostsListView are shown below:

Running the tests again, we see that all of our authentication checks are now being properly enforced:

Authentication check tests passing
Authentication check tests passing

Great! We now know that all of our post-auth endpoints are properly enforcing authentication checks.

Testing For HTTP Response Headers

For the uninitiated, there are a number of HTTP response headers that can greatly improve the security posture of the browsers used by your application’s clients. Even better, many of these headers require little-to-no configuration in your application to just work! To test our application for the presence of these headers, lets first check out the next tag:

git checkout tags/v0.5

Once checked out, we can find the test functionality for checking response headers in the HeaderKeyExistsTestCase and the HeaderValueAccurateTestCase test cases found in tests/cases/headers.py

These two test cases test (1) that a header key exists in every one of the HTTP responses and (2) that the value associated with each header key has the expected content. To generate these unit tests, we first have a list of the headers that we want to see in the application configured in settings.py:

When generating the unit tests, we iterate over the contents of the expected HTTP response headers and the views/verbs in our application as shown in the __get_response_header_tests method in the StreetArtTestRunner class:

In order to properly run these tests, configure the middleware in settings.py to exclude the middleware used for populating the headers:

With the relevant middleware commented out, configure settings.py to only test for the presence of HTTP response headers:

We can now run the tests:

python manage.py test

The results of running these tests are shown below:

HTTP response header tests failing
HTTP response header tests failing

As shown above, we have failing and erroring unit tests indicating which views are lacking which HTTP response headers. It turns out that the only security-related response header that we currently have in the application is the X-Frame-Options header! Let’s go ahead and fix this by checking out the next tag:

git checkout tags/v0.6

This tag introduces the SecurityHeadersMiddleware middleware which populates the relevant security headers in all responses that pass through it:

Let’s change the settings.py file to include this middleware class in the middleware used by the application:

With the middleware now enabled, we run the tests again:

HTTP response header tests passing
HTTP response header tests passing

Fantastic – we now know that all of the HTTP verbs supported by all of our views are returning the HTTP response security headers that we want!

Testing For OPTIONS Accuracy

Sure we know that all of the functionality in our application is currently working, and that we are testing all of the HTTP verbs reported by OPTIONS responses returned by our views, but what if the OPTIONS responses weren’t being entirely honest? What if there were HTTP verbs that could be invoked but were not included in the Allow headers returned via OPTIONS requests? Well let’s test for it! First, let’s check out the next tag:

git checkout tags/v0.9

Once checked out, we can find the test cases that test for this functionality in the RegularVerbNotSupportedTestCase and the AdminVerbNotSupportedTestCase test cases:

These tests check to make sure that the relevant view and HTTP verb return a 405 HTTP status indicating that the verb is not supported. To generate these tests, we iterate over all the views in the application and create test cases for every HTTP verb that is supposedly not supported as found in the __get_options_accuracy_tests method in the StreetArtTestRunner class:

We then configure settings.py to only enable the unknown verb tests:

We can then run the tests:

python manage.py test

The result of running these tests is shown below:

HTTP OPTIONS tests failing
HTTP OPTIONS tests failing

What’s this?! It looks like the OPTIONS response from one of our views wasn’t being entirely honest. The offending code can be found in the MyPostsListView view:

It looks like some sneaky devil has put an (albeit completely useless) backdoor into our code! Let’s check out the next tag and verify that the backdoor has been removed:

git checkout tags/v0.10

We take a look at the MyPostsListView view to see that the backdoor has been removed:

Sure enough, the backdoor has been removed, and when we run the tests now we see that there is no hidden functionality within our application:

HTTP OPTIONS tests passing
HTTP OPTIONS tests passing

Testing For CSRF Protection

Cross-site request forgery (CSRF) is a rather tricky vulnerability to describe, so I’ll let the good folks over at OWASP do the heavy lifting here. Long story short, for any non-idempotent HTTP verbs handled by our application we should have something called a CSRF token required in the request. If this token is either not present or not accurate, the request should be considered invalid and dropped immediately. To test that our application is properly guarded against CSRF, let’s check out the next tag:

git checkout tags/v0.11

The test case that will be checking for CSRF enforcement is entitled CsrfEnforcementTestCase:

This test case sends a request to the view and tells the Django unit testing framework to enforce CSRF checks. If the HTTP response indicates that the request was successful, the test case fails (as the request does not have a CSRF token)! To generate these unit tests, we iterate over all of the views in our application and create a test case for every non-idempotent HTTP verb as shown in the __get_csrf_enforcement_tests method in the StreetArtTestRunner class:

We then modify settings.py to enable only the CSRF enforcement tests:

We can then run the unit tests:

python manage.py test

The results of running these unit tests are shown below:

CSRF enforcement tests failing
CSRF enforcement tests failing

Whoa! It looks like there are a handful of endpoints in our application that are not properly enforcing CSRF protections. Taking a look at the CreatePostView view, we can see exactly where the protections are being disabled:

On line 1 above we see that a csrf_exempt method is wrapping the dispatch method in the view. This is effectively disabling CSRF protections for the view. Let’s go ahead and check out the next tag to fix this issue:

git checkout tags/v0.12

Once checked out, we take another look at the CreatePostView and confirm that the decorator is no longer present:

We can then run the unit tests again to confirm that CSRF protections are properly enabled in our application:

CSRF enforcement tests passing
CSRF enforcement tests passing

The Benefits Of Dynamic Generation

At this point, our application now has the following security guarantees:

  • All of our views have requestors mapped to them
  • All of the verbs supported by our views are working properly (or at least they are returning HTTP status codes indicating success)
  • All endpoints that require authentication are properly enforcing authentication checks
  • None of our views support any HTTP verbs other than what they report in the OPTIONS response
  • CSRF tokens are properly being enforced for non-idempotent requests
  • HTTP response security headers are present in all responses from all of the verbs from all of our views

That’s great and all, but perhaps you’re wondering why we should use dynamic generation to test for any of this. We could just write traditional unit tests to check for all of this functionality, right?

That’s right! We definitely could. However, this would require developers to write those unit tests for all of the new views that they author. As we discussed at the beginning of this post, just because we have passing unit tests for the functionality that we’ve written already doesn’t mean anything about code that we have yet to write/bring into our codebase. This is where dynamic generation truly shines – all of our dynamically-generated unit tests will automatically be applied to all of the views that are added to our application (even the ones that haven’t been written yet). As such, the same guarantees that we have about our application right now will hold true for all of the functionality that we implement moving forward. Let’s take a look at what I mean here by checking out the next tag:

git checkout tags/v0.15

In this version, some devious developer has introduced a new view into our application that has quite a few problems with it. This view, entitled NewCreatePostView, is shown below:

With this view added, lets configure settings.py to enable all of the test case types that we’ve written:

Let’s then run unit tests and see what happens:

Bad view lacking requestor test cases failing
Bad view lacking requestor test cases failing

We immediately see an error indicating that a Requestor class does not exist for this troublesome view! Ok, so we go and talk to the developer and get them to add a Requestor and they commit to the next tag:

git checkout tags/v0.16

We check the contents of the NewCreatePostView and confirm that a Requestor is now configured for the view:

Let’s run the tests again and see what happens:

Bad view with requestor class tests failing
Bad view with requestor class tests failing

Boom – we have failing test cases that indicate:

  • This view does not have CSRF protections enabled
  • This view is not properly enforcing authentication checks
  • This view has hidden functionality in the TRACE verb when an admin user requests it

Without writing a single additional unit test, we have the same security guarantees around this new view that we’ve had for all of the other views in our application. This is powerful.

In addition to providing protections around code that hasn’t even been authored yet, we have a significant return on investment for the amount of effort we put into writing unit tests. In exchange for writing the plumbing for the test generation as well as writing the test case itself, we get a large number of tests for all of our views and verbs. Specifically in this demo application, in exchange for writing 12 unit tests we have a total of 693 data points indicating that the security posture of our application has not regressed to a prior state:

All dynamically generated unit tests passing
All dynamically generated unit tests passing

693 data points in exchange for 12 unit tests seems like a pretty great trade off to me.

Where To From Here

So we now have a pretty solid security foundation for our codebase through the use of dynamic unit test generation, but does this cover everything?

Absolutely not! We can really only check for some basic security controls through dynamic generation. What about instances where someone discovers a specific vulnerability in a specific view? Well that’s something that we can write traditional unit tests for, and then we can use those traditional unit tests in conjunction with these dynamic tests to provide not only a solid foundation of security posture but also guarantees that discovered vulnerabilities do not re-emerge in our codebase. This will be the topic of Addressing Security Regression Through Unit Testing – Part 2, coming to my blog in the near future!

I hope you all enjoyed this, and keep your eyes peeled for part two soon!