A smorgasbord of a bug chain: postMessage, JSONP, WAF bypass, DOM-based XSS, CORS, CSRF...
A fun little chain to get a one-click CSRF attack on a redacted domain.
It starts off with an understanding of how the application worked, subdomains could retrieve a CSRF token from redacted.com/profile
and make POST requests to redacted.com/api
.
The first problem was that the /profile
endpoint that provided the CSRF token had a permissive CORS policy, any subdomain could reach it. Meaning an XSS on any subdomain could be valuable. And so the hunt moved to finding an insecure subdomain. This led jub0bs to an out of scope domain’s /search
endpoint. This page had a message handler without any origin check, and could be used to make a function call against any property (including nested properties) of the window.APP
object.
The window.APP
object has an apiCall
method, which was designed to communicate with a JSONP endpoint, so that provides an arbitrary function call. And in-fact the server was permissive in what it allowed in the callback parameter. Double-URL encoding the param, it would get decoded and reflected/used exactly. Allowing for not just a function call, but arbitrary JS to be included as the callback
is a bit of a trusted parameter. There was also a quick WAF bypass, by using &
instead of ?
for the first url parameter, the server still accepted it but the Akami WAF didn’t seem to recognize it.
The final hurdle was that the out of scope /search
page couldn’t be simply framed to send messages to it because of the X-Frame-Origin
header. So, it becomes a one-click attack, single click, to allow the page to open a popup, and then messages could be sent through that handle.
ANd so the whole chain puts all this together:
- Open a popup to the
/search
page - Send a message, triggering the JSONP call and XSS
- Use XSS to hit the
/profile
endpoint on the main domain to steal the CSRF token - Use the primary
/api
endpoint.
If you’re unfamiliar with JSONP, it was more common in the days before CORS and was a way of allowing cross-domain communication getting around same-site policy. The basic idea is that you would include your API call as a script, and include a callback
URL parameter. The response would then be Javascript where the response data was the argument to a call to a function with whatever name you send as the callback parameter.