Accessing IIS Express remotely (with a valid certificate)

There are a lot of articles on the web explaining how to access IIS Express from another machine on the network. There are not a lot of articles explaining how to do this with a valid certificate which is what I'm going to do here. If you're not interested in the background skip down to Allow Remote Connections with a Valid Certificate.

Background

Certificate Bindings

There first thing to note about IIS Express is it tries for the 'pit of success' approach by automatically configuring ports 44300 - 44399 with a self-signed certificate that is trusted by the local machine. You can see this certificate by runing the following in PowerShell:

PS>dir Cert:\LocalMachine\My | ? FriendlyName -eq 'IIS Express Development Certificate'

Thumbprint                                Subject
----------                                -------
931E30C9DC64C7EE350A3430185011CE78C4E789  CN=localhost

You can see above there the certificate with the Thumbprint 931E30C9DC64C7EE350A3430185011CE78C4E789 is my IIS Express certificate. Now run the following netsh.exe command:

PS>netsh http show sslcert

SSL Certificate bindings:
-------------------------
    IP:port                      : 0.0.0.0:44300
    Certificate Hash             : 931e30c9dc64c7ee350a3430185011ce78c4e789
    Application ID               : {214124cd-d05b-4309-9af9-9caa44b2b74a}
    Certificate Store Name       : MY
    Verify Client Certificate Revocation : Enabled
    Verify Revocation Using Cached Client Certificate Only : Disabled
    Usage Check                  : Enabled
    Revocation Freshness Time    : 0
    URL Retrieval Timeout        : 0
    Ctl Identifier               : (null)
    Ctl Store Name               : (null)
    DS Mapper Usage              : Disabled
    Negotiate Client Certificate : Disabled
    Reject Connections           : Disabled
    Disable HTTP2                : Not Set
    Disable QUIC                 : Not Set
    Disable TLS1.3               : Not Set
    Disable OCSP Stapling        : Not Set

You'll see there is a binding for every port from 44300 - 44399 with a certificate hash that match the Thumbprint we collected earlier. This is important as we need to make sure we use a port that is not in this range because a binding already exists.

HTTP Lisetener Permissions

For an application to start listening for http requests it will eventually involve http.sys on Windows. The default behaviour does not allow a non-administrative users to create HTTP listeners. To allow a non-administrative user to create http listeners a Url ACL is required. To view the current acls on your machine run the following:

C:\Windows\system32>netsh http show urlacl

URL Reservations:
-----------------

    Reserved URL            : http://*:5357/
        User: BUILTIN\Users
            Listen: Yes
            Delegate: No
        User: NT AUTHORITY\LOCAL SERVICE
            Listen: Yes
            Delegate: No
            SDDL: D:(A;;GX;;;BU)(A;;GX;;;LS)

    Reserved URL            : http://+:80/Temporary_Listen_Addresses/
        User: \Everyone
            Listen: Yes
            Delegate: No
            SDDL: D:(A;;GX;;;WD)

    Reserved URL            : https://*:5358/
        User: BUILTIN\Users
            Listen: Yes
            Delegate: No
        User: NT AUTHORITY\LOCAL SERVICE
            Listen: Yes
            Delegate: No
            SDDL: D:(A;;GX;;;BU)(A;;GX;;;LS)
...

Even a fresh install of Windows will have many URLs defined. The information is stored in the registry at HKLM:\SYSTEM\CurrentControlSet\Services\HTTP\Parameters\UrlAclInfo. The Value is a binary security descriptor. As a breif aside you can use PowerShell to correlate the registry values with what netsh displays.

# Get WSMAN Url
$bytes = Get-ItemPropertyValue HKLM:\SYSTEM\CurrentControlSet\Services\HTTP\Parameters\UrlAclInfo -Name https://+:5986/wsman/
$securityDescriptor = [System.Security.AccessControl.RawSecurityDescriptor]::new($bytes,0)

#Output SDDL
$securityDescriptor.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All)
D:(A;;GX;;;S-1-5-80-569256582-2953403351-2909559716-1301513147-412116970)

If you compare the SDDL string with what netsh returns you'll see they are the same.

Reserved URL            : https://+:5986/wsman/
        User: NT SERVICE\WinRM
            Listen: Yes
            Delegate: No
            SDDL: D:(A;;GX;;;S-1-5-80-569256582-2953403351-2909559716-1301513147-412116970)

Allow Remote Connections with a Valid Certificate

The process of allowing remote connection with a valid certifcate involves the following steps:

  1. Create a certificate binding
  2. Add a URL ACL
  3. Allow traffic through the firewall
  4. Update the IIS Express configuration

Before you begin choose a port that is outside of the range 44300 - 44399. In my case I'm going to choose 4431.

Create a certificate binding

I'm going to assume that you already have a trusted/valid certificate imported into the local machine certificate store. Be sure to grant your user account access to the private key.

At an elevated command prompt run the following to create a binding:

netsh http add sslcert ipport=0.0.0.0:4431 appid={2c4cc7b7-e5a4-4c94-af1e-33c9d655ed4b} certhash=49E3B29CB74B45E3203F4741E86D1B2196D4324E.

Note the following:

  • The appid is just a GUID, use New-Guid in PowerShell to make one.
  • Make sure you change ipport= to include the port you have choosen.
  • The Certhash is your certificate thumbrint.

Add a URL ACL

Rather than specify a specific hostname here we are going to use a wildcard. Run the following at an elevated command prompt.

netsh http add urlacl url=https://*:4431/ user=Everyone

Don't forget to change the port number!

Allow traffic through the Firewall

This might go without saying but you do need to allow the traffic through the Windows Firewall if it's ever going to reach IIS Express. Run the following at an elevated command prompt.

netsh advfirewall firewall add rule name="IISExpress" dir=in protocol=tcp localport=4431 profile=private,domain action=allow

Feel free to use PowerShell or even the UI to create this rule. I only used netsh to stick with the theme. :-)

Update IIS Express configuration

This is the last part of the process but is vitally important. Visual Studio will create a file at the solution level at .\.vs\fathom\config\applicationhost.config. This file contains the IIS Express configuration. Ensure that Visual Studio is not running and open the file up in a text editor. Look for the section called <sites>. Find the site that you'd like to make available remotely, mine is below:

<site name="Application" id="3">
    <application path="/" applicationPool="Clr4IntegratedAppPool">
        <virtualDirectory path="/" physicalPath="C:\Data\Application" />
    </application>
    <bindings>
        <binding protocol="https" bindingInformation="*:44330:localhost" />
        <binding protocol="http" bindingInformation="*:5939:localhost" />
    </bindings>
</site>

In the <bindings> section add a new entry <binding protocol="https" bindingInformation="*:4431:" /> as shown below:

<bindings>
    <binding protocol="https" bindingInformation="*:4431:" />
    <binding protocol="https" bindingInformation="*:44330:localhost" />
    <binding protocol="http" bindingInformation="*:5939:localhost" />
</bindings>

The binding *:4431: essentially means listen on all hosts on port 4431. It will cause IIS Express to attempt to listen on https://*:4431/ which matches the Url Acl added earlier.

Running the site

That's it! You can now debug the site in Visual Studio and it will also listen on 4431 as well as any other bindings the site has. The certificate being served will be the one you configured above.

References

https://docs.microsoft.com/en-us/windows/desktop/http/netsh-commands-for-http
https://serverfault.com/questions/822207/how-does-url-reservation-actually-work-in-windows-particularly-the-acls

Author image
About Jacob Hodges