In this post I will walk through how I setup the open-source MFA solution privacyIDEA to protect the OWA and ECP endpoints on my Exchange server. This is the setup that was shown in the demo from my Defcon26 talk “One-Click to OWA”
Disclaimer: I would NOT use this guide verbatim to stand up anything in production without performing more security and performance related background research, as well as applying other best practices (eg. use Exchange Edge Transport Servers, use a web proxy with AD FS, etc).
At the end of this guide you will have:
This guide is broken down into the following parts:
Before we begin, let’s assume we already have an Exchange server that went through a standard install with the Client Access Role(CAS) option selected. For the purposes of this blog post, we will be calling this Exchange CAS server MAILMAN.
The first thing we need to do is stand up the privacyIDEA MFA server. This server will listen for requests passed from AD FS to check the validity of a given one-time password for a user, and respond appropriately.
Once you have the fresh install up, log into it and run the following commands to install the privacyIDEA server. Take note of the last line where you will need to swap in your own chosen username for the admin, and enter in the password for the account twice when prompted.
# Install git/python
apt update && apt install git python python-pip
pip install virtualenv
# Make a directory for the server & clone the data
mkdir /privacyIDEA
cd /privacyIDEA
# This guide used the commit ff9427c80d4704bafff9fe4597f1ecffb230172d
git clone https://github.com/privacyIDEA/privacyIDEA .
# Generate an SSL cert for it to use (unless you have an internal CA to issue one)
mkdir SSL
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout SSL/privacyIdeaCert.key -out SSL/privacyIdeaCert.crt
# Install server requirements
virtualenv venv
source venv/bin/activate
pip install -r requirements.txt
# Set up server
git submodule init
git submodule update
git pull --recurse-submodules
./pi-manage createdb
./pi-manage create_enckey
# Set up admin on privacyIDEA server - you will be promted for your password, and to confirm it
./pi-manage admin add <InsertUsernameHere>
Now that the server is installed, let’s run it.
./pi-manage runserver -h 0.0.0.0 -p 443 --ssl-crt SSL/privacyIdeaCert.crt --ssl-key SSL/privacyIdeaCert.key
You should now be able to open a browser to the server at https://PISERVER
Now let’s log into the privacyIDEA server, hook it up to LDAP on our DC, and set up an MFA token for a of user.
Now click “New ldapresolver” on the left sidebar.
This form will create an ldapresolver which tells privacyIDEA how to access LDAP, and where in the LDAP structure to look for user objects. The privacyIDEA server requires a domain user account to query LDAP (unless anonymous LDAP binding is permitted - which is BAD), so be sure to create a basic user account for the privacyIDEA service. My user is called “privacyidea”. Here are the basics on the ldapresolver configuration:
Field | Basic Description |
---|---|
Resolver Name | Some arbitrary name for the connector of your choosing |
Server URI | The LDAP URI to your domain controller (eg ldap://10.0.0.250) |
Base DN | The location within LDAP where the user objects are located |
Scope | How far down in LDAP to search - SUBTREE should be fine |
Bind DN | The domain\username of the account to use for accessing LDAP |
Bind Password | Password for the Bind DN account |
I left the remaining fields with their default values. Once you hit the bottom half of the form containing the “Loginname Attribute” field, click the “Preset Active Directory” button to populate all of the remaining fields. I’ve included a snapshot of what the config for my demo lab environment looked like:
You can run “Quick Resolver Test” to test if a single user could be retrieved from LDAP, or “Test LDAP Resolver” to try and retrieve all of the users. Once you’ve gotten the pop up showing a successful retrieval, click save resolver.
Now that privacyIDEA is linked to AD, lets assign a user a MFA token.
Click “Enroll Token”. You should then be brought to a screen like this:
Scan the QR code provided with Google Authenticator or a similar app. This is the user’s MFA secret.
You should now see this token in the “Tokens” tab of the navigation bar at the top. Now that we have a user paired with a MFA code, we need to hook this MFA solution up AD FS to handle MFA for Exchange.
You now need to stand up a domain connected Windows Server to become the AD FS server. This server can either be it’s own stand-alone Sever 2008/2012/2016 box, or it can be the same box as the Exchange CAS server. If this is a lab, you’re probably fine just using the Exchange server - but for larger environments I would recommend a stand-alone server.
If a self-signed certificate was used for the privacyIDEA server, we need to install that certificate on the server becoming the AD FS server so that the privacyIDEA cert is trusted.
We will also need a certificate for the AD FS server. If you have a CA that can issue a cert, then use that - otherwise here is an openssl one-liner to generate a self-signed PFX cert called “certificate.pfx”
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout server.key -out server.crt && openssl pkcs12 -export -out certificate.pfx -inkey server.key -in server.crt
Notice: After standing up the AD FS server role, make sure you create a DNS entry in your DC that matches the FQDN of the “Federation Service Name” from step 4 of 3b in the MSDN article. My AD FS server was installed on the same box as my exchange server (mail.quickbreach.com), so I manually created a record for adfs.quickbreach.com to point to the IP of the server.
If you followed the instructions of that article successfully, your AD FS Management pane should look like this
Now we need to add claims for both OWA and ECP.
In the “Custom rule” box, copy and paste the below code
c:[Type * "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer * "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid"), query = ";objectSID;{0}", param = c.Value);
Click finish. You should now see the claim rule you just created in the “Issuance Transform Rules” pane. You need to repeat this process to create two more claim rules with the information below:
Rule name: privacyIDEA_ActiveDirectoryUPN
c:[Type * "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer * "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"), query = ";userPrincipalName;{0}", param = c.Value);
Rule name: privacyIDEA_GroupSID
c:[Type * "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer * "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid"), query = ";tokenGroups(SID);{0}", param = c.Value);
You should now see a total of three rules in your “Issuance Transform Rules” pane as shown below
Notice: This next step is more of a bugfix than anything I’ve read officialy, probably because I used self-signed certs, but I haven’t gotten AD FS to work without it. I’m also unsure if this step should be executed on the AD FS server, or the Exchange server. In my case they were both the same machine, but your experience may vary.
Now that AD FS is up and running, we need to link it up with privacyIDEA. All of the steps below are being run from the AD FS server.
Below is what my config.xml looked like
Open Powershell as an administrator and run the following commands
cd "C:\Program Files\privacyIDEAProvider\Install"
.\Install-ADFSProvider.ps1
In the “Primary” tab of the window that just opened, ensure forms authentication is checked.
A couple of notes:
Select “privacyIDEA-ADFSProvider_1.2.0.0” in the bottom of the “Multi-Factor” tab. Your setup should look similarly to the below screenshot:
We have now configured AD FS to talk to our privacyIDEA server to verify tokens, and which users should be required to provide a token. Now let’s set up OWA/ECP to use AD FS for authentication - the final piece of the puzzle.
The last step is to instruct Exchange to force authentication to OWA and ECP through AD FS. You can either follow this guide, or you can finish up step 6 of this MSDN Guide
On the AD FS server, run the following command to get the thumbprint for the AD FS token-signing certificate
Set-Location Cert:\LocalMachine\Root; Get-ChildItem | Sort-Object Subject | where { $_.Subject -Like "ADFS Signing*" }
CRITICAL: Review the following commands, replace the FQDN for your CAS server, FQDN for your ADFS server, and the Thumbprint obtained in step 1, and then run them from within the Exchange Management Shell
$uris = @(" https://CAS_SERVER_FQDN/owa/","https://CAS_SERVER_FQDN/ecp/")
Set-OrganizationConfig -AdfsIssuer "https://ADFS_SERVER-FQDN/adfs/ls/" -AdfsAudienceUris $uris -AdfsSignCertificateThumbprint THUMBPRINT_FROM_STEP1
This was my setup:
$uris = @(" https://mail.quickbreach.com/owa/","https://mail.quickbreach.com/ecp/")
Set-OrganizationConfig -AdfsIssuer "https://adfs.quickbreach.com/adfs/ls/" -AdfsAudienceUris $uris -AdfsSignCertificateThumbprint 88970C64278A15D642934DC2961D9CCA5E28DA6B
Finally, the last step is to restart IIS services on the Exchange CAS server
iisreset /noforce
At long last, we are done! You should now be able to navigate to your OWA, log in, and see a screen prompting you for your MFA token!
Remember: There are around 8 different ways to access a user’s mailbox on Exchange, and we have only protected 2 of them with MFA. Organizations must require MFA protected Modern Authentication, or MFA protected Hybrid Modern Authentication, on their Exchange infrastructure to truly be covered.
Outlook Anywhere (aka. RPC/HTTP), POP3, and IMAP do not support Modern Authentication. Ensuring these endpoints are disabled is also strongly advised.