> IT-Sentinel.com

// Cybersecurity & IT News Aggregator - Real-time Threat Intelligence Feed

NEWS CVE
← messages.back_to_articles

> Revoking the token didn’t kill the backdoor

[SOURCE] CSO Online [DATE: 21/09/2026 09:00] [LANGUAGE: EN]
Revoking the token didn’t kill the backdoor
Every identity-compromise runbook I have written, read or inherited has the same step near the top: revoke the tokens. Reset the password, kill the sessions, invalidate the refresh tokens, then go hunting. It is the right instinct. Against adversary-in-the-middle phishing, where the whole prize is a stolen session cookie, revocation is the move that ends the incident. Then I spent a few days taking apart a backdoor where that step buys you nothing at all. The reason sits in one function, and it is the last place most of us would think to look. The sample is GraphWorm, a custom implant tied to the China-nexus APT group Webworm. One thing I found there rewrote a line in my own incident response procedure, and I think it belongs in yours. The C2 channel is somebody’s OneDrive GraphWorm has no C2 domain, no beacon to a rented VPS, no hardcoded address to block. It authenticates to Microsoft Graph as an OAuth application and uses a OneDrive account as a dead drop. The layout is simple enough to sketch on a napkin. The operator writes an encrypted task file into a job folder. The implant polls that folder, runs the task, then uploads the encrypted result into a result folder. A heartbeat file gets a fresh timestamp every interval and a fingerprint file carries the victim’s system details. The command set covers shell execution, file upload and download, sleep, kill and a key exchange pair that upgrades the deployment key to per-session keys. All of it rides graph.microsoft.com over TLS, from a host that is already talking to Microsoft 365 all day. There is no unusual destination for a firewall to flag, no freshly registered domain for a reputation engine to penalize, no odd port. The network layer has nothing to say. That is the design goal, and it is the same shift I have written about before, where operators stop building their own infrastructure and start running on somebody else’s. The credentials are all sitting in the binary in cleartext: a client ID, a client secret, a tenant ID and a refresh token more than 1,300 characters long. The operator also shipped a debug build with the full PDB path intact. One more detail matters for anyone planning a response. The implant does not identify its victims by hostname or address. It builds an identifier by hashing the network adapter’s hardware address together with CPU and disk serial numbers pulled through WMI. Rename the machine, move it to a different subnet or put it behind a new egress address, and the operator still recognizes it as the same box. Any containment plan that leans on changing the victim’s network identity is solving a problem this implant does not have. So far this is a good story but a familiar one. Cloud-hosted C2 is not new. The part I did not expect was in the tasking handler. The function that breaks the runbook Among the commands the implant accepts is one called upgrade. The flow is short, and it is worth walking through. The handler parses a configuration blob out of the incoming task. It then destroys all five credential strings held in the beacon object, copies five new ones in, rebuilds the OAuth scope structure, tests a connection against the new OneDrive account, writes the replacement configuration to disk and swaps the live API instance. One task. Entire identity replaced. Sit with the consequence for a second. Your investigation reaches the point where you have identified the application, you file with the platform and the tokens get revoked. The implant’s next poll fails. If the operator is watching, and an operator writing a heartbeat file every interval is watching, they queue an upgrade task pointing at a second OneDrive account they registered months ago. The implant collects it, rotates and resumes. Nothing on the endpoint changed. No new binary, no new persistence mechanism, no new process. Same file on disk, different identity behind it. Revocation removed a credential. It did not remove access. This does not appear in the public reporting on the family, which is not a criticism. Vendor writeups are scoped to the campaign they observed, and this is the kind of detail you only see with the decompiler open on a function nobody had a reason to prioritize. I confirmed it twice before I was willing to write it down, once from the extracted strings and once from the decompiled function itself. The credential field offsets I recovered from the strings had to line up with the offsets the constructor actually reads from, and they did, which is the difference between a finding and a guess. What I changed afterward Three things came out of this that I would put into an identity response procedure tomorrow. Treat revocation as a delay rather than an eviction whenever the C2 rides an application identity. The durable object is the registration, not the tokens it issues. Tokens are leaves, the registration is the root, and containment that stops at revoking has started a timer rather than closed a door. If the registration lives in a tenant, you do not control, suspension means filing a report and waiting on somebody else’s queue, so file early rather than as a last step. Assume the operator holds a spare. Credential rotation costs an attacker almost nothing and costs you an entire response cycle. That asymmetry should drive sequencing: remove the endpoint’s ability to reach the channel at the same moment you burn the credential, not afterward. Isolating the host, or blocking that specific application from authenticating in your own tenant, are both moves that do not depend on anyone else’s cooperation. Hunt on the identity plane, because the network plane is blind here. The detections that actually fire on this family are not network signatures at all. They are cloud telemetry queries: the operator’s application ID appearing in your sign-in events, authentication against an unfamiliar tenant, a non-browser HTTP library user agent hitting OneDrive, the beacon and fingerprint file names showing up in file telemetry. The application ID is a fixed string; it belongs to nobody legitimate, and a single query across your sign-in telemetry answers whether it has ever asked your tenant for a token. None of that needs new tooling. It needs the application registration treated as the thing you are trying to kill. The wider pattern is what stayed with me. We spent a decade learning to hunt infrastructure, and adversaries answered by not having any. When the channel is a folder in a cloud tenant, and the identity behind that folder can be swapped by remote command, the artifacts we are trained to chase are precisely the ones an operator can replace most cheaply. What they cannot replace cheaply is the code sitting on the endpoint and the behavior of that code inside your telemetry. So, I check the assumption now. Before calling an identity incident contained, I ask what specifically I killed, and whether the adversary can hand themselves a replacement without ever touching the victim’s machine. In this sample, the answer was yes, and it took one function to prove it. The full teardown and detection content from this analysis are published on GitHub.
[messages.read_original_source] →