Detecting MFA Fatigue

Compromising an account via delegate authority requires the organization to be enrolled in delegate authority within the Okta admin portal and requires this enrollment to be active. Additionally, a service principal name (SPN) must be configured to handle the authentication.

$ getST.py -spn HTTP/org.kerberos.okta.com -dc-ip 1.2.3.4 LAB/testuser

The following section will cover persistence mechanisms related to Okta Terrify and lateral movement techniques that abuse Okta’s delegate authority configurations.

Okta Terrify

The following rule looks for instances where multiple MFA push notifications are sent to a given user and identifies scenarios where multiple failed push notifications are sent and a successful push notification followed. Note that when a push notification is sent, it’s also transmitted to each registered device, which may result in a slightly skewed threshold used below.
The following CRT identifies the two distinct user.authentication.auth_via_mfa events that occur within the same session with the corresponding properties. Since Okta Terrify creates a fake Okta Verify instance, there will be two different enrollment IDs during its authentication steps. The rule accounts for this by counting the number of enrollment IDs within a given session.
To outline an example attack, we can hypothesize that a user logs in to their workstation and browses to their organization’s Okta portal, such as https://org.okta.com. The browser, which is configured to utilize integrated Windows authentication, makes a request to the network’s key distribution center (KDC) and obtains a Kerberos ticket for org.kerberos.okta.com. The browser sends this ticket to the *.kerberos.okta.com endpoint over HTTPS, which is received and validated, and the user is redirected to https://org.okta.com to be logged in. If the user has MFA methods enabled on their account, the user will be required to submit the MFA token or push notification to log in to their Okta portal. This is an important distinction because the attack described here will fail if the adversary does not have access to the user’s MFA method.
The adversary will need to run Okta Terrify on their attack host and run OktaInk.exe from the compromised user’s workstation. Okta Terrify works by dumping information from the stolen DataStore.db database and performing the functionality of Okta Verify from the adversary’s attack host.

Identifying Okta Terrify in Use

#event.module="sso" #Vendor="okta"
| case {
user_agent.original=/OktaVerify/ user_agent.original=/WPFDeviceSD/i user_agent.original=/Windows/i user_agent.original=/Microsoft_Corporation/Virtual_Machine/i;
user_agent.original="OktaVerify/5.1.3.0 WPFDeviceSDK/1.8.0.30 Windows/10.0.22621.3155 Microsoft_Corporation/Virtual_Machine";
}

Account Compromise via Delegate Authority

#Vendor="okta" #event.kind="event" #event.module="sso"
| event.action=~in(values=["system.push.send_factor_verify_push", "user.mfa.okta_verify.deny_push", "user.authentication.auth_via_mfa"])
| case {
Vendor.outcome.result=/fail|deny/i
| mfa_failure_time := @timestamp;
* | mfa_success_time := @timestamp;
}
| case{
Vendor.debugContext.debugData.threatSuspected="true";
Vendor.securityContext.isProxy="true";
Vendor.debugContext.debugData.behaviors=/(NewsDevice|DevicesEval)=POSITIVE,s(NewsIP|IPsEval)=POSITIVE/;
Vendor.debugContext.debugData.logOnlySecurityData=/reasons":"[^"]+|level":"(?:high|medium|crit)|(?:(?:velocity|news+(?:geo-location|city|device|ip|state|country))":"(?:POSITIVE|UNKNOWN))/i;
Vendor.debugContext.debugData.behaviors!=*;
}
| groupBy([user.name, Vendor.authenticationContext.externalSessionId],
function=[
count(mfa_failure_time, as="fail_count"),
count(mfa_success_time, as="success_count"),
selectLast(mfa_failure_time),
selectLast(mfa_success_time)
]
)
| fail_count >= 5 success_count >= 1
| test(mfa_success_time > mfa_failure_time)
| test(fail_count > success_count)

Post-Compromise Attack Vectors

Because Okta Terrify has been used to create a fake device bind key with Okta, the adversary’s attack host can be used to continuously log in to the compromised user’s account without the need to maintain access to the compromised user’s workstation.
The adversary steals sensitive data from the compromised user’s workstation and passes that information to Okta Terrify on the attack host. When executed, Okta Terrify will launch a browser session pointing to the compromised user’s Okta user portal, where the adversary logs in as the compromised user. Okta Terrify will then siphon out data from the interaction. The data is passed to OktaInk.exe — a helper tool for the Okta Terrify toolkit used to sign JSON Web Tokens (JWTs) and steal encryption keys from the compromised user’s workstation — on the compromised user’s host. The adversary uses OktaInk to sign Device Bind JWTs, which are passed back to the attack host with Okta Terrify. Okta Terrify then facilitates the rest of the compromised user’s login session.

#Vendor="okta" #event.module="sso" #event.kind="event"
| array:contains(array="event.category[]", value="authentication")
| event.action="user.authentication.auth_via_mfa"
| groupBy([Vendor.authenticationContext.externalSessionId, user.name],
function=[
count(Vendor.AuthenticatorEnrollment.id, as=enrollmentIdCount, distinct=true),
collect(Vendor.debugContext.debugData.url),
collect(user_agent.original),
collect(Vendor.debugContext.debugData.keyTypeUsedForAuthentication),
collect(Vendor.AuthenticatorEnrollment.detailEntry.methodTypeUsed),
collect(Vendor.AuthenticatorEnrollment.id)
]
)
| Vendor.debugContext.debugData.url = /^/idp/authenticators//mi Vendor.debugContext.debugData.url=/^/idp/idx/identify?/mi
| Vendor.AuthenticatorEnrollment.detailEntry.methodTypeUsed=/Use Okta FastPass/mi
| Vendor.debugContext.debugData.keyTypeUsedForAuthentication=/PROOF_OF_POSSESSION/mi
| enrollmentIdCount > 1

Okta Terrify is a tool designed to enable persistence to a compromised user’s Okta account. An adversary achieves this persistence by first stealing the compromised user’s DataStore.db database from %LocalAppData%OktaOktaVerifyDataStore.db.
When executing Okta Terrify, we typically noticed that two user.authentication.auth_via_mfa events were recorded, with different APIs being hit: One to grab a nonce and another to collect the crypto challenge information needed to be imported into OktaInk. Once the OktaInk phase is executed on the victim host, the adversary will have a signed JWT created from the database key and DataStore.db details stored on the victim system. This second request to submit the JWT is an abnormal step in the Okta Verify sequence, offering defenders a detection opportunity. 

The next step for an adversary is to request a Kerberos service ticket using common attack tools like those from Impacket. This is one area where Falcon Identity Threat Protection could provide attack detection and tool/technique identification as an early warning of the attack in addition to blocking or challenging for access. Going unnoticed or unchallenged, an adversary can obtain a foothold within an organization using tools like Impacket’s GetST.py or ticketer.py to obtain a Kerberos ticket for a compromised user:

Similar Posts