Convertr / Plausible Analytics Supply-Chain Skimmer
Executive Summary
A Brazilian Magecart group that Sansec has tracked since July 2024 planted a payment-card skimmer in the analytics script of Convertr, a Brazilian ecommerce platform that runs checkout for dozens of fashion brands. The group appended an obfuscated block to the stock tracker served from Convertr's own self-hosted Plausible Community Edition server, so every merchant that loads the tracker served the skimmer on its checkout pages. This is the group's first supply-chain attack: until now it compromised stores one at a time (Magento, OpenCart, WooCommerce).
The skimmer activates only on Convertr checkout pages, reads card number, expiry, CVV, cardholder name and the Brazilian CPF taxpayer number, joins them into a pipe-delimited record ending in a short victim tag, Base64-encodes it and POSTs it to 1x0.is. Sansec detected it on 2026-10-24, saw it updated on 2026-10-26 to match Convertr's payment API field names, and confirmed it removed on 2026-10-27 after Convertr cleaned the script and upgraded Plausible. Browsers can cache the infected script for up to 24 hours, so exposure runs to about 2026-10-28.
Entry point is unconfirmed. Sansec suspects CVE-2026-8467 (unauthenticated remote code execution in a component library exposed at /storybook in Plausible CE 3.0.0 through 3.2.0, fixed in 3.2.1) but states it has not seen Convertr's server logs and cannot confirm it.
Source Review & Web Hunter Notes
| Tier | Source | Key Finding | Carry |
|---|---|---|---|
| 1 · Vendor research | Sansec: Notorious Brazilian Magecart group hijacks Convertr checkouts via outdated Plausible install | Primary source. Skimmer behavior, exfiltration endpoint, 14 earlier domains, 6 Discord webhook IDs, infected-file SHA-256, timeline, suspected CVE (unconfirmed) | yes |
| 1 · Vulnerability record | NVD API record for CVE-2026-8467 | Code injection in the phoenix_storybook library (0.5.0 up to, not including, 1.1.0), CVSS 4.0 base 9.5, published 2026-05-20, status Deferred. NVD does not name Plausible; the Plausible CE version range comes from Sansec | yes |
| 2 · News of record | BleepingComputer: Hackers hijack Convertr analytics script to steal payment card data | Corroborates scope, dates, the unconfirmed CVE and the field-name update. Direct fetch was blocked (HTTP 403); only a condensed rendering was saved, so no IOC value is taken from it | partial |
| 3 · News | SecurityOnline: Brazilian Magecart Group Hits Convertr in Plausible Supply Chain Attack | Restates Sansec: eval of an appended character-code array, URL check, Base64 POST to a short Icelandic domain. Names no indicator values | partial |
Decisions: Sansec is the only source for the exfiltration domains, the webhook IDs and the file hash, so every indicator ships at detect, none at prevent or critical (a second independent source is required for those). The victim tracker host analytics.convertr.app.br and Plausible itself are legitimate and are never block candidates. CVE-2026-8467 is presented as a suspected, unconfirmed entry point throughout. A public proof-of-concept gist for the CVE turned up in search; it was not fetched or cited, because this pack carries no exploit code. No fetched source contained instructions directed at the reader.
Hunt Brief & Attack Chain
Working hypothesis: an organization either (a) has staff, QA or kiosk machines that loaded a Convertr checkout between 2026-10-24 and about 2026-10-28 and so ran the skimmer, or (b) runs its own Plausible Community Edition and is exposed to the same flaw. In case (a) the browser resolved an exfiltration domain, usually 1x0.is; in case (b) the analytics host may show command execution, a modified tracker file, or unusual outbound traffic.
| Step | Behavior | Telemetry | Hunt Angle |
|---|---|---|---|
| 1 · Initial access (suspected) | Unauthenticated command execution on an outdated Plausible CE via /storybook (T1190). Unconfirmed for this incident | Web access logs (outside endpoint telemetry); Process creation with command line and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | N1; Q4, Q5 |
| 2 · Tracker poisoned | Obfuscated character-code block appended to the served tracker file (T1195.002, T1565.001) | File integrity monitoring (outside endpoint telemetry); Process creation with command line and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | N2; Q6 |
| 3 · Skimmer loaded | Every merchant checkout loads the tracker; the appended block is decoded and run with eval (T1059.007, T1027) | Browser content (outside endpoint telemetry); DNS lookup with the requesting process (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents, DnsQueryResponse) | N4; Q3, Q9 |
| 4 · Gate on checkout | Runs only when the page URL contains checkout.convertr; field selectors updated 2026-10-26 to the payment API names | Browser content (outside endpoint telemetry) | N4 (CSP, script scan) |
| 5 · Capture | Card number, expiry, CVV, cardholder name and CPF read from the payment form and API requests (T1056) | Browser content (outside endpoint telemetry) | Known gap |
| 6 · Exfiltration | Pipe-delimited record, Base64, POST to 1x0.is/ws (T1041, T1132.001); earlier builds used other short domains, Discord webhooks or planted scripts such as cartao.php | DNS lookup with the requesting process (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents, DnsQueryResponse); proxy URL logs | Q1, Q2, Q8; N3 |
Consolidated IOC Table
| Type | Value | Conf | Action | Context |
|---|---|---|---|---|
| Domain (exfil) | 1x0.is | medium | detect | Current endpoint, path /ws over port 443; also seen a day earlier in a skimmer on another Brazilian store |
| Domain (exfil, earlier) | snf.is, kk.land, nmn.is, kof.one, anl.is, xsox.diy, eqp.is, shy.is, blz.is, tray.is, miorl.net, xwv.is, fasthook.app, antiddos-protection.net | medium | detect | 14 earlier exfiltration domains of the same group, replaced every few months; may be dormant or reassigned |
| Discord webhooks | 1349318491479605268, 1349948669419327508, 1351613753069801583, 1398562090934993036, 1414969348262596778, 1557650706259902495 | medium | hunt | Webhook IDs under discord.com/api/webhooks/; visible only in proxy URL logs. Do not block discord.com |
| URL | https://1x0.is/ws | medium | detect | POST target of the skimmer (/ws/<base64> also listed) |
| Filename | cartao.php, pagamento.php | low | hunt | Portuguese exfiltration script names; common legitimate page names, so a lead only |
| Victim host (legitimate) | analytics.convertr.app.br | high | detect (context) | Tracker host; clean since 2026-10-27. Exposure only, never prevent |
| Script path | /js/script.pageview-props.tagged-events.js | high | hunt | Infected file path on the tracker host |
| SHA-256 | ee4618d24737eff785e5a267b55903d5ef6ff7ecbeb099347a67fee77734d7f7 | medium | hunt | Infected tracker as observed 2026-10-26; one build, the skimmer was updated again |
| Skimmer strings | indexOf('checkout.convertr'); _s+=String.fromCharCode(_a[_i]);(0,eval)(_s) | medium | hunt | Strings in the loader; useful for script-content scans |
| Vulnerable range | Plausible CE 3.0.0 to 3.2.0 (fixed 3.2.1) | medium | audit | Suspected, unconfirmed entry point (CVE-2026-8467) |
Affected Surface & Telemetry Matrix
| Surface | Required Telemetry | Priority | Gap |
|---|---|---|---|
| Shoppers' browsers (the primary victims) | Page content and network requests in the browser | Critical | Outside endpoint telemetry and outside your control; only the platform, the merchant and the card issuers can act |
| Staff, QA and kiosk machines that load storefront checkouts | DNS lookup with the requesting process (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents, DnsQueryResponse) | High | Low where the sensor is deployed. Unmanaged and personal devices are invisible |
| Self-hosted Plausible CE analytics hosts | Process creation with command line and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents); Outbound network connection (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents); web access logs | High | Medium. Access logs and file contents are outside endpoint telemetry |
| Tracker script as served to customers | File integrity monitoring, CDN and cache captures, CSP reports | High | High. No endpoint query sees a write to a non-executable script file |
| Web proxy and gateway | URL and POST logs for 1x0.is/ws and Discord webhook paths | Medium | Depends on TLS inspection and log retention |
ATT&CK Mapping
| Tactic | Technique | Observed Behavior | Query / Control |
|---|---|---|---|
| Initial Access | T1195.002 Compromise Software Supply Chain | Poisoned tracker served to every merchant checkout | Q3; N2; S12 |
| Initial Access | T1190 Exploit Public-Facing Application | Suspected CVE-2026-8467 on Plausible CE (unconfirmed) | Q4, Q5, Q7; N1 |
| Impact / Defense Evasion | T1565.001 Stored Data Manipulation (closest fit) | Tracker file on disk modified to append the skimmer | Q6; N2 |
| Execution | T1059.007 JavaScript | Appended block executes in the browser via eval | Q9 (script hosts only); N4 |
| Defense Evasion | T1027 Obfuscated Files or Information | Array of character codes decoded at runtime | Q9; N4 |
| Collection | T1056 Input Capture | Card, expiry, CVV, name and CPF read from checkout form and payment API requests | Known gap; S12 controls |
| Exfiltration | T1041 Exfiltration Over C2 Channel | POST to 1x0.is/ws; earlier domains, webhooks, planted scripts | Q1, Q2, Q8; N3 |
| Command and Control | T1132.001 Standard Encoding | Base64 of a pipe-delimited record | Q1, Q2; N3 |
Native Audit-Log Hunts
| Hunt | Source | Logic | Response |
|---|---|---|---|
| N1 · Storybook requests | Reverse-proxy and Plausible access logs | Any request whose path starts with /storybook, any date. Sansec says to search for these on self-hosted Plausible CE | Any hit: rebuild the server, rotate its secrets, compare tracker files (N2) |
| N2 · Tracker integrity | Filesystem on the Plausible host; file-integrity monitoring | Compare every file in priv/tracker/js/ with the official release of the version you run. Look for an appended block after the stock script and the strings in section 10. Compare the SHA-256 of served copies with the infected value | Any difference: treat the host as compromised and rebuild |
| N3 · Proxy and WAF exfil traffic | Web proxy, gateway, WAF logs | POSTs to 1x0.is (path /ws), the earlier domains, discord.com/api/webhooks/ with the listed IDs, and requests naming cartao.php or pagamento.php | Identify the client, then run Q1 and Q2 for that machine |
| N4 · Checkout script review | CSP violation reports, synthetic checkout crawl, script inventory | Any script on a payment page that is not on the approved list; fetch the served tracker and scan for fromCharCode followed by eval and for checkout.convertr | Remove or block the script; escalate to the platform operator |
| N5 · Cache exposure window | CDN, reverse-proxy and browser cache settings | Confirm the served tracker hash is no longer the infected one, and purge caches. Browsers can hold the infected file for up to 24 hours after cleanup | Purge; extend the exposure window to 24 hours after the purge |
| N6 · Payment fraud signals | Payment provider and issuer fraud reports | Cards used on the affected checkout from 2026-10-24 to about 2026-10-28, and CPF misuse reports | Ask issuers to reissue; follow your payment provider's incident process |
Hunt Queries: CrowdStrike CQL, SentinelOne PowerQuery and Defender XDR KQL
Use your tenant's own address. SentinelOne does not document a link that pre-fills a query, so each "Copy & open SentinelOne" button copies the query and opens this console: paste it into PowerQuery.
Pick your portal. Microsoft documents a Share link in advanced hunting but not its URL format, so each "Copy & open Defender" button copies the query and opens Advanced hunting: paste it into the editor.
Looks for: any DNS request for 1x0.is or one of the 14 earlier exfiltration domains Sansec lists. Accomplishes: catches a staff, QA or kiosk machine whose browser ran the skimmer on a poisoned checkout. The skimmer runs in the shopper's browser, so this only sees machines your sensor covers.
Known limitations and deployment notes
- PowerQuery: Lookups made by a resolver outside the endpoint, or on a device without the agent, are not seen.
- PowerQuery assumes: event.dns.request holds the queried name on DNS Resolved events; the vendor docs also name dns.request, so the readiness test is the check.
- KQL – Defender XDR: Lookups made by a resolver outside the endpoint, or on a device without the sensor, are not seen.
- KQL – Defender XDR assumes: The queried name is stored under DnsQueryString or QueryName inside AdditionalFields; the readiness test prints the field so a reader sees which key their tenant populates.
// HUNT: DNS lookups for the campaign's exfiltration domains (any process) // MITRE: T1041, T1132.001 | CONF: high FP: low COST: low // REQUIRES: DnsRequest telemetry from staff, kiosk, QA and test machines that load storefront checkouts // FALSE POSITIVES: short, generic-looking names (fasthook.app, tray.is) could be reused by unrelated parties after the group stopped using them // TUNING: 1x0.is is the current endpoint, treat any hit as high priority; for the older names compare the first-seen date with the 2026-10-24 window before escalating #event_simpleName=DnsRequest | DomainName=/(^|\.)(1x0\.is|snf\.is|kk\.land|nmn\.is|kof\.one|anl\.is|xsox\.diy|eqp\.is|shy\.is|blz\.is|tray\.is|miorl\.net|xwv\.is|fasthook\.app|antiddos-protection\.net)$/i | table([@timestamp, aid, ComputerName, DomainName, ContextProcessId_decimal, RequestType], limit=200) | sort(@timestamp, order=desc)
3 required fields · View requirements
Required telemetry
- DNS Resolved
Required fields
event.time: Event timeevent.type: Event typeevent.dns.request: Queried name
Recommended fields
src.process.name: Requesting process
Fallbacks
event.dns.request→dns.request: Same value under the older field name; swap it if the readiness test shows only that one populated.
Readiness test
event.type = 'DNS Resolved' event.dns.request = * | limit 5 | columns event.time, event.type, event.dns.request
// HUNT: DNS lookups for the campaign's exfiltration domains (any process) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1041, T1132.001 // CONF: high | FP: low // FALSE POSITIVES: short, generic-looking names (fasthook.app, tray.is) could be reused by unrelated parties after the group stopped using them // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: a DNS lookup whose name equals, or is a subdomain of, one of the fifteen campaign domains. The end anchor and the label boundary reproduce the CQL regex, and matches is case-insensitive like its /i flag. // REQUIRES: SentinelOne DNS Resolved events from staff, kiosk, QA and test machines that load storefront checkouts // TUNING: 1x0.is is the current endpoint, treat any hit as high priority; for the older names compare the first-seen date with the 2026-10-24 window before escalating. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Lookups made by a resolver outside the endpoint, or on a device without the agent, are not seen. // VALIDATION LEVEL: static-reviewed event.type = 'DNS Resolved' event.dns.request matches '(^|\\.)(1x0\\.is|snf\\.is|kk\\.land|nmn\\.is|kof\\.one|anl\\.is|xsox\\.diy|eqp\\.is|shy\\.is|blz\\.is|tray\\.is|miorl\\.net|xwv\\.is|fasthook\\.app|antiddos-protection\\.net)$' | columns event.time, agent.uuid, endpoint.name, event.dns.request, src.process.name, src.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceEvents
Required fields
Timestamp: Event timeActionType: Action typeAdditionalFields: Queried name
Recommended fields
InitiatingProcessFileName: Requesting process
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceEvents | where Timestamp > ago(1d) | where ActionType == "DnsQueryResponse" | project Timestamp, ActionType, AdditionalFields | take 5
// HUNT: DNS lookups for the campaign's exfiltration domains (any process) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1041, T1132.001 // CONF: high | FP: low // FALSE POSITIVES: short, generic-looking names (fasthook.app, tray.is) could be reused by unrelated parties after the group stopped using them // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate on the lookup name, read from DeviceEvents DnsQueryResponse records where Defender stores it inside AdditionalFields. The regex keeps the label boundary and end anchor, and (?i) reproduces the CQL /i flag. // REQUIRES: Defender for Endpoint DNS response telemetry (DeviceEvents, ActionType DnsQueryResponse) // TUNING: 1x0.is is the current endpoint, treat any hit as high priority; for the older names compare the first-seen date with the 2026-10-24 window before escalating. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Lookups made by a resolver outside the endpoint, or on a device without the sensor, are not seen. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceEvents | where Timestamp >= ago(7d) | where ActionType == "DnsQueryResponse" | extend QueryName = tostring(coalesce(parse_json(AdditionalFields)["DnsQueryString"], parse_json(AdditionalFields)["QueryName"])) | where QueryName matches regex @"(?i)(^|\.)(1x0\.is|snf\.is|kk\.land|nmn\.is|kof\.one|anl\.is|xsox\.diy|eqp\.is|shy\.is|blz\.is|tray\.is|miorl\.net|xwv\.is|fasthook\.app|antiddos-protection\.net)$" | project Timestamp, ReportId, DeviceId, DeviceName, QueryName, InitiatingProcessFileName, InitiatingProcessCommandLine | sort by Timestamp desc | take 200
Looks for: the same DNS lookups as Q1, joined to the process that made them. Accomplishes: separates a browser on a staff machine (checkout visit) from a service or script host (server-side activity on a compromised host). The join adds cost, so run Q1 first.
Known limitations and deployment notes
- PowerQuery: A lookup whose process the agent could not attribute shows empty process columns.
- PowerQuery assumes: On DNS Resolved events the acting process is src.process.* and its parent is src.process.parent.*.
- PowerQuery assumes: event.dns.request holds the queried name; the vendor docs also name dns.request.
- KQL – Defender XDR: A lookup whose process the sensor could not attribute shows empty process columns.
- KQL – Defender XDR assumes: The queried name is stored under DnsQueryString or QueryName inside AdditionalFields.
- KQL – Defender XDR assumes: DnsQueryResponse rows populate the InitiatingProcess* columns.
// HUNT: Which process resolved an exfiltration domain (browser vs script host vs server service) // MITRE: T1041, T1132.001 | CONF: high FP: low COST: medium // REQUIRES: DnsRequest and ProcessRollup2 from the same sensor (join on aid + process id) // FALSE POSITIVES: security tooling that detonates or resolves threat-intel domains from a sandbox or scanner host // TUNING: exclude sandbox and scanner hosts by ComputerName; a browser process on a staff machine is the expected shape, a service or script host is not #event_simpleName=DnsRequest | DomainName=/(^|\.)(1x0\.is|snf\.is|kk\.land|nmn\.is|kof\.one|anl\.is|xsox\.diy|eqp\.is|shy\.is|blz\.is|tray\.is|miorl\.net|xwv\.is|fasthook\.app|antiddos-protection\.net)$/i | rename(field=ContextProcessId_decimal, as=TargetProcessId_decimal) | join(query={#event_simpleName=ProcessRollup2}, field=[aid, TargetProcessId_decimal], mode=left, include=[FileName, CommandLine, ParentBaseFileName, UserName]) | table([@timestamp, aid, ComputerName, DomainName, FileName, CommandLine, ParentBaseFileName, UserName], limit=200) | sort(@timestamp, order=desc)
4 required fields · View requirements
Required telemetry
- DNS Resolved
Required fields
event.time: Event timeevent.type: Event typeevent.dns.request: Queried namesrc.process.name: Requesting process
Recommended fields
src.process.cmdline: Requesting command linesrc.process.parent.name: Parent process
Optional fields
src.process.user: Process user
Fallbacks
event.dns.request→dns.request: Same value under the older field name.
Readiness test
event.type = 'DNS Resolved' src.process.name = * | limit 5 | columns event.time, event.type, event.dns.request, src.process.name
// HUNT: Which process resolved an exfiltration domain (browser vs script host vs server service) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1041, T1132.001 // CONF: high | FP: low // FALSE POSITIVES: security tooling that detonates or resolves threat-intel domains from a sandbox or scanner host // EQUIVALENCE: equivalent-with-schema-assumptions - Same lookup predicate as Q1. The CQL joins the lookup to the process-start record to name the process; a SentinelOne DNS Resolved event already carries the acting process as src.process.*, so no join is needed. // REQUIRES: SentinelOne DNS Resolved events that carry the acting process (src.process.*) // TUNING: Exclude sandbox and scanner hosts by endpoint.name; a browser process on a staff machine is the expected shape, a service or script host is not. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A lookup whose process the agent could not attribute shows empty process columns. // VALIDATION LEVEL: static-reviewed event.type = 'DNS Resolved' event.dns.request matches '(^|\\.)(1x0\\.is|snf\\.is|kk\\.land|nmn\\.is|kof\\.one|anl\\.is|xsox\\.diy|eqp\\.is|shy\\.is|blz\\.is|tray\\.is|miorl\\.net|xwv\\.is|fasthook\\.app|antiddos-protection\\.net)$' | columns event.time, agent.uuid, endpoint.name, event.dns.request, src.process.name, src.process.cmdline, src.process.parent.name, src.process.user | sort -event.time | limit 200
Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceEvents
Required fields
Timestamp: Event timeActionType: Action typeAdditionalFields: Queried nameInitiatingProcessFileName: Requesting process
Recommended fields
InitiatingProcessCommandLine: Requesting command lineInitiatingProcessParentFileName: Parent process
Optional fields
InitiatingProcessAccountName: Process user
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceEvents | where Timestamp > ago(1d) | where ActionType == "DnsQueryResponse" | project Timestamp, ActionType, AdditionalFields, InitiatingProcessFileName | take 5
// HUNT: Which process resolved an exfiltration domain (browser vs script host vs server service) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1041, T1132.001 // CONF: high | FP: low // FALSE POSITIVES: security tooling that detonates or resolves threat-intel domains from a sandbox or scanner host // EQUIVALENCE: equivalent-with-schema-assumptions - Same lookup predicate as Q1. The CQL joins the lookup to the process-start record; DeviceEvents DnsQueryResponse rows already carry the initiating process, so the join is unnecessary. // REQUIRES: Defender for Endpoint DNS response telemetry (DeviceEvents, ActionType DnsQueryResponse) // TUNING: Exclude sandbox and scanner hosts by DeviceName; a browser process on a staff machine is the expected shape, a service or script host is not. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A lookup whose process the sensor could not attribute shows empty process columns. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceEvents | where Timestamp >= ago(7d) | where ActionType == "DnsQueryResponse" | extend QueryName = tostring(coalesce(parse_json(AdditionalFields)["DnsQueryString"], parse_json(AdditionalFields)["QueryName"])) | where QueryName matches regex @"(?i)(^|\.)(1x0\.is|snf\.is|kk\.land|nmn\.is|kof\.one|anl\.is|xsox\.diy|eqp\.is|shy\.is|blz\.is|tray\.is|miorl\.net|xwv\.is|fasthook\.app|antiddos-protection\.net)$" | project Timestamp, ReportId, DeviceId, DeviceName, QueryName, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessParentFileName, InitiatingProcessAccountName | sort by Timestamp desc | take 200
Looks for: machines that resolved analytics.convertr.app.br or a checkout.convertr host. FP: normal use of the platform; this host is the victim's legitimate infrastructure and is never a block candidate. Accomplishes: builds the list of people to ask whether they paid by card between 2026-10-24 and 2026-10-28.
Known limitations and deployment notes
- PowerQuery: The CQL lists every distinct domain per host; this query returns one sample and a distinct count because the collect aggregate is not available.
- PowerQuery assumes: event.dns.request holds the queried name.
- PowerQuery assumes: contains matches checkout.convertr anywhere in the name, so a name such as xcheckout.convertr.com also matches; the CQL requires a label boundary.
- KQL – Defender XDR: Advanced Hunting keeps 30 days, so after 2026-11-23 the start of the 2026-10-24 window is no longer queryable here; use the CQL tab.
- KQL – Defender XDR assumes: The queried name is stored under DnsQueryString or QueryName inside AdditionalFields.
- KQL – Defender XDR assumes: contains matches checkout.convertr anywhere in the name; the CQL requires a label boundary.
// HUNT: Machines that resolved the victim tracker host or a checkout host (exposure only, not compromise) // MITRE: T1195.002 | CONF: medium FP: high COST: low // REQUIRES: DnsRequest telemetry from staff and test machines; run over 2026-10-24 through 2026-10-28 // FALSE POSITIVES: ordinary legitimate shopping or merchant-staff use of the platform outside the poisoned window // TUNING: set the search window to 2026-10-24 00:00 UTC through 2026-10-28 14:00 UTC (cleanup 2026-10-27 12:54 UTC plus up to 24 hours of browser caching); a hit means exposure, so ask the user whether they paid by card, then pivot to Q1 and Q2 #event_simpleName=DnsRequest | DomainName=/(^analytics\.convertr\.app\.br$|(^|\.)checkout\.convertr)/i | groupBy([aid, ComputerName], function=[count(as=lookups), min(@timestamp, as=firstSeen), max(@timestamp, as=lastSeen), collect([DomainName])]) | sort(lastSeen, order=desc, limit=200)
4 required fields · View requirements
Required telemetry
- DNS Resolved
Required fields
event.time: Event timeevent.type: Event typeevent.dns.request: Queried nameagent.uuid: Host identifier
Recommended fields
endpoint.name: Hostname
Fallbacks
event.dns.request→dns.request: Same value under the older field name.
Readiness test
event.type = 'DNS Resolved' event.dns.request = * | limit 5 | columns event.time, event.type, event.dns.request, agent.uuid
// HUNT: Machines that resolved the victim tracker host or a checkout host (exposure only, not compromise)
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1195.002
// CONF: medium | FP: high
// FALSE POSITIVES: ordinary legitimate shopping or merchant-staff use of the platform outside the poisoned window
// EQUIVALENCE: equivalent-with-schema-assumptions - Same exposure grouping: machines that resolved the tracker host or a checkout host, with count and first and last seen. The label-boundary on the checkout name is relaxed to a substring match, which can only add hosts.
// REQUIRES: SentinelOne DNS Resolved events from staff and test machines
// TUNING: Set the query window to 2026-10-24 00:00 UTC through 2026-10-28 14:00 UTC (cleanup 2026-10-27 12:54 UTC plus up to 24 hours of browser caching); a hit means exposure, so ask the user whether they paid by card, then pivot to Q1 and Q2.
// LOOKBACK: Set the console time range to 2026-10-24 00:00 UTC through 2026-10-28 14:00 UTC
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The CQL lists every distinct domain per host; this query returns one sample and a distinct count because the collect aggregate is not available.
// VALIDATION LEVEL: static-reviewed
event.type = 'DNS Resolved' (event.dns.request in:anycase ('analytics.convertr.app.br') or event.dns.request contains 'checkout.convertr')
| group lookups = count(), first_seen = min(event.time), last_seen = max(event.time), sample_domain = any(event.dns.request), distinct_domains = estimate_distinct(event.dns.request) by agent.uuid, endpoint.name
| sort -last_seen
| limit 200Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceEvents
Required fields
Timestamp: Event timeActionType: Action typeAdditionalFields: Queried nameDeviceId: Host identifier
Recommended fields
DeviceName: Hostname
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceEvents | where Timestamp > ago(1d) | where ActionType == "DnsQueryResponse" | project Timestamp, ActionType, AdditionalFields, DeviceId | take 5
// HUNT: Machines that resolved the victim tracker host or a checkout host (exposure only, not compromise) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1195.002 // CONF: medium | FP: high // FALSE POSITIVES: ordinary legitimate shopping or merchant-staff use of the platform outside the poisoned window // EQUIVALENCE: equivalent-with-schema-assumptions - Same exposure grouping over DnsQueryResponse records. The checkout name is matched as a substring, which can only add hosts. The lookback is 30 days so the 2026-10-24 start of the window is inside it. // REQUIRES: Defender for Endpoint DNS response telemetry (DeviceEvents, ActionType DnsQueryResponse) // TUNING: Narrow the window to 2026-10-24 00:00 UTC through 2026-10-28 14:00 UTC (cleanup 2026-10-27 12:54 UTC plus up to 24 hours of browser caching); a hit means exposure, so ask the user whether they paid by card, then pivot to Q1 and Q2. // LOOKBACK: 30d (Advanced Hunting retention; covers the 2026-10-24 start of the window while it is under 30 days old) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Advanced Hunting keeps 30 days, so after 2026-11-23 the start of the 2026-10-24 window is no longer queryable here; use the CQL tab. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0 DeviceEvents | where Timestamp >= ago(30d) | where ActionType == "DnsQueryResponse" | extend QueryName = tostring(coalesce(parse_json(AdditionalFields)["DnsQueryString"], parse_json(AdditionalFields)["QueryName"])) | where QueryName =~ "analytics.convertr.app.br" or QueryName contains "checkout.convertr" | summarize lookups = count(), first_seen = min(Timestamp), last_seen = max(Timestamp), domains = make_set(QueryName) by DeviceId, DeviceName | sort by last_seen desc | take 200
Looks for: BEAM processes whose command line mentions Plausible. Accomplishes: lists which of your hosts could carry the suspected entry point (CVE-2026-8467, reported but unconfirmed for this incident), so version checks and the /storybook log search (N1) start from a list, not a guess.
Known limitations and deployment notes
- PowerQuery: The CQL collects every parent name per host; this query returns one sample parent.
- PowerQuery: A BEAM VM started before the window or before the agent was installed is not listed.
- PowerQuery assumes: tgt.process.name holds beam.smp or beam for the BEAM VM on Linux agents.
- KQL – Defender XDR: Advanced Hunting keeps 30 days; a BEAM VM started earlier is not listed here.
- KQL – Defender XDR: A process the sensor did not observe starting is not captured.
- KQL – Defender XDR assumes: FileName holds beam.smp or beam for the BEAM VM on Linux sensors.
// HUNT: Linux hosts running Plausible Community Edition (scope the exposure to the suspected entry point) // MITRE: T1190 | CONF: medium FP: low COST: low // REQUIRES: ProcessRollup2 from Linux servers; a lookback longer than the service uptime, because a process started before the window is not listed // FALSE POSITIVES: any unrelated BEAM (Erlang or Elixir) application whose command line contains the word plausible // TUNING: widen the search window to cover the last restart; confirm the version on each listed host (3.0.0 through 3.2.0 is the reported vulnerable range, 3.2.1 is the fix) #event_simpleName=ProcessRollup2 | FileName=/^beam(\.smp)?$/i | CommandLine=/plausible/i | groupBy([aid, ComputerName], function=[count(as=starts), min(@timestamp, as=firstSeen), max(@timestamp, as=lastSeen), collect([ParentBaseFileName])]) | sort(lastSeen, order=desc, limit=200)
5 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typetgt.process.name: Created process nametgt.process.cmdline: Created command lineagent.uuid: Host identifier
Recommended fields
src.process.name: Creator processendpoint.name: Hostname
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, event.type, agent.uuid, tgt.process.name, tgt.process.cmdline
// HUNT: Linux hosts running Plausible Community Edition (scope the exposure to the suspected entry point)
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1190
// CONF: medium | FP: low
// FALSE POSITIVES: any unrelated BEAM (Erlang or Elixir) application whose command line contains the word plausible
// EQUIVALENCE: equivalent-with-schema-assumptions - Same inventory: BEAM VM process starts whose command line names plausible, grouped per host with count and first and last seen. On a Process Creation event the BEAM process is the new process, tgt.process.*, and its creator is src.process.*.
// REQUIRES: SentinelOne Process Creation events from Linux servers; a lookback longer than the service uptime, because a process started before the window is not listed
// TUNING: Widen the search window to cover the last restart; confirm the version on each listed host (3.0.0 through 3.2.0 is the reported vulnerable range, 3.2.1 is the fix).
// LOOKBACK: Widen to cover the last service restart
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The CQL collects every parent name per host; this query returns one sample parent. A BEAM VM started before the window or before the agent was installed is not listed.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' tgt.process.name in:anycase ('beam', 'beam.smp') tgt.process.cmdline contains 'plausible'
| group starts = count(), first_seen = min(event.time), last_seen = max(event.time), parent_sample = any(src.process.name) by agent.uuid, endpoint.name
| sort -last_seen
| limit 200Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command lineDeviceId: Host identifier
Recommended fields
InitiatingProcessFileName: Creator processDeviceName: Hostname
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, DeviceId, FileName, ProcessCommandLine | take 5
// HUNT: Linux hosts running Plausible Community Edition (scope the exposure to the suspected entry point)
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1190
// CONF: medium | FP: low
// FALSE POSITIVES: any unrelated BEAM (Erlang or Elixir) application whose command line contains the word plausible
// EQUIVALENCE: equivalent-with-schema-assumptions - Same inventory over DeviceProcessEvents: the created process is the BEAM VM and its command line names plausible. The creator is InitiatingProcessFileName.
// REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) from Linux servers
// TUNING: Widen the search window to cover the last restart; confirm the version on each listed host (3.0.0 through 3.2.0 is the reported vulnerable range, 3.2.1 is the fix).
// LOOKBACK: 30d (Advanced Hunting retention); a service older than that needs the CQL tab
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Advanced Hunting keeps 30 days; a BEAM VM started earlier is not listed here. A process the sensor did not observe starting is not captured.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(30d)
| where FileName in~ ("beam", "beam.smp")
| where ProcessCommandLine contains "plausible"
| summarize starts = count(), first_seen = min(Timestamp), last_seen = max(Timestamp), parents = make_set(InitiatingProcessFileName) by DeviceId, DeviceName
| sort by last_seen desc
| take 200Looks for: the Plausible runtime launching sh, curl, wget, nc or an interpreter. FP: helper scripts the service launches itself. Accomplishes: catches command execution through an unauthenticated remote-code flaw on the analytics host, which is how the server side of this attack would look.
Known limitations and deployment notes
- PowerQuery: A shell started by a process other than the BEAM VM, for example after the attacker pivots, is not caught.
- PowerQuery assumes: src.process.user holds the account that ran the creator process.
- KQL – Defender XDR: A shell started by a process other than the BEAM VM is not caught.
- KQL – Defender XDR assumes: FileName holds the bare binary name on Linux sensors.
// HUNT: Shell, download tool or interpreter spawned by the BEAM VM that runs Plausible (post-exploitation on the analytics host) // MITRE: T1190, T1059.004 | CONF: medium FP: medium COST: low // REQUIRES: ProcessRollup2 with parent-process context from the Plausible host // FALSE POSITIVES: operator maintenance tasks, release helper scripts and container health checks that the service launches itself // TUNING: baseline for 14 days on the Plausible host, then allow-list the exact CommandLine of each known helper; keep curl, wget, nc, socat and interpreter hits unexcluded #event_simpleName=ProcessRollup2 | ParentBaseFileName=/^(beam\.smp|beam|erl_child_setup)$/i | FileName=/^(sh|bash|dash|ash|busybox|curl|wget|nc|ncat|socat|python[0-9.]*|perl|ruby|base64|openssl)$/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200) | sort(@timestamp, order=desc)
4 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typesrc.process.name: Creator processtgt.process.name: Created process
Recommended fields
tgt.process.cmdline: Created command linesrc.process.user: Creator user
Readiness test
event.type = 'Process Creation' src.process.name = * | limit 5 | columns event.time, event.type, src.process.name, tgt.process.name
// HUNT: Shell, download tool or interpreter spawned by the BEAM VM that runs Plausible (post-exploitation on the analytics host)
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1190, T1059.004
// CONF: medium | FP: medium
// FALSE POSITIVES: operator maintenance tasks, release helper scripts and container health checks that the service launches itself
// EQUIVALENCE: equivalent-with-schema-assumptions - Same spawn hunt with the lineage re-derived: on a Process Creation event the child shell, download tool or interpreter is tgt.process.* and the BEAM VM that created it is src.process.*. The CQL name list is kept, with python versions covered by an anchored regex.
// REQUIRES: SentinelOne Process Creation events from the Plausible host, with creator-process context
// TUNING: Baseline for 14 days on the Plausible host, then allow-list the exact command line of each known helper; keep curl, wget, nc, socat and interpreter hits unexcluded.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A shell started by a process other than the BEAM VM, for example after the attacker pivots, is not caught.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' src.process.name in:anycase ('beam.smp', 'beam', 'erl_child_setup') (tgt.process.name in:anycase ('sh', 'bash', 'dash', 'ash', 'busybox', 'curl', 'wget', 'nc', 'ncat', 'socat', 'perl', 'ruby', 'base64', 'openssl') or tgt.process.name matches '^python[0-9.]*$')
| columns event.time, agent.uuid, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline
| sort -event.time
| limit 200Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeInitiatingProcessFileName: Creator processFileName: Created process
Recommended fields
ProcessCommandLine: Created command lineAccountName: Account
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, InitiatingProcessFileName, FileName | take 5
// HUNT: Shell, download tool or interpreter spawned by the BEAM VM that runs Plausible (post-exploitation on the analytics host)
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1190, T1059.004
// CONF: medium | FP: medium
// FALSE POSITIVES: operator maintenance tasks, release helper scripts and container health checks that the service launches itself
// EQUIVALENCE: equivalent-with-schema-assumptions - Same spawn hunt: the created process is FileName and its creator, the BEAM VM, is InitiatingProcessFileName. in~ reproduces the CQL /i flag.
// REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) from the Plausible host
// TUNING: Baseline for 14 days on the Plausible host, then allow-list the exact command line of each known helper; keep curl, wget, nc, socat and interpreter hits unexcluded.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A shell started by a process other than the BEAM VM is not caught.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(7d)
| where InitiatingProcessFileName in~ ("beam.smp", "beam", "erl_child_setup")
| where FileName in~ ("sh", "bash", "dash", "ash", "busybox", "curl", "wget", "nc", "ncat", "socat", "perl", "ruby", "base64", "openssl") or FileName matches regex @"(?i)^python[0-9.]*$"
| project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine
| sort by Timestamp desc
| take 200Looks for: write-capable tools that name script.pageview-props.tagged-events.js or priv/tracker/js. FP: admin upgrades and restores. Gap: no CQL sees a file write to a non-executable .js file Pair with file-integrity monitoring (N2).
Known limitations and deployment notes
- PowerQuery: The CQL also reads synthetic process-start events that the agent can raise for processes it did not see start; this query reads Process Creation only.
- PowerQuery assumes: The tracker directory is matched as the substring priv/tracker/js or its backslash form.
- KQL – Defender XDR: The CQL also reads synthetic process-start events; this query reads real process creation only.
- KQL – Defender XDR assumes: FileName holds the bare binary name on Linux sensors.
// HUNT: Commands that reference the Plausible tracker script or its directory (tampering with the served file) // MITRE: T1565.001, T1195.002 | CONF: medium FP: medium COST: low // REQUIRES: ProcessRollup2 command-line telemetry from the Plausible host // FALSE POSITIVES: administrators upgrading Plausible, restoring backups or comparing tracker files against a release // TUNING: exclude read-only verification such as sha256sum, ls and diff from known admin accounts; any write-capable tool (sed, tee, cp, mv, dd, curl, wget) acting on the tracker file outside a change window is the signal #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/(script\.pageview-props\.tagged-events\.js|priv[\/\\]tracker[\/\\]js)/i | FileName=/^(sed|tee|cp|mv|dd|curl|wget|echo|printf|perl|python[0-9.]*|node|vi|vim|nano|sh|bash)$/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200) | sort(@timestamp, order=desc)
4 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typetgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creator processsrc.process.user: Creator user
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, event.type, tgt.process.name, tgt.process.cmdline
// HUNT: Commands that reference the Plausible tracker script or its directory (tampering with the served file)
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1565.001, T1195.002
// CONF: medium | FP: medium
// FALSE POSITIVES: administrators upgrading Plausible, restoring backups or comparing tracker files against a release
// EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: a write-capable or editing tool whose command line names the tracker script or its directory, with either path separator. contains is case-insensitive like the CQL /i flag.
// REQUIRES: SentinelOne Process Creation command-line telemetry from the Plausible host
// TUNING: Exclude read-only verification such as sha256sum, ls and diff from known admin accounts; any write-capable tool (sed, tee, cp, mv, dd, curl, wget) acting on the tracker file outside a change window is the signal.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The CQL also reads synthetic process-start events that the agent can raise for processes it did not see start; this query reads Process Creation only.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' (tgt.process.cmdline contains 'script.pageview-props.tagged-events.js' or tgt.process.cmdline contains 'priv/tracker/js' or tgt.process.cmdline contains 'priv\\tracker\\js') (tgt.process.name in:anycase ('sed', 'tee', 'cp', 'mv', 'dd', 'curl', 'wget', 'echo', 'printf', 'perl', 'node', 'vi', 'vim', 'nano', 'sh', 'bash') or tgt.process.name matches '^python[0-9.]*$')
| columns event.time, agent.uuid, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline
| sort -event.time
| limit 200Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creator processAccountName: Account
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine | take 5
// HUNT: Commands that reference the Plausible tracker script or its directory (tampering with the served file)
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1565.001, T1195.002
// CONF: medium | FP: medium
// FALSE POSITIVES: administrators upgrading Plausible, restoring backups or comparing tracker files against a release
// EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate on the created process: a write-capable or editing tool whose command line names the tracker script or its directory, with either path separator. contains and in~ are case-insensitive like the CQL /i flag.
// REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) from the Plausible host
// TUNING: Exclude read-only verification such as sha256sum, ls and diff from known admin accounts; any write-capable tool (sed, tee, cp, mv, dd, curl, wget) acting on the tracker file outside a change window is the signal.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The CQL also reads synthetic process-start events; this query reads real process creation only.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(7d)
| where ProcessCommandLine contains "script.pageview-props.tagged-events.js" or ProcessCommandLine contains "priv/tracker/js" or ProcessCommandLine contains "priv\\tracker\\js"
| where FileName in~ ("sed", "tee", "cp", "mv", "dd", "curl", "wget", "echo", "printf", "perl", "node", "vi", "vim", "nano", "sh", "bash") or FileName matches regex @"(?i)^python[0-9.]*$"
| project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine
| sort by Timestamp desc
| take 200Looks for: the Plausible runtime connecting to public addresses, rare destinations first. FP: SMTP relay and managed database. Accomplishes: a low-confidence lead for a reverse shell or staging download after exploitation.
Known limitations and deployment notes
- PowerQuery: IPv6 destinations are not excluded by the listed ranges, and the CQL reads IPv4 events only.
- PowerQuery assumes: IP Connect records connections the process initiated, so inbound connections are not in the population.
- PowerQuery assumes: dst.ip.address holds an IPv4 address for these connections.
- KQL – Defender XDR: Failed connection attempts are not counted, and IPv6 destinations are not excluded by the listed ranges.
- KQL – Defender XDR assumes: ConnectionSuccess is the ActionType for an established outbound connection.
- KQL – Defender XDR assumes: RemoteIP holds an IPv4 address for these connections.
// HUNT: BEAM VM on the Plausible host connecting outbound to non-private addresses // MITRE: T1190, T1071.001 | CONF: low FP: medium COST: medium // REQUIRES: NetworkConnectIP4 with initiating-process context from the Plausible host // FALSE POSITIVES: outbound SMTP relay, geolocation database or version-check downloads, and externally hosted databases // TUNING: allow-list the remote addresses of your SMTP relay and managed database; rare destinations (low conns) on unusual ports are the interesting rows #event_simpleName=NetworkConnectIP4 | ContextBaseFileName=/^(beam\.smp|beam)$/i | !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16"]) | groupBy([aid, ComputerName, RemoteAddressIP4, RemotePort], function=[count(as=conns), min(@timestamp, as=firstSeen), max(@timestamp, as=lastSeen)]) | sort(conns, order=asc, limit=200)
5 required fields · View requirements
Required telemetry
- IP Connect
Required fields
event.time: Event timeevent.type: Event typesrc.process.name: Connecting processdst.ip.address: Destination addressdst.port.number: Destination port
Recommended fields
agent.uuid: Host identifierendpoint.name: Hostname
Readiness test
event.type = 'IP Connect' src.process.name = * | limit 5 | columns event.time, event.type, src.process.name, dst.ip.address, dst.port.number
// HUNT: BEAM VM on the Plausible host connecting outbound to non-private addresses
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1190, T1071.001
// CONF: low | FP: medium
// FALSE POSITIVES: outbound SMTP relay, geolocation database or version-check downloads, and externally hosted databases
// EQUIVALENCE: equivalent-with-schema-assumptions - Same rare-destination grouping: outbound connections from the BEAM VM to addresses outside the five listed private, loopback and link-local ranges, counted per destination. On an IP Connect event the acting process is src.process.*.
// REQUIRES: SentinelOne IP Connect events with the acting process from the Plausible host
// TUNING: Allow-list the remote addresses of your SMTP relay and managed database; rare destinations (low conns) on unusual ports are the interesting rows.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: IPv6 destinations are not excluded by the listed ranges, and the CQL reads IPv4 events only.
// VALIDATION LEVEL: static-reviewed
event.type = 'IP Connect' src.process.name in:anycase ('beam.smp', 'beam') !(net_ipsubnet(dst.ip.address, '10.0.0.0/8') or net_ipsubnet(dst.ip.address, '172.16.0.0/12') or net_ipsubnet(dst.ip.address, '192.168.0.0/16') or net_ipsubnet(dst.ip.address, '127.0.0.0/8') or net_ipsubnet(dst.ip.address, '169.254.0.0/16'))
| group conns = count(), first_seen = min(event.time), last_seen = max(event.time) by agent.uuid, endpoint.name, dst.ip.address, dst.port.number
| sort +conns
| limit 200Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceNetworkEvents
Required fields
Timestamp: Event timeInitiatingProcessFileName: Connecting processRemoteIP: Destination addressRemotePort: Destination port
Recommended fields
DeviceId: Host identifierDeviceName: Hostname
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceNetworkEvents | where Timestamp > ago(1d) | project Timestamp, InitiatingProcessFileName, RemoteIP, RemotePort | take 5
// HUNT: BEAM VM on the Plausible host connecting outbound to non-private addresses
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1190, T1071.001
// CONF: low | FP: medium
// FALSE POSITIVES: outbound SMTP relay, geolocation database or version-check downloads, and externally hosted databases
// EQUIVALENCE: equivalent-with-schema-assumptions - Same rare-destination grouping over DeviceNetworkEvents: connections whose initiating process is the BEAM VM, to addresses outside the five listed ranges.
// REQUIRES: Defender for Endpoint network telemetry (DeviceNetworkEvents) from the Plausible host
// TUNING: Allow-list the remote addresses of your SMTP relay and managed database; rare destinations (low conns) on unusual ports are the interesting rows.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Failed connection attempts are not counted, and IPv6 destinations are not excluded by the listed ranges.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0
DeviceNetworkEvents
| where Timestamp >= ago(7d)
| where ActionType == "ConnectionSuccess"
| where InitiatingProcessFileName in~ ("beam.smp", "beam")
| where not(ipv4_is_in_any_range(RemoteIP, "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16"))
| summarize conns = count(), first_seen = min(Timestamp), last_seen = max(Timestamp) by DeviceId, DeviceName, RemoteIP, RemotePort
| sort by conns asc
| take 200Looks for: curl, wget, PHP or shell commands that name the group's exfiltration script names. FP: common legitimate page names on Brazilian stores. Accomplishes: a lead on compromised stores or admin hosts, never an alert on its own.
Known limitations and deployment notes
- PowerQuery: The CQL also reads synthetic process-start events; this query reads Process Creation only.
- PowerQuery assumes: tgt.process.name holds the bare binary name.
- KQL – Defender XDR: The CQL also reads synthetic process-start events; this query reads real process creation only.
- KQL – Defender XDR assumes: FileName holds the bare binary name.
// HUNT: Commands naming the group's Portuguese exfiltration script names (cartao.php, pagamento.php) // MITRE: T1041 | CONF: low FP: medium COST: low // REQUIRES: ProcessRollup2 command-line telemetry, typically from web servers or administrator workstations // FALSE POSITIVES: both names are ordinary Portuguese words (card, payment) and legitimate Brazilian stores often ship pages with these exact names // TUNING: treat a hit as a lead only; correlate with Q1 and Q2 on the same host, and require the command to POST to an external host before escalating #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/(cartao|pagamento)\.php/i | FileName=/^(curl|wget|php[0-9.]*|python[0-9.]*|perl|sh|bash|powershell|pwsh)(\.exe)?$/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200) | sort(@timestamp, order=desc)
4 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typetgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creator processsrc.process.user: Creator user
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, event.type, tgt.process.name, tgt.process.cmdline
// HUNT: Commands naming the group's Portuguese exfiltration script names (cartao.php, pagamento.php) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1041 // CONF: low | FP: medium // FALSE POSITIVES: both names are ordinary Portuguese words (card, payment) and legitimate Brazilian stores often ship pages with these exact names // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: a curl, wget, php, python, perl or shell process whose command line names cartao.php or pagamento.php. The process-name regex is anchored and case-insensitive, matching the CQL /i flag, with an optional .exe. // REQUIRES: SentinelOne Process Creation command-line telemetry, typically from web servers or administrator workstations // TUNING: Treat a hit as a lead only; correlate with Q1 and Q2 on the same host, and require the command to POST to an external host before escalating. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: The CQL also reads synthetic process-start events; this query reads Process Creation only. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' (tgt.process.cmdline contains 'cartao.php' or tgt.process.cmdline contains 'pagamento.php') tgt.process.name matches '^(curl|wget|php[0-9.]*|python[0-9.]*|perl|sh|bash|powershell|pwsh)(\\.exe)?$' | columns event.time, agent.uuid, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creator processAccountName: Account
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine | take 5
// HUNT: Commands naming the group's Portuguese exfiltration script names (cartao.php, pagamento.php) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1041 // CONF: low | FP: medium // FALSE POSITIVES: both names are ordinary Portuguese words (card, payment) and legitimate Brazilian stores often ship pages with these exact names // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate on the created process. contains is case-insensitive like the CQL /i flag; the process-name regex is anchored with (?i). // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) // TUNING: Treat a hit as a lead only; correlate with Q1 and Q2 on the same host, and require the command to POST to an external host before escalating. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: The CQL also reads synthetic process-start events; this query reads real process creation only. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(7d) | where ProcessCommandLine contains "cartao.php" or ProcessCommandLine contains "pagamento.php" | where FileName matches regex @"(?i)^(curl|wget|php[0-9.]*|python[0-9.]*|perl|sh|bash|powershell|pwsh)(\.exe)?$" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine | sort by Timestamp desc | take 200
Looks for: node, deno, bun, cscript or wscript run with fromCharCode and eval in the command line. Accomplishes: the skimmer loader's decode-and-eval pattern wherever script content reaches a command line. The shopper-side copy runs inside a browser, which endpoint telemetry cannot see.
Known limitations and deployment notes
- PowerQuery: The CQL also reads synthetic process-start events; this query reads Process Creation only.
- PowerQuery: Code run inside a browser is not visible to a command-line hunt.
- PowerQuery assumes: tgt.process.name holds the bare binary name.
- KQL – Defender XDR: The CQL also reads synthetic process-start events; this query reads real process creation only.
- KQL – Defender XDR: Code run inside a browser is not visible to a command-line hunt.
- KQL – Defender XDR assumes: FileName holds the bare binary name.
// HUNT: Script host run with an inline character-code decode followed by eval (the skimmer's loader pattern) // MITRE: T1059.007, T1027 | CONF: medium FP: low COST: low // REQUIRES: ProcessRollup2 command line for node, deno, bun or Windows script hosts; browsers are NOT covered // FALSE POSITIVES: developers testing obfuscation or running minified code with -e, and security researchers reproducing the sample // TUNING: exclude developer build hosts by ComputerName; a production server or kiosk running this is unusual #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^(node|nodejs|deno|bun|cscript|wscript)(\.exe)?$/i | CommandLine=/fromCharCode/i | CommandLine=/eval/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200) | sort(@timestamp, order=desc)
4 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typetgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creator processsrc.process.user: Creator user
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, event.type, tgt.process.name, tgt.process.cmdline
// HUNT: Script host run with an inline character-code decode followed by eval (the skimmer's loader pattern) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1059.007, T1027 // CONF: medium | FP: low // FALSE POSITIVES: developers testing obfuscation or running minified code with -e, and security researchers reproducing the sample // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: a script host whose command line contains both fromCharCode and eval. contains is case-insensitive like the CQL /i flag, and the process-name regex is anchored with an optional .exe. // REQUIRES: SentinelOne Process Creation command-line telemetry for node, deno, bun or Windows script hosts; browsers are not covered // TUNING: Exclude developer build hosts by endpoint.name; a production server or kiosk running this is unusual. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: The CQL also reads synthetic process-start events; this query reads Process Creation only. Code run inside a browser is not visible to a command-line hunt. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' tgt.process.name matches '^(node|nodejs|deno|bun|cscript|wscript)(\\.exe)?$' tgt.process.cmdline contains 'fromCharCode' tgt.process.cmdline contains 'eval' | columns event.time, agent.uuid, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creator processAccountName: Account
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine | take 5
// HUNT: Script host run with an inline character-code decode followed by eval (the skimmer's loader pattern)
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1059.007, T1027
// CONF: medium | FP: low
// FALSE POSITIVES: developers testing obfuscation or running minified code with -e, and security researchers reproducing the sample
// EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate on the created process: a script host whose command line contains both fromCharCode and eval. contains and in~ are case-insensitive like the CQL /i flag.
// REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) for node, deno, bun or Windows script hosts; browsers are not covered
// TUNING: Exclude developer build hosts by DeviceName; a production server or kiosk running this is unusual.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The CQL also reads synthetic process-start events; this query reads real process creation only. Code run inside a browser is not visible to a command-line hunt.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(7d)
| where FileName in~ ("node", "node.exe", "nodejs", "nodejs.exe", "deno", "deno.exe", "bun", "bun.exe", "cscript", "cscript.exe", "wscript", "wscript.exe")
| where ProcessCommandLine contains "fromCharCode" and ProcessCommandLine contains "eval"
| project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine
| sort by Timestamp desc
| take 200Detection Rules by Platform
CrowdStrike: Custom IOA
| IOA name | Promotes | Pattern | Exclusions | Deployment |
|---|---|---|---|---|
| Exfil domain lookup | Promote Q1, Q2 | Domain Name rule matching 1x0.is and the 14 earlier domains (Q1 regex) | Sandbox and scanner hosts | Detect only. Single-sourced (Sansec), so no prevent until a second independent source confirms |
| Plausible runtime spawns shell or download tool | Promote Q5 | Process Creation, parent image beam.smp or erl_child_setup, child sh, bash, curl, wget, nc, interpreters | Known helper command lines after a 14-day baseline | Detect on the Plausible host group only |
| Tracker script touched by a write-capable tool | Promote Q6 | Process Creation, command line naming script.pageview-props.tagged-events.js with sed, tee, cp, mv, dd, curl or wget | Change-window admin accounts | Detect only |
SentinelOne: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| Exfil domain lookup | Q1, Q2, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
| Plausible runtime spawns shell or download tool | Q5, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device". This pack's own text mentions a baseline or pilot; follow it as written |
| Tracker script touched by a write-capable tool | Q6, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
Create the rule with POST /web/api/v2.1/cloud-detection/rules, queryType: "scheduled" and queryLang: "2.0", with the query in data.scheduledParams.query. Use 2.0 operator spellings: a rule written with the 1.0 token ContainsCIS was accepted and never fired on a tenant. The API does not warn, so lint the body before deploying.
Microsoft Defender: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| Exfil domain lookup | Q1, Q2, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself |
| Plausible runtime spawns shell or download tool | Q5, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself. This pack's own text mentions a baseline or pilot; follow it as written |
| Tracker script touched by a write-capable tool | Q6, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself |
Q3, Q4, Q7, Q8 and Q9 stay a hunt in this pack, not a rule. A rule can generate only 150 alerts per run. Continuous (NRT) frequency needs a single table and no comment lines, so delete the generated comment header from the query first.
Machine-Readable IOC Appendix
detect. Severity is high for the current endpoint, medium for the earlier domains and informational for the victim's legitimate tracker host (exposure context only). Nothing is at prevent or critical.Grouped IOC Quick-Copy
type,value,action,severity,expiration,description,tags domain,1x0.is,detect,high,2027-05-01,Skimmer exfiltration endpoint (Sansec only; path /ws on port 443),campaign:Convertr-Plausible-Skimmer domain,snf.is,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,kk.land,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,nmn.is,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,kof.one,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,anl.is,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,xsox.diy,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,eqp.is,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,shy.is,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,blz.is,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,tray.is,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,miorl.net,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,xwv.is,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,fasthook.app,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,antiddos-protection.net,detect,medium,2027-05-01,Earlier exfiltration domain of the same group (Sansec only),campaign:Convertr-Plausible-Skimmer domain,analytics.convertr.app.br,detect,informational,2027-05-01,VICTIM legitimate tracker host. Exposure context only. NEVER prevent,campaign:Convertr-Plausible-Skimmer # sha256 ee4618d24737eff785e5a267b55903d5ef6ff7ecbeb099347a67fee77734d7f7: the infected tracker script on the victim server on 2026-10-26 (a JavaScript file; use it for file-integrity and proxy comparison, not as a Falcon execution IOC)
indicatorType,indicatorValue,action,title,description,expirationTime,severity,recommendedActions,rbacGroups,category,mitretechniques,GenerateAlert DomainName,1x0.is,Audit,Skimmer exfiltration endpoint,Skimmer exfiltration endpoint (Sansec only; path /ws on port 443),2027-05-01T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True DomainName,snf.is,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,kk.land,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,nmn.is,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,kof.one,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,anl.is,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,xsox.diy,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,eqp.is,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,shy.is,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,blz.is,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,tray.is,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,miorl.net,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,xwv.is,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,fasthook.app,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,antiddos-protection.net,Audit,Earlier exfiltration domain of the same group,Earlier exfiltration domain of the same group (Sansec only),2027-05-01T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True DomainName,analytics.convertr.app.br,Audit,VICTIM legitimate tracker host. Exposure context only. NEVER,VICTIM legitimate tracker host. Exposure context only. NEVER prevent,2027-05-01T00:00:00.0Z,Informational,Investigate the device and pivot into the pack's queries,,,,True
Column names and allowed values are from Microsoft's documented parameter table. Check each expirationTime before importing: an indicator whose date has passed is already expired. Download the sample CSV from the Import page to confirm the column order before a bulk import. Each file takes at most 500 indicators. Learn describes the import per entity type, so if a mixed file is rejected, split it by indicatorType. An imported indicator does nothing unless the matching Advanced features are on (for example Custom network indicators and Allow or block file).
This pack does not ship a SentinelOne import block: the import format could not be verified against SentinelOne's own documentation. Export a sample from your console and the block can be added with confidence.
# Discord webhook IDs used for exfiltration (Sansec only). Hunt in web proxy or gateway URL logs. # Endpoint DNS cannot see the path. Do not block discord.com itself. discord.com/api/webhooks/1349318491479605268 discord.com/api/webhooks/1349948669419327508 discord.com/api/webhooks/1351613753069801583 discord.com/api/webhooks/1398562090934993036 discord.com/api/webhooks/1414969348262596778 discord.com/api/webhooks/1557650706259902495
https://1x0.is/ws https://1x0.is/ws/<base64> cartao.php pagamento.php # Infected tracker (victim host, legitimate service, detect only): https://analytics.convertr.app.br/js/script.pageview-props.tagged-events.js sha256 ee4618d24737eff785e5a267b55903d5ef6ff7ecbeb099347a67fee77734d7f7 (infected copy observed on 2026-10-26)
# Strings Sansec lists in the skimmer (search served tracker copies, CDN caches, WAF captures)
indexOf('checkout.convertr')
_s+=String.fromCharCode(_a[_i]);(0,eval)(_s)
# Record shape: card | month | year | cvv | cpf | name | short victim tag (ctr for Convertr)
# Body is Base64 of the pipe-delimited record, sent by POST with mode no-cors and keepalive
# Structural check: the stock Plausible tracker followed by an appended block of character codes
# Reported affected: Plausible Community Edition 3.0.0 through 3.2.0 # Fixed in: 3.2.1 (upgrade to 3.2.1 or later) # CVE-2026-8467 (phoenix_storybook 0.5.0 up to, not including, 1.1.0): suspected entry point, NOT confirmed # Search reverse-proxy and application access logs for requests to /storybook # Any hit: rebuild the server, rotate its secrets, compare priv/tracker/js against the official release
Detection Validation Gates
| Gate | Check |
|---|---|
| Telemetry ready | DNS lookup with the requesting process (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents, DnsQueryResponse) flows from staff, QA and kiosk machines; Process creation with command line and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) and Outbound network connection (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents) flow from the Plausible host. Browser content is outside endpoint telemetry |
| Benign baseline | Run Q1 and Q2 over 30 days: expect zero hits. Run Q5 to Q7 on the Plausible host for 14 days and record legitimate helpers and destinations to build the TUNING exclusions |
| Positive tests | In an isolated lab only: resolve one listed domain with nslookup from a sensored host (Q1, Q2, no connection is made). On a lab Linux host, copy a shell to a file named beam.smp and have it start sh -c id (Q5). Never browse to a listed domain or run the sample |
| Field check | Confirm ParentBaseFileName and ContextBaseFileName are populated on your Linux sensor events, and that the Plausible BEAM command line contains the word plausible (Q4) |
| Promotion | Q1 and Q2 to scheduled alerts after a clean baseline; Q5 and Q6 as detections on the Plausible host group after tuning; Q3, Q4, Q7, Q8, Q9 stay hunts |
Hardening: Tiered & Deployable
Immediate actions remove the suspected entry point and confirm the served script is clean. Near-term and strategic controls stop a third-party script from reading payment fields, which is the underlying failure.
- Upgrade self-hosted Plausible CE to 3.2.1 or later (MITRE M1051 Update Software; vendor release notes, no formal benchmark: best-practice). Verify: the image tag or version string, and that
/storybooknow returns 404 (Playbook 1). - Search access logs for
/storybookand verify tracker files against the official release (M1047 Audit; Sansec guidance). Any hit means rebuild, not patch in place (Playbook 1, N1, N2). - Block
/storybookat the reverse proxy until the upgrade lands (M1037 Filter Network Traffic; best-practice). Verify with a request that returns 404 or 403 (Playbook 2). - Alert on the exfiltration domains at the DNS resolver or gateway (M1031 Network Intrusion Prevention). Alert, not block, until a second source confirms the list; do not block the victim tracker host.
- Purge CDN and proxy caches of the tracker and confirm the served hash (M1051). Browsers can keep the infected file for up to 24 hours.
- Content-Security-Policy on payment pages with a strict script-src and connect-src allowlist (M1021 Restrict Web-Based Content; PCI DSS v4.0 requirement 6.4.3). Deploy in report-only mode first (Playbook 3). Verify: violation reports arrive and an unlisted host is reported.
- Inventory and authorize every script on payment pages, with integrity checks and tamper detection (PCI DSS v4.0 requirements 6.4.3 and 11.6.1; M1047). Verify: a script that changes hash raises an alert.
- File-integrity monitoring on the Plausible tracker directory (M1047; Linux auditd watch, Playbook 4). Verify with
ausearch -kafter a test touch in a lab. - Restrict outbound egress from the analytics host to an allowlist (M1037). Verify: a test connection to an arbitrary external host fails (Q7 logic).
- Use payment provider hosted fields or an iframe so card data never exists in the merchant page DOM (M1048 Application Isolation and Sandboxing). Removes the skimmer's data source even if a script is poisoned.
- Remove third-party and analytics scripts from payment pages or serve them from a sandboxed origin (M1021, M1048). Analytics on the checkout rarely justifies the risk.
- Patch SLA and exposure management for self-hosted tooling, including developer components accidentally exposed to the internet (M1051, M1016 Vulnerability Scanning). A 2025 install that was never updated is the reported root cause pattern.
- Network segmentation and secrets rotation for analytics servers (M1030 Network Segmentation; M1026 Privileged Account Management).
Deployable Playbooks
Playbook 1: Verify and upgrade a self-hosted Plausible CE (Docker Compose)
Prerequisites: shell access to the Plausible host, Docker Compose, a backup or snapshot of the ClickHouse and Postgres volumes, and a maintenance window. Container and path names vary; the steps discover them.
Reboot required: No. The Plausible container restarts.
Rollback: restore the saved compose file and image tag and restore the volume snapshot. Do not leave a 3.0.0 to 3.2.0 instance reachable from the internet; keep the Playbook 2 block in place if you roll back.
# 1. Record the running version and save the compose file (rollback evidence)
docker compose ps
docker compose images
cp docker-compose.yml docker-compose.yml.before-upgrade
# 2. Search reverse-proxy and application logs for storybook requests (any hit: rebuild, rotate secrets)
grep -rIn "/storybook" /var/log/nginx/ /var/log/caddy/ 2>/dev/null | head -50
# 3. Locate the tracker directory inside the running container and hash it
docker compose exec plausible sh -c 'find / -type d -path "*priv/tracker/js" 2>/dev/null'
docker compose exec plausible sh -c 'cd "$(find / -type d -path "*priv/tracker/js" 2>/dev/null | head -1)" && sha256sum *.js' | tee /root/tracker-running.sha256
# 4. Hash the same directory from a fresh container of the SAME tag you currently run, then compare
# (replace TAG with the tag from step 1)
docker run --rm --entrypoint sh ghcr.io/plausible/community-edition:TAG -c 'cd "$(find / -type d -path "*priv/tracker/js" 2>/dev/null | head -1)" && sha256sum *.js' | tee /root/tracker-official.sha256
diff /root/tracker-running.sha256 /root/tracker-official.sha256 && echo "tracker files match the official release"
# 5. Upgrade: set the image to v3.2.1 or later in docker-compose.yml, then
docker compose pull && docker compose up -d
# 6. Verify the fix: the storybook path must no longer answer
curl -s -o /dev/null -w "%{http_code}\n" https://YOUR-PLAUSIBLE-HOST/storybook
Playbook 2: Block /storybook at an nginx reverse proxy (stopgap)
Prerequisites: nginx in front of Plausible and permission to reload it.
Reboot required: No. nginx -s reload applies it.
Rollback: remove the location block and reload nginx.
# Add inside the server block that proxies to Plausible
location ^~ /storybook {
return 404;
}
# Apply and verify
nginx -t && nginx -s reload
curl -s -o /dev/null -w "%{http_code}\n" https://YOUR-PLAUSIBLE-HOST/storybook
Playbook 3: Content-Security-Policy in report-only mode for payment pages
Prerequisites: control of the response headers for the payment pages, a CSP report endpoint, and an inventory of the script and API hosts the page legitimately uses. The hosts below are placeholders.
Reboot required: No.
Rollback: delete the header. Report-only mode blocks nothing, so rollback is also safe to do last.
# nginx example: report-only first, enforce after one clean review cycle add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' https://scripts.example.com; connect-src 'self' https://payments.example.com; report-uri /csp-report" always; # Verify a violation is reported: from a lab browser, load the page with a test script from an unlisted host # and confirm the report arrives at /csp-report. Enforce by renaming the header to Content-Security-Policy.
Playbook 4: auditd watch on the Plausible tracker directory (Linux)
Prerequisites: auditd installed on the Plausible host and the real path of the tracker directory (Playbook 1, step 3). If Plausible runs in a container, watch the volume or overlay path that holds the files, or use your FIM product.
Reboot required: No. Add the rule to /etc/audit/rules.d/ to persist it across reboots.
Rollback: auditctl -W TRACKER_DIR -p wa -k plausible_tracker and remove the persisted rule.
TRACKER_DIR=/path/to/priv/tracker/js auditctl -w "$TRACKER_DIR" -p wa -k plausible_tracker auditctl -l | grep plausible_tracker # After a test touch in a lab: ausearch -k plausible_tracker -i | tail -20
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | If a Plausible host shows a Q5, Q6 or N1/N2 hit, network-isolate it through EDR host containment. Alert on or sinkhole the exfiltration domains at DNS. For a merchant: take the analytics script off payment pages (tag manager or CSP) until the operator confirms it is clean | SOC / IR | Containment timestamp, change ticket |
| Isolate: by platform | CrowdStrike: network-contain the host. SentinelOne: network quarantine (Disconnect from Network; confirm the label in your console version). Microsoft Defender: Isolate device (full network isolation). | SOC / IR | Containment timestamp, change ticket |
| Isolate: by platform | CrowdStrike: network-contain the host. SentinelOne: network quarantine. Microsoft Defender: isolate device. Keep the host powered on for evidence on all of them | SOC / IR | Containment timestamp |
| Scope | Run Q1 to Q3 fleet-wide for 2026-10-24 through 2026-10-28; list machines and users that resolved the tracker or checkout hosts; ask whether they paid by card; run N3 on proxy logs | SOC L2 | Host list, user list |
| Preserve | Copy the tracker files, access logs and a disk snapshot before any cleanup; record the SHA-256 of every file in the tracker directory | IR | Evidence archive with hashes |
| Eradicate | Rebuild the Plausible host from a clean image at 3.2.1 or later rather than patching in place; rotate application secrets, database and mail credentials and any API keys stored on it; purge CDN and proxy caches | IR + IT | Rebuild record, rotation log, served-hash check |
| Notify | Tell the payment provider and card issuers; follow your breach-notification duties (the platform operator reports it is notifying merchants, payment providers and authorities). Advise affected shoppers to ask their bank to replace the card and to watch for CPF misuse | Legal / Fraud | Notification log |
| Recover | Return the host after a clean 7-day Q4 to Q7 baseline; deploy Playbooks 1 to 4 and the CSP in enforce mode | IT / Detection Eng | Clean query results, sign-off |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | CrowdStrike coverage | SentinelOne | Defender XDR |
|---|---|---|---|---|---|---|
| T1195.002 | Poisoned tracker served to checkouts | Q3 | none | Partial exposure only; the script itself is outside endpoint telemetry | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1190 | Suspected CVE-2026-8467 entry (unconfirmed) | Q4, Q5, Q7 | IOA 2 | Partial HTTP requests to /storybook are outside endpoint telemetry (N1) | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1565.001 | Tracker file modified | Q6 | IOA 3 | Partial command-line evidence only; file writes need FIM (N2) | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1059.007 / T1027 | Character-code array decoded and run with eval | Q9 | none | GAP in the browser; Q9 sees script hosts only | GAP: same limit as CrowdStrike | GAP: same limit as CrowdStrike |
| T1056 | Card, CVV and CPF captured in the browser | none | none | GAP outside endpoint telemetry | GAP | GAP |
| T1041 / T1132.001 | POST of Base64 record to exfiltration domains | Q1, Q2, Q8 | IOA 1 | Partial sensored machines only; shoppers' devices are not covered | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
Known gaps: the skimmer executes in shoppers' browsers, so the people most harmed are outside your endpoint telemetry. Endpoint hunts cover your own machines and the analytics host. Webhook IDs and URL paths need proxy or gateway logs. Validation gates (section 11): telemetry ready, 14 to 30 day baseline, lab positive tests, then promotion.
Hunt Summary Ticket
TITLE: Hunt - Convertr / Plausible analytics supply-chain skimmer (Brazilian Magecart group)
SEVERITY: High (card, CVV and CPF theft at checkout; remediated by the operator 2026-10-27; entry point unconfirmed)
SCOPE: Staff, QA and kiosk machines that loaded a Convertr checkout 2026-10-24 to about 2026-10-28;
any self-hosted Plausible Community Edition 3.0.0 to 3.2.0; web proxy and gateway logs
HYPOTHESIS: A machine of ours ran the skimmer and resolved 1x0.is, or our Plausible host was reachable
through /storybook and its tracker file was modified.
QUERIES RUN: Q1 exfil DNS | Q2 process attribution | Q3 exposure | Q4 Plausible inventory | Q5 BEAM child
| Q6 tracker file commands | Q7 BEAM outbound | Q8 cartao/pagamento | Q9 eval pattern
+ Native N1-N6
DO FIRST: Run Q1 and Q2 over 30 days; run Q3 for 2026-10-24 to 2026-10-28 and ask each user about card
use; search access logs for /storybook (N1); verify tracker files (N2).
FINDINGS: <pending analyst execution>
GAPS: Browser-side skimmer execution and shopper devices are outside endpoint telemetry; file writes
to the tracker need FIM; webhook IDs and URL paths need proxy logs; all indicators single-sourced.
ACTIONS: Upgrade Plausible to 3.2.1+, rebuild on any hit, rotate secrets, CSP report-only then enforce,
payment-page script inventory, alert on exfil domains (detect only), notify provider if exposed.
OWNER: HuntPack
VERSION: v0.1 2026-11-01
Changelog
References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| 1 | Sansec: Notorious Brazilian Magecart group hijacks Convertr checkouts via outdated Plausible install | Behavior, timeline, all atomic indicators, suspected CVE | 2026-11-01 |
| 1 | NVD: CVE-2026-8467 | Vulnerability description, affected library versions, CVSS | 2026-11-01 |
| 2 | BleepingComputer: Hackers hijack Convertr analytics script to steal payment card data | Corroboration of narrative only (condensed text saved; no IOC values) | 2026-11-01 |
| 3 | SecurityOnline: Convertr supply-chain attack via Plausible | Corroboration of behavior (no IOC values) | 2026-11-01 |
Source snapshots are saved next to this file in Convertr-Plausible-Magecart-Hunt-sources/. MITRE ATT&CK technique and mitigation identifiers and PCI DSS requirement numbers are cited by ID only.