Recently Okta, along with many other identity providers, have gotten a lot of media attention due to the incident at Okta involving Lapsus$. In all areas of tech, we give a lot of trust to and rely heavily on our identity providers, and as such it is important to monitor them effectively and know what normal behavior looks like.
In this article we will:
Many security practitioners are familiar with what have become ‘standard’ detections for anything to do with authentication. These are things like brute force login attempts, logins without MFA, and high numbers of failed logins for non-existent users (another example of brute force). These are still useful detections for Okta, but due to the verbose nature of Okta’s audit logs, there are many other useful detections we can implement. Some examples of these types of detections are below, and all of these are built-in detections in the Panther threat detection platform.
Sometimes referred to as the “Superman Rule” (as in “these logins are impossible unless the user is Superman”), this rule alerts in the case that a user logs in from two geographically distant locations in an amount of time that is not possible, even when traveling by air. Because Okta logs include full geographical information for the IP address of the user and Panther provides the ability to create detections in Python, this rule is possible with no additional geolocation service by making use of the Python math and datetime modules along with the Haversine distance formula. You can see how this can be accomplished in the snippet of this rule below. The full rule code can be found here.
def rule(event):
— snip —
distance = haversine_distance(old_login_stats, new_login_stats)
old_time = datetime.strptime(old_login_stats["time"][:26], PANTHER_TIME_FORMAT)
new_time = datetime.strptime(event.get("p_event_time")[:26], PANTHER_TIME_FORMAT)
time_delta = (new_time - old_time).total_seconds() / 3600 # seconds in an hour
# Don't let time_delta be 0 (divide by zero error below)
time_delta = time_delta or 0.0001
# Calculate speed in Kilometers / Hour
speed = distance / time_delta
return speed > 900 # Boeing 747 cruising speed
—- snip —
def haversine_distance(grid_one, grid_two):
# approximate radius of earth in km
radius = 6371.0
# Convert the grid elements to radians
lon1, lat1, lon2, lat2 = map(
radians, [grid_one["lon"], grid_one["lat"], grid_two["lon"], grid_two["lat"]]
)
dlat = lat2 - lat1
dlon = lon2 - lon1
distance_a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
distance_c = 2 * asin(sqrt(distance_a))
return radius * distance_c
Code language: Python (python)
Unlike many older identity services, Okta has a fully featured API, and as such it is possible to generate API tokens to automate authentication functions in Okta. While useful in many cases, these keys also introduce many risks as they essentially act as a password and session token for Okta. This rule will alert you if such a token is generated, and allows you to validate with the user that the creation was intentional and necessary.
Additionally, response and investigation of these types of alerts that require contacting the user to verify an action was performed by them can be automated via SOAR platforms such as Tines. Here at Panther, we utilize a Tines workflow for situations such as this to Slack the user in question, collect their response, and add it to the task that was opened in Asana by Panther. This can be accomplished by setting an alert_context function in the rule which will send fields you define to the destination of your alerts. This is demonstrated in the snippet from this rule below.
from panther_base_helpers import okta_alert_context
–snip–
def alert_context(event):
return okta_alert_context(event)
def okta_alert_context(event: dict):
"""Returns common context for automation of Okta alerts"""
return {
"ips": event.get("p_any_ip_addresses", []),
"actor": event.get("actor", ""),
"target": event.get("target", ""),
"client": event.get("client", ""),
}
Code language: Python (python)
It is important for security and IT staff to understand how these tokens work, and restrict their usage only to where they are required. More information about Okta API tokens can be found in Okta’s documentation.
Enriching your logs with threat intelligence data greatly increases their usefulness and can increase the speed of triage tremendously. Knowing that an IP is known to be malicious or is a TOR exit node can greatly increase the severity of the alert. Conversely, a known benign IP has the opposite effect, i.e. it is unlikely that a search engine web crawler is performing an attack on your infrastructure. Seeing a user successfully log into Okta from a known malicious IP is a strong sign of account compromise, and should be treated as a critical alert.
In the most recent version of Panther (1.32), we added an integration with GreyNoise Intelligence to allow users to enrich IP addresses in their logs with threat intelligence data. Stay tuned for updates to our public detections repo for new detections that alert on login actions or adjust alert severity based on IP addresses that are known to be malicious by GreyNoise.
You can learn more about GreyNoise on their website, and the Panther integration with GreyNoise in our docs.
Many environments have expected patterns that can be determined from log analysis. In order to effectively search for threats in your Okta environment, it is important to understand first what the normal operation looks like. Panther allows you to search large amounts of data quickly in its security data lake, so determining these patterns of normal is a much faster operation than with a traditional SIEM product.
Below are some sample queries that can be run to start baselining your Okta environment.
NOTE: These queries are for Panther environments using Snowflake as the data lake.
This query shows what country all of the successful logins came from over the last 30 days. If your business is more geographically isolated, then state or province can be substituted for country in this query. This query is useful for setting a baseline as mentioned above, but by changing ORDER BY from DESC to ASC you can immediately see outlier logins from rare locations.
SELECT client:geographicalContext:country as Country, COUNT(*) as Events
FROM panther_logs.public.okta_systemlog
WHERE Country is not null
and eventType ='user.session.start' and outcome:result = 'SUCCESS'
AND p_occurs_since('30 days')
GROUP BY Country
ORDER BY Events DESC
Code language: SQL (Structured Query Language) (sql)
This query is not dissimilar from our previous query, as it looks for what are the most common patterns interacting with your Okta account over 30 days. In this instance, we focus on devices that are accessing Okta. This will also provide you with information to create detections specific to your environment for abnormal activity.
A good example would be if your company supplies its employees with Macs, and has created a policy that only managed Google Chrome should be used. In that instance, a Linux or Windows workstation running Firefox would be something security might want to investigate.
SELECT client:device AS Device, client:userAgent:os AS OS,
client:userAgent:browser AS Browser, COUNT(*) AS Events
FROM panther_logs.public.okta_systemlog
WHERE p_occurs_since('30 days')
and Device is not null
GROUP BY Device, OS, Browser
ORDER BY Events DESC
Code language: SQL (Structured Query Language) (sql)
Once you have run the queries above and determined what normal behavior looks like, it is quite simple to create a detection in Panther using Python to detect deviations. Let’s create a simple detection that would alert us if a deviation from that norm occurs, with a few comments to help explain the flow:
# Import Panther's deep get function. This allows us to easily search nested JSON
from panther_base_helpers import deep_get
# Define the rule function, this will return True if we detect activity in need of further investigation
def rule(event):
# Early filter to limit the scope of this rule to workstations, not mobile devices
if deep_get(event, "client", "device") != "Computer":
return False
# If we don't see the OS and browser combination we expect, alert.
if (
deep_get(event, "client", "userAgent", "os") != "Mac OS X"
and deep_get(event, "userAgent", "browser") != "CHROME"
):
return True
# Return False if we see our expected combination and do not alert
return False
# Define the title function. Gives the alert a Unique title and distinguishes it from other similar alerts
def title(event):
# Simply return a string, with some values from the event
return (
f"Unauthorized Workstation Configuration Accessing Okta from IP "
f"{deep_get(event, 'client', 'ipAddress')} and User {deep_get(event, 'Actor', 'displayName')}"
Code language: Python (python)
This is only one example of how a detection can be created using Python to look for behavior that differs from the observed norm. Panther ships with many built-in detections and SQL queries to get you started. Full documentation for writing rules can be found at https://docs.panther.com/writing-detections.
In every step of a company’s cloud journey, it’s vital to have an understanding of what a “normal day” in your identity provider logs looks like, and having high-fidelity detections to alert you when the abnormal occurs is crucial to keeping your environment secure. Even as more sophisticated security controls such as zero-trust are put into place, the IdP will remain one of the most critical parts of your infrastructure. Panther gives you the tools to improve your Okta monitoring posture on day 1, and additional tools and expertise to level up those detections on day 2.