AppsFlyer Web SDK Compromise: Independent Payload Analysis
A deep-dive into the modular crypto-theft toolkit injected via AppsFlyer’s Web SDK on March 10, 2026, based on original deobfuscation of…
AppsFlyer Web SDK Compromise: Independent Payload Analysis

Modular Crypto-Theft Toolkit
A deep-dive into the modular crypto-theft toolkit injected via AppsFlyer’s Web SDK on March 10, 2026, based on original deobfuscation of two independently captured samples.
On March 10, 2026, AppsFlyer notified customers that their Web SDK had been compromised. Between 01:00 and 10:00 UTC, any website loading the SDK served malicious JavaScript to visitors.
The initial community analysis characterized this as a simple crypto clipper — a script that swaps cryptocurrency wallet addresses. After performing my own independent deobfuscation of two separate captures of the payload, I can confirm the crypto-clipping function, but the full picture is significantly more concerning. This is a professional-grade interception framework with seven distinct modules, anti-detection capabilities, value-threshold targeting, and a polymorphic obfuscation pipeline that defeats signature-based detection.
Here’s everything I found.
Analysis Methodology
The payload is a ~170 KB minified JavaScript file using multi-layered base91 string encoding with 17 distinct shuffled alphabets. Every meaningful string — function names, API methods, error messages, URLs — is encoded and resolved at runtime through a caching decoder.
I built a sandboxed Node.js VM that executed only the string decoding infrastructure while stubbing all browser APIs, network access, and DOM interaction. This extracted 786 decoded strings from Sample A and cross-validated 319 readable strings from Sample B, confirming functional identity despite completely different obfuscation layers.
No network requests were made, no C2 servers were contacted, and no malicious functionality was invoked during analysis.
Payload Architecture: 7 Modules
The decoded string table reveals named module exports. This is not a simple clipboard hijacker — it is a modular toolkit:
[166] ActuateElements — DOM mutation surveillance
[167] ConsoleGuard — Console output suppression
[168] Destinations — Attacker wallet management
[169] KillElements — Anti-forensics element removal
[170] NetHooksmith — Network request/response interception
[171] XorCipherBytes — XOR encryption for C2 comms
[713] Accounting — Multi-currency portfolio tracking
[714] AccountingForTransfer — Value-threshold transfer logic
Let’s go through each one.
Module 1: NetHooksmith — Full Network Interception
This is the most dangerous module. It replaces the browser’s native fetch function and patches XMLHttpRequest.prototype.open, .send, and .setRequestHeader to intercept all HTTP traffic on the page.
Request manipulation: setUrl [519], setMethod [521], setHeader [522], deleteHeader [523], setBody [525] — full control over outgoing requests. block [526] can prevent requests entirely and return synthetic responses via respond [528].
Response manipulation: getResponseBody [610], setResponseBody [611], setStatus [612], setResponseHeader [613] — can silently modify API responses before the application processes them. Supports json, text, blob, and arraybuffer response types.
Interceptor registry: A register/unregister system with before/after hooks. A when field with test and match support suggests regex-based URL or content matching to selectively activate interceptors.
Cookie manipulation: Strings expires= [509] and ;path=/ [510] indicate the payload can write cookies to the host domain — useful for session fixation, visitor fingerprinting, or C2 state tracking.
XHR edge cases: The payload explicitly handles synchronous XHR edge cases ("NetHooksmith: deleteHeader not supported in Sync XHR", "NetHooksmith: responseType not supported in Sync XHR", "Error: Async hook detected in Synchronous XHR. It will be ignored.") — indicating thorough engineering to handle all browser contexts without crashing the host page.
Bottom line: Any website loading this payload had every fetch/XHR request and response flowing through attacker-controlled proxy functions. This includes API calls carrying auth tokens, session cookies (
withCredentialsis explicitly handled), and any data the server returns.
Module 2: ActuateElements — Shadow DOM-Aware DOM Surveillance
A sophisticated MutationObserver-based system that monitors the entire DOM for changes — including inside Shadow DOM boundaries.
Core watching: Uses MutationObserver with childList and subtree for full-tree monitoring. CSS selector-based targeting via selector and matches identifies specific elements (likely input fields).
Shadow DOM penetration: This is a critical finding. The decoded strings includeShadowDOM [176], attachShadow [353], _patchAttachShadow [351], _patchedAttachShadow [352], and _origAttachShadow [447] reveal that the payload monkey-patches Element.prototype.attachShadow so that every Shadow Root created on the page is automatically monitored. _coverExistingShadowRoots [344] also scans for Shadow DOMs created before the payload initialized.
Why this matters: Many modern payment forms, password managers, and secure input components use Shadow DOM to isolate sensitive fields from third-party JavaScript. This payload explicitly defeats that encapsulation by intercepting the
attachShadowcall itself.
Additional capabilities include rescanAncestors with configurable ancestorDepth, _queueRescan for deferred re-analysis, createNodeIterator with SHOW_ELEMENT for deep tree walking, and observeAttributes with attributeFilter for watching attribute changes on targeted elements.
Module 3: KillElements — Anti-Forensics
Actively removes DOM elements matching specified selectors: querySelectorAll → forEach → parentNode → remove. Operates across Shadow DOM boundaries. Has its own _pendingRescans system to catch newly added elements.
Likely uses: removing CSP violation banners, stripping security warning overlays, eliminating competing detection scripts or ad fraud tools.
Module 4: Destinations — Wallet Rotation Pool
Manages a pool of attacker-controlled cryptocurrency wallet addresses. addDestination stores wallets with currency + address + label. **getRandomDestination** uses Math.floor(Math.random()) to randomly select from multiple wallets per currency.
By distributing stolen funds across multiple wallets, the attacker makes blockchain analysis significantly harder — each wallet receives fewer transactions, reducing automated detection likelihood.
Module 5: Accounting — Value-Threshold Targeting
This was not identified in the initial community analysis. It reveals the payload doesn’t blindly swap every crypto address — it evaluates economic value first.
A full multi-currency portfolio tracker: baseCurrency, setBaseCurrency, setBalance, setRate/rateToBase for exchange rate conversion, getBalanceValueInBase, getTotalInBase.
The key finding: **getBalancesValuedAboveInBase** with a minBaseValue parameter converts all observed wallet balances to a common base currency (likely USD) and filters for only those exceeding a configurable minimum. AccountingForTransfer [714] is a separate class for the steal-or-skip decision.
This is operationally sophisticated. By ignoring low-value wallets, the payload:
- Avoids triggering alerts on insignificant transactions
- Reduces on-chain traces pointing to attacker infrastructure
- Focuses on high-value targets where risk/reward is favorable
Validation strings ("currency must be a non-empty string", "must be a finite number", "Base currency rate must be exactly 1", "rateToBase must be > 0") confirm production-quality code with defensive error handling.
Module 6: ConsoleGuard — Console Suppression
Replaces every method on the console object with no-ops:
log, info, warn, error, debug, dir, dirxml, table,
trace, group, groupCollapsed, groupEnd, count, countReset,
assert, profile, profileEnd, time, timeLog, timeEnd, timeStamp
disableOnStart activates suppression immediately. Original methods stored in originalConsoleMethods for potential restoration. This means: if you had dev tools open during the incident window, you would not have seen any output from the payload's operation.
Module 7: XorCipherBytes — C2 Encryption
XOR-based symmetric encryption (encrypt/decrypt) with a configurable key. Used to obfuscate data sent to and received from the C2 server.
The payload also includes an inline SHA-256 implementation, confirmed by decoded string [507] containing the known SHA-256 hash of "abc" (ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad). Built-in hex encoding and Base64 utilities make the payload fully self-contained — it avoids browser crypto APIs that security tools might monitor.
Polymorphic Obfuscation: Multi-Variant Builds
Two independently captured samples were analyzed and confirmed to be functionally identical but structurally distinct. The attacker uses a polymorphic build pipeline.
What changes per build: Every variable name (kFmXE0 → eD9kupT), every base91 alphabet (all 17 reshuffled), numeric encoding (hex vs decimal), file formatting (minified vs prettified), and all file hashes.
What stays constant: Module export names, API method names, error message strings, the number of globalThis references (12), RegExp uses (18), fetch references (2), MutationObserver references (2), and stripped file size (~167 KB).
Sample A (minified): 170,223 bytes, MD5 4be8975f21979a14d826cf07ad50a152
Sample B (prettified): 222,156 bytes, MD5 f13a118acc7d5b1a1432d4bf48deb4d2
Both decode to byte-identical module names, method names, and error strings.
Detection implication: Signature-based detection (matching specific variable names, string hashes, regex patterns) will only catch the specific sample analyzed. A fresh build evades immediately. Detection must target structural patterns or runtime behaviors.
Exposure Assessment for Non-Crypto Sites
While the primary objective is cryptocurrency address swapping, the infrastructure has far broader implications for any website that loaded the compromised SDK during the 9-hour window.
CRITICAL — All network traffic visible. Every fetch/XHR request and response passed through attacker-controlled proxies, including API calls with auth tokens and session data.
CRITICAL — Shadow DOM defeated. Secure form components using Shadow DOM for input isolation were penetrated via attachShadow monkey-patching.
HIGH — Dynamic C2 control. Wallet addresses were fetched at runtime. The C2 could have served different instructions to different domains — a crypto clipper for exchanges, a credential harvester for banks.
HIGH — Cookie write access. The payload could set cookies on the host domain for session fixation, visitor tracking, or persistent state.
MEDIUM — Console suppressed. Developer tools would not show errors or warnings from the payload.
MEDIUM — DOM elements killed. Security warnings, CSP violation banners, or detection scripts could be silently removed.
Key uncertainty: Because the C2 server provided runtime configuration, static code analysis alone cannot determine the full scope of what was exfiltrated. The crypto-clipping behavior is what’s visible in the code, but the framework was capable of arbitrary data interception per C2 instruction. Only AppsFlyer or law enforcement with C2 server access can confirm the full operational scope.
One bright spot: The browser’s same-origin policy still applies. If your site uses a cross-origin payment iframe (Stripe Elements, Braintree, Adyen, etc.), the payload’s hooks cannot reach across that origin boundary. Payment data in a cross-origin iframe remains protected. However, if your payment flow collects card data in your own DOM and submits it via your own JavaScript, the network hooks would have had full visibility.
Indicators & Detection
Network Indicators
Check server/CDN logs for the 01:00–10:00 UTC window on March 10, 2026. Look for requests to AppsFlyer CDN domains that served the Web SDK bundle. A suspicious endpoint websdk.appsflyer.com/v1/api/plugin was observed during the compromise window and is not present in current AppsFlyer documentation — this may have been the C2 endpoint.
Stable Identifiers (across builds)
Module exports: ActuateElements, ConsoleGuard, Destinations,
KillElements, NetHooksmith, XorCipherBytes,
Accounting, AccountingForTransfer
Error strings:
"ActuateElements.updateCallback: callback must be a function."
"KillElements.updateSelector: selector must be a non-empty string."
"NetHooksmith: deleteHeader not supported in Sync XHR"
"[NetHooksmith] Fetch before hook failed:"
"[NetHooksmith] Error: Async hook detected in Synchronous XHR."
Inline SHA-256: ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
Structural Detection (for static analysis)
- 15+ distinct strings of length 85–91 characters (base91 alphabets) in a single file
- Constant array of 700+ entries mixing integers, strings, booleans, and
null - Multiple nested functions with pattern:
var X = "91-char-string"; indexOf()with bitwise shift/mask (& 8191,> 88,<<,>>=) - Stripped file size between 165–170 KB
- Exactly 12
globalThisreferences, 18RegExpuses, 2fetchreferences, 2MutationObserverreferences
Runtime / Behavioral Detection
window.fetch !== originalFetch(fetch replaced)XMLHttpRequest.prototype.openwrappedElement.prototype.attachShadowwrapped- All
consolemethods point to the same no-op function - New cookies with
path=/set during the incident window MutationObserveractive on document root withchildList+subtree+ Shadow DOM traversal
Recommendations
Confirm exposure. Review CDN/server logs for the 01:00–10:00 UTC window.
Assess payment isolation. Cross-origin payment iframes were protected. Same-origin payment handling was not.
Request forensic data from AppsFlyer. Ask: (1) what was the C2 endpoint, (2) was data exfiltrated from your domain, (3) did the C2 serve different configs to different domains, (4) full root cause analysis.
Implement Subresource Integrity (SRI). SRI hashes on third-party script tags would have caused the browser to reject the tampered SDK. This is the single most effective prevention.
Deploy Content Security Policy (CSP). A strict CSP with script-src restricting allowed origins would limit damage from compromised scripts and prevent C2 connections.
Monitor third-party scripts behaviorally. Due to polymorphic obfuscation, hash-based or signature-based monitoring is insufficient. Detection must flag fetch/XHR replacement, attachShadow patching, and wholesale console suppression at runtime.
Evaluate breach notification obligations. The network interception infrastructure had broad data visibility beyond crypto. Consult legal counsel.
Analysis based on two independently captured samples of the compromised AppsFlyer Web SDK payload: a minified variant (170 KB, MD5 4be8975f…a152) and a prettified variant from cometkim's gist (222 KB, MD5 f13a118a…b4d2). No malicious code was executed during analysis — only string decoding infrastructure was run in a sandboxed VM with all browser APIs stubbed.
Update (03/11/2026): AppsFlyer expanded the exposure window to March 9 20:40 UTC to March 10 10:30 UTC.
메타데이터
- post_id
- 109afd72aba9
- slug
- appsflyer-web-sdk-compromise-independent-payload-analysis-109afd72aba9
- url
- https://medium.com/@_ifnull/appsflyer-web-sdk-compromise-independent-payload-analysis-109afd72aba9
- canonical_url
- https://medium.com/@_ifnull/appsflyer-web-sdk-compromise-independent-payload-analysis-109afd72aba9
- author_url
- https://medium.com/@_ifnull
- status
- ok
- fetched_at
- 2026-07-14 20:10:23