PostMessage Vulnerabilities. Part II
This is the second part of a two series of articles about postMessage vulnerabilities. The first part was an introduction to what is a postMessage, basic exploitation, detection and mitigation. This part is an analysis of real cases reported in Bug Bounty scenarios. Two disclossed Hackerone reports will be analyzed and a few tips to exploit/bypass postMessage Vulnerabilities will be shown.
- DOM Based XSS in www.hackerone.com via PostMessage and Bypass
- CVE-2020-8127: XSS by calling arbitrary method via postMessage in reveal.js
- Tips/Bypasses PostMessage vulnerabilities
- Recommended writeups
DOM Based XSS in www.hackerone.com via PostMessage and Bypass (#398054 and #499030)
In #398054 report, a Dom XSS is exploited in Hackerone through an insecure message
event listener in Marketo. The flow of the code could be seen in the following image:
According to the report, If there is no error set in the response, it creates a variable named u and sets it to the return value of the findCorrectFollowUpUrl method. This performs some processing on a property named followUpUrl in the response object, which seemed to be a URL to redirect to after the form submission was complete.
This was not used by the HackerOne form, but by setting it to an absolute URL, it was possible to control the value of the u variable, which was later used to change the location.href of the window. When the following mktoResponse message was sent to the Hackerone window, the window was redirected to the JavaScript URI, and the code alert(document.domain)
was executed.
To exploit this vulnerability, the following snippet could be used:
In the snippet there are three parts:
- 1. PostMessage with the first JSON element as
mktoResponse
, to invoke the function in:
else if (d.mktoResponse){
onResponse(d.mktoResponse)
}
- 2. Reaching this function, a JSON structure is needed with the following elements:
for
,error
anddata
. Iferror
is false, the functionsuccess
will be called.
var requestId = mktoResponse["for"];
var request = inflight[requestId];
if(request){
if(mktoResponse.error){
request.error(mktoResponse.data);
}else{
request.success(mktoResponse.data);
- 3. In this function, the
followUpUrl
value will be associated tou
, and passed to location.href. Therefore, a payload withjavascript:alert(document.domain)
will trigger JavaScript execution.
var u = findCorrectFollowUpUrl(data);
location.href = u;
After that, Hackerone team changed the OnMessage
function to add an origin validation:
if (a.originalEvent && a.originalEvent.data && 0 === i.indexOf(a.originalEvent.origin)) {
var b;
try {
b = j.parseJSON(a.originalEvent.data)
} catch (c) {
return
}
b.mktoReady ? f() : b.mktoResponse && e(b.mktoResponse)
}
The Bypass
@honoki reported a smart bypass in #499030.
The variable i
resolves to https://app-sj17.marketo.com/
and indexOf
checks if the origin is contained in the string. Therefore registering a marcarian domain .ma
, the validation will be bypassed:
("https://app-sj17.marketo.com").indexOf("https://app-sj17.ma")
0
If the previous exploit is hosted in the registered domain https://app-sj17.ma
, the XSS will be executed.
CVE-2020-8127: XSS by calling arbitrary method via postMessage in reveal.js (#691977)
In #691977, @s_p_q_r reported a DOM XSS exploited via PostMessage. The flow of the code could be seen in the following image:
First, the setupPostMessage is invoked with the method addKeyBinding to define a JSON element with the malicious payload. After that, the function showHelp() is called to render in the browser the malicios payload defined in registeredKeyBindings[binding].description
To exploit this vulnerability, the following snippet could be used:
In the snippet there are three parts:
- 1. PostMessage with the first JSON element as
"method" : "addKeyBinding"
, to call the method and apply theargs
:
if( data.method && typeof Reveal[data.method] === 'function' ) {
Reveal[data.method].apply( Reveal, data.args );
- 2. Reaching the function
addKeyBinding
with the argumentsargs
, allows the construction of a JSON object with the valuecallback
,key
anddescription
.
function addKeyBinding( binding, callback ) {
if( typeof binding === 'object' && binding.keyCode ) {
registeredKeyBindings[binding.keyCode] = {
callback: callback,
key: binding.key,
description: binding.description
};
}
- 3. The function
toggleHelp()
is invoked because renders the content of the previous JSON without validation, triggering the JavaScript execution.
function showHelp() {
...
for( var binding in registeredKeyBindings ) {
if( registeredKeyBindings[binding].key && registeredKeyBindings[binding].description ) {
html += '<tr><td>' + registeredKeyBindings[binding].key + '</td><td>' + registeredKeyBindings[binding].description + '</td></tr>';
}
}
...
}
Tips/Bypasses in PostMessage vulnerabilities
-
If
indexOf()
is used to check the origin of the PostMessage event, remember that it can be bypassed if the origin is contained in the string as seen in The Bypass -
@filedescriptor: Using
search()
to validate the origin could be insecure. According to the docs ofString.prototype.search()
, the method takes a regular repression object instead of a string. If anything other than regexp is passed, it will get implicitly converted into a regexp.
"https://www.safedomain.com".search(t.origin)
In regular expression, a dot (.) is treated as a wildcard. In other words, any character of the origin can be replaced with a dot. An attacker can take advantage of it and use a special domain instead of the official one to bypass the validation, such as www.s.afedomain.com.
- @bored-engineer: If
escapeHtml
function is used, the function does not create anew
escaped object, instead it over-writes properties of the existing object. This means that if we are able to create an object with a controlled property that does not respond tohasOwnProperty
it will not be escaped.
// Expected to fail:
result = u({
message: "'\"<b>\\"
});
result.message // "'"<b>\"
// Bypassed:
result = u(new Error("'\"<b>\\"));
result.message; // "'"<b>\"
File
object is perfect for this exploit as it has a read-only name
property which is used by our template and will bypass escapeHtml
function.