OneDrive: an easter egg into MS library - XSS on Microsoft and not only

- 6 mins

Website, web services or web apps continuously evolves to add new features for customers/users. That’s why a good hunter comes back and restart searching. That’s what happened when I noticed a new features on Vimeo (maybe it’s and old one for you but I’m not Vimeo addicted so it was the first time I saw it).

The feature

It’s possible to upload video from Google Drive, Dropbox, OneDrive and Box. I knew how Google Drive and Dropbox work so I decided to focus on OneDrive first and then Box. Vimeo’s dev self-hosted a so called OneDrive.min.js library subjected to a XSS attack. Written a poc, submitted the bug to Vimeo and then quitted.

Something I missed

Have you ever had that feeling? That’s why I reopened my laptop once back and googled these words: onedrive, microsoft, js. As first result I got this page:

https://dev.onedrive.com/sdk/javascript-picker-saver.htm

It was late, as I said, and I was a bit tired so I didn’t noticed that Microsoft hosts different version of OneDrive library so I picked up the v7, and after few changes to vimeo poc the famous alert(document.domain) was executed on dev.onedrive.com.

How does it works?

And now, the interesting stuffs:

e.handleRedirect = function() {
    var t = p.readCurrentUrlParameters()
      , r = f.getWindowState()
      , n = t[i.PARAM_STATE];
    if (!n)
        return null ;
    a.logMessage("current state: " + n);
    n !== i.STATE_OPEN_POPUP || r.options || (r = JSON.parse(t[i.PARAM_SDK_STATE]));
    var s = r.options
      , d = r.optionsMode
      , h = c[d];
    s || o.throwError("missing options from serialized state");
    if (t[i.PARAM_ERROR] === i.ERROR_LOGIN_REQUIRED || t[i.PARAM_ERROR] === i.ERROR_AUTH_REQUIRED) {
        l.deleteLoghinHint(s.clientId);
        l.loginHint = null ;
        e.redirectToAADLogin(s, r, !0);
        return null
    }
    l.getLoginHint(s.clientId);
    var g = u.validateType(s.openInNewWindow, i.TYPE_BOOLEAN);
    g && e._displayOverlay();
    s.advanced && s.advanced.sharePointTenantPersonalUrl || o.throwError("advanced options is missing");
    switch (n) {
    case i.STATE_OPEN_POPUP:
        e.redirectToAADLogin(s, r);
        break;
    case i.STATE_AAD_LOGIN:
        e._handleAADLogin(t, s, h);
        break;
    case i.STATE_MSA_PICKER:
    case i.STATE_AAD_PICKER:
        var _ = {
            windowState: r,
            queryParameters: t
        };
        a.logMessage("sending invoker response");
        if (!g)
            return _;
        e._sendResponse(_);
        break;
    default:
        o.throwError("invalid value for redirect state: " + n)
    }
    return null
}

Once initialized, the code get params from GET query calling the readCurrentUrlParameters method and parse the content of window.name calling getWindowState:

e.getWindowState = function() {
  return n.deserializeJSON(window.name || "{}")
}

We can control both GET params and window.name so we can pass to next step and see what the code does:

n = t[i.PARAM_STATE];
...
switch (n) {
  ...
}

The n param control the switch jump and there’s an interesting case (STATE_AAD_LOGIN).

case i.STATE_AAD_LOGIN:
  e._handleAADLogin(t, s, h);

e._handleAADLogin = function(t, r, n) {
  r.openInNewWindow || e._displayOverlay();
  r.advanced.accessToken = t[i.PARAM_ACCESS_TOKEN];
  if (r.sharePointTenantPersonalUrl)
      e._redirectToTenant(r, n, r.openInNewWindow);
      ...
}

e._redirectToTenant = function(t, r, n) {
  var a = function(i) {
      ...
      e.redirect(i, {
          picker: {
              accessLevel: n,
              selectionMode: a,
              viewType: o,
              filter: t.advanced.filter
          },
          options: t,
          ODBParams: {
              p: "2"
          }
      })
  };

  if (t.sharePointTenantPersonalUrl) {
      var l = t.advanced.sharePointTenantPersonalUrl;
      a(l)
  } else {
    ...
  }
}

e.redirect = function(e, t, r) {
    void 0 === t && (t = null );
    void 0 === r && (r = null );
    t && f.setWindowState(t, r);
    window.location.replace(e)
}

handleAADLogin call _redirectToTenant that call a(l) which falls into redirect method. Wow! There’s no check before _window.location.replace(e). We can trig a XSS with a simple payload.

All in one(drive)

And here you’re a simple poc:

Easter egg

While writing this post I noticed that Microsoft hosts other two OneDrive libraries (v5 and v6). After some checks I reported another XSS in v6 and discovered that v5 (the oldest one) is not affected.

Luciano Corsalini

Luciano Corsalini

another it sec guy with coding passion

rss facebook twitter github youtube mail spotify instagram linkedin google pinterest medium vimeo