Between 28 September and 17 October 2023 an attacker used the credentials of a service account, which an employee had saved to a personal Google profile in Chrome on a work laptop, to download files from Okta's customer support system. Some were HAR files that customers had uploaded for troubleshooting, and they contained live session tokens, which the attacker replayed to hijack the Okta sessions of five customers, including 1Password, BeyondTrust and Cloudflare. Treat HAR files as credentials, bind and shorten administrator sessions, keep service-account secrets out of browsers, and rotate everything an incident exposes.
- The service account could view and update support cases; its username and password were saved in an employee's personal Google account, most likely exposed through that account or a personal device, according to Okta.
- HAR files record every header, including cookies and Authorization tokens, and a valid session cookie needs no password and no MFA.
- Okta logged downloads from the support system's Files tab under a different event type from case attachments, so its first 14 days of investigation missed them.
- Customers found it first: 1Password reported suspicious activity on 29 September, BeyondTrust on 2 October and Cloudflare detected an attack on 18 October.
- The attacker also ran a report listing the names and email addresses of all support-system users, which Okta disclosed on 29 November.
- Cloudflare's later Thanksgiving 2023 intrusion used one service token and three service account credentials from this breach that it had not rotated.
Incident at a glance
- Dates
- to
- Category
- Credential theft
- Organizations
- Okta
- 1Password
- BeyondTrust
- Cloudflare
- Impact
- With a stolen service-account password, an attacker read files of 134 customers in Okta’s support system and replayed session tokens from HAR files to hijack the Okta sessions of five, including 1Password, BeyondTrust and Cloudflare.
- How it was found
- Customers saw it first: 1Password and BeyondTrust reported suspicious activity in their Okta instances, and an IP address from BeyondTrust led Okta to the service account on 16 October.
Timeline
- First access to the support system through the service account.
- 1Password reports suspicious activity; Okta opens an investigation.
- BeyondTrust reports suspicious activity.
- Okta finds the service account using an IP address from BeyondTrust.
- Okta disables the service account and starts revoking the session tokens found in the HAR files.
- Cloudflare detects an attack using a session token from a support ticket.
- Okta discloses the breach; Cloudflare publishes its account.
- Okta publishes its root cause analysis.
- An attacker enters Cloudflare with credentials from the breach that it had not rotated, staying until 24 November.
- Okta discloses that a report of all support-system users’ names and email addresses was downloaded.
- Cloudflare discloses its Thanksgiving 2023 intrusion.
| Setting | Value | Note | Source |
|---|---|---|---|
| Attacker access to the support system | 28 September to 17 October 2023 | — | Okta root cause analysis (3 November 2023) |
| Customers whose files were accessed | 134, under 1% of Okta customers | — | Okta root cause analysis |
| Customers whose sessions were hijacked | 5 | 1Password, BeyondTrust and Cloudflare described their own detection publicly. | Okta root cause analysis |
| Entry point | A support-system service account whose password was saved in a personal Google profile | — | Okta root cause analysis |
| Okta's added control for admin sessions | Re-authentication when the network (ASN) of an admin session changes | Plus new Admin Console defaults: 12-hour session, 15-minute idle timeout. | Okta update, 29 November 2023 |
| Chrome HAR exports | Exclude Cookie, Set-Cookie and Authorization headers by default since Chrome 130 | Tokens in URLs and bodies are not removed. | Chrome for Developers: What's new in DevTools 130 |
| Credentials Cloudflare failed to rotate | 1 service token and 3 service accounts, out of thousands | Used in its 14 to 24 November 2023 intrusion. | Cloudflare: Thanksgiving 2023 security incident |
- Upload a test HAR file to your support or ticketing system and confirm it is sanitized, quarantined or rejected before any agent can open it.
- Replay an administrator session cookie from another network and confirm your identity provider or admin console forces re-authentication.
- Confirm administrator sessions end after a short idle time and a fixed maximum, and that admin actions require phishing-resistant MFA.
- Check that managed browsers block personal profile sign-in and that no service-account password is stored in any browser or personal password manager.
- Search your audit logs for every way a file can be read or downloaded, not only the usual one, and confirm each produces an alertable event.
- After any third-party breach, list every credential that the provider ever held or could have seen, rotate all of them and record the evidence.
- Applies to
Teams that ask customers or colleagues for HAR files, run support or ticketing systems, operate identity providers or admin consoles, or hold service-account credentials.
- Part of
- Workflows: Fix a finding, Respond to a vulnerability. Collection: case studies
Tested fixes: the vulnerable code, the fix, a plausible fix that still fails, and the regression tests that tell them apart.
Learning goals and prerequisites
What you will learn
- Explain why a HAR file is a credential and remove the credentials from one before sharing it.
- Trace the attack from a saved service-account password to hijacked customer sessions.
- Name the controls that limit a stolen session: binding, short lifetimes, reauthentication and revocation.
- Plan a complete credential rotation after a provider's breach.
Before you start
Developers and support engineers who collect or share HAR files, identity and platform teams who run admin consoles and service accounts, and incident responders.
- Know what a session cookie is and why it stands in for the password after login.
- Read JSON and an HTTP request.
- For the code: Python 3.11 or later, no packages needed.
Refresh first: Session management: cookies, timeouts, rotation and revocation
Go to the guided practiceOkta's support engineers can ask customers for a HAR file, a recording of the browser's HTTP traffic, to reproduce a problem, and customers attach them to cases in Okta's customer support system. Okta said its production service was not affected. On 20 October 2023 Okta disclosed that an attacker had used stolen credentials to reach that system and view files uploaded by some customers, and warned that HAR files can contain cookies and session tokens.
Okta's root cause analysis of 3 November 2023 gave the details. From 28 September to 17 October the attacker had access to files belonging to 134 customers, under 1% of Okta's customer base. Some files were HAR files whose session tokens the attacker used to hijack the legitimate Okta sessions of five customers. Okta revoked the session tokens embedded in the stolen files on 17 and 19 October.
| Date (2023) | Event |
|---|---|
| 28 September | First access to the support system through the service account. |
| 29 September | 1Password reports suspicious activity in its Okta instance; Okta opens an investigation. |
| 2 October | BeyondTrust reports suspicious activity. |
| 13 October | BeyondTrust gives Okta an IP address linked to the attacker. |
| 16 October | Okta uses the IP address to find the service account in its support-system logs. |
| 17 October | Okta disables the service account and revokes the session tokens found in the HAR files. |
| 18 to 19 October | Cloudflare detects an attack using a session token from a support ticket; Okta finds more downloaded files and identifies Cloudflare as the fifth targeted customer. |
| 20 October | Okta discloses the breach; Cloudflare publishes its account the same day. |
| 3 November | Okta publishes its root cause analysis. |
| 29 November | Okta discloses that the attacker downloaded a report of all support-system users' names and email addresses. |
The attacker logged in as a service account that could view and update customer support cases. During the investigation Okta found that an employee had signed in to a personal Google profile in Chrome on an Okta-managed laptop, and that the service account's username and password had been saved into that personal Google account. Okta concluded that the most likely route to the credential was a compromise of the employee's personal Google account or personal device.
Two weaknesses combined. A shared service account authenticated with a password that a person could type, save and sync, and the managed laptop allowed a personal browser profile that synced saved passwords to an account the company did not control. Okta's fixes followed: it disabled the service account and configured Chrome Enterprise so employees cannot sign in to Chrome on managed laptops with a personal Google profile. Chrome's BrowserSignin and RestrictSigninToPattern enterprise policies control which accounts can sign in to the browser.
For your own systems, give integrations workload identity (/domains/identity-crypto/identity/workload-identity-models/) or vault-issued, short-lived credentials instead of passwords, keep any remaining service-account secrets in a secret manager that people cannot browse, and make sure no human-usable credential for a privileged system can end up in a synced browser profile.
A HAR (HTTP Archive) file records every request and response the browser made while recording: URLs with their query strings, request and response headers, cookies, form fields and response bodies. That includes the Cookie header carrying the session ID, Authorization: Bearer tokens, OAuth authorization codes in callback URLs, and tokens returned in JSON. A valid session cookie stands in for the user: it does not need the password, and the MFA prompt already happened before the cookie was issued.
Browsers have improved. Since Chrome 130, DevTools exports HAR files without the Cookie, Set-Cookie and Authorization headers by default, and including them needs an explicit setting. Tokens in URLs, request bodies and responses are still exported. After the breach Cloudflare published an open-source HAR sanitizer (github.com/cloudflare/har-sanitizer), and Okta now tells customers to sanitize credentials and session tokens before sharing a HAR file.
The script below removes the headers and cookies that carry credentials, redacts query parameters, form fields and JSON keys whose names suggest secrets, replaces anything shaped like a JWT, and drops base64-encoded bodies it cannot inspect. It was tested on Python 3.14 against a HAR containing a session cookie, a Bearer JWT, an OAuth code, an ID token and a refresh token in a JSON response, and a password in a form post. Treat its output as reduced risk, not zero: review the file, revoke the sessions you recorded, and have support systems sanitize uploads on arrival.
"""Strip credentials from a HAR file before it leaves your machine: python sanitize_har.py in.har out.har"""
import json
import re
import sys
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
DROP_HEADERS = {"authorization", "proxy-authorization", "cookie", "set-cookie", "x-api-key"}
SECRET_NAME = re.compile(r"(?i)token|code|secret|passw|session|sid|key|assertion|signature|auth")
JWT = re.compile(r"eyJ[\w-]{4,}\.[\w-]{4,}\.[\w-]*") # header.payload.signature
GONE = "[REDACTED]"
def scrub(value):
"""Redact secret-looking fields in JSON, and JWTs in any text."""
if isinstance(value, dict):
return {k: GONE if SECRET_NAME.search(k) else scrub(v) for k, v in value.items()}
if isinstance(value, list):
return [scrub(v) for v in value]
return JWT.sub(GONE, value) if isinstance(value, str) else value
def scrub_pairs(pairs):
return [(k, GONE if SECRET_NAME.search(k) else scrub(v)) for k, v in pairs]
def scrub_text(text, mime=""):
if "x-www-form-urlencoded" in mime:
return urlencode(scrub_pairs(parse_qsl(text, keep_blank_values=True)), safe="[]")
try:
return json.dumps(scrub(json.loads(text)))
except ValueError:
return scrub(text)
def scrub_message(message):
message["headers"] = [h for h in message.get("headers", []) if h["name"].lower() not in DROP_HEADERS]
message["cookies"] = [] # the parsed copy of the Cookie and Set-Cookie headers
for field in ("queryString", "params"):
for pair in message.get(field, []) + message.get("postData", {}).get(field, []):
pair["value"] = GONE if SECRET_NAME.search(pair["name"]) else scrub(pair["value"])
if "url" in message:
parts = urlsplit(message["url"])
query = scrub_pairs(parse_qsl(parts.query, keep_blank_values=True))
message["url"] = urlunsplit(parts._replace(query=urlencode(query, safe="[]")))
for body in (message.get("postData"), message.get("content")):
if body and body.get("encoding") == "base64":
body["text"] = "" # binary or encoded bodies cannot be inspected: drop them
elif body and body.get("text"):
body["text"] = scrub_text(body["text"], body.get("mimeType", ""))
if __name__ == "__main__":
har = json.load(open(sys.argv[1], encoding="utf-8"))
for entry in har["log"]["entries"]:
scrub_message(entry["request"])
scrub_message(entry["response"])
json.dump(har, open(sys.argv[2], "w", encoding="utf-8"), indent=2)Okta's investigation started on 29 September, after 1Password's report, but for 14 days it found no suspicious downloads. The reason was in the logs: opening a file attached to a support case produced one log event, while going straight to the support system's Files tab, as the attacker did, produced a different event type with a different record ID. The early searches looked for case access. Only the IP address BeyondTrust supplied on 13 October led Okta to the service account and its activity.
Customers detected the attack before the provider did. 1Password and BeyondTrust reported suspicious activity in their own Okta instances, BeyondTrust supplied the IP address that unlocked the investigation, and Cloudflare detected activity on 18 October, more than 24 hours before Okta notified it. Detection that depends on one expected access path is blind to the others: list every way data can be read or exported, make each one produce an alertable event, and alert on a service account downloading files it has never touched.
Phishing-resistant MFA protects the login, not the session that follows it. Once a valid session token is copied, the controls that matter are those the server applies on every request: binding the session to the client, keeping it short, and requiring fresh authentication for sensitive actions.
Okta's response shows the pattern. It released session token binding based on network location, so an administrator must re-authenticate when a session is reused from a network with a different autonomous system number (ASN), and set new Admin Console defaults of a 12-hour session and a 15-minute idle timeout, the NIST AAL3 limits. Cloudflare's advice to Okta customers was to use hardware MFA, shorten session lifetimes and investigate unexpected MFA and support-initiated changes. Device Bound Session Credentials, which bind cookies to a key in the device's TPM, address the same theft for browser sessions; the session guide at /domains/identity-crypto/authentication/session-lifecycle/ covers it with timeout values.
Cloudflare contained the October attack, then rotated credentials it believed had been exposed. On 1 February 2024 it disclosed that an attacker had been in its systems from 14 to 24 November 2023 using credentials stolen in the Okta compromise: Cloudflare had failed to rotate one service token and three service account credentials, out of thousands, because it mistakenly believed they were unused. They gave access to its Atlassian systems and source code repositories.
Its response was thorough: it rotated more than 5,000 production credentials, triaged 4,893 systems, reimaged and rebooted every machine in its global network, and deleted the HAR files on its own wiki. The lesson is that after a provider's breach you rotate every credential the provider held or could have seen, including the ones you think are dead, and you keep a record of each rotation; see /domains/identity-crypto/secrets/leaked-secret-response/ for the order.
- Treat HAR files, logs, crash dumps and screen recordings as credentials: sanitize before upload, sanitize again on arrival, restrict who can read them and delete them when the case closes.
- Never ask a customer for a HAR file without telling them how to sanitize it, and revoke the sessions it recorded after use.
- Give service accounts non-human credentials, and block personal browser profiles and password sync on managed devices.
- Bind administrator sessions to the client, keep them short, and require reauthentication for sensitive actions.
- Make every data-access path produce an alertable log event, and test detection against the paths an attacker would choose.
- After any breach of a provider, rotate everything it could have exposed, including credentials believed unused.
Guided practice
A customer has uploaded the HAR file below to your support ticket. Analyze it, decide what an attacker who stole it could do, and produce the sanitized version and the revocation list. Write your answer before reading the worked answers.
Supplied scenario material
{"log": {"entries": [
{"request": {"method": "GET", "url": "https://acme.id.example/app/admin?tab=users",
"headers": [{"name": "Cookie", "value": "sid=1023fa77c9; theme=dark"}]},
"response": {"status": 200, "headers": []}},
{"request": {"method": "GET", "url": "https://app.example/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj"},
"response": {"status": 302, "headers": [{"name": "Set-Cookie", "value": "__Host-session=Qm9iJ3Mgc2Vzc2lvbg; Path=/; Secure; HttpOnly"}]}},
{"request": {"method": "POST", "url": "https://api.example/v1/token",
"postData": {"mimeType": "application/x-www-form-urlencoded", "text": "grant_type=refresh_token&refresh_token=8xLOxBtZp8"}},
"response": {"status": 200, "content": {"mimeType": "application/json",
"text": "{\"access_token\":\"eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJhbGljZSJ9.c2ln\",\"expires_in\":600}"}}}
]}}
Context: sid is the identity provider's admin session cookie; the customer is an administrator.
The OAuth code was redeemed when this HAR was recorded 20 minutes ago; codes expire after 60 seconds.1. Find the credentials
List every value in the file that grants access, with what it grants and whether it is probably still valid.
Expected result and reasoning
sid(admin session at the identity provider, valid until it expires or is revoked: the most dangerous item);__Host-session(the app session issued after login, valid);refresh_token8xLOxBtZp8(can mint new access tokens until rotated or revoked); the access token (a JWT valid for 600 seconds from issue, probably expired after 20 minutes). The OAuth code is single-use and already redeemed, so it is not usable.theme=darkandstateare not credentials.2. Describe the attack
Explain what an attacker who downloaded this file could do, and why MFA does not stop it.
Expected result and reasoning
Replay sid from their own browser to act as the administrator in the identity provider's admin console, for example to add an MFA factor or an admin account, and use the refresh token to keep minting access tokens. MFA protected the login that created these sessions; the tokens are what the server checks afterwards, so unless sessions are bound to the client or re-authentication is required, the attacker is the administrator.
3. Produce the sanitized file
Write the sanitized HAR by hand or with the script on this page, and check it.
Expected result and reasoning
CookieandSet-Cookieheaders removed;codereplaced by[REDACTED]whilestateandtabremain;refresh_tokenin the form body replaced by[REDACTED];access_tokenreplaced by[REDACTED]in the JSON body. Running the page's script gives this result; grep the output for1023fa77c9,Qm9iJ3Mand8xLOxBtZp8and confirm nothing is found.4. Write the revocation list
Decide what to revoke now, even after sanitizing.
Expected result and reasoning
Revoke the administrator's identity-provider sessions (
sid), the app session and the refresh token, because the unsanitized file existed in the ticket and possibly elsewhere. Ask the customer to sign in again, and review the admin console's audit log since the recording time for unexpected factor, user or policy changes.5. Fix the process
Write two rules for your support process and one for your identity provider configuration.
Expected result and reasoning
Support: give customers sanitization instructions with every HAR request, and sanitize or quarantine uploads automatically and delete them when the ticket closes. Identity provider: bind admin sessions to the client network or device and keep admin idle and absolute timeouts short (for example 15 minutes and 12 hours), so a copied cookie fails or expires quickly.
The HAR is shortened and synthetic, and the token values are made up. Name-based redaction misses secrets in fields with unremarkable names; review files manually before sharing.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- Okta: tracking unauthorized access to Okta's support system (20 October 2023)
- Okta: unauthorized access to Okta's support case management system, root cause and remediation (3 November 2023)
- Okta customer support incident update, 29 November 2023 (SEC Form 8-K exhibit 99.2)
- Cloudflare: how Cloudflare mitigated yet another Okta compromise (20 October 2023)
- Cloudflare: introducing HAR Sanitizer (26 October 2023)
- Cloudflare: Thanksgiving 2023 security incident (1 February 2024)
- 1Password: Okta support system incident and 1Password
- Chrome for Developers: What's new in DevTools (Chrome 130), HAR exports exclude sensitive data
Reference information
- SDLC phase
- DevelopmentMaintenance
- ASVS 5.0.0 requirements
- v5.0.0-7.4.1 · v5.0.0-7.4.2 · v5.0.0-7.5.1 · v5.0.0-16.2.1
- CAPEC attack patterns