VULONE Threat Intelligence
Executive summary
a paid service let its customers log into other people's Brazilian pharmacy accounts without ever triggering the account's SMS code. It did this by forging the "trusted device" record the storefront relies on, so the login looked like a returning customer on a known device.
For eleven days a single cloud VPS ran a commercial account-takeover operation against Brazilian retail accounts, alongside parallel credential campaigns against payment providers. On 26 October 2026 we identified the host through services it exposed to the internet without authentication, and over the course of the day captured and analysed its code, logs and configuration (the scope and limits of that access are in §8).
This was not a kit or a proof of concept. It was a product: a multi-tenant panel with roles and per-customer entitlements, four headless credential checkers, a customer-facing Chrome extension, a Windows "tester" package shipped in four versions, and a private clone of a commercial breach-search service. The output files held 732 unique accounts the crew's own checker had authenticated (783 record blocks before de-duplication) and 3,965 accounts whose password was correct but which were held at a forced reset. A further 85,773 unique CPF/password pairs appear in the logs, 48,296 of them queued and never run.
The technique matters. In the observed flow no second-factor challenge is presented at any point. The operation performs the tenant's OAuth2 sign-in server-side, returns the token to a buyer's browser through the storefront's own callback, and writes the storefront's device-trust values before the token is redeemed. Whether the accounts had a second factor, and whether the forged device record would have satisfied it, the artefacts do not say; we describe what was observed and label the rest inference.
1. Key judgements
| # | Judgement | Basis | Confidence |
|---|---|---|---|
| 1 | The host is the production back end of a commercial account-takeover service, not a lab or a honeypot | client roles and entitlements, victim records, tooling downloads by unrelated IPs | High |
| 2 | The session flow abuses client-controlled device-trust attributes plus a native token callback; both are design choices, not a software vulnerability | panel and extension source, observed HTTP sequence | High |
| 3 | 732 unique retail accounts were authenticated by the crew's checker and stored in plaintext on the host | recomputed from output files, de-duplicated by CPF | High |
| 4 | The crew ran a customer model with per-brand entitlements | users.json roles and entitlements | High |
| 5 | Customers paid for access | no billing, pricing or transfer record was found | Unconfirmed |
| 6 | The same host runs mass exploitation and credential campaigns against payment and hosting providers | scripts, logs, target lists | High for capability, not established for success |
| 7 | No ransomware capability is present on the host | no C2, loader, exfil staging, encryptor or leak site (one host only) | Moderate-high |
| 8 | The crew is Brazilian, Portuguese-speaking and domestically focused | language, targets, working hours | Moderate |
| 9 | The operation can be attributed to named individuals | no reliable identifier in the artefacts | Not supported |
2. Discovery and host
The entry point was not an exploit but a one-line file server: python3 -m http.server running as root over /root, on all interfaces, no auth, directory listing on. It exposed source trees, archives, credential lists, session stores, screenshots of the crew's own tooling, and the full-machine backup made the day the login flow first worked.

A setup note the operator wrote that same day (/root/[redacted]/LEIA-ME.md) proved accurate on every technical point we could verify, which let us read the implementation rather than infer it from traffic.
The box was shared ground. Besides the crew's tooling, two unrelated footholds sat on the same host (a root bind shell, and a WordPress vhost with a POST-only PHP webshell running as www-data), both assessed as third-party. The operator either did not notice or did not mind.
The host is a Brazilian-operated cloud VPS on a US-registered /25 (no NAT), running Kali Rolling 2026.3, reinstalled 2026-10-13/14. It has 3 vCPU / 3.9 GB RAM (swap saturated at collection), 74 GB disk, 182,092 files, 11d 17h uptime and 23 interactive sessions. iptables was ACCEPT on all chains, with no fail2ban and SSH root login by password enabled. Runtime: Node 20.18.1, Chromium 150, MariaDB, Apache, xrdp. Specific addresses and ports are reserved for the AMBER pack.
At capture the operation ran as ten systemd units bound to all interfaces: the bypass panel (Retailer A) and a second panel/relay (Retailer C); four checkers (Retailer A ×3, Retailer B ×1); two bulk bypass workers; and the breach-search clone's static and auth services. Supporting Python listeners served the /root directory index (the engagement's origin, hung by capture time), a secondary file-delivery server, and the clone front end. Chromium DevTools endpoints backed the headless checkers.
3. The panel: a multi-tenant product

The panel is one Node HTTP server, sp_check/web/bypass-server.js (df608cb3…a60ed45, 38 KB / 265 lines), with no framework and no build step: server-rendered HTML with a handful of JSON endpoints. Sessions are a 48-hex HttpOnly; SameSite=Lax cookie with a 24-hour lifetime; customer credentials are salted SHA-256 in /root/bypass/users.json; and there is a per-IP lockout (HTTP 429).
One detail explains how a single binary serves two very different audiences: a /quem endpoint injects the logged-in user's name and, for admins, an ADMIN link into the page template. Regular customers get the flow routes (/ir to start a bypass for one identity, /done as the extension callback, /ext.zip to download the extension); admins additionally get the customer-management console (/admin, /admin/cliente, /admin/lista, plus create/role/reset/remove routes).
The customer record is what turns tooling into a service: { nome, role, salt, hash, criadoEm, ultimoUso, contagem, lojas:[...] }. lojas is a per-brand entitlement list, so a customer entitled to one brand sees the other tab padlocked, enforced both client- and server-side. The admin console carries a "gives ADMIN power" checkbox, which lets the crew resell reseller rights, and the ultimoUso + contagem fields give the owner a per-customer usage meter: the shape of a metered service. At capture there were four accounts (owner/architect, a second admin, an everyday operator, and one dual-brand client); nothing on the host records whether the client paid.
Two instances run a second binary, workers/bypass-wk.js (de47daa9…1a583fe4), in bulk mode: it takes a credential database rather than a single CPF and iterates. The panel is retail; the workers are wholesale.
4. The checkers: from credential list to authenticated accounts

Four instances of one codebase differ only in the storefront they target. web.js (05a8e235…945d0c86) is the UI and queue; engine.js (6043da4c…e843348631) is the protocol. The engine does not drive a browser DOM; it reproduces the front-channel requests directly, in four steps:
GETthe retailer's Azure AD B2C/authorizeendpoint, capturing the transaction state, the CSRF token and the self-asserted endpoint from the returned HTML.POSTthe credentials to the self-asserted sign-in endpoint, withOriginandRefererspoofed to the login host.- Follow the redirect chain with redirects disabled, pulling the authorisation code out of the
Locationheader. - Exchange the code server-side and read the profile from the account GraphQL endpoint, using the resulting bearer token.
The HTTP client is curl-cffi bound for Node and pinned to Chrome impersonation profiles, which is why TLS fingerprinting does not stop it (the units carry an LD_PRELOAD for libidn2, without which the binding will not load).
The most useful artefact for defenders is the error taxonomy the engine retries: BLOQUEADO_AKAMAI, FALHA_REDIRECT, ERRO_RESPOSTA, FALHA_EXCECAO, TRANSITORIO. The crew's own note explains why the Akamai edge block never stopped them: it applies to page loads from bad-reputation IPs, while the credential test is pure API and the final session is minted in the buyer's own clean residential browser. The edge control and the authentication path do not share a risk signal, and the architecture is built around that gap. Results sort into aprovadas / mortas / senhas_novas / captchas; the captchas bucket is the tell that they were tripping per-account attempt counters, not per-IP throttles.
Volume, and a correction. Across the four stores, 783 approved record blocks resolve to 732 unique CPFs over 66,447 input rows (Retailer A instances account for 445 + 180 + 1 blocks; Retailer B for 157). Earlier drafts counted lines of aprovadas.txt as accounts, but each approved account is a 2-to-20+ line block, so the "14,306" line-count figure overstated reality by roughly twenty. Parsing by CPF and de-duplicating gives 732. That changes the language from "tens of thousands of victims" to "hundreds of working accounts plus ~4,000 reset-blocked ones": both serious, only one accurate. The recomputation script is kept with the evidence.
Two overlap figures appear in this report and measure different things. The 51 CPFs that differentiate 783 blocks from 732 uniques are accounts seen in more than one of the four stores (including the three Retailer A instances overlapping each other); the 7 in §7 is the narrower Retailer A ∩ Retailer B cross-brand subset. Likewise three scale figures measure different pipelines: 66,447 is checker input rows; 85,773 is the full de-duplicated credential corpus across all logs (feeding both checkers and bulk workers), of which 48,296 were queued but never run.
5. The session flow
The flow runs in five stages: two are ordinary OAuth2, and three are an abuse of the storefront's own conveniences. The description here is deliberately semantic; exact identifiers, call sequence and GraphQL operation names are reserved for the AMBER pack.
(1) Server-side authorisation. The panel runs a real authorisation-code flow against the retailer's B2C tenant. The tenant, public client id, policy and callback path are hardcoded, and shared with the checker engine. The returned ID token carries more than identity (CPF, legacy customer id, commerce-platform customer id), which the extension later parses client-side with no extra API call.
(2) Hand the token to the victim's browser. The panel keeps nothing. It opens a tab in the customer's browser at the storefront, with a marker parameter the extension matches by regex, and returns the token through a redirect to the storefront's own native login callback. The session cookie is therefore minted by the retailer's code, in the retailer's origin, with the retailer's fingerprint and reputation. There is no API replay to detect, because no API is being abused.
(3) Create the device before you claim it. The retailer binds sessions to a trusted device using attributes set when a device completes a login; in this implementation those are ordinary client-side cookie values plus a serialised customer record. The extension writes them first, from panel-supplied data, before the token is redeemed. Asked "is this a known device?", the storefront answers yes, because the record was written seconds earlier by the same client now claiming it. Trust is bound to data the client controls, so the client manufactures trust.
The MV3 extension (bypass/ext.zip, 6daa3855…b38c47e, v1.10, with v1.8 retained) is 9.3 KB and runs that sequence: match marker → open redirect → wait for the access-token cookie → decode the ID token for CPF and customer ids → run the storefront's own token-coexistence and lookup calls in-context → write device attributes and customer record → land on the account page.
(4) The panel-side jar. Where the in-browser flow fails (an inconsistent device record), the panel has a fallback: it assembles a full cookie jar server-side, and the extension installs each cookie with the correct domain, path, flags and expiry. The result is the same session, delivered as data instead of as navigations.
This is the most significant path for defenders, and it sits awkwardly: a browser receiving a full cookie jar in one burst, for a domain it did not just navigate to, is visibly abnormal on the client, yet leaves little trace in a server log that records only the resulting session. That asymmetry, not the volume, is the point.
(5) Keep it, cheaply. Retail accounts are valuable for stored payment methods, loyalty balances and health-benefit credits. The panel exposes exactly two actions mapping to those: log in without SMS, and change the phone number (the standard takeover-completion step, since the phone is the recovery channel). Nothing touches the phone in the observed flow, so no OTP is generated. The traces that do exist are identity-side: a token on a navigation callback, a device record from a new context, and a session appearing seconds after a password auth from a different network than the browser. §9 builds draft rules on those signals.
6. Distribution and the intelligence clone
The file listing is the crew's customer portal, and its filenames read like a release history: four Windows "tester" packages (testador-v3.3…v3.7-win.zip, 1.76 MB each, a packaged Chromium profile with the extension preloaded, for non-technical buyers), the extension current and previous, a 27 MB local checker build, brand probe scripts, and panel state. Four tester versions in ten days matches the panel's own changelog discipline (web.js.bak-v35, …-v43bug). The customer link is served from a lookalike domain, and the extension is mirrored as a single JS file on a public paste host; those domains are in the AMBER pack.

Roughly 3 MB of /root is a private rebranded clone of a commercial breach-search service: a static front end (a97418fd…b9f8ef34), an auth service with a hidden admin route (6514b2c4…40d20da9), and a config pointing at the vendor's real endpoints with a full API key (6e69e601…082a88d4). It is a real client, not a mock; the key is now dead (HTTP 401 on 2026-10-26 from both the host and our network), which for a crew whose input is breached credentials is a supply problem. The clone is defended like a product: cookie-scoped sessions, a secret key regenerated each start, a hidden env-supplied admin path, no local breach DB, and no exfiltration, beaconing or telemetry in the code.
7. Beyond the panel, and victimology
The bypass business is the profitable core, but the host also runs a scripted, end-to-end intrusion pipeline (pipeline.sh: discovery → HTTP fingerprinting → template scans → SQLi triage, resumable). Reconnaissance output is branded, deduplicated and prioritised: ~2,883 payment hosts, ~13,000 live hosts, 5,384 subdomains across 17 brands, shortlists, and curated password lists from 5K to 14M entries.
On top of that recon, credential attacks ran against a payments API front end (argo_brute.py 04740142…0a5e5345, up to 380,889 entries, 64 workers, hit file empty), a Keycloak identity provider, an exposed MySQL, a WordPress estate, and hosting panels. The most telling job is only three lines of bash: a daily Metabase cron using pg_sleep(3) in a JSON field as a blind-SQLi timing oracle, with a rotated X-Forwarded-For. It is notable precisely because it is unsophisticated, run as a background task. Output in lab/hunt includes 42 HS256 JWTs for a payments-as-a-service platform and its signing-secret wordlist; whether they were used to move money the host does not say, and the brute campaigns left no successful login behind.
Affected brands are referred to by label and are being notified directly; named attribution will follow notification, where the party consents. Pharmacy Retailer A: 582 unique CPFs (626 blocks) across three checker instances. Pharmacy Retailer B: 157 unique CPFs, 7 of them also in Retailer A (582 + 157 − 7 = 732). Pet-supply Retailer C: email-code entry and password bypass via the panel, accounts not counted. Payments platforms (Vindi- and Asaas-class) and a third platform were targets of brute and blind-SQLi campaigns with no recorded success (42 JWTs captured, four 403s on the brute). University and federal-court systems appear only as reconnaissance and credential probing. We assert no breach of any named company beyond what the operator's own output files demonstrate.
Why this matters to ransomware-focused defenders: the same host sells the initial-access half of an intrusion chain. Present: mass exploitation, credential harvesting/validation/storage at scale, session minting via client-controlled device trust, and a per-brand customer model. Absent: C2, loaders, exfil staging, encryptors and leak sites. We tested four hypotheses: a fraud crew explains every artefact; an access broker fits the capability but has no transactional evidence on the host; a ransomware affiliate fails on the absent payload stack and the retail (not domain-admin) target mix; and a simulation is only weakly rejected (real profile/order context, unrelated IPs pulling the tooling). Assessment, moderate confidence: a small commercial fraud service that also produces access it does not appear to consume, and idle footholds get sold.
8. Method, access and disclosure
Collection relied on services the host exposed without authentication, plus artefacts a third party had left on the same machine. We did not exploit a vulnerability in the crew's code. We added a single SSH public key to authorized_keys to enumerate and copy evidence; every other change was a file we wrote to /tmp or /var/tmp. No service was stopped, no operator account used, and no recovered credential tested against any storefront. Victim data was not taken beyond the evidence copy; §7's counts come from parsing the operator's own output in the forensic mirror, and this report contains no victim rows.
This is a corrected second version; four items changed after re-validation: the account total (line-count to de-duplicated CPF), the payment-processor brute outcome (no recorded success), the MFA description (no challenge was observed, which is not the same as a bypass), and the detection-rule status (drafts, untested). We would rather publish the correction than the convenient figure.
This public version is released in coordination with notification to the affected brands' security teams and the relevant national CSIRT, with named attribution and live infrastructure withheld while the host is active. Deliberately withheld: live infrastructure (host address, ports, lookalike/clone domains, egress proxies); identity configuration (tenant/client ids, policy, callback path, clone admin route, device-cookie and marker literals); all credentials and hashes belonging to third parties; victim rows; the exact call sequence and GraphQL operation names; operator handles and any personal/geographic inference; and our own access artefacts. The private version and full detection pack are available to affected storefronts and jurisdictional CSIRT/LE under TLP:AMBER.
9. Detection engineering
These rules are drafts: none has been run against a live sensor or log corpus, so test and tune before relying on them. Literal match values (marker parameter, panel cookie name, extension strings, clone domain) ship in the AMBER pack so they do not appear in a redistributable document; the logic is shown here.
Sigma, token on a front-end callback (the core invariant):
title: OAuth token arriving via front-end navigation callback
status: experimental
logsource: { category: proxy }
detection:
callback: { cs-uri-query|contains: 'access_token=' }
front: { cs-uri-stem|contains: ['/callback-login', '/callback', '/auth/callback'] }
filter_static: { cs-uri-stem|endswith: '.js' }
condition: callback and front and not filter_static
level: high
# Invariant: a bearer token in a query string on a front-end route,
# followed within ~1s by a session cookie minted for that user.
# A second cheap, deterministic rule matches the marker parameter on a storefront URL (value in AMBER pack).
KQL, device trust that appears out of nowhere (hunt, not alert):
// Requires SigninLogs from the B2C tenant with a populated DeviceDetail.deviceId.
SigninLogs
| where TimeGenerated > ago(30d) and ResultType == 0
| extend DeviceId = tostring(DeviceDetail.deviceId)
| where isnotempty(DeviceId)
| summarize FirstSeen=min(TimeGenerated), Auths=count(), IPs=make_set(IPAddress,50)
by UserPrincipalName, AppDisplayName, DeviceId
| where FirstSeen > ago(1d) and Auths >= 2 and array_length(IPs) >= 2
| order by FirstSeen desc
// A new device, used more than once, from >1 address on its first day: a fabricated record reused
// across buyers. Also fires on travel and NAT changes, hence a hunt.
Suricata / YARA / host-triage: Suricata rules match the marker parameter and panel cookie in URI/cookie headers; a YARA rule flags the unpacked extension (product name + two of {device attrs, marker regex, chrome.cookies.set, panel cookie}, filesize < 2MB); host triage greps *.js for the panel cookie name, locates aprovadas.txt and users.json, and matches running bypass-server|engine.js|testador processes. Full rules with literals are in the AMBER pack. Note: the device-attribute names also occur in the storefront's own bundled JS, so scope YARA to unpacked extension directories and server code, not browser caches.
10. Timeline and indicators
Timeline (UTC). 10-13/14 host reinstalled (Kali Rolling, Node 20, Chromium). 10-15 first successful login, immediate full backup; panels, checkers and extension v1.8 deployed. 10-15→17 checker fleet scaled to three Retailer A instances + Retailer B, 732 accounts authenticated, breach-search clone deployed. 10-18→20 extension v1.10, tester packages v3.3→v3.7, distribution via file server, lookalike domain and paste host. 10-20→24 payments pivot: argo/Keycloak brute, daily Metabase SQLi cron, JWT capture, DB brute. 10-24→26 mass exploitation: PHP RCE scanner, WordPress/hosting-panel brute, university and court probing. 10-26 17:17 we gain visibility; snapshot 18:17, capture to 19:20. 10-26 this report; brands and CSIRT being notified ahead of any named publication.
Live network indicators: host address, ports, tenant/client ids, callback path, clone admin route, lookalike/clone domains and egress proxies are withheld while the host is active; available under TLP:AMBER.
Behavioural artefacts (patterns, safe to hunt on): a hex marker parameter on storefront URLs (extension trigger); a 48-hex panel session cookie (HttpOnly); two client-side device-trust cookies plus a serialised customer blob (device-trust forgery); a bearer token on a front-end callback followed within ~1s by a minted session (detection invariant); an MV3 extension with cookies+scripting, v1.10 (v1.8 retained).
File hashes (SHA-256), the portable indicator, safe to publish:
6043da4c209f99151eaf5a356c7729e1ab2048dc6a09080d5b10be6843348631 checker/[a]/engine.js
05a8e23569b97bb12c2109420fc5f940da8ec91d0fd48d6a25d9d64f945d0c86 checker/[a]/web.js
058c7ad6aa7382854a1a7e3b10a5285ab6e6fc2372c0d698ef76278e8382c4ea checker/[b]/engine.js
f34990b0e3dfa9721560d5df7efda42f0152c54473a75140d8427e50f0840328 checker/[b]/web.js
df608cb325dd27c9b5c3c5d3d85a915f4b82a3acc025126ca9d8f5a86a60ed45 sp_check/web/bypass-server.js
872ddbdf1267164cc1bc4e34eccc4c7ea762424d3614c602bab95cd2ecdb0bd4 sp_check/web/[c]_web.js
de47daa9aa5d14a1439e115d8dbdc130d6e8433382161a41c05be98e1a583fe4 workers/bypass-wk.js
6daa38550abcc392fbac9213ce5eb53f23c9680c779d4873150378a87b38c47e bypass/ext.zip
2d05a252bf61a1a57b22e74d2b9a1f5d74790bb71ed3b7ba93f90dfcb6e37a12 bypass/serve.py
545856ff4e01b9a3560808004e7c3c0aa4957b835b08d244d676b02be5759d88 bypass/[a]-checker-local.zip
d21b804a47a2505eab4ce640bd9c82e3eb96261c37a14a65c3a817ee96c85658 bypass/users.json
a97418fd9d7cc99e801e53a905de47ca5a0e44f2ab24e4fbc0094d29b9f8ef34 intelx/static_server/static_app.py
6514b2c4340f2fad77b200187b8efe2d9cad392cb228298bbdc7c72240d20da9 intelx/auth_service/auth_app.py
6e69e601b0dbd08cc9c6ddf61536addebddbf03f74e9364376e02614082a88d4 intelx/static_server/js/config.js
047401429a0e4c400250281065d4c94f8a8194de1a4a3957996743470a5e5345 lab/hunt/argo_brute.py
11. Closing
The notable part of this operation is not a broken cipher or a zero-day. It is three ordinary product decisions that do not compose safely: "remember this device", "finish the login in your browser", and "trust the token once it arrives". Each is reasonable alone; together they let a client write its own trust record, and once it can, a second factor that is never consulted provides no signal.
The second lesson is structural: this operation stayed visible because one service exposed /root to the internet, and stayed running because nobody watched it. Infrastructure like this is small, cheap and unremarkable, and it produces the accounts, sessions and footholds the rest of the ecosystem buys. Watching it is cheaper than cleaning up after it, and counting it properly is part of watching it.