In this article we take a closer look at a flaring script for report-uri., one that we will use to confuse and distract the blue team by sending random false positives. There is no question that CSP and report-uri are a strong pair that helps the blue team to detect your intrusion. But what if this could help us in some way? While you may trigger a WAF to report some false positives, with report-uri it's even easier.
You can send your own “alerts” and determine when and what to be reported.
Recently in a red team exercise I was involved with we had an externally available employee portal, it was kind of self-written and we assumed it was built around a template engine. We also had a few other external sites, perfect material for XSS and many of these deployed a solid CSP. Even when they are not that sophisticated they make your life on the red team harder, so lets turn them to our advantage.
So my Idea was simple: Let’s feed something to the blue team to keep them busy.
A Flare is used to distract heat seeking missiles – that’s exactly what we gonna do. So I came up with the name flaReportUri for the script I’ve developed.
If you’re into pentesting, I assume you know about the Content Security Policy. It looks somewhat like this:
script-src 'none'; report-uri https://secreslab.report-uri.com/r/d/csp/enforce; [...]
If the Browser determines a violation against those rules he will block them (if stated in “Content-Security-Policy”) and Report the violation to report-uri. If the Policy is within “Content-Security-Policy-Report-Only”, he won’t block the violation, but still report it. This is useful for testing the policy.
As you can see, I’ve used Scott Helmes report-uri.com Service for this example. You can see what your Browser is doing, if you visit https://badredirect-1.lab.sec-research.net/csp-violation.html (and have a fuzzing proxy enabled). The Browser will post something like this to the report-uri:
{
"csp-report": {
"blocked-uri": "self",
"document-uri": "https://badredirect-1.lab.sec-research.net/csp-violation.html",
"line-number": 16,
"original-policy": "script-src 'none'; report-uri https://secreslab.report-uri.com/r/d/csp/enforce",
"script-sample": "alert('Hello!')",
"violated-directive": "script-src 'none'"
}
}
In the report-uri.com Report it will look like this:
So we will need different violations for the blue team to draw their attention away.
First of all, I randomised the agents from the following list: https://techblog.willshouse.com/2012/01/03/most-common-user-agents/
Second, I randomised the violation script sample, I took a few lines from here https://gist.githubusercontent.com/kurobeats/9a613c9ab68914312cbb415134795b45/raw/954b4d3a29cd0fbeb5b841b54126326a6c14a5c1/xss_vectors.txt (kudos to you sir!)
I also counted the lines in the document, to return a feasible number for line-number.
I stored the variables in text-files so it can be easily adapted.
Now, lets see what the script does to the Report:
The little variations are owed to the very small CSP itself. In real life, the variations are much bigger. Also the script is not perfect – but it did its job: It distracted the blue team for like 6 hours (in the debriefing the blue team said, it kept them quite busy until the realised they cannot trust the reports). In the first moment they searched for XSS vulnerabilities in the external Web App.
While they were busy we found a different way in, which I’m not allowed to disclose. I know now, that they have recognized the network monitoring alert, but were to distracted from the Report-URI reports (which was exactly what I hoped for).
You can download the FlareScript here: https://badredirect-1.lab.sec-research.net/flaReportURI.tar.gz
Or just copy paste and create user-agent/violation/etc files for your own:
import requests
import json
import re
import random
import time
import argparse
param={}
param['agents'] = './flare/useragents.txt'
param['violations'] = './flare/violations.txt'
param['uris'] = './flare/uris.txt'
def parseCsp(cspstring):
cspstring=cspstring.strip()
csp={}
cspEntries=cspstring.split(";")
cspEntries=list(filter(None, cspEntries))
for entry in cspEntries:
entry=entry.strip()
entrysplitted = entry.split(" ")
cspkey=entrysplitted.pop(0) #debug purposes
csp[cspkey.lower()] = entrysplitted
return csp