Pythian Blog: Technical Track

Cloud Armor pt. 1 – Securing your Application with Google’s WAF Cloud Armor

If you’re running an application on the public internet, security in this day and age is key. With the adage of defense in mind, you want to have multiple layers of checks as requests get further into your application layers and infrastructure.

 

 

A Web Application Firewall (WAF) is one of the easiest ways to protect your application from some of the more straightforward attacks that your application might see and block the offending requests right away rather than relying on expensive compute time further down the infrastructure and application layers.  

That means overall you can save time and money on your infrastructure (less compute, storage of logs), application (less time spent investigating odd, but persistent errors) and security (security can focus more on real issues).

What is Cloud Armor?

Google Cloud Armor is a managed service from Google which works in conjunction with your Google Load Balancer to help protect your applications.  

Primarily Cloud Armor serves 2 main goals:

  • Distributed Denial of Service (DDoS) Attack protection 
  • Mitigate OWASP Top 10 risks

As we are discussing WAF, the focus here will be on the OWASP Top 10 and how Cloud Armor is configured to help mitigate these attacks.

Setting up

If you have an environment with an external Load Balancer at Google already in a test environment you may be able to do testing already.  However if you need somewhere more isolated, you can see a basic setup and test scenario which I will use for the rest of the article:

https://github.com/pythian/blog-files/tree/cloud-armour-sample

Cloud Armor Initial Setup

Ideally all applications would start green field and as an initial test and security measure we could just turn on all of the basic settings and dial things back as appropriate during the testing cycles.  However in the real world there is normally an existing application with existing traffic and nobody really knows exactly what traffic is coming and exactly what format it is in.  Even if you have some good synthetic test coverage, it would still be tough to really “know” what every browser and user is doing out there.

With that in mind, the below scenario will cover implementing Cloud Armor with something that already exists.

gcloud compute security-policies create security-policy-crs \
    --description "Cloud Run Policy"
 
gcloud compute backend-services update backend-service-crs \
    --security-policy security-policy-crs \
    --global
 
gcloud compute security-policies rules create 1000 \
    --security-policy security-policy-crs \
    --expression "evaluatePreconfiguredExpr('xss-stable')" \
    --action "deny-403" \
    --description "XSS attack filtering" \
    --preview

First up we need to create a policy and attach it to our load balancer backend.  Then we will apply the preconfigured rules that Google has in place for Cloud Armor.  For this example we will use the Cross Site Scripting (XSS) ruleset, but it should be noted that there are existing rulesets to cover:

  • SQL injection
  • Cross-site scripting
  • Local file inclusion
  • Remote file inclusion
  • Remote code execution
  • Method enforcement
  • Scanner detection
  • Protocol attack
  • PHP injection attack
  • Session fixation attack

More information about all these are available on Google’s site and I would highly recommend reading about these in detail if you are going to be implementing Cloud Armor in a production environment.  For more information see this page: https://cloud.google.com/armor/docs/rule-tuning

How to Tune Cloud Armor

We have implemented our XSS ruleset so the job is done right!!!  Not so fast the general versions of these rules (i.e. evaluatePreconfiguredExpr(‘xss-stable’)) implement the most stringent version of the ruleset so while very basic commands may work, more complex scenarios will need to be evaluated.

For instance a basic scenario would be simple get request:

curl http://myURL.com

This would almost certainly pass any of the XSS rules and return the proper webpage.

However a more common scenario would be that your application has a restful service that is used to both send and receive data.  Sending data back to the client isn’t an issue, Cloud Armor will not inspect that, but receiving data from the client is.  Also, for the purpose of this discussion we know that XSS is using HTML facilities to try and manipulate the browser and user.

curl -X POST -H "Content-Type: application/json" \
    --data '{"companyname": "pythian<body>", "address": "123 main st"}' \
    "http://myURL.com/api/myendpoint"

Now we have received the web page back as normal.  Remember at the beginning though we put the rule in “Preview” mode (–preview).  So how do we know if the rule would have passed or failed?

You can go look in the Google Cloud Console Logging facilities, but an easy way is to run commands with what you are looking for.  In this case:

gcloud logging read 'resource.type="http_load_balancer" AND jsonPayload.previewSecurityPolicy.outcome="DENY"' \
    --format=json \
    --limit=1

There are really 2 things to look for here, the “previewSecurityPolicy” item and then the “DENY” item.  This will return to us the most recent item that has failed a preview Cloud Armor rule.  It will return quite a bit of data in the json string, but what is most important is a section that looks like this:

"previewSecurityPolicy": {
  "name": "security-policy-crs",
  "outcome": "DENY",
  "priority": 1000,
  "configuredAction": "DENY",
  "preconfiguredExprIds": [
    "owasp-crs-v030001-id941320-xss"
  ]
}

Again from before, we were only looking for the “outcome” field to equal “DENY”, but we can see a bit more information now.  What is the main interest is “preconfiguredExprIds” item.  This tells us what rule failed such that we can go look up what it is and make a decision of what to do about it.  

Next is where decisions are made, some are easy and some are hard, but all are a balance of what your application does (or needs to do) and how secure it needs to be. Obviously no application wants to be insecure, but each will have its own requirements for customers (contractual obligations), compliance rules (PCI, GDPR) and legal requirements. With these in mind there is a big difference between an open application like Twitter, a closed application like the one your Bank lets you use where somebody can steal your data or maybe worse your money and say a military application where someone could set off a nuclear bomb.

With all of that said there are a few options here:

1. Disable it

The easiest route for most is simply to disable the rule.  This may very well be the case for a rule that is very low value for your application and would have little chance to cause potentially undo harm.  So in our previous example, we decided that we want to allow standard “HTML” tags like “<body>” though.  To do this with Cloud Armor we need to exclude that rule (owasp-crs-v030001-id941320-xss) from the base ruleset (xss-stable) for XSS.

gcloud compute security-policies rules update 1000 \
    --security-policy security-policy-crs \
    --expression "evaluatePreconfiguredExpr('xss-stable',  \
        ['owasp-crs-v030001-id941320-xss' \
        ])" \
    --action "deny-403" \
    --description "XSS attack filtering" \
    --preview

Above will disable that one rule and still leave the rule in “preview” mode.  This allows us to continue testing, which is important because there are 2 items that start to come into play.  

  • If we have all of the rules on by default (and you probably should) they are executed in order.  You may have noticed the number 1000 in the updates.  This serves 2 purposes
    1. An identifier for the rule so we can update or delete it later.
    2. The order in which rules are executed.  
  • Once a rule passes (which then causes a deny in our case) it exits with that rule logged, but will not continue to process future rules.

This means we need to retest our previous command to see if it passes.  In our case the previous command will pass and the output from the logs will look like this:

enforcedSecurityPolicy: {
    configuredAction: "ALLOW"
    name: "security-policy-crs"
    outcome: "ACCEPT"
    priority: 2147483647
}

You can see the outcome is now “ACCEPT”, but notice the priority is a rule we didn’t create.  This is the default rule in Cloud Armor.  By default the last rule run will allow access, that is if you passed everything before the end you are allowed through.  This can be edited to deny access as well, which you could do if you were attempting to be extra secure.

In any case, next let’s increase our threat level a little bit.

2. Work Around the Rule

Next we will change the request to the server just a little from the pretty standard HTML tag of <body> to a more aggressive html tag of <script>.

curl -X POST -H "Content-Type: application/json" \
    --data '{"companyname": "pythian<script>", "address": "123 main st"}' \
     "http://myURL.com/api/myendpoint"

Going through the same cycle above of getting the log and looking at the details, what you will notice is.

previewSecurityPolicy: {
    configuredAction: "DENY"
    name: "security-policy-crs"
    outcome: "DENY"
    preconfiguredExprIds: [
        "owasp-crs-v030001-id941110-xss"
    ]
    priority: 1000
}

A different rule has now been triggered “id941110” vs “id941320”.  After some investigation there is a decision that this rule is too important, we can’t disable it, so what to do?  There are still a few options here.

  • Code Around and For the item: Cloud Armor is looking for simple patterns, there are ways to get around these.  For example you could Base64 the value before sending to the backend.  This obviously increases the need for the application team to ensure proper handling of the item, but would be a valid option.
  • Exception Process/Code: We might leave the basic rule in place, but allow either very specific exceptions to the rule by URL or Page, or potentially allow certain trusted individuals to bypass the rule as well.  This would likely be the last resort as if these types of rules proliferate then the interplay between all of the rules during processing (remember the order matters)  could be opening a much bigger security problem.
  • “Do Nothing” option: It may be decided that the issue isn’t something that we would accept the risk of and there is not a certainty that it would even happen in production or that the user can not easily work around it.  As such, the Cloud Armor rule is left as is with the understanding that it will block the request.  An example of this might be to not allow a string like “C:\windows\” through the WAF, but to force the user to “correct” it.  Spaces will often allow for this where something like “C : \ windows” as the command may need to be readable, but not executable and as such the WAF would now allow it.    

Final Deployment

After much time and effort, all of the scenarios have been previewed and found and reviewed and the final ruleset is to be implemented.  This is quite straightforward such that you simply recreate the rule without the preview flag.

gcloud compute security-policies rules delete 1000 \
    --security-policy security-policy-crs \
    --quiet
 
gcloud compute security-policies rules create 1000 \
    --security-policy security-policy-crs \
    --expression "evaluatePreconfiguredExpr('xss-stable',  \
        ['owasp-crs-v030001-id941320-xss' \
        ])" \
    --action "deny-403" \
    --description "XSS attack filtering" 

Obviously this has the largest risk and should be monitored closely for the first few minutes/hours/days to ensure everything is working as expected.

Conclusion

Cloud Armor is one layer in your security protocols and is one of the first layers of the defense in your depth strategy that you should have in place for your applications.  It will help ensure most of the basic items are handled and can be very useful for older applications which are difficult to change and maintain.

However, it’s important not to ignore your application security, as properly implemented variable handling, sanitation and processing can’t be underestimated. You never know when you’ll meet up with little Bobby Tables.

 

XKCD: little Bobby Tables

Source: https://xkcd.com/327/

Links:  

 

No Comments Yet

Let us know what you think

Subscribe by email