XSS on forums.oculusvr.com leads to Oculus and Facebook account takeovers
This bug could allow an attacker to steal a first party Oculus access token which would allow him to access the victim account. This is achieved by exploiting a cross-site scripting bug in forums.oculusvr.com.
Technical Details
This was possible because forums.oculusvr.com domain uses oculus.com authentication mechanism to login users to the forum using https://graph.oculus.com/authenticate_web_application/ endpoint which would redirect him to https://forums.oculusvr.com/entry/oculus with an oculus access_token that could access graph.oculus.com/graphql and make GraphQL mutations/queries that allow him to takeover the account.
The forums.oculus.com domain is out of scope in the Facebook program according to the program policy page since it hosts a third party web application (Vanilla Forums). However, this bug was found in the authentication flow added by Facebook to the forum to allow Oculus users to access it without the need to create a new account in the forum.
The code served in https://forums.oculusvr.com/entry/oculus had debug mode enabled and if we check the script embedded https://forums.oculusvr.com/plugins/oculus/js/oculus-oauth.js, we'd notice that if the debug mode is enabled, it would unsafely use document.write to add the content of the state parameter inside the fragment part of the URL (#state=PAYLOAD) to the document.
var oculusConnect = function(params) {
if (typeof params === "undefined") {
return;
}
if (typeof params.connect === "undefined") {
return;
}
var response = decodeURIComponent(document.location.hash);
var hash = response.substring(response.indexOf("#") + 1, response.indexOf("&"));
var queryString = response.replace("#" + hash, "");
var queryStringSplit = queryString.split("&");
var state = getParam(queryStringSplit, "state");
var savedState = params.connect.savedState;
var hashSplit = hash.split("=");
var hashKey = hashSplit[0];
var hashValue = hashSplit[1];
var loginType = this.frameElement.id;
if (params.connect.debug) {
document.write("login type : " + loginType +
";
document location:" + document.location +
";
Saved State:" + savedState +
";
State:" + state +
";
Hash Key:" + hashKey);
}
...
document.addEventListener("DOMContentLoaded", function() {
var params = {
"connect":
{
"debug" : "1" ,
"savedState": "G1H7LE7UOJ" ,