14
votes

I have an Exchange tenant with Microsoft (company.onmicrosoft.com), an admin account ([email protected]), and the admin page through which I manage my (settings).

Recently, there is a project I took on to automatically parse some web data from the admin page via C#. I've read a lot of articles all relating to SharePoint and have read up on STS, SRF files, and active/passive federation services.

So, let's get to it:

I started off with https://login.microsoftonline.com/login.srf but with Fiddler realized I could just skip straight through to https://login.microsoftonline.com/ppsecure/post.srf and achieve the same result.

So, I go to login, enter my credentials, and it then forwards me the admin page. Simple enough, right?

So, I decided to replicate the network traffic and have relied on the following posts and examples:

All these websites have great C# examples on screen scraped authentication, ADFS, and a bunch of other useful information. The problem is that all of it is for SharePoint.

So, I mixed and matched code and came up with the following:

static void Main3()
{
    CookieContainer cookies = new CookieContainer();
    //using Bungie example from http://stackoverflow.com/questions/2508656/logging-into-a-site-that-uses-live-com-authentication
    //Uri url = new Uri("https://login.live.com/login.srf?wa=wsignin1.0&rpsnv=11&ct=1268167141&rver=5.5.4177.0&wp=LBI&wreply=http:%2F%2Fwww.bungie.net%2FDefault.aspx&id=42917");

    Uri url = new Uri("https://portal.microsoftonline.com/");
    HttpWebRequest http = (HttpWebRequest)HttpWebRequest.Create(url);

    http.Timeout = 30000;
    http.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0";
    http.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
    http.AllowAutoRedirect = false;
    http.Headers.Add("Accept-Language", "en-us,en;q=0.5");
    http.Headers.Add("Accept-Encoding", "gzip, deflate");  //this option creates the two cookies but loads garbage HTML.  Removing this option allows the HTML to load normally
    http.KeepAlive = true;
    http.CookieContainer = new CookieContainer();
    http.Method = WebRequestMethods.Http.Get;

    HttpWebResponse response = (HttpWebResponse)http.GetResponse();

    //gets the cookies (they are set in the eighth header)
    string[] strCookies = response.Headers.GetValues(8);
    response.Close();


    Cookie manualCookie;
    string sManualCookiesString = "MSPRequ|lt=1389810702&id=271346&co=1;MSPOK|$uuid-02eeaf29-b8a5-441f-a6a6-319ed926d8bc$uuid-7f106156-1398-405f-83e5-61f177c7be25$uuid-3d2f189d-8f79-4216-97cf-23c5c22ff8ad$uuid-b93c9354-5802-4c82-ac7d-7838d2f7bdbc$uuid-071c3106-1c97-4e1e-930c-36f33b6f0b93; MSPShared|1; MSPSoftVis|@:@; MSPBack|1389810501";


    //Manually insert the cookies since the request only returns two cookies and we need six cookies
    foreach (string sCookieAndValue in sManualCookiesString.Split(';'))
    {
        string sCookieName = sCookieAndValue.Split('|')[0].Trim();
        string sCookieValue = sCookieAndValue.Split('|')[1].Trim();
        manualCookie = new Cookie(sCookieName, "\"" + sCookieValue + "\"");

        Uri manualURL = new Uri("http://login.microsoftonline.com");
        http.CookieContainer.Add(manualURL, manualCookie);
    }

    /*      //Removed because the above request only returns MSPRequ and MSPOK cookies but leaves out the others.  It's obviously broken :(
                string name, value;
                for (int i = 0; i < strCookies.Length; i++)
                {
                    name = strCookies[i].Substring(0, strCookies[i].IndexOf("="));
                    value = strCookies[i].Substring(strCookies[i].IndexOf("=") + 1, strCookies[i].IndexOf(";") - strCookies[i].IndexOf("=") - 1);
                    manualCookie = new Cookie(name, "\"" + value + "\"");

                    Uri manualURL = new Uri("http://login.microsoftonline.com");
                    //http.CookieContainer.Add(manualURL, manualCookie);
                }
     */

    //stores the cookies to be used later
    cookies = http.CookieContainer;

    http = (HttpWebRequest)HttpWebRequest.Create(url);
    response = (HttpWebResponse)http.GetResponse();
    StreamReader readStream = new StreamReader(response.GetResponseStream());
    string HTML = readStream.ReadToEnd();
    readStream.Close();

    //Get the PPSX value: these values are a bit strange and could indicate progress since I've seen a few of the following:
    //      P, Pa, Pas, Pass, Passp, Passpo, Passport, PassportRN
    //  As you can see it adds one letter at a time which could indicate where in the login process it is, but I don't know
    string PPSX = HTML.Remove(0, HTML.IndexOf("PPSX"));
    PPSX = PPSX.Remove(0, PPSX.IndexOf("value") + 7);
    PPSX = PPSX.Substring(0, PPSX.IndexOf("\""));
    //PPSX = "PassP"; //debug

    //Get this random PPFT value
    string PPFT = HTML.Remove(0, HTML.IndexOf("PPFT"));
    PPFT = PPFT.Remove(0, PPFT.IndexOf("value") + 7);
    PPFT = PPFT.Substring(0, PPFT.IndexOf("\""));

    //Get the random URL you POST to
    //string POSTURL = HTML.Remove(0, HTML.IndexOf("https://login.microsoftonline.com/ppsecure/post.srf?wa=wsignin1.0&rpsnv=2&ct="));
    //POSTURL = POSTURL.Substring(0, POSTURL.IndexOf("\""));
    //debug:
    //based on Fiddler, this page is the next page that's loaded
    string POSTURL = "https://login.microsoftonline.com/GetUserRealm.srf?login=admin%company.onmicrosoft.com&handler=1&extended=1 ";

    //POST with cookies
    HttpWebRequest http2 = (HttpWebRequest)HttpWebRequest.Create(POSTURL);

    //http.AllowAutoRedirect = false;
    http2.Accept = "application/json, text/javascript, */*; q=0.01";
    http2.Headers.Add("Accept-Encoding", "gzip, deflate");
    http2.Headers.Add("Accept-Language", "en-us,en;q=0.5");
    http2.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0";

    //http.ContentLength = 0;
    http2.KeepAlive = true;
    http.CookieContainer = cookies;
    http2.Referer = "https://login.microsoftonline.com/ppsecure/post.srf";
    http2.Method = WebRequestMethods.Http.Post;

    Stream ostream = http2.GetRequestStream();

    //used to convert strings into bytes
    System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();

    //Post information found via Fiddler. Values have been altered for anonymity
    byte[] buffer = encoding.GetBytes(
                                                                        "login=" + sUsername
                                                                        + "&passwd=" + sPassword
                                                                        + "&PPSX=" + PPSX
                                                                        + "&PPFT=" + PPFT
                                                                        + "&n1=107313"
                                                                        + "&n2=-1389941230500"
                                                                        + "&n3=-1389941230500"
                                                                        + "&n4=112373"
                                                                        + "&n5=112373"
                                                                        + "&n6=112373"
                                                                        + "&n7=112373"
                                                                        + "&n8=NaN"
                                                                        + "&n9=112373"
                                                                        + "&n10=112360"
                                                                        + "&n11=112358"
                                                                        + "&n12=112323"
                                                                        + "&n13=112324"
                                                                        + "&n14=112396"
                                                                        + "&n15=26"
                                                                        + "&n16=11239"
                                                                        + "&n17=112369"
                                                                        + "&n18=112315"
                                                                        + "&n19=880.9711230112345"
                                                                        + "&n20=1"
                                                                        + "&n21=1"
                                                                        + "&n22=1381236981084.398"
                                                                        + "&n23=1"
                                                                        + "&n24=46.789501123103664"
                                                                        + "&n25=0"
                                                                        + "&n26=0"
                                                                        + "&n27=0"
                                                                        + "&n28=0"
                                                                        + "&n29=-1318912363023"
                                                                        + "&n30=-1318912363023"
                                                                        + "&n31=false"
                                                                        + "&n32=false"
                                                                        + "&type=11"
                                                                        + "&LoginOptions=3" //this is 2 sometimes
                                                                        + "&NewUser=1"
                                                                        + "&idsbho=1"
                                                                        + "&PwdPad="
                                                                        + "&sso="
                                                                        + "&vv="
                                                                        + "&uiver=1"
                                                                        + "&i12=1"
                                                                        + "&i13=Firefox"
                                                                        + "&i14=26.0"
                                                                        + "&i15=1480"
                                                                        + "&i16=964"
                                                                        );
    ostream.Write(buffer, 0, buffer.Length);
    ostream.Close();

    HttpWebResponse response2 = (HttpWebResponse)http.GetResponse();
    readStream = new StreamReader(response2.GetResponseStream());
    HTML = readStream.ReadToEnd();

    response2.Close();
    ostream.Dispose();
    foreach (Cookie cookie in response2.Cookies) //this returns no cookies
    {
        Console.WriteLine(cookie.Name + ": ");
        Console.WriteLine(cookie.Value);
        Console.WriteLine(cookie.Expires);
        Console.WriteLine();
    }

    POSTURL = "https://login.microsoftonline.com/ppsecure/post.srf?bk=1389198967";
    //POST with cookies
    http = (HttpWebRequest)HttpWebRequest.Create(POSTURL);

    http.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0";
    http.AllowAutoRedirect = false;
    http.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
    http.Headers.Add("Accept-Language", "en-us,en;q=0.5");
    http.KeepAlive = true;
    http.CookieContainer = cookies;
    http.Referer = "https://login.microsoftonline.com/ppsecure/post.srf";
    http.Method = WebRequestMethods.Http.Post;

    ostream = http.GetRequestStream();

    //used to convert strings into bytes
    encoding = new System.Text.ASCIIEncoding();

    //Post information
    ostream.Write(buffer, 0, buffer.Length);
    ostream.Close();

    HttpWebResponse response3 = (HttpWebResponse)http.GetResponse();
    readStream = new StreamReader(response3.GetResponseStream());
    HTML = readStream.ReadToEnd();

    response3.Close();
    ostream.Dispose();
    foreach (Cookie cookie in response3.Cookies)  //sadly this returns no cookies when it should have 18 cookies in addition to a bunch of BOX.CacheKey cookies
    {
        Console.WriteLine(cookie.Name + ": ");
        Console.WriteLine(cookie.Value);
        Console.WriteLine(cookie.Expires);
        Console.WriteLine();
    }
}

Does anyone know enough about how the website works to provide some guidance on where I'm failing? The website also uses JavaScript to create cookies (you can visit the first page and enter a random address and you'll see some dots move from left to right in the username field.

Maybe I'm taking the wrong approach but any assistance would be appreciated. I can provide the Fiddler summary session log if need be.

Thank you!

P.S. Sorry about all .com replacements for the actual period character but I don't have enough reputation points. Just search for [dot]com and replace with .com to undo.

3

3 Answers

1
votes

Put a WebBrowser control on a form and control it using its methods and events - it will handle all the redirects/cookies/etc for you.

You can hide it if you need to so the user won't even know you're using a web browser...

1
votes

You want to login into Office 365 portal, not SharePoint admin portal, so the articles about how to login into SharePoint won't help for your case.

I did a search, I think to login into Office 365 portal, you need Microsoft Online Services Sign-In Assistant first.

Microsoft provides some examples on how to manage office365 user:

  1. Office 365: Office 365 Dashboard
  2. Office 365: Manage licenses and subscriptions for Office 365

Both examples are based on Microsoft Online Services Sign-In Assistant and powershell cmdlet , so in C#, we can use the functions in this component to login. But it seems there's not much document about this component. So if you can use Powershell, you can follow the examples to do it. If you can't, you may need dig into the login command and see how to implement it in C#.

0
votes

You will need the Modules for Office 365 installed in the machine for communication.

Powershell Commands

$UserCredential = Get-Credential

Import-PSSession $Session
Import-Module ActiveDirectory
Import-Module MsOnline

$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection

Import-PSSession $Session
Import-Module ActiveDirectory
Import-Module MsOnline
Get-Module

Then you can do any thing Create user & mailboxes, Email alias etc. You may have to reference power shell in the C# code to make it work work from a custom website or windows app

This will connect to Office 365:
http://technet.microsoft.com/en-us/library/jj984289(v=exchg.150).aspx