There is no doubt multi-factor authentication (MFA) is a simple and effective way to reduce account compromise, yet only 11% of all enterprise accounts use a MFA solution overall, accordingly to latest data from Microsoft.

Are there users without MFA enabled?

That’s a question our security team wants answered and alerted. In our internal JupiterOne account, we have an audit/alert rule configured to check for user accounts without MFA enabled. The J1QL query for that particular rule is written as such:

Find User with mfaEnabled != true 
  that !(ASSIGNED|USES|HAS) mfa_device

This checks for any user that does not have the mfaEnabled attribute directly on the user entity and does not have a relationship (assigned/has/uses) to an MFA device entity.

Hmm… so many false positives because of SSO

However, this query by itself has false positives in an environment with Single Sign On (SSO) configured.

For example, our users in Bitbucket, Jira, KnowBe4, and many other apps authenticate via Okta SSO. The users within these app accounts do not have MFA directly configured, and do not have an MFA device directly associated, because users never authenticate directly.

To accurately determine whether any user in these accounts does or does not use MFA for authentication, we will have to correlate them to SSO users and applications configured in Okta. For each user in an account connected via SSO, we have to check the following:

  1. An application is configured in Okta that connects it to the corresponding account
  2. A user from the target account has a matching active SSO user assigned to the SSO application in Okta
  3. The matching active user in Okta has MFA configured/assigned

This configuration scenario can be seen in the following graph, showing my Bitbucket user does not have MFA configured but it is actually authenticating via a connected Okta application, and my Okta user does have a number of MFA devices configured:

We need an automated way to “enrich” my Bitbucket user in the above example so that it is not included in the “user accounts without MFA enabled” alert rule. Here’s how we make this work.

Reduce false positives using results from graph queries

First, we can use the following query to check for #1 and #2:

Find User with _key='<unique_key_of_the_user_entity>' as userA
  that has Account
  that connects okta_application
  that assigned okta_user with active=true as userB
where
  userA.name = userB.name or 
  userA.username = userB.username or
  userA.email = userB.email

Enrichment: if this query returns a match, we can set ssoUser: true on the originating user entity (i.e. userA, the provider app user).

If your primary SSO provider is not Okta, replace okta_application and okta_user in the query above (and below) with the appropriate ones.

Next, we can use a second query to check for condition #3:

Find User with _key='<unique_key>' and ssoUser=true as userA
  that has Account
  that connects okta_application
  that assigned okta_user with active=true as userB
  that (assigned|has|uses) mfa_device
where
  userA.name = userB.name or 
  userA.username = userB.username or
  userA.email = userB.email

Enrichment: if this query returns a match, we can set mfaEnabled: true on the originating user entity (i.e. userA, the provider app user).

You may notice this second query is very similar to the first one. It has an additional relationship traversal at the end: that (assigned|has|uses) mfa_device. So why not do this in one query? The reason is the additional filter at the beginning: and ssoUser=true. This filter is added to ensure we only set the mfaEnabled property on user entities that were identified as SSO users in the previous step.

Graph enrichment FTW!

Here’s my Bitbucket user after the enrichment is complete:

Now we repeat these two steps on each user that is not an Okta user (or whichever is your primary SSO provider — OneLogin, JumpCloud, etc.). This allows us to use our original “user accounts without MFA enabled” alert rule with all false positives virtually eliminated!

Review of the results validated that system accounts did not get the ssoUser or mfaEnabled flag, as intended, and identified a handful of users needing remediation.

Check out the entire code here.