Ted Backdoor & curlRAT: Trojanized HAProxy Load-Balancer Toolkit
Executive Summary
Rapid7 Labs documented a previously undocumented Linux toolkit at two South Korean organizations (automotive, media) in which a custom backdoor, internally referred to as "ted" from debug strings left in the binary, is compiled directly into the victims' own HAProxy 2.8.12 source tree as a native filter-API plugin. It is not a separate process bolted onto HAProxy; it rides HAProxy's own memory pools, event scheduler, and process-management infrastructure, so ordinary load-balancing continues to work while the implant intercepts decrypted HTTP traffic underneath it.
The attacker's objective is threefold: (1) a self-erasing command-and-control channel triggered by a request for a specific fake image path, with commands relayed through a named pipe and the triggering request scrubbed from HAProxy's own connection counters and logs before it ever reaches the backend; (2) a watering-hole capability that selectively rewrites or swaps page content for visitors matched by IP, User-Agent, referer, or a credential smuggled in the Accept-Language header; and (3) long-term persistence and credential theft via trojanized replacements of crond, sshd, agetty, atd, and polkitd, including an SSH keylogger and a companion beacon, curlRAT.
The single highest-value defensive angle is that this implant's entire design goal is making the load balancer's own logs and counters lie. Any hunt or control that depends solely on HAProxy's own logging, access logs, or connection statistics will not see it. Effective detection has to come from independent telemetry: EDR-visible process and file-write events on the underlying Linux host, binary integrity/hash verification, and out-of-band network egress monitoring, none of which the implant is documented to tamper with.
Defender priority: treat every internet-facing HAProxy (or any edge component doing SSL termination and traffic routing) with the same binary-integrity and EDR coverage as an application server. A load balancer that is only monitored through its own logs cannot be trusted to report its own compromise.Source Review & Web Hunter Notes
| Tier | Source | Key Finding | Carry Forward |
|---|---|---|---|
| 1 | Rapid7 Labs (primary research) | Original technical report: ted backdoor architecture, curlRAT, SSH keylogger, timestomping, log/history scrubbing, stager behavior, attribution reasoning | Yes |
| 2 | The Hacker News | Journalistic synthesis of the Rapid7 report; consolidated IOC list (domains, hashes, file paths) and confirmed all six C2 domains were NXDOMAIN as of publication | Yes |
| 2 | Security Affairs | Extended verbatim quotes from the Rapid7 report; confirms C2 trigger endpoint, Accept-Language operator credential, Accept-Ranges header deletion, connection-counter scrubbing | Yes |
| 3 | GBHackers | Independent summary; corroborates hash 72e70936…, adds two additional hashes (94630b96…, a8bfab4d…) and the "Ricochet Chollima" APT37 alias | Partial (see hash confidence note below) |
| 3 | hendryadrian.com (Rapid7 report mirror) | Confirms timestomping detail, log-scrubbing keyword list, and file artifact paths; introduces one hash (5db1b6d5…) not seen elsewhere | Partial (see hash confidence note below) |
Web Hunter Decisions
Hash confidence tiering. Five distinct SHA-256 values surfaced across the five sources above. Three are corroborated by two or more independently-fetched snapshots (72e70936…, 4bb923eb…, 94630b96…) and are shipped as high-confidence. Two appear in only a single snapshot each (a8bfab4d… in GBHackers only, 5db1b6d5… in the hendryadrian mirror only) and are shipped as single-source/lower-confidence rather than silently dropped or silently trusted: both are still traceable to a saved snapshot, not invented, so they ship with an honest confidence label instead.
No hostile or injected content encountered. All five fetched sources returned ordinary journalistic/technical reporting. None contained instructions directed at the research agent, no prompt-injection attempts, and no request to fetch additional URLs, change output paths, or skip pipeline steps. All five are retained as legitimate citations.
Unconfirmed detail flagged, not shipped as fact. The task brief's claim that curlRAT "aborts on non-virtualized hosts" is confirmed verbatim in the Rapid7 source (it refuses to run unless it detects virtualization signatures, consistent with confirming deployment onto the intended cloud/VM-hosted production infrastructure rather than a bare-metal analysis box). This is stated accurately in Section 6 below rather than assumed to mean conventional sandbox-evasion (which is normally the inverse check).
Hunt Brief & Attack Chain
Hunt Hypotheses
- H1 (high conf). A host runs a HAProxy, crond, sshd, agetty, atd, or polkitd binary whose SHA-256 matches a known ted/curlRAT sample.
- H2 (high conf, FP medium). The HAProxy process itself spawns a shell or network utility as a child process, which a load balancer has no legitimate reason to do outside of a configured external-check script.
- H3 (medium conf). A named pipe matching the pattern
/tmp/t[ID]_w.pipeis created by a process whose parent is haproxy, consistent with the documentedted_make_pipe_name/ted_create_multi_pipe_filecommand-relay mechanism. - H4 (high conf, perishable). A host resolves one of the six documented C2 domains. All six were NXDOMAIN as of 2026-09-04, so this is a retrospective/historical-log hunt, not a live-detection control.
- H5 (medium conf, FP medium). A cron/at/getty/polkit daemon image makes an outbound network connection directly, which none of these programs do as part of normal operation.
- H6 (medium conf). A process executes from one of the documented non-standard artifact paths (
/var/lib/snapd/g580,/tmp/jasper-log), consistent with the stager's root-verification / OS-fingerprinting step before dropping the toolkit. - H7 (high conf). A file write occurs at one of the four documented artifact paths (cache file, SSH-keylogger output, stager drop paths).
- H8 (high conf, requires log ingestion). HAProxy's own access log records a request for the specific fake image path used to trigger C2 mode. Because the implant is documented to scrub this from HAProxy's live counters and connection logs, this only works against out-of-band-shipped logs (e.g. a syslog/Filebeat forwarder that ships the line before ted can erase it, or an upstream WAF/CDN log that never passed through the compromised HAProxy at all).
Attack Chain
| Step | Telemetry | Hunt Angle |
|---|---|---|
| 1. Initial access (assessed, not directly confirmed by Rapid7's public writeup) | Web/mail server access logs, WAF logs | Out of scope for this pack; see Gap note in Section 15 |
| 2. Root-verifying stager profiles host, drops toolkit, verifies root | Process execution from non-standard paths | H6 / Q6 |
| 3. HAProxy binary replaced with ted-compiled build; crond/sshd/agetty/atd/polkitd replaced | Process execution, file writes, known-bad hash | H1 / Q1, H7 / Q7 |
| 4. Indicator removal: crond timestomped to /usr/bin/ssh's creation time; auth.log/audit.log/bash_history keyword-scrubbed | File metadata, log integrity | Native hunts, Section 7 (no reliable CQL equivalent) |
| 5. Operator sends trigger request for the fake image path; ted enters C2 mode via named pipe | HAProxy access log (out-of-band), named pipe creation | H8 / Q8, H3 / Q3 |
| 6. Operator executes commands, harvests cookies/credentials, selectively rewrites pages for chosen visitors | Not independently visible to endpoint EDR (in-process HAProxy filter logic) | GAP: see Section 15 |
| 7. curlRAT beacons out (12h default / 30s fast-poll) from trojanized daemons; SSH keylogger captures plaintext passwords | Network connections from daemon images, DNS requests | H5 / Q5, H4 / Q4 |
Consolidated IOC Table
| Type | Value | Confidence | Action | Context | Expiry |
|---|---|---|---|---|---|
| sha256 | 72e70936f0dbe459142a1d867617c35f8d0cce5d18c6a49e1090a2a5adc8e558 | high | detect | ted backdoor sample; corroborated by 3 independent snapshots | 2027-03-13 |
| sha256 | 4bb923eb040aa13ca8fd409c31ee4729c60ddff32e350efe1c5a4a9168a065f5 | high | detect | ted backdoor sample; corroborated by 2 independent snapshots, matches task-brief intel | 2027-03-13 |
| sha256 | 94630b96f628c96a6bff7904b40ffc9ad67c86f8a4ff6080c3b524831c93f402 | high | detect | ted backdoor sample; corroborated by 2 independent snapshots (Rapid7 direct + GBHackers) | 2027-03-13 |
| sha256 | a8bfab4de81a1acb04aacdf757346946b0f5e30f0c9f402004016d0e425119c7 | low | hunt | single-source (GBHackers only); ship as hunt-only, not auto-block, until corroborated | 2026-12-13 |
| sha256 | 5db1b6d52faf60b4f32d6fd0c7c938e4d05d29a14c32ded4a9668357c08b6a91 | low | hunt | single-source (hendryadrian mirror only); ship as hunt-only, not auto-block, until corroborated | 2026-12-13 |
| domain | img.monderhouse.space | medium | hunt | ted C2; NXDOMAIN as of 2026-09-04, retrospective log value only | 2026-12-13 |
| domain | img.smartnords.site | medium | hunt | ted C2; NXDOMAIN as of 2026-09-04, retrospective log value only | 2026-12-13 |
| domain | img.darklights.store | medium | hunt | ted C2; NXDOMAIN as of 2026-09-04, retrospective log value only | 2026-12-13 |
| domain | img.responsive.pstatic.autos | medium | hunt | ted C2; NXDOMAIN as of 2026-09-04, retrospective log value only | 2026-12-13 |
| domain | img.socialteams.store | medium | hunt | ted C2; NXDOMAIN as of 2026-09-04, retrospective log value only | 2026-12-13 |
| domain | img.worksongo.store | medium | hunt | ted C2; NXDOMAIN as of 2026-09-04, retrospective log value only | 2026-12-13 |
| filename | ~/cache/haproxy-1000.cache | high | hunt | ted configuration cache file | 2026-12-13 |
| filename | /var/lib/sshd/c8c68e629bba773a10ac80012d10bf19 | high | hunt | SSH keylogger encrypted credential output | 2026-12-13 |
| filename | /var/lib/snapd/g580 | high | hunt | stager / toolkit drop artifact | 2026-12-13 |
| filename | /tmp/jasper-log | high | hunt | toolkit artifact / HAProxy cache reference | 2026-12-13 |
| url | /favorite_list_2x_m500_ico.jpg | high | hunt | C2 activation trigger endpoint; requires HAProxy access-log ingestion to see, since ted scrubs it from live counters/logs | 2026-12-13 |
| pattern | /tmp/t[ID]_w.pipe (e.g. /tmp/t4471_w.pipe) | medium | hunt | named-pipe command-relay naming convention (ted_make_pipe_name / ted_create_multi_pipe_file); [ID] varies per connection, not an exact-match indicator | 2026-12-13 |
| behavioral | haproxy, crond, sshd, agetty, atd, polkitd binaries replaced/trojanized | high | hunt | persistence mechanism across six system binaries | 2026-12-13 |
The named-pipe pattern is not shipped in the machine-readable IOC appendix (Section 10) as an exact-match value because [ID] varies per HAProxy connection; it is hunted via a wildcard query (Q3) instead of an import-ready atomic indicator.
Affected Surface & Telemetry Matrix
| Surface | Required Telemetry | Priority | Gap Risk |
|---|---|---|---|
| Internet-facing HAProxy / load-balancer hosts | Falcon Linux sensor (process, file, network) + HAProxy access/error logs ingested to NGSIEM | Critical | High: the implant erases its own traces from HAProxy's own logs and connection counters, so log-only monitoring is blind to the C2 channel itself |
| System daemons (crond, sshd, agetty, atd, polkitd) | Falcon Linux sensor process + file-write telemetry | Critical | Medium: detection depends on known-bad hash or a file-integrity baseline; a newly recompiled sample defeats hash-only detection |
| DNS resolution / egress | DNS request telemetry, egress firewall/proxy logs | High | Medium: the known C2 domains are NXDOMAIN as of 2026-09-04; live value is retrohunt only, not real-time blocking |
| SSH authentication | Falcon Linux sensor UserLogon/UserLogoff, auth.log via NGSIEM | High | High: the trojanized sshd captures credentials at the application layer, below where normal auth-success/failure logging would show anything unusual |
| Root filesystem / binary integrity | File-integrity monitoring (AIDE) or package-manager verification baseline | High | Medium: most Linux estates do not run FIM by default; without a pre-established baseline, integrity checks have nothing to compare against |
ATT&CK Mapping
| Tactic | Technique ID | Technique Name | Observed Behavior |
|---|---|---|---|
| Initial Access | T1190 | Exploit Public-Facing Application | Rapid7 assesses (not independently confirmed in the public writeup) likely initial entry via an externally accessible Groupware or mail portal; treated here as an operational assumption, not a confirmed fact |
| Persistence / Defense Evasion | T1554 | Compromise Client Software Binary | HAProxy, crond, sshd, agetty, atd, and polkitd binaries replaced with trojanized builds that retain original functionality |
| Defense Evasion | T1070.006 | Indicator Removal: Timestomp | Trojanized crond binary given the same creation timestamp as /usr/bin/ssh |
| Defense Evasion | T1070.002 | Indicator Removal: Clear Linux or Mac System Logs | Keywords tmp, wget, cron, crond stripped from /var/log/messages, audit/audit.log, cmd.log, secure, syslog, auth.log |
| Defense Evasion | T1070.003 | Indicator Removal: Clear Command History | Same keyword set stripped from /root/.bash_history |
| Defense Evasion | T1027 | Obfuscated Files or Information | Ted configuration protected with chained XOR plus a monoalphabetic substitution cipher; C2 traffic uses feedback XOR with Base64 encoding |
| Credential Access | T1556 | Modify Authentication Process | Trojanized sshd captures plaintext passwords at authentication time, encrypts them with a custom substitution cipher, and writes them to a fixed path |
| Command and Control | T1071.001 | Application Layer Protocol: Web Protocols | C2 responses disguised as standard HTTP/1.0 200 OK traffic; curlRAT beacons every 12 hours by default, or every 30 seconds in operator-enabled fast-poll mode |
| Command and Control | T1205 | Traffic Signaling | A request for a specific non-existent image path (/favorite_list_2x_m500_ico.jpg) silently switches the HAProxy filter into C2 mode |
| Collection | T1557 | Adversary-in-the-Middle | Ted hooks HAProxy's HTTP parsing to inspect decrypted traffic before it reaches backend services, harvesting cookies, headers, and selectively injecting or swapping page content for chosen visitors |
| Discovery | T1082 | System Information Discovery | Stager profiles hostname, OS distribution, and version before installing the toolkit, and verifies root privileges first |
| Defense Evasion | T1497.001 | Virtualization/Sandbox Evasion: System Checks | curlRAT checks for virtualization signatures and refuses to run if it does not find them, consistent with confirming deployment onto the intended cloud/VM-hosted production host rather than a bare-metal analysis environment (the inverse of the more common sandbox-evasion direction) |
Native Audit-Log Hunts
These checks have no reliable CQL equivalent (file birth-time comparison and log-content-gap detection are not standard EDR event fields) and are run directly on the host or via auditd.
1. Timestomp check: crond birth time vs. /usr/bin/ssh
stat --format='%W %n' /usr/sbin/crond /usr/bin/ssh 2>/dev/null # Note: %W (birth/creation time) returns 0 on filesystems/tools that do not expose # it. Where that happens, use a forensic tool that reads the statx btime field, or # debugfs -R 'stat <inode>' /dev/<device> on ext4. Flag any host where crond's # birth time exactly equals ssh's birth time to the second.
2. Log / history tamper detection via auditctl
auditctl -w /root/.bash_history -p wa -k ted_history_tamper auditctl -w /var/log/auth.log -p wa -k ted_log_tamper auditctl -w /var/log/audit/audit.log -p wa -k ted_log_tamper # Alert on any write to these paths from a process other than the expected # logging daemon (rsyslogd, auditd itself, or the shell's own history-append).
3. Root-verifying stager / OS-fingerprint sequence
Look for a short-lived process reading /etc/os-release, hostname, and CPU/hypervisor identification (/proc/cpuinfo, dmidecode, systemd-detect-virt) in quick succession, immediately followed by a new file write to /var/lib/snapd/g580 or /tmp/jasper-log. See Q6/Q7 in Section 8 for the EDR-side correlation of the resulting artifacts.
4. Live named-pipe enumeration
find /tmp -type p -name 't*_w.pipe' -newer /var/log/wtmp # Any FIFO matching this naming convention under /tmp is worth immediate # investigation regardless of age; the -newer filter is a triage convenience, # not a requirement.
CrowdStrike LogScale CQL Hunt Queries
Looks for: execution of haproxy, crond, sshd, agetty, atd, or polkitd whose SHA-256 matches a documented ted/curlRAT sample. FP: effectively none: an exact hash match on these binary names is not expected to occur benignly.
// HUNT: Known-bad hash execution -- trojanized system daemon // MITRE: T1554 // CONF: high FP: low COST: low // REQUIRES: Falcon Linux sensor process telemetry (ProcessRollup2 / SyntheticProcessRollup2) // FALSE POSITIVES: none expected -- these are exact hash matches on named samples // LOOKBACK: 30d -- set the window with the Falcon time picker #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^(haproxy|crond|sshd|agetty|atd|polkitd)$/i | in(SHA256HashData, values=["72e70936f0dbe459142a1d867617c35f8d0cce5d18c6a49e1090a2a5adc8e558","4bb923eb040aa13ca8fd409c31ee4729c60ddff32e350efe1c5a4a9168a065f5","94630b96f628c96a6bff7904b40ffc9ad67c86f8a4ff6080c3b524831c93f402","a8bfab4de81a1acb04aacdf757346946b0f5e30f0c9f402004016d0e425119c7","5db1b6d52faf60b4f32d6fd0c7c938e4d05d29a14c32ded4a9668357c08b6a91"]) | table([@timestamp, aid, ComputerName, FileName, ImageFileName, SHA256HashData, UserName, CommandLine])
Looks for: a shell or common network/scripting tool spawned directly by haproxy, which a load balancer has no routine reason to do. FP: environments with a configured HAProxy external-check command, or a health-check wrapper script that legitimately shells out, will trigger this.
// HUNT: HAProxy process spawning shell or network utility // MITRE: T1557, T1071.001 // CONF: high FP: medium COST: low // REQUIRES: Falcon Linux sensor process telemetry with parent/child lineage // FALSE POSITIVES: configured HAProxy external-check commands or health-check wrapper scripts that shell out legitimately // TUNING: exclude known-good external-check CommandLine patterns (e.g. your specific health-check script path) by name before promoting this to a blocking Custom IOA // LOOKBACK: 14d -- set the window with the Falcon time picker #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName=/^haproxy$/i | FileName=/^(sh|bash|dash|curl|wget|nc|ncat|socat|python3?|perl|busybox)$/i | table([@timestamp, aid, ComputerName, ParentBaseFileName, FileName, ImageFileName, CommandLine, UserName])
Looks for: a named pipe matching ted's documented /tmp/t[ID]_w.pipe naming convention, opened by a process whose parent is haproxy. FP: low; this naming pattern is not a standard Linux or HAProxy convention. ⚠ Event coverage unverified: validate FIFO/pipe visibility in your tenant Falcon's capture of raw mkfifo() calls on Linux is not independently confirmed against the vendor data dictionary for this pack; treat this as a starting point and confirm against a live tenant before relying on it alone.
// HUNT: Named pipe command-relay pattern consistent with ted backdoor // MITRE: T1071.001, T1205 // CONF: medium FP: low COST: low // REQUIRES: Falcon Linux file-open telemetry (FileOpenInfo) -- Field/event unverified, validate in tenant // FALSE POSITIVES: none expected from this naming pattern under normal HAProxy operation // LOOKBACK: 14d -- set the window with the Falcon time picker #event_simpleName=/FileOpenInfo/ | ParentBaseFileName=/^haproxy$/i | TargetFileName = "*/tmp/t*_w.pipe" | table([@timestamp, aid, ComputerName, ParentBaseFileName, TargetFileName, UserName])
Looks for: any historical DNS request to the six documented C2 domains. FP: none expected: these are campaign-specific subdomains. Coverage note: all six domains were confirmed NXDOMAIN as of 2026-09-04, so this query has retrospective/historical value only and will not catch a live, ongoing beacon against current infrastructure.
// HUNT: DNS resolution to known ted/curlRAT C2 domains (retrohunt) // MITRE: T1071.001 // CONF: high FP: low COST: low // REQUIRES: Falcon Linux sensor DNS telemetry (DnsRequest) // FALSE POSITIVES: none expected; these are campaign-specific subdomains // LOOKBACK: 90d -- set the window with the Falcon time picker; domains are NXDOMAIN as of 2026-09-04, so this is historical-log value only #event_simpleName=/DnsRequest/ | DomainName=/^img\.(monderhouse\.space|smartnords\.site|darklights\.store|responsive\.pstatic\.autos|socialteams\.store|worksongo\.store)$/i | table([@timestamp, aid, ComputerName, DomainName, ContextBaseFileName, UserName])
Looks for: the daemon image itself (not a child it forked) making a direct outbound connection, consistent with curlRAT's beacon riding inside a trojanized crond/atd/agetty/polkitd. FP: some environments configure cron-triggered mail notification (sendmail) or polkit-adjacent directory/LDAP lookups that could appear similar.
// HUNT: Outbound connection initiated directly by cron/at/getty/polkit daemon image // MITRE: T1071.001, T1554 // CONF: medium FP: medium COST: low // REQUIRES: Falcon Linux sensor network telemetry (NetworkConnectIP4 / NetworkConnectIP6) // FALSE POSITIVES: cron-triggered mail notifications (sendmail) or polkitd directory/LDAP lookups in some environments // TUNING: exclude RemoteAddressIP4 values that resolve to your internal mail relay or directory/LDAP servers before treating a hit as C2 // LOOKBACK: 14d -- set the window with the Falcon time picker #event_simpleName=/NetworkConnectIP4|NetworkConnectIP6/ | ContextBaseFileName=/^(crond|atd|agetty|polkitd)$/i | table([@timestamp, aid, ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort, UserName])
Looks for: any process executing from the two documented non-standard drop paths. FP: low: these are not standard executable locations for any legitimate package.
// HUNT: Process execution from documented ted/curlRAT stager artifact paths // MITRE: T1082, T1497.001, T1554 // CONF: medium FP: low COST: low // REQUIRES: Falcon Linux sensor process telemetry (ProcessRollup2 / SyntheticProcessRollup2) // FALSE POSITIVES: none expected; these paths are not standard executable locations // LOOKBACK: 30d -- set the window with the Falcon time picker #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | (ImageFileName = "*/var/lib/snapd/g580" or ImageFileName = "*/tmp/jasper-log") | table([@timestamp, aid, ComputerName, ImageFileName, CommandLine, UserName])
Looks for: creation of any of the four documented artifact files (config cache, SSH-keylogger output, stager drops). FP: none expected; these are highly specific, non-standard paths.
// HUNT: File write at documented ted/curlRAT artifact paths // MITRE: T1554, T1556 // CONF: high FP: low COST: low // REQUIRES: Falcon Linux sensor file-write telemetry (NewExecutableWritten / ELFFileWritten / FileOpenInfo) // FALSE POSITIVES: none expected; these are highly specific non-standard paths // LOOKBACK: 30d -- set the window with the Falcon time picker #event_simpleName=/NewExecutableWritten|ELFFileWritten|FileOpenInfo/ | (TargetFileName = "*/cache/haproxy-1000.cache" or TargetFileName = "*/var/lib/sshd/c8c68e629bba773a10ac80012d10bf19" or TargetFileName = "*/var/lib/snapd/g580" or TargetFileName = "*/tmp/jasper-log") | table([@timestamp, aid, ComputerName, TargetFileName, ContextBaseFileName, UserName])
Looks for: a request for the specific fake image path that switches ted into C2 mode. FP: none expected: this is not a real asset path on any legitimate site. Coverage note: this only works against logs shipped out-of-band (e.g. a forwarder that ships the access-log line before ted can scrub it, or an upstream WAF/CDN that never passed through the compromised HAProxy). If the only log source is the compromised HAProxy's own live output, this query will typically return nothing, by design of the implant. Field names below are illustrative for a generic parsed-access-log schema; adjust to match your actual ingested field names.
// HUNT: HAProxy access log request for ted C2 trigger endpoint // MITRE: T1205, T1071.001 // CONF: high FP: low COST: medium // REQUIRES: HAProxy access logs ingested into Falcon Next-Gen SIEM via a log connector (NOT Falcon endpoint sensor telemetry) -- Field unverified, validate in tenant against your parser's actual field names // FALSE POSITIVES: none expected; this is not a real asset path // LOOKBACK: 90d -- set the window with the Falcon time picker #repo = "haproxy_access_logs" | url = "*/favorite_list_2x_m500_ico.jpg*" | table([@timestamp, client_ip, http_user_agent, http_referer, url, status])
CrowdStrike Custom IOA Recommendations
| IOA Candidate | Source Query | Pattern | Exclusions | Deployment Path |
|---|---|---|---|---|
| Known-bad hash execution: system daemon | Q1 | FileName matches system-daemon basenames AND SHA256HashData in known-bad set | None known | Promote directly to a Custom IOA in prevention mode; low FP, high specificity |
| Process execution from stager artifact paths | Q6 | ImageFileName under /var/lib/snapd/g580 or /tmp/jasper-log | None known | Promote to detect-mode Custom IOA; monitor for 2 weeks before considering prevent-mode |
| File write at artifact paths | Q7 | New executable/file write matching the four documented paths | None known | Promote to detect-mode Custom IOA |
| HAProxy spawning shell/network tool | Q2 | ParentBaseFileName=haproxy AND FileName in shell/net-tool set | Configured external-check / health-check wrapper scripts: allowlist by CommandLine before promoting | Keep as investigate-only hunt query until the environment's legitimate external-check commands are enumerated and excluded |
| Daemon-initiated outbound connection | Q5 | ContextBaseFileName in cron/at/getty/polkit set making a network connection | Internal mail relay / directory-LDAP destinations | Investigate-only; FP profile needs a baseline period per environment before any promotion |
Machine-Readable IOC Appendix
type,value,action,severity,expiration,description,tags sha256,72e70936f0dbe459142a1d867617c35f8d0cce5d18c6a49e1090a2a5adc8e558,detect,critical,2027-03-13,ted backdoor sample,campaign:Ted-HAProxy sha256,4bb923eb040aa13ca8fd409c31ee4729c60ddff32e350efe1c5a4a9168a065f5,detect,critical,2027-03-13,ted backdoor sample,campaign:Ted-HAProxy sha256,94630b96f628c96a6bff7904b40ffc9ad67c86f8a4ff6080c3b524831c93f402,detect,critical,2027-03-13,ted backdoor sample,campaign:Ted-HAProxy sha256,a8bfab4de81a1acb04aacdf757346946b0f5e30f0c9f402004016d0e425119c7,hunt,high,2026-12-13,ted backdoor sample -- single-source (GBHackers),campaign:Ted-HAProxy sha256,5db1b6d52faf60b4f32d6fd0c7c938e4d05d29a14c32ded4a9668357c08b6a91,hunt,high,2026-12-13,ted backdoor sample -- single-source (hendryadrian mirror),campaign:Ted-HAProxy domain,img.monderhouse.space,hunt,medium,2026-12-13,ted C2 -- NXDOMAIN as of 2026-09-04,campaign:Ted-HAProxy domain,img.smartnords.site,hunt,medium,2026-12-13,ted C2 -- NXDOMAIN as of 2026-09-04,campaign:Ted-HAProxy domain,img.darklights.store,hunt,medium,2026-12-13,ted C2 -- NXDOMAIN as of 2026-09-04,campaign:Ted-HAProxy domain,img.responsive.pstatic.autos,hunt,medium,2026-12-13,ted C2 -- NXDOMAIN as of 2026-09-04,campaign:Ted-HAProxy domain,img.socialteams.store,hunt,medium,2026-12-13,ted C2 -- NXDOMAIN as of 2026-09-04,campaign:Ted-HAProxy domain,img.worksongo.store,hunt,medium,2026-12-13,ted C2 -- NXDOMAIN as of 2026-09-04,campaign:Ted-HAProxy filename,~/cache/haproxy-1000.cache,hunt,high,2026-12-13,ted configuration cache file,campaign:Ted-HAProxy filename,/var/lib/sshd/c8c68e629bba773a10ac80012d10bf19,hunt,high,2026-12-13,SSH keylogger encrypted output,campaign:Ted-HAProxy filename,/var/lib/snapd/g580,hunt,high,2026-12-13,stager drop artifact,campaign:Ted-HAProxy filename,/tmp/jasper-log,hunt,high,2026-12-13,toolkit artifact,campaign:Ted-HAProxy url,/favorite_list_2x_m500_ico.jpg,hunt,high,2026-12-13,C2 activation trigger endpoint,campaign:Ted-HAProxy
# Not atomic indicators -- behavioral patterns to hunt, see Section 8 for full CQL haproxy parent process spawning: sh, bash, dash, curl, wget, nc, ncat, socat, python, perl, busybox named pipe pattern under /tmp: t[ID]_w.pipe (e.g. /tmp/t4471_w.pipe) cron/at/getty/polkit daemon image initiating outbound network connection directly process executing from: /var/lib/snapd/g580, /tmp/jasper-log crond binary creation timestamp exactly matching /usr/bin/ssh creation timestamp
ted backdoor -- custom HAProxy filter-API plugin, compiled into HAProxy 2.8.12 curlRAT -- companion RAT embedded in trojanized crond/atd/agetty/polkitd SSH keylogger -- trojanized sshd, plaintext password capture, substitution-cipher encrypted Attribution: medium confidence, DPRK state-sponsored Clusters cited: APT37 (aka Ricochet Chollima), Lazarus Group, Kimsuky Basis: South Korean targeting, XOR/substitution cipher usage, APT37-linked infra, watering-hole delivery pattern; Rapid7 explicitly states further evidence is needed for a stronger linkage (ngx_* routine naming overlap with a Funnull/nginx backdoor is noted but not treated as significant code-level overlap)
# Run on every host running HAProxy or any of: crond, sshd, agetty, atd, polkitd debsums -c haproxy openssh-server cron util-linux polkitd # Debian/Ubuntu rpm -V haproxy openssh-server cronie util-linux polkit # RHEL/CentOS/Rocky sha256sum /usr/sbin/haproxy /usr/sbin/crond /usr/sbin/sshd /sbin/agetty /usr/sbin/atd /usr/lib/polkit-1/polkitd stat --format='%W %n' /usr/sbin/crond /usr/bin/ssh find /tmp -type p -name 't*_w.pipe'
Detection Validation Gates
| Gate | Requirement | Status Guidance |
|---|---|---|
| Telemetry ready | Falcon Linux sensor deployed and reporting ProcessRollup2, file-write, network, and DNS events on every HAProxy and system-daemon host in scope | Confirm sensor coverage before trusting a zero-hit result on Q1/Q2/Q6/Q7 |
| Benign baseline | Run Q2 and Q5 for at least 14 days in investigate-only mode to characterize normal external-check and cron-notification behavior in this environment | Do not promote to prevention mode until the exclusion list in Section 9 is populated from real baseline data |
| Positive test | Confirm Q1 fires against a deliberately-labeled test file carrying one of the known-bad hashes in a lab/sandbox host (never on production) | Validates the query actually executes and the hash comparison syntax is correct in your tenant |
| Promotion criteria | Zero false positives over the baseline period for Q1, Q6, Q7 before enabling prevention mode; Q2 and Q5 remain investigate-only until an environment-specific exclusion list exists | See Section 9 for per-query promotion guidance |
See Section 15 for the full Detection Coverage Map, including attack-chain steps this pack does not cover.
Hardening: Tiered
1. Verify binary integrity of HAProxy and the five system daemons against package-manager checksums (M1022 Restrict File and Directory Permissions, M1047 Audit). Authority: CIS Distribution Independent Linux Benchmark, "Filesystem Integrity Checking" control family; DISA Red Hat Enterprise Linux STIG, file-integrity verification requirements. Run debsums -c / rpm -V across the fleet (see Section 10 audit checklist). Read-only check, no deployment risk.
2. Capture a SHA-256 baseline of the live haproxy/crond/sshd/agetty/atd/polkitd binaries on every host, stored off-host, so any future silent replacement is detectable (M1047 Audit). Authority: CIS Controls v8, Control 4 (Secure Configuration of Enterprise Assets and Software).
3. Rotate every credential that has ever authenticated over SSH to a HAProxy or edge host in scope, given the documented plaintext-password-capture capability (M1026 Privileged Account Management, M1032 Multi-factor Authentication). Authority: NIST SP 800-63B, credential-compromise response guidance. Treat this as containment, not a config change: see Section 14.
1. Enforce SSH key-only authentication and disable password auth on all HAProxy/edge hosts (M1043 Credential Access Protection analog for Linux: PasswordAuthentication no). Authority: CIS Distribution Independent Linux Benchmark, "Ensure SSH PasswordAuthentication is disabled"; DISA Red Hat Enterprise Linux STIG, SSH daemon authentication requirements; OpenSSH sshd_config vendor documentation. This directly closes the credential-capture path documented in this campaign, since a trojanized sshd cannot harvest a plaintext password that is never sent.
2. Deploy file-integrity monitoring (AIDE) against the six critical binaries and their containing directories, alerting on any change outside a change-managed maintenance window (M1047 Audit). Authority: CIS Distribution Independent Linux Benchmark, "Ensure AIDE is installed" and "Ensure filesystem integrity is regularly checked".
3. Forward logs off-host in near-real-time (syslog/Filebeat to a central, append-only sink) so on-host log and history scrubbing cannot erase the record before it is captured (M1029 Remote Data Storage, M1047 Audit). Authority: NIST SP 800-92, Guide to Computer Security Log Management; CIS Distribution Independent Linux Benchmark, "Ensure rsyslog is configured to send logs to a remote log host".
4. Enforce egress filtering on edge/load-balancer hosts, default-deny outbound except to explicitly allowlisted backend/management/DNS destinations (M1037 Filter Network Traffic). Authority: NIST SP 800-41 Rev. 1, Guidelines on Firewalls and Firewall Policy; CIS Controls v8, Control 13 (Network Monitoring and Defense). This directly breaks curlRAT's beacon and any DNS lookup to C2 infrastructure.
1. Move to an immutable/read-only-root or golden-image redeploy model for edge load balancers (e.g. a containerized HAProxy pulled from a signed internal registry on every deploy, rather than a long-lived host with an in-place binary) (M1022, M1046 Boot Integrity analog). Authority: CIS Controls v8, Control 4 (Secure Configuration of Enterprise Assets and Software); CIS Docker Benchmark, image-provenance controls where HAProxy is containerized.
2. Network-segment edge/DMZ load balancers from general server egress and from direct SSH management-plane exposure, requiring a bastion/jump host for administrative access (M1030 Network Segmentation, M1035 Limit Access to Resource Over Network). Authority: CIS Controls v8, Control 12 (Network Infrastructure Management); NIST SP 800-207, Zero Trust Architecture, management-plane isolation guidance.
3. Establish a build-provenance program for any internally compiled HAProxy (reproducible builds, binary provenance attestation) so a compile-time trojanization of the kind documented here is independently verifiable against a known-good build artifact (M1045 Code Signing, M1051 Update Software). Authority: NIST SP 800-218, Secure Software Development Framework (SSDF), practices PS.2 and PS.3; SLSA build-provenance levels.
Deployable Playbooks
Playbook A: Binary Integrity Verification & Baseline (Immediate)
# Step 1: verify installed package integrity # Debian/Ubuntu: debsums -c haproxy openssh-server cron util-linux polkitd 2>&1 | tee /var/log/ted-hunt-debsums.log # RHEL/CentOS/Rocky: rpm -V haproxy openssh-server cronie util-linux polkit | tee /var/log/ted-hunt-rpmverify.log # Step 2: capture current binary hashes for the ongoing baseline sha256sum /usr/sbin/haproxy /usr/sbin/crond /usr/sbin/sshd /sbin/agetty /usr/sbin/atd /usr/lib/polkit-1/polkitd > /var/log/ted-hunt-binary-hashes.txt
Prerequisites: debsums or rpm package installed; read access to the package database and the listed binary paths (paths vary by distro: adjust to your layout before running at scale). Reboot required: none. Rollback: none required: this is a read-only verification and reporting step.
Playbook B: Egress Filtering for Edge/Load-Balancer Hosts (Immediate/Near term)
nft add table inet ted_hunt_egress
nft add chain inet ted_hunt_egress output { type filter hook output priority 0 \; policy drop \; }
nft add rule inet ted_hunt_egress output ct state established,related accept
nft add rule inet ted_hunt_egress output ip daddr { REPLACE_WITH_BACKEND_CIDR, REPLACE_WITH_MGMT_CIDR } accept
nft add rule inet ted_hunt_egress output udp dport 53 ip daddr REPLACE_WITH_INTERNAL_DNS accept
Prerequisites: nftables installed; a validated list of legitimate backend/management/DNS destinations for this specific host populated into the REPLACE_WITH_ placeholders before deploying: wrong values will break production load balancing. Reboot required: no; rules apply immediately. Persist across reboot with nft list ruleset > /etc/nftables.conf and enable the nftables service. Rollback: nft delete table inet ted_hunt_egress removes the entire policy immediately.
Playbook C: SSH Hardening: Disable Password Authentication (Near term)
# /etc/ssh/sshd_config.d/99-ted-hunt-hardening.conf PasswordAuthentication no KbdInteractiveAuthentication no PermitRootLogin prohibit-password # Apply: sshd -t && systemctl reload sshd
Prerequisites: key-based access already provisioned and tested for every admin and service account that needs to reach this host, before disabling password auth: otherwise you will lock yourself out. Reboot required: no; an sshd reload is sufficient. Rollback: remove /etc/ssh/sshd_config.d/99-ted-hunt-hardening.conf and run systemctl reload sshd.
Playbook D: File Integrity Monitoring via AIDE (Near term)
apt-get install -y aide # or: yum install -y aide aideinit mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db echo "0 */6 * * * root /usr/bin/aide --check | /usr/bin/mail -s 'AIDE report' REPLACE_WITH_SOC_MAILBOX" > /etc/cron.d/ted-hunt-aide
Prerequisites: the baseline must be captured from a host already verified clean via Playbook A before trusting the AIDE database as a source of truth. Reboot required: no. Rollback: rm /etc/cron.d/ted-hunt-aide stops scheduled checks immediately; removing the aide package itself is optional and non-disruptive.
Containment Runbook
| Phase | Actions | Owner | Evidence to Preserve |
|---|---|---|---|
| 1. Isolate | Network-isolate the affected load balancer(s) via Falcon host containment or an out-of-band edge firewall rule (block all traffic except forensic/management access). Pull the host out of DNS/VIP rotation in favor of a known-clean standby. | IR lead + Network team | Memory image of the HAProxy process (e.g. LiME), current iptables/nftables rules, running process list, open files (lsof): captured before any shutdown |
| 2. Eradicate | Rebuild the host from a trusted golden image or verified-clean package repository rather than attempting in-place remediation: the implant is compiled into the binary and process memory, so in-place cleaning cannot be trusted. Rotate every credential that ever authenticated over SSH to the host. Reset any session/auth cookies the load balancer could have harvested for downstream applications. | IR lead + System owners | Copy of the trojanized binaries and any recovered artifact files (Section 4) before wipe, for further analysis |
| 3. Recover | Redeploy HAProxy from the verified-clean build pipeline (Playbook A / Strategic tier). Re-enable in DNS/VIP rotation only after the FIM baseline (Playbook D) is clean and egress filtering (Playbook B) is enforced. Add the host to enhanced monitoring for 30 days post-recovery. | System owners + SOC | Post-recovery binary hashes and AIDE baseline, filed alongside the pre-incident baseline for comparison |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | Coverage |
|---|---|---|---|---|
| T1554 | Trojanized haproxy/crond/sshd/agetty/atd/polkitd binaries | Q1, Q6, Q7 | Q1, Q6, Q7 candidates | Good for known samples; a newly recompiled build defeats hash-only detection |
| T1557 | In-process HAProxy filter interception, header/content manipulation, cookie harvesting | None | None | GAP: not visible to endpoint EDR telemetry; requires network capture, memory forensics, or HAProxy-external traffic comparison |
| T1070.006 (timestomp) | crond timestomped to /usr/bin/ssh's creation time | Native hunt only (Section 7) | None | Partial: detectable only via direct host inspection, no streaming EDR event for file birth-time comparison |
| T1070.002 / T1070.003 (log & history scrubbing) | Keyword stripping from auth.log/audit.log/bash_history | Native hunt only (Section 7) | None | Partial: requires off-host log forwarding or auditctl watch rules; on-host-only logging is exactly what the implant defeats |
| T1556 | Trojanized sshd captures plaintext passwords | Q1 (hash-based), Q7 (keylog artifact path) | Q1, Q7 candidates | Partial: a new sshd sample with a different hash and a different output path would evade both queries |
| T1071.001 / T1205 | C2 channel disguised as HTTP/1.0, hidden trigger endpoint, curlRAT beacon | Q4, Q5, Q8 | None promoted | Partial: Q4's domains are dead (NXDOMAIN), Q8 requires log ingestion that bypasses the implant's own scrubbing; live beacon interval alone (12h/30s) is not independently queryable without a network baseline |
| T1497.001 | curlRAT virtualization check / conditional execution | None | None | GAP: not visible to process/network telemetry; would require behavioral or memory analysis of the RAT binary itself |
| T1082 | Stager profiles hostname/OS/root before install | Q6 (post-hoc artifact only) | Q6 candidate | Partial: the profiling activity itself is not distinctly queryable; only its resulting artifact drop is |
| T1190 (initial access) | Assessed Groupware/mail portal exploitation | None | None | GAP: out of scope for this pack; not independently confirmed in public reporting and no specific CVE or exploit chain was documented to hunt against |
Validation gates for the queries above are detailed in Section 11. In short: confirm sensor coverage before trusting a clean result, baseline Q2/Q5 for at least 14 days before any prevention-mode promotion, and re-run the audit checklist in Section 10 fleet-wide as the first action on any new host brought into scope.
Hunt Summary Ticket
TITLE: Ted Backdoor / curlRAT -- Trojanized HAProxy & Linux System Daemon Hunt
SEVERITY: High (nation-state, medium-confidence DPRK attribution; techniques are
portable to any HAProxy 2.8.x deployment, not limited to the two
confirmed South Korean victims)
SCOPE: Linux hosts running HAProxy (confirmed on 2.8.12) and any host sharing
the crond/sshd/agetty/atd/polkitd persistence toolkit
HYPOTHESIS: The adversary trojanizes a load balancer's HAProxy binary at compile
time and replaces core Linux system daemons to gain covert,
self-erasing C2 and credential capture that never appears in
application or connection logs.
QUERIES: Q1-Q8 (Section 8)
DO FIRST: Run Playbook A (binary integrity verification) against every
HAProxy/edge host in the estate; run Q1, Q6, and Q7 fleet-wide for
atomic-IOC and artifact-path hits before anything else.
FINDINGS: REPLACE_WITH_HUNT_FINDINGS -- populate after hunt execution
GAPS: No EDR visibility into in-process HAProxy filter/C2 logic (T1557) or
curlRAT's virtualization check (T1497.001); C2 domains are NXDOMAIN as
of 2026-09-04 so Q4 is retrospective only; initial-access vector is
assessed, not confirmed.
ACTIONS: Escalate any Q1/Q6/Q7 hit as a confirmed compromise; isolate per
Containment Runbook Phase 1 immediately -- do not attempt in-place
remediation, the implant is compiled into the binary itself.
OWNER: REPLACE_WITH_HUNT_OWNER
VERSION: v0.1 - 2026-09-13
Changelog
References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| 1 | Rapid7 Labs: DPRK APTs: Ted backdoor and curlRAT target South Korean media and automotive sectors | Primary technical report: architecture, IOCs, timestomping, log scrubbing, attribution | 2026-09-13 |
| 2 | The Hacker News: New Ted Backdoor Hides Inside Victims' Own HAProxy Builds | IOC consolidation, NXDOMAIN confirmation | 2026-09-13 |
| 2 | Security Affairs: North Korea-linked Hackers Hide a Backdoor Inside HAProxy | Extended verbatim quotes, C2 trigger endpoint confirmation | 2026-09-13 |
| 3 | GBHackers: DPRK-Linked Hackers Backdoor HAProxy Servers | Additional hash corroboration, APT37/Ricochet Chollima attribution detail | 2026-09-13 |
| 3 | hendryadrian.com: DPRK APTs: Ted backdoor and curlRAT (Rapid7 report mirror) | Timestomping/log-scrub verbatim confirmation, additional hash | 2026-09-13 |
Full fetched-text snapshots of all five sources are saved alongside this pack in Ted-HAProxy-Backdoor-Hunt-sources/ for provenance verification.