secure by design - leveraging your delivery pipeline for security

Abstract

  • By dividing tests into normal testing, boundary testing, invalid input testing, and extreme input testing, you can include security in your unit test suites.
  • Regular expressions can be sensitive to inefficient backtracking, and, therefore, you should check the length of input before sending it to the regular expression engine.
  • Feature toggles can cause security vulnerabilities, but you can mitigate those vulnerabilities by verifying the toggle mechanisms using automated tests.
  • A good rule of thumb is to create a test for every toggle you add, and you should test all possible combinations of them.
  • You should watch out for the combinatory complexity that large numbers of toggles can lead to. The best way to avoid this is by keeping the number of toggles as small as possible.
  • The toggle mechanism itself can be subject to auditing and record keeping.
  • Incorporating automated security tests into your build pipeline can give you the ability to run a mini penetration test as often as you like.
  • Availability is an important security aspect that needs to be considered in every system.
  • Simulating DoS attacks helps in understanding weaknesses in the overall design.
  • A domain DoS attack is extremely difficult to protect against because it’s only the intent that distinguishes it from regular usage.
  • Many security problems are caused by misconfiguration, and the cause for faulty configuration can be either unintentional changes, intentional changes, or misunderstood configuration.
  • Configuration hot spots are good indicators for finding areas in your configuration where testing is most critical.
  • It’s important to know the default behavior of the tools you use and assert that behavior with tests.

Using a delivery pipeline

A delivery pipeline is an automated manifestation of the process for delivering software to production.

Using a delivery pipeline guarantees that the process is executed consistently, no one can choose to skip a step or cheat when delivering to production or some other environment.

By including security tests in the pipeline, you gain immediate feedback and an understanding of how secure your software is.

Securing your design using unit tests

  • Normal input testing: Verifies that the design accepts input that clearly passes the domain rules, ensuring that the code handles vanilla input in a correct way.
  • Boundary input testing: Verifies that only structurally correct input is accepted. Examples of boundary checks are length, size, and quantity, but they could also include complex invariants and domain rules.
  • Invalid input testing: Verifies that the design doesn’t break when invalid input is handled. Empty data structures, null, and strange characters are often considered invalid input.
  • Extreme input testing: Verifies that the design doesn’t break when extreme input is handled. For example, such input might include a string of 40 million characters.

Verifying feature toggles

Whenever you use feature toggles, you introduce complexity. The more toggles you add, the more complexity you end up with, especially if the toggles depend on each other.

For every toggle your create, also create a test verifying the toggle works as intended.

You should also consider if any modifications to the state of a toggle should be logged for auditing purposes. When and by whom a toggle is changed in production is a fundamental question you should be able to answer.

Automated security tests

Types of security tests:

  • Application focused: These tests verify the application in parts other than the domain. Examples include checking HTTP headers in a web application or testing input validation.
  • Infrastructure focused: These tests verify correct behavior from the infrastructure running the application. Examples include checking for open ports and looking at the privileges of the running process.

Leveraging infrastructure as code

The basic concept of IaC is that it allows you to declaratively define infrastructure. This can be anything from servers and network topologies to firewalls, routing, and more. This has multiple advantages, one of which is making the setup of your infrastructure deterministic, giving you the ability to recreate your entire infrastructure as many times as you want. It also becomes a breeze to use version control to track the history of every change to your infrastructure, no matter how small or big.

Testing for availability

Testing availability is therefore something every application needs to do, but how do you do this in practice? One way is to simulate a denial of service (DoS) attack, which lets you understand what the behavior is before and after data becomes unavailable. To do this, you need to start by estimating the headroom.

Estimating the headroom

By simulating a DoS attack, you can easily get a feel for how well your application scales and how it behaves before it fails to meet its availability requirements. It’s important to note that regardless of how well a system is designed, an attack large enough will eventually break it. This makes it practically impossible to design a system that’s 100% resilient, but estimating the headroom is a good strategy to use when trying to understand where the weak spots are in your design.

You can use some open source tools to load test your application, e.g. bees with machine guns:

# spins up eight EC2 server instances to attack the website
bees up -s 8 -g public -k your_ssh_key
# send 100 000 requests, 500 at a time
bees attack -n 100000 -c 500 -u website_url
# spins down the EC2 server instances
bees down

Exploiting domain rules

When exploiting domain rules, you’re actually creating a domain DoS attack in which rules are executed in a way that’s accepted by the business, but with malicious intent.

Example

To provide great customer service, the hotel manager has decided to fully refund any reservation that’s canceled before 4 p.m. on the day of arrival. This allows for great flexibility, but what if someone makes a reservation without the intent of staying at the hotel? Won’t that prevent someone else from making a reservation, causing the hotel to lose business?

Validating configuration

Security flaws resulting from faulty configuration can generally be said to stem from either:

  • unintentional changes
    • unintuitive API
    • poor documentation
    • missing test
  • intentional changes
    • missing test
    • upgraded dependency
    • new feature
  • misunderstood configuration
    • bad merge
    • typo
    • missing test

Automated tests as your safety net to protect yourself from accidentally introducing security vulnerabilities in other parts of the software.

Configuration hot spots

Think in terms of configuration hot spots, i.e. an area in your configuration where the type of behavior you’re controlling has a direct or indirect impact on how secure your system is.

Examples of behavior controlled:

  • web containers
    • HTTP headers
    • CSRF tokens
    • output encoding
  • network communication
    • Transport Layer Security (HTTPS and so on)
  • data parsing
    • behavior of data parsers (such as XML and JSON parsers)
  • authentication mechanisms
    • authentication on/off
    • integration settings (e.g. CAS and LDAP)

Knowing your defaults and verifying them

In addition to the behaviors you explicitly configure, it’s also important to verify the implicit behaviors you get when using a library or framework. An implicit behavior is one you get without adding any configuration. This is sometimes also referred to as a default behavior. Because there’s no configuration, the tricky part here is even knowing you have an important feature to verify. In order to gain that knowledge, you need to know the defaults of the tool you use.

In many cases, the defaults will help make your application more secure, but in some cases, they might sacrifice some level of security for an increased ease of use. If you aren’t aware of those trade-offs, you might expose security vulnerabilities without knowing it.