Mitigating clickJacking—the DevSecOps way!
In the past few weeks, we got a few reports (through our bug bounty program) that some of our inner domains are vulnerable to a clickjacking attack. Of course, our main site was protected from the first day, but there were a few small subdomains that didn’t have this protection. Fixing the issue was easy but we wanted to go one step further and ensure all our endpoints are protected—and stop worrying about this issue. Let’s have a look at how we did that.
What is clickjacking?
Looking at the OWASP definition:
“Clickjacking, also known as a “UI redress attack”, is when an attacker uses multiple transparent or opaque layers to trick a user into clicking on a button or link on another page when they were intending to click on the top-level page. Thus, the attacker is “hijacking” clicks meant for their page and routing them to another page, most likely owned by another application, domain, or both.“
Well, this is a pretty serious attack. The common attack vector is by creating another website and embed the target site inside an iframe—this way the attacker can easily launch a clickjacking attack and trick the unsuspecting users to click on the malicious website.
As shown above, the main attack vector is using iframe. So, all we need to do is tell the browser that our site cannot be loaded inside an iframe and we mitigate this attack entirely! Luckily, there is a header that does exactly that: x-frame-options. So, all we need to do is set this header and we’re done!
But there is just one small problem—how can we know which of our endpoints has this header and which not? At Snyk, we have a lot of endpoints and it is very hard to keep track of all of them. We need a tool that will help us with that!
Introducing OWASP AMASS
OWASP AMASS is a tool by OWASP that offers “In-depth Attack Surface Mapping and Asset Discovery”, which is exactly what we need. One of its features is DNS enumeration—finding all the DNS records for a given domain. Using it is very simple:
amass enum -d <domain> -o out.txt
This will search for all the relevant records and output them to out.txt. Now, all we need to do is iterate on the list and check each DNS record for the x-frame-options header. For this, we are going to use a tool called, not surprisingly, Security Headers Checkers.
Scanning security headers
Security Headers Checker is a python utility that can scan a given URL for various security issues, including missing security headers, like x-frame-options. It has a lot of cool features and, more importantly, the readme well documents all the issues it can find and why you should care. But the most relevant feature for this use case is that it can accept a file containing a list of URLs—like the one produced by AMASS. So all we need to do is:
python ./securityheaders/securityheaders.py out.txt --checkers HeaderMissingChecker --format json --flatten --file ./result
We’re passing in the output from amass, limiting the check only to HeaderMissingChecker, as this is what we care about. The next argument is the format—I choose JSON, but there are a lot of other formats supported.
Now we have a file with all the vulnerable URLs and we just need a quick JQ trick to filter just for URLs without x-frame-options header, as Security Headers reports many other interesting headers. This is a simple as:
jq 'map(select(.header == "x-frame-options"))’ ./result/1.csv
Yes, the output file suffix is csv—this is a bug 🙂
Now we have the list and all that’s left is the actual hard work—going over the list and starting fixing all those vulnerable endpoints. The next step is running this on a daily basis to ensure there are no new endpoints without x-frame-option header. Another challenge is to ensure all the URLs we found are actually websites—there is no need for clickjacking protection on APIs.
This incident demonstrates the importance of having a bug bounty program—a way to let researchers report about security issues in your system. But it is even more important to know how to handle those reports—fixing the issue is not enough. Without adding a test that reproduces the issue, you cannot ensure it will not happen again. .We are all human and we all make mistakes. – Let’s ensure we use code that helps us to avoid making the same mistake again!