Feature Post

Top

WebBrowser Control: Bypass integrated authentication



How bypass integrated authentication using WebBrowser control?

Well, sometime back, I was hosting the WebBrowser control over WinForm and I wanted to access a remote/secure(https) website; The website was throwing a windows authentication dialog box whenever the url was typed in.

Fig1: The well known network credentials popup dialog box

This started bugging me and my program as well; since I wanted my program to automate this behavior and provide the credentials “hautomatically” (handle-automatically :0) So I told myself, issues are sweet, that’s how you get to learn more; Lets get sweetened!

Therefore, with absolute positive energy, I dived into google, and my “at-the-moment-favourite” – overflowing stack

Now the question was how to provide integrated authentication while having no access to the site, that is, my integrated windows credentials are not valid for the remote site; So how to force a different credential(other than the logged in one) plus override the WebBrowser credentials popup prompt.

Note, for those, who are in a hurry and running after the unstretchable deadline, please close your eyes - copy and paste the "The Code" section in your .NET WinForm application... and follow following steps:

STEP 1: Create a plain windows form application, double click over the form in design more to implement its OnLoad event.
STEP 2: Copy paste all of the code in the FormTest.cs file

...and well, hit the Run!

-- UPDATE 2011/04/09 --

Please download the zipped solution (.SLN) from here. Thanks to Eduardo for his feedback.

Rest, please follow.

There are four types of authentication, Windows, Forms, Passport, and None. Usual use is of the forms based authentication and integrated windows authentication (IWA). My scenario obviously had an integrated authentication system; and the logged in user is popped up with a window for required credentials.

Fig 2: [Illustration by Vesku’s blog]

Following is somewhat, in most cases, a generic site authentication process:

Fig 3: [Illustration by IBM] A generic authentication flow.

For this, following interfaces needed implementation. IAuthenticate, IServiceProvider, IOleClientSite;

IAuthenticate, is used by the WebBrowser object to automatically retrieve the user credentials and provides it to the website upon request.

Urlmon.dll uses the QueryInterface method on the client application's implementation of IBindStatusCallback to get a pointer to the client application's IAuthenticate interface.
If the client application is hosting Mshtml.dll, Mshtml.dll requests a pointer to the client application's implementation of IAuthenticate interface by calling QueryInterface on the client application's IServiceProvider interface.
IOleClientSite, is used to notify the WebBrowser about the website that is going to require the credentials.

Within a compound document, each embedded object has its own client site — the place where it is displayed and through which it receives information about its storage, user interface, and other resources. IOleObject::SetClientSite is the only method enabling an embedded object to obtain a pointer to its client site.

A container can notify an object of its client site either at the time the object is created or, subsequently, when the object is initialized.
The IServiceProvider interface is a generic access mechanism to locate a GUID-identified service that is provided through a control or any other object that the service can communicate with. For example, an embedded object (such as an OLE control) typically communicates only with its associated client site object in the container through the IOleClientSite interface that is supplied by using IOleObject::SetClientSite. The embedded object must ask the client site for some other service that the container supports when that service might not be implemented in the client site.

The client site must provide a means by which the control that is managed by the site can access the service when necessary. For example, the IOleInPlaceSite::GetWindowContext function can be used by an in-place object or control to access interface pointers for the document object that contains the site and the frame object that contains the document.

Because these interface pointers exist on separate objects, the control cannot call the site's QueryInterface to obtain those pointers. Instead, use the IServiceProvider interface.

The IServiceProvider interface has only one member, QueryService, through which a caller specifies the service ID (SID, a GUID), the IID of the interface to return, and the address of the caller's interface pointer variable.
The Code

Following is how IOleObject is going to be implemented into your windows form;
[ComImport,
    Guid("00000112-0000-0000-C000-000000000046"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IOleObject
    {
    void SetClientSite(IOleClientSite pClientSite);
    void GetClientSite(IOleClientSite ppClientSite);
    void SetHostNames(object szContainerApp, object szContainerObj);
    void Close(uint dwSaveOption);
    void SetMoniker(uint dwWhichMoniker, object pmk);
    void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk);
    void InitFromData(IDataObject pDataObject, bool
    fCreation, uint dwReserved);
    void GetClipboardData(uint dwReserved, IDataObject ppDataObject);
    void DoVerb(uint iVerb, uint lpmsg, object pActiveSite,
    uint lindex, uint hwndParent, uint lprcPosRect);
    void EnumVerbs(object ppEnumOleVerb);
    void Update();
    void IsUpToDate();
    void GetUserClassID(uint pClsid);
    void GetUserType(uint dwFormOfType, uint pszUserType);
    void SetExtent(uint dwDrawAspect, uint psizel);
    void GetExtent(uint dwDrawAspect, uint psizel);
    void Advise(object pAdvSink, uint pdwConnection);
    void Unadvise(uint dwConnection);
    void EnumAdvise(object ppenumAdvise);
    void GetMiscStatus(uint dwAspect, uint pdwStatus);
    void SetColorScheme(object pLogpal);
    }

IOleClientSite requires following implementation:
[ComImport,
    Guid("00000118-0000-0000-C000-000000000046"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IOleClientSite
    {
    void SaveObject();
    void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk);
    void GetContainer(object ppContainer);
    void ShowObject();
    void OnShowWindow(bool fShow);
    void RequestNewObjectLayout();
    } 

IServiceProvider has following:
[ComImport,
    GuidAttribute("6d5140c1-7436-11ce-8034-00aa006009fa"),
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown),
    ComVisible(false)]
    public interface IServiceProvider
    {
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int QueryService(ref Guid guidService, ref Guid riid, out IntPtr
    ppvObject);
    } 

[ComImport, GuidAttribute("79EAC9D0-BAF9-11CE-8C82-00AA004BA90B"),
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown),
    ComVisible(false)]
    public interface IAuthenticate
    {
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int Authenticate(ref IntPtr phwnd,
    ref IntPtr pszUsername,
    ref IntPtr pszPassword
    );
    } 

You may implement your form class like following:
public partial class FrmTest : Form, IOleClientSite, IServiceProvider, IAuthenticate
    {
    public static Guid IID_IAuthenticate = new Guid("79eac9d0-baf9-11ce-8c82-00aa004ba90b");
    public const int INET_E_DEFAULT_ACTION = unchecked((int)0x800C0011);
    public const int S_OK = unchecked((int)0x00000000);


    public FrmTest()
    { InitializeComponent(); }


    private void button1_Click(object sender, EventArgs e)
    {
    string oURL = this.textBox1.Text;
    webBrowser1.Navigate(oURL);
    }

Note, that now, when we host the WebBrowser control and implement the IAuthenticate interface to programmatically bypass the authentication process, sometimes Internet Explorer does not call the Authenticate function, which causes an authentication dialog to appear. So, this is somewhat buggy.
So, the workaround is to; "first" move to "about:blank" i.e.: Navigate("about:blank"), before moving to the secure site.

private void FrmTest_Load(object sender, EventArgs e)
    {
    //Navigate to about:blank
    string oURL = "about:blank";
    webBrowser1.Navigate(oURL);

    //Notify the WebBrowser object about the client site.
    //The client site, informs an embedded object of its display location
    object obj = webBrowser1.ActiveXInstance;
    IOleObject oc = obj as IOleObject;
    oc.SetClientSite(this as IOleClientSite);
    } 

IOleClientSite member definitions:
public void SaveObject()
    {
    // TODO: Add FrmTest.SaveObject implementation
    }


    public void GetMoniker(uint dwAssign, uint dwWhichMoniker, object
    ppmk)
    {
    // TODO: Add FrmTest.GetMoniker implementation
    }


    public void GetContainer(object ppContainer)
    {
    ppContainer = this;
    }


    public void ShowObject()
    {
    // TODO: Add FrmTest.ShowObject implementation
    }


    public void OnShowWindow(bool fShow)
    {
    // TODO: Add FrmTest.OnShowWindow implementation
    }


    public void RequestNewObjectLayout()
    {
    // TODO: Add FrmTest.RequestNewObjectLayout implementation
    } 

Interface IServiceProvider explicit implementation:

public int QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject)
    {
    int nRet = guidService.CompareTo(IID_IAuthenticate); // Zero returned if the compared objects are equal
    if (nRet == 0)
    {
    nRet = riid.CompareTo(IID_IAuthenticate); // Zero returned if the compared objects are equal
    if (nRet == 0)
    {
    //This method returns an interface pointer that represents the requested interface on the specified object. It is particularly useful if you have an unmanaged method that expects to be passed an interface pointer; Add to the COM interface reference count.
    ppvObject = Marshal.GetComInterfaceForObject(this, typeof(IAuthenticate));
    return S_OK;
    }
    }
    ppvObject = new IntPtr();
    return INET_E_DEFAULT_ACTION;
    }

The main, IAuthenticate implementation
public int Authenticate(ref IntPtr phwnd, ref IntPtr pszUsername,
    ref IntPtr pszPassword)
    {
    //Copies the contents of a managed String to a block of memory allocated from the unmanaged COM task allocator.
    IntPtr strUser = Marshal.StringToCoTaskMemAuto(txtUserID.Text);
    IntPtr strPassword = Marshal.StringToCoTaskMemAuto(txtPassword.Text);


    pszUsername = strUser;
    pszPassword = strPassword;
    return S_OK;
    }

    }

Here is how to pass the proxy credentials in WebBrowser control:
Uri uri = new Uri("http://www.somewheresurl.com");

//Alternatively, you may use System.Text.UnicodeEncoding.UTF8.GetBytes()
string additionalHeaderInfo = "Authorization: Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("MyUsername" + ":" + "MyPassword")) + System.Environment.NewLine;

webBrowser1.Navigate(uri, null, null, additionalHeaderInfo);
This is for me, and for those who came across similar sweet issue. Hope this helps.