How do you protect all of your APIs from SQL injection attacks? API Gateways like Apigee are meant to make it easy; just copy the recommended pattern into a Regular Expression Protection policy and run it against every request. Easy, right? Not quite…
Configuring the suggested policy
The samples repository provides an example implementation with a slightly more robust regular expression (it is case insensitive). Notice that the policy is applied to just a single query parameter? This policy would need to be applied to every variable of every proxy individually, which requires each proxy to implement the logic multiple times and it is difficult to ensure that all user input is covered.
Making a single variable to scan
A workaround to apply the expression to an entire request is to construct a string containing the headers, query parameters, and request body. The combined string can then be passed as the variable to scan for the Regular Expression Protection policy. This process can be completed in a shared flow attached to a flow hook so that it is managed centrally for all proxies. There are performance impacts with applying this to every request, in particular for larger payloads, but the extra security will be worth it, right?
Now users are reporting that some of their requests are failing with a 400 status code. It’s an API to change an item, and one of the fields in the request is a description of why the change is being made. A user has reported that it works when the description is “change name”, but not when the description is “update name”. Ah, seems like the word “update” triggers the SQL injection protection!
There are two pathways to choose from here: we either remove the “update” part of the regular expression, or we make the expression more specific so that it doesn’t match non-SQL usage of “update”. Both approaches will result in fewer requests being blocked, but it’s difficult to quantify if security is reduced or improved by either. If we follow the same process for every false positive and take the process to the extreme then we will end up with a policy that matches nothing; the only difference is whether we get there via exclusion or inclusion.
You may find a balance where your organisation is happy with both the security gained and the risk of false positives, but the balance will be transient because attacks and your organisation will evolve. For example, it may be appropriate to block any usage of the SQL keyword “TABLE” if your APIs are for an online store that sells shirts, but that would change if the organisation expands to sell furniture as well, or if a new shirt is added to the collection that has a picture of (and presumably also a description including) a table.
Is there a better way to approach this problem?
Defence-in-Depth, also known as layered security or the “swiss cheese model”, is a method of implementing multiple layers of protection so that failure of any one layer doesn’t result in a catastrophic failure of the whole system. Importantly, it doesn’t imply defending everything perfectly at each layer.
Instead of trying to design the perfect SQL injection protection policy in Apigee, we could use a layered approach to security with:
- an appliance with an existing set of well-tested SQL injection protection rules,
- the strengths of our API Gateway to catch anything that still gets through, and
- SQL best practices implemented at the backend.
There are many Web Application Firewalls that provide SQL injection protection rules. Google Cloud Armor is a good choice if you’re already using Google’s Apigee platform, and the two complement each other nicely.
What strengths does Apigee itself provide to help prevent these attacks? Well, injection attacks are caused by unexpected parameters, but OpenAPI Specifications (OAS) provide well-defined contracts with clients for what those parameters should be. Apigee understands OpenAPI Specifications and can validate a request against an OAS using an OASValidation policy. A request trying to inject “DROP TABLES” into an integer field, for example, would be quickly rejected by this validation step. While it is less effective when validating strings, it is still adding to your defensive layers overall. It’s likely to catch other, non-security issues as well.
A better use for the regular expression policy
Imagine your database provider suddenly announces there’s a log4shell-style zero-day that bypasses all of your injection mitigations. You’re going to have a difficult time getting all of your backend servers updated before the automated attacks start flooding in.
This is the perfect example of when a tailored regular expression rule in Apigee can save the day. The rule can be specific because Cloud Armor should have taken care of any obfuscation, so anything that gets through is either safe or should be formatted to trigger the zero-day. Being specific means it’s easy to build and test and there’s a low risk of false positives, which should make the change quick to deploy. Now the attackers need to bypass both Cloud Armor and Apigee before you patch your backends, which is a high bar; most attackers will just move on to an easier target instead.
Getting the most security out of Apigee depends on using the strengths of the platform and the strengths of the rest of your network together. The biggest strength of an API Gateway is that it understands the well-structured contracts between your APIs and your clients: the OpenAPI Specification. This is context that a traditional WAF doesn’t have, and unlike pattern matching on a per-proxy basis or solely relying on hardening backends it can scale to cover all of your APIs.