Plex2Shell: Plex Media Server Exploit Chain
Executive Summary
Attacker objective: turn an internet-reachable Plex Media Server into a free compute and proxy node. The Plex2Shell chain (CVE-2026-96652, CVE-2026-96654, CVE-2026-96655, CVE-2026-96656) needs no valid login: a request with any junk token makes the server call itself, reach a loopback-only plugin API, leak the owner's admin token to an attacker-chosen host, then plant a shared object in the Codecs directory that the transcoder loads as code, running as the Plex service user. Researcher Zach Main published the write-up and proof of concept on 2026-10-16. A honeypot operator saw exploitation the next day, dropping an XMRig-based miner and a proxy tool named myawesomeproxy. Plex confirmed the flaws are "actively and widely exploited" on 2026-10-19 and CISA added all four CVEs to the KEV catalog on 2026-10-20. The fix is Plex Media Server 1.43.3.10861 or later.
Highest-value defensive angle: on a sensored host, a Plex process (especially Plex Transcoder) creating a shell, and an executable written into a Codecs directory by anything other than the server binary. Both are post-exploitation and survive payload renaming. Network indicators and file names are perishable.
Honest scope: Plex is prosumer software. In a managed fleet it mostly runs on NAS appliances, containers and home or unmanaged hosts that carry no EDR sensor. Those installs are outside endpoint visibility. Network egress controls, perimeter scanning for port 32400 and asset inventory carry that part. The CQL here covers only the sensored subset. Defender priority: (1) find every Plex install, sensored or not, and confirm it is on 1.43.3.10861 or later; (2) remove any internet exposure of port 32400; (3) on old builds that were reachable after 2026-10-17, assume compromise and check the Codecs directory, Preferences.xml and the process tree before trusting a patch alone.Source Review & Web Hunter Notes
| ID | Tier | Source | Key finding | Carry forward |
|---|---|---|---|---|
| S01 | 1 | Plex forum advisory (vendor), updated 2026-10-19 | Vendor lists the four CVEs, states they are being actively and widely exploited, names myawesomeproxy, mylovelyminer, x12345, .so/.dll files in Codecs and proxystream.st as signs of compromise. Fixed in 1.43.3.10861 and later. | yes |
| S02 | 1 | Researcher write-up (zmain.info), 2026-10-16 | Primary description of the five-stage chain and the code-loading behavior. Used for defensive hunt logic only. Exploit code is not reproduced here. | yes |
| S03 | 1 | BoredHackerBlog honeypot report, 2026-10-17 and updates | Primary observation: three exploiting IPs, payload host, SHA-256 values for five files, myawesomeproxy, proxystream.st, first-request URI shape. Origin of nearly every atomic IOC in the press coverage. | yes |
| S04 | 1 | CISA KEV alert, 2026-10-20 | All four Plex CVEs added to the KEV catalog. | yes |
| S05 | 1/2 | Researcher detection script and YARA rules (code-zm repository) | Defender-oriented triage logic: log patterns, Preferences.xml check, Codecs file shape, shell-under-Plex process check. Third-party content treated as data; not executed. | partial (logic only) |
| S06 | 2 | SentinelOne vulnerability database, CVE-2026-96652 | Endpoint of the SSRF, affected range, egress-restriction workaround. | partial |
| S07 | 2 | The Hacker News, 2026-10-19 | CVE roles, payload file names x12345, myawesomeproxy, mylovelyminer, pool host ota.proxystream.st, attacking IP list (one address printed twice). | yes |
| S08 | 2 | SecurityOnline, Security Affairs, Cyber Security News, Daily Security Review, Cord Cutters News, Gizmodo, Safeguard | Corroborating coverage: timeline, exposure counts (Shadowserver about 49,000 on 2026-10-18), residential-proxy risk, remediation. All trace back to S01 to S03 for exploitation facts. | partial |
Decisions and discrepancies
- Independence: the press IOC lists all derive from one honeypot (S03). Indicators are therefore treated as single-sourced telemetry. IP addresses and hashes ship at detect. Only proxystream.st is also named independently by the vendor (S01), so it is the one indicator proposed at prevent.
- CVE role mapping differs by source. The CVE records, SecurityOnline and The Hacker News describe CVE-2026-96654 as the URL-injection step and CVE-2026-96655 as the transcoder SSRF. Plex's 2026-10-19 table attaches the loopback agent access and the admin token disclosure to different IDs. The fixed version is identical everywhere. This pack uses the CVE-record wording.
- CVSS differs by version. SecurityOnline mixes CVSS 3.1 and 4.0 values. Use: CVE-2026-96652 5.3 (v4) / 4.3 (v3.1); CVE-2026-96654 6.9 / 6.5; CVE-2026-96655 5.3 / 4.3; CVE-2026-96656 8.6 / 7.2. The researcher scores the chain 9.8.
- Fix date conflict. Most sources say the fixed build shipped 2026-08-03 or 2026-08-04. One source says September. Plex's public warning was 2026-09-01 and the CVE records were published 2026-09-23.
- Excluded: a VirusTotal sample the honeypot operator found by search as "similar" and the global.relay subdomain of proxystream.st tied by a third party to a different botnet. Both are related-cluster material and are not shipped. A site at zmain.com returned unrelated content and was discarded.
- Not retrievable: the pcap repository and the Reddit thread. No unreviewed content is cited.
- Attribution: none. Exploitation is opportunistic and the operator is unknown. The sources give the campaign no name beyond Plex2Shell, which names the exploit chain.
Hunt Brief & Attack Chain
Hypotheses (highest fidelity first)
| # | Hypothesis | MITRE | Event types | Expected FP | Conf |
|---|---|---|---|---|---|
| H1 | A Plex process, above all Plex Transcoder, creates a shell, interpreter or downloader (Q1, Q2, Q3) | T1059.004, T1105 | Process creation with lineage | DVR post-processing scripts | high |
| H2 | An executable is written into a Codecs directory by a process other than the server binary (Q8) | T1129 | Executable file written | Codec updates, package upgrades | medium |
| H3 | A loopback-prefixed wildcard-DNS name is resolved (Q7) | T1528 | DNS lookup | Local dev testing | high |
| H4 | A named payload, pool argument or published hash runs (Q4, Q5) | T1496, T1090 | Process creation, file hash | None expected | high but perishable |
| H5 | Contact with published attacker addresses or proxystream.st (Q6) | T1105, T1090 | Network connection, DNS | Address reuse | high but perishable |
| H6 | A Plex process talks to public hosts on unusual ports (Q9) | T1090 | Network connection | Remote streaming | low (hunt) |
Attack chain
| Step | Behavior | Telemetry (plain terms first) | Hunt angle |
|---|---|---|---|
| 1 | Unauthenticated SSRF, CVE-2026-96652: the player timeline endpoint checks only that a token header exists, so the server POSTs to a URL the attacker supplies | Web or proxy access log, Plex application log, network sensor (outside endpoint telemetry) | Native hunts (section 7) |
| 2 | Loopback pivot, CVE-2026-96654: the forged callback reaches the loopback-only plugin API and an injected identifier calls a plugin function | Plex application log, network sensor (outside endpoint telemetry) | Native hunts |
| 3 | Owner token theft, CVE-2026-96655 path: a hostname beginning 127.0.0.1. resolves to an attacker address, so the server attaches its admin token | DNS lookup with requesting process (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents, DnsQueryResponse) | Q7 |
| 4 | Payload staging, CVE-2026-96656: a transcoder preference makes the encoder write attacker-controlled bytes into the Codecs directory | Executable file written (CrowdStrike: ElfFileWritten or PeFileWritten. SentinelOne: File Creation. Defender: DeviceFileEvents, FileCreated) | Q8 |
| 5 | Code execution: the transcoder loads every shared object in Codecs with no signature check, the constructor forks a shell as the Plex user | Process creation with command line and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | Q1, Q2, Q3 |
| 6 | Payload fetch by shell script using curl or wget from the payload host, then miner and proxy launched | Outbound network connection with process (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents), process creation | Q4, Q5, Q6 |
| 7 | Persistence by position: the planted file stays in Codecs and re-runs on the next transcode, so killing the miner does not evict the attacker | Executable file written, process creation | Q1, Q8 and the containment runbook |
Consolidated IOC Table
Every value below was re-derived from the saved snapshots in Plex2Shell-CVE-2026-96652-Hunt-sources/. All four addresses are public attacker or staging addresses from the honeypot report; none is a software version. Software versions in this pack (1.43.3.10861 and similar) are four-part strings that resemble an address but exceed the valid octet range and are never shipped as indicators.
| Type | Value | Confidence | Action | Context | Expiry |
|---|---|---|---|---|---|
| IPv4 | 45.194.92.222 | medium | detect | Exploiting source, first request in the honeypot, Python-urllib user agent (S03) | 2027-01-22 |
| IPv4 | 91.92.40.202 | medium | detect | Exploiting source (S03, S07) | 2027-01-22 |
| IPv4 | 176.65.139.228 | medium | detect | Exploiting source (S03, S07). Printed twice in one list; counted once. | 2027-01-22 |
| IPv4 | 217.60.194.116 | medium | detect | Payload source: serves the files named docker and x (S03, S07) | 2027-01-22 |
| Domain | proxystream.st | high | prevent | Named by the vendor (S01) and the honeypot report (S03) as the proxy and miner infrastructure. Two sources. | 2027-04-22 |
| Domain | ota.proxystream.st | medium | detect | Mining server named by The Hacker News (S07). Single-sourced. Covered by the parent entry if you block the parent. | 2027-04-22 |
| SHA-256 | 5712ef510481f15fe8b18fdd10b73026d57cb3d9d151878a692358b99a464441 | medium | detect | File named docker, downloaded from the payload host (S03) | 2027-04-22 |
| SHA-256 | 68938fb428f2bcfef4ede62f7b29cd0f15f37ed59e94462c099f2ca64e36baa7 | medium | detect | File named x, UPX packed (S03) | 2027-04-22 |
| SHA-256 | b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49 | medium | detect | File named x, unpacked, XMRig-based miner (S03) | 2027-04-22 |
| SHA-256 | e546b10aee5edc754da4f9b0baace00392257121911f630145078eafad1e6bd9 | medium | detect | myawesomeproxy binary (S03) | 2027-04-22 |
| SHA-256 | 2d3c618b21a7e2f8bcd735da188e44a7609ff3f204832dd0781cd09e4388c9db | medium | detect | XMRig sample from the honeypot (S03). XMRig is shared tooling; detect only. | 2027-04-22 |
| Filename | x12345 | medium | hunt | Miner process name listed by the vendor and The Hacker News (S01, S07). Attacker-chosen. | 2027-01-22 |
| Filename | myawesomeproxy | medium | hunt | Proxy tool name (S01, S03, S07). Prints a consent notice about external use of internet traffic. | 2027-01-22 |
| Filename | mylovelyminer | medium | hunt | Miner process name (S01, S07). Reddit reports of the same name are unconfirmed as Plex2Shell. | 2027-01-22 |
Affected Surface & Telemetry Matrix
| Surface | Telemetry needed (plain terms first) | Priority | Gap risk |
|---|---|---|---|
| Linux server or Docker host running Plex, with an EDR sensor | Process creation, DNS lookup, network connection, executable file written (CrowdStrike: ProcessRollup2, DnsRequest, NetworkConnectIP4, ElfFileWritten. SentinelOne: Process Creation, DNS Resolved, IP Connect, File Creation. Defender: DeviceProcessEvents, DeviceEvents, DeviceNetworkEvents, DeviceFileEvents) | high | Container-internal processes depend on sensor container support. Validate lineage fields. |
| Windows or macOS host running Plex, with a sensor | Same telemetry (PeFileWritten for the DLL case) | medium | Auto-update defaults to prompting, so old builds persist. |
| NAS appliance, router-hosted or vendor-app-store Plex | None. These devices usually cannot run a sensor. | high | Outside endpoint telemetry. Network flow, perimeter scanning and asset inventory only. |
| Internet-facing port 32400 and Remote Access forwards | Web or proxy logs, network flow, external attack-surface scan | high | Outside endpoint telemetry. Shadowserver reported about 49,000 exposed vulnerable hosts on 2026-10-18. |
| Plex application logs on the server | Plex Media Server log files, Preferences.xml | medium | Only a few log rotations are kept. Preserve them before cleanup. |
ATT&CK Mapping
| Tactic | Technique | Observed behavior | Query or control |
|---|---|---|---|
| Initial Access | T1190 Exploit Public-Facing Application | Four chained CVEs against port 32400, no valid login | Patch, exposure removal, native hunts |
| Credential Access | T1528 Steal Application Access Token | Owner admin token leaked through a loopback-prefixed wildcard-DNS hostname | Q7 |
| Execution | T1129 Shared Modules | Transcoder loads an attacker-written shared object from Codecs | Q8, Q1 |
| Execution | T1059.004 Unix Shell | Constructor forks a shell as the Plex user | Q1, Q2, Q3 |
| Command and Control | T1105 Ingress Tool Transfer | Shell script fetches the miner and proxy with wget or curl | Q1, Q6 |
| Impact | T1496 Resource Hijacking | XMRig-based miner, process name mylovelyminer or x12345 | Q4, Q5 |
| Command and Control | T1090 Proxy | myawesomeproxy routes third-party traffic through the victim's address | Q4, Q5, Q6, Q9 |
Native Audit-Log Hunts
Most of the chain is visible only in server and network logs, not endpoint telemetry. Run these on the Plex host, its reverse proxy and its network sensor.
| Where | Check | Signal |
|---|---|---|
| Plex Media Server log | Search requests to the player timeline subscribe path where the protocol parameter holds a full URL, and search-agent requests whose identifier carries a function path | Exploit request shapes published by the researcher (S05). First honeypot hit was a request of this shape from 45.194.92.222 with user agent Python-urllib/3.10 (S03). The user agent is a weak signal because the attacker can change it. |
| Reverse proxy, Zeek or Suricata | Same URI shapes; a transcode request whose path parameter points to a non-loopback http host | Network-side view for hosts with no sensor. Researcher pcaps exist for signature work (S03). |
| Plex Media Server log | Any lookup or request containing sslip.io or nip.io | Wildcard-DNS token theft step |
| Preferences.xml | Look for a transcoder H.264 options value you did not set | Plex lists this as a check (S01). The proof of concept clears the value after use, so an empty value is a weak signal, not proof of safety (S05). |
| Codecs directory | List files newer than 2026-10-16, with hashes. Unexpected shared objects or DLLs, or binaries whose last 64 KiB is uniform, are suspect | Plex check (S01). Uniform-tail shape comes from the researcher's script (S05). |
| Live process table | Shell, interpreter or netcat child of a Plex process; a shell running as the Plex user with an empty environment; a process mapping a deleted file from Codecs | Researcher triage logic (S05) |
| Host performance | Sustained high CPU while nothing is playing | Plex and Safeguard list this (S01, S09). No endpoint event carries CPU, so this is a native check. |
Read-only triage commands (Linux, adjust the data path for Docker and NAS)
# Version curl -s http://127.0.0.1:32400/identity # Process tree and CPU pid=$(pgrep -f "Plex Media Server" | head -1); pstree -ap "$pid" ps -eo pid,ppid,user,%cpu,cmd --sort=-%cpu | head -15 # Codecs files newer than the public PoC PMS="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" find "$PMS/Codecs" -type f \( -name '*.so' -o -name '*.dll' \) -newermt 2026-10-16 -ls # Transcoder options grep -o 'Transcoder[A-Za-z0-9]*Options="[^"]*"' "$PMS/Preferences.xml" # Outbound connections from Plex ss -tnp | grep -i plex
Hunt Queries: CrowdStrike CQL, SentinelOne PowerQuery and Defender XDR KQL
Use your tenant's own address. SentinelOne does not document a link that pre-fills a query, so each "Copy & open SentinelOne" button copies the query and opens this console: paste it into PowerQuery.
Pick your portal. Microsoft documents a Share link in advanced hunting but not its URL format, so each "Copy & open Defender" button copies the query and opens Advanced hunting: paste it into the editor.
Looks for: A Plex-family process (server, transcoder, script host) creating sh, bash, cmd, PowerShell, python, curl, wget or a netcat-style tool. After CVE-2026-96656 the transcoder loads the planted shared object and its constructor forks a shell, so the shell's parent is Plex Transcoder. This is the highest-fidelity endpoint signal on a sensored host. FP: User-configured DVR post-processing scripts and some container wrapper scripts launched by the Plex service.
Known limitations and deployment notes
- PowerQuery: A shell started by a process the agent did not observe starting is not captured.
- PowerQuery: The SHA256 of the new image is not projected here; the hash pivot is the separate hash card.
- PowerQuery assumes: src.process.name holds the creator name as the agent reports it, possibly truncated to 15 characters on Linux.
- PowerQuery assumes: src.process.user is the creating account; a created-process user field is not attested.
- KQL – Defender XDR: A shell started by a process the sensor did not observe starting is not captured.
- KQL – Defender XDR assumes: InitiatingProcessFileName holds the full creator name as the sensor reports it.
- KQL – Defender XDR assumes: The regex is case-insensitive through the (?i) prefix.
// HUNT: Plex service process spawns a shell, interpreter or downloader // MITRE: T1190, T1059.004, T1105 // CONF: high FP: medium COST: low // REQUIRES: Process creation with command line on a sensored Plex host (Linux, Windows or macOS). Parent names are truncated to 15 characters on some Linux sensors, so the pattern is prefix-anchored. // FALSE POSITIVES: user-configured DVR post-processing or media scripts launched by Plex; wrapper shells in some container images // TUNING: after a 30 day baseline, exclude approved script names with | CommandLine!=/approved_script_name/i. Never exclude Plex Transcoder as the parent. // LOOKBACK: 14d (exploitation began 2026-10-17) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | ParentBaseFileName=/^(plex (media serv|transcoder|script hos|tuner serv|commercial)|plexmediaserver)/i | FileName=/^(sh|bash|dash|zsh|ash|ksh|busybox|cmd|powershell|pwsh|wscript|cscript|mshta|python[0-9.]*|perl|curl|wget|nc|ncat|socat|script|setsid|nohup)(\.exe)?$/i | table([@timestamp, ComputerName, aid, UserName, ParentBaseFileName, FileName, CommandLine, ImageFileName, SHA256HashData])
3 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timesrc.process.name: Creating process nametgt.process.name: Created process name
Recommended fields
tgt.process.cmdline: Created command linesrc.process.user: Creating account
Readiness test
event.type = 'Process Creation' src.process.name = * | limit 5 | columns event.time, endpoint.name, src.process.name, tgt.process.name
// HUNT: Plex service process spawns a shell, interpreter or downloader // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1190, T1059.004, T1105 // CONF: high | FP: medium // FALSE POSITIVES: user-configured DVR post-processing or media scripts launched by Plex; wrapper shells in some container images // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: a Plex service process is the creator of a shell, interpreter or downloader image. On a Process Creation event the creator is src.process.* and the new process is tgt.process.*. Matching is case-insensitive and the Plex name is prefix-anchored, which reproduces the CQL /i intent and the 15 character truncation allowance. // REQUIRES: SentinelOne agent process-creation telemetry on the Plex host (Linux, Windows or macOS) // TUNING: After a 30 day baseline exclude approved DVR post-processing scripts by tgt.process.cmdline. Never exclude Plex Transcoder as the creating process. // LOOKBACK: 14d (set in the time picker; exploitation began 2026-10-17) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A shell started by a process the agent did not observe starting is not captured. The SHA256 of the new image is not projected here; the hash pivot is the separate hash card. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' src.process.name matches '^(plex (media serv|transcoder|script hos|tuner serv|commercial)|plexmediaserver)' tgt.process.name matches '^(sh|bash|dash|zsh|ash|ksh|busybox|cmd|powershell|pwsh|wscript|cscript|mshta|python[0-9.]*|perl|curl|wget|nc|ncat|socat|script|setsid|nohup)(\\.exe)?$' | columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline, tgt.process.image.path | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeInitiatingProcessFileName: Initiating processFileName: Created process name
Recommended fields
ProcessCommandLine: Created command line
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, InitiatingProcessFileName, FileName, ProcessCommandLine | take 5
// HUNT: Plex service process spawns a shell, interpreter or downloader // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1190, T1059.004, T1105 // CONF: high | FP: medium // FALSE POSITIVES: user-configured DVR post-processing or media scripts launched by Plex; wrapper shells in some container images // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate on process creation: the initiating process is a Plex service image and the created process is a shell, interpreter or downloader. contains-style matching is done with a case-insensitive regex, which reproduces the CQL /i intent and keeps the Plex name prefix-anchored. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) on the Plex host // TUNING: After a 30 day baseline exclude approved DVR post-processing scripts by ProcessCommandLine. Never exclude Plex Transcoder as the initiating process. // LOOKBACK: 14d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A shell started by a process the sensor did not observe starting is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(14d) | where InitiatingProcessFileName matches regex @"(?i)^(plex (media serv|transcoder|script hos|tuner serv|commercial)|plexmediaserver)" | where FileName matches regex @"(?i)^(sh|bash|dash|zsh|ash|ksh|busybox|cmd|powershell|pwsh|wscript|cscript|mshta|python[0-9.]*|perl|curl|wget|nc|ncat|socat|script|setsid|nohup)(\.exe)?$" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine, FolderPath, SHA1 | sort by Timestamp desc | take 200
Looks for: Anything started by a shell whose own parent is a Plex-family process. Catches the miner or proxy binary one step after the shell, including names the attacker changed. Complements Q1, which sees only the shell itself. FP: The same user-configured scripts as Q1; any wrapper that runs Plex helpers through a shell.
Known limitations and deployment notes
- PowerQuery: Chains where the shell was started by a process the agent did not observe are not captured.
- PowerQuery assumes: src.process.parent.name is populated for the creating process on this agent version.
- PowerQuery assumes: The Plex name is possibly truncated to 15 characters on Linux and is matched by prefix.
- KQL – Defender XDR: Chains where the parent shell was not observed starting are not captured.
- KQL – Defender XDR assumes: InitiatingProcessParentFileName is populated on the Linux and Windows sensors for this tenant.
// HUNT: Process started by a shell that Plex launched // MITRE: T1059.004, T1105, T1496 // CONF: medium FP: medium COST: low // REQUIRES: Process creation with parent and grandparent lineage (validate GrandparentBaseFileName is populated by your Linux sensor version) // FALSE POSITIVES: approved DVR post-processing chains; container entrypoint wrappers // TUNING: exclude approved chains with | FileName!=/^(ffmpeg|comskip|mediainfo)$/i after baselining // LOOKBACK: 14d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | GrandparentBaseFileName=/^(plex (media serv|transcoder|script hos|tuner serv|commercial)|plexmediaserver)/i | ParentBaseFileName=/^(sh|bash|dash|zsh|ash|busybox|cmd|powershell|pwsh|python[0-9.]*|perl)(\.exe)?$/i | table([@timestamp, ComputerName, aid, UserName, GrandparentBaseFileName, ParentBaseFileName, FileName, CommandLine, ImageFileName, SHA256HashData])
3 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timesrc.process.parent.name: Grandparent processsrc.process.name: Parent process
Recommended fields
tgt.process.name: Created process nametgt.process.cmdline: Created command line
Readiness test
event.type = 'Process Creation' src.process.parent.name = * | limit 5 | columns event.time, endpoint.name, src.process.parent.name, src.process.name
// HUNT: Process started by a shell that Plex launched // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1059.004, T1105, T1496 // CONF: medium | FP: medium // FALSE POSITIVES: approved DVR post-processing chains; container entrypoint wrappers // EQUIVALENCE: equivalent-with-schema-assumptions - Same lineage: a Plex process is the grandparent and a shell or interpreter is the parent of the new process. On a Process Creation event src.process.* is the creator of the new process (the shell) and src.process.parent.* is that creator's own parent (the Plex process), so the grandparent of the new process. // REQUIRES: SentinelOne agent process-creation telemetry with creator-parent lineage // TUNING: Exclude approved chains by tgt.process.name (for example ffmpeg, comskip, mediainfo) after baselining. // LOOKBACK: 14d (set in the time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Chains where the shell was started by a process the agent did not observe are not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' src.process.parent.name matches '^(plex (media serv|transcoder|script hos|tuner serv|commercial)|plexmediaserver)' src.process.name matches '^(sh|bash|dash|zsh|ash|busybox|cmd|powershell|pwsh|python[0-9.]*|perl)(\\.exe)?$' | columns event.time, endpoint.name, src.process.user, src.process.parent.name, src.process.name, tgt.process.name, tgt.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeInitiatingProcessParentFileName: Grandparent processInitiatingProcessFileName: Parent process
Recommended fields
FileName: Created process nameProcessCommandLine: Created command line
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, InitiatingProcessParentFileName, InitiatingProcessFileName, FileName | take 5
// HUNT: Process started by a shell that Plex launched // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1059.004, T1105, T1496 // CONF: medium | FP: medium // FALSE POSITIVES: approved DVR post-processing chains; container entrypoint wrappers // EQUIVALENCE: equivalent-with-schema-assumptions - Same lineage: InitiatingProcessParentFileName is the grandparent of the created process and InitiatingProcessFileName is its parent shell. Matching is a case-insensitive regex with the Plex name prefix-anchored. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) with parent lineage populated // TUNING: Exclude approved chains by FileName (for example ffmpeg, comskip, mediainfo) after baselining. // LOOKBACK: 14d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Chains where the parent shell was not observed starting are not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(14d) | where InitiatingProcessParentFileName matches regex @"(?i)^(plex (media serv|transcoder|script hos|tuner serv|commercial)|plexmediaserver)" | where InitiatingProcessFileName matches regex @"(?i)^(sh|bash|dash|zsh|ash|busybox|cmd|powershell|pwsh|python[0-9.]*|perl)(\.exe)?$" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessParentFileName, InitiatingProcessFileName, FileName, ProcessCommandLine, FolderPath, SHA1 | sort by Timestamp desc | take 200
Looks for: Command lines carrying a /dev/tcp or /dev/udp redirect or a script-based PTY upgrade, the shapes the researcher's own hunt script flags for this chain. Scoped to any parent so it still fires if the shell is re-parented. FP: Admin port-check one-liners that use /dev/tcp; remote-support tooling.
Known limitations and deployment notes
- PowerQuery: A reverse shell built without these command-line shapes is not captured.
- PowerQuery assumes: tgt.process.cmdline holds the full command line of the new process.
- KQL – Defender XDR: A reverse shell built without these command-line shapes is not captured.
- KQL – Defender XDR assumes: ProcessCommandLine holds the full command line of the created process.
// HUNT: Reverse-shell and PTY-upgrade command-line shapes // MITRE: T1059.004, T1071 // CONF: medium FP: medium COST: medium // REQUIRES: Process creation with command line on Linux or macOS // FALSE POSITIVES: administrators using /dev/tcp for port checks; monitoring scripts // TUNING: exclude known monitoring hosts with | ComputerName!=/^monitor-/i and review hits whose user is the Plex service account first // LOOKBACK: 14d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | (CommandLine="*/dev/tcp/*" or CommandLine="*/dev/udp/*" or CommandLine=/script +-qc +\S+sh/i) | table([@timestamp, ComputerName, aid, UserName, ParentBaseFileName, FileName, CommandLine, ImageFileName])
2 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timetgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creating processsrc.process.user: Creating account
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, endpoint.name, tgt.process.cmdline
// HUNT: Reverse-shell and PTY-upgrade command-line shapes // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1059.004, T1071 // CONF: medium | FP: medium // FALSE POSITIVES: administrators using /dev/tcp for port checks; monitoring scripts // EQUIVALENCE: equivalent-with-schema-assumptions - Same command-line predicate on process creation: a /dev/tcp or /dev/udp redirect, or a script -qc invocation of a shell used for PTY upgrade. contains and matches are case-insensitive by default, which reproduces the CQL /i intent. // REQUIRES: SentinelOne agent process-creation telemetry with command line on Linux or macOS // TUNING: Exclude known monitoring hosts by endpoint.name and review hits whose creating account is the Plex service account first. // LOOKBACK: 14d (set in the time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A reverse shell built without these command-line shapes is not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' (tgt.process.cmdline contains '/dev/tcp/' or tgt.process.cmdline contains '/dev/udp/' or tgt.process.cmdline matches 'script +-qc +\\S+sh') | columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
2 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Initiating process
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, ProcessCommandLine | take 5
// HUNT: Reverse-shell and PTY-upgrade command-line shapes // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1059.004, T1071 // CONF: medium | FP: medium // FALSE POSITIVES: administrators using /dev/tcp for port checks; monitoring scripts // EQUIVALENCE: equivalent-with-schema-assumptions - Same command-line predicate on process creation: a /dev/tcp or /dev/udp redirect, or a script -qc invocation of a shell. The regex is case-insensitive through the (?i) prefix. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) on Linux or macOS // TUNING: Exclude known monitoring hosts by DeviceName and review hits whose account is the Plex service account first. // LOOKBACK: 14d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A reverse shell built without these command-line shapes is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(14d) | where ProcessCommandLine contains "/dev/tcp/" or ProcessCommandLine contains "/dev/udp/" or ProcessCommandLine matches regex @"(?i)script +-qc +\S+sh" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine | sort by Timestamp desc | take 200
Looks for: Process names the Plex advisory and the honeypot report list (myawesomeproxy, mylovelyminer, x12345), plus command lines with proxystream.st, a stratum URL, a donate-level flag or a rig identifier. Names and pool strings are attacker-chosen and perishable, so a miss proves nothing. FP: Only a deliberately run miner in a lab.
Known limitations and deployment notes
- PowerQuery: A renamed payload run with none of these arguments is not captured; the hash card covers published samples.
- PowerQuery assumes: tgt.process.name holds the base name of the new image.
- KQL – Defender XDR: A renamed payload run with none of these arguments is not captured; the hash card covers published samples.
- KQL – Defender XDR assumes: FileName holds the base name of the created image.
// HUNT: Named Plex2Shell payloads and miner or proxy arguments // MITRE: T1496, T1090, T1105 // CONF: high FP: low COST: low // REQUIRES: Process creation with command line (any platform) // FALSE POSITIVES: sanctioned mining or proxy tooling in a lab // LOOKBACK: 14d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | (FileName=/^(myawesomeproxy|mylovelyminer|x12345)(\.exe)?$/i or CommandLine=/(proxystream\.st|stratum\+(tcp|ssl)|--donate-level|--rig-id)/i) | table([@timestamp, ComputerName, aid, UserName, ParentBaseFileName, FileName, CommandLine, ImageFileName, SHA256HashData])
3 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timetgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creating process
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, endpoint.name, tgt.process.name, tgt.process.cmdline
// HUNT: Named Plex2Shell payloads and miner or proxy arguments // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1496, T1090, T1105 // CONF: high | FP: low // FALSE POSITIVES: sanctioned mining or proxy tooling in a lab // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: a created process named like one of the published payloads, or a command line carrying a proxystream.st host, a stratum URL or a miner donate or rig flag. Matching is case-insensitive by default, which reproduces the CQL /i intent. // REQUIRES: SentinelOne agent process-creation telemetry with command line (any platform) // TUNING: Exclude sanctioned mining or proxy tooling in a lab by endpoint.name. // LOOKBACK: 14d (set in the time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A renamed payload run with none of these arguments is not captured; the hash card covers published samples. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' (tgt.process.name matches '^(myawesomeproxy|mylovelyminer|x12345)(\\.exe)?$' or tgt.process.cmdline matches '(proxystream\\.st|stratum\\+(tcp|ssl)|--donate-level|--rig-id)') | columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Initiating process
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine | take 5
// HUNT: Named Plex2Shell payloads and miner or proxy arguments // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1496, T1090, T1105 // CONF: high | FP: low // FALSE POSITIVES: sanctioned mining or proxy tooling in a lab // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: a created process named like one of the published payloads, or a command line carrying a proxystream.st host, a stratum URL or a miner donate or rig flag. The regex is case-insensitive through the (?i) prefix. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) // TUNING: Exclude sanctioned mining or proxy tooling in a lab by DeviceName. // LOOKBACK: 14d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A renamed payload run with none of these arguments is not captured; the hash card covers published samples. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(14d) | where FileName matches regex @"(?i)^(myawesomeproxy|mylovelyminer|x12345)(\.exe)?$" or ProcessCommandLine matches regex @"(?i)(proxystream\.st|stratum\+(tcp|ssl)|--donate-level|--rig-id)" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine, SHA1 | sort by Timestamp desc | take 200
Looks for: Execution of any of the five SHA-256 values published by the honeypot operator for the downloaded dropper files, the XMRig-based miner (packed and unpacked) and the proxy tool. Single-sourced, so a hit is high confidence but a miss is weak, because the operator can recompile. FP: None expected for these values.
Known limitations and deployment notes
- PowerQuery: A rebuilt or repacked payload with a different hash is not captured.
- PowerQuery assumes: tgt.process.image.sha256 is populated on process-creation events for this tenant; the field is mapped by an independent backend but population is worth a readiness check.
- KQL – Defender XDR: SHA256 is usually unpopulated in DeviceProcessEvents; the source has no SHA1 or MD5 values to pivot on instead.
- KQL – Defender XDR assumes: SHA256 is populated for the created process on this tenant.
// HUNT: Published Plex2Shell sample hashes executed // MITRE: T1496, T1090, T1105 // CONF: high FP: low COST: low // REQUIRES: Process creation with file hash (any platform) // FALSE POSITIVES: none expected; hashes are single-sourced so ship as detect only // LOOKBACK: 14d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | in(SHA256HashData, values=[ "5712ef510481f15fe8b18fdd10b73026d57cb3d9d151878a692358b99a464441", "68938fb428f2bcfef4ede62f7b29cd0f15f37ed59e94462c099f2ca64e36baa7", "b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49", "e546b10aee5edc754da4f9b0baace00392257121911f630145078eafad1e6bd9", "2d3c618b21a7e2f8bcd735da188e44a7609ff3f204832dd0781cd09e4388c9db" ]) | table([@timestamp, ComputerName, aid, UserName, ParentBaseFileName, FileName, CommandLine, ImageFileName, SHA256HashData])
2 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timetgt.process.image.sha256: Created image hash
Recommended fields
tgt.process.name: Created process name
Readiness test
event.type = 'Process Creation' tgt.process.image.sha256 = * | limit 5 | columns event.time, endpoint.name, tgt.process.image.sha256
// HUNT: Published Plex2Shell sample hashes executed
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1496, T1090, T1105
// CONF: high | FP: low
// FALSE POSITIVES: none expected; hashes are single-sourced so ship as detect only
// EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: the new process image carries one of the five published SHA-256 values. The in operator is case-sensitive and the published values are lowercase.
// REQUIRES: SentinelOne agent process-creation telemetry with image SHA-256 populated
// TUNING: Hashes are single-sourced, so treat a hit as a detect lead and confirm with the process lineage cards.
// LOOKBACK: 14d (set in the time picker)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A rebuilt or repacked payload with a different hash is not captured.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' tgt.process.image.sha256 in ('5712ef510481f15fe8b18fdd10b73026d57cb3d9d151878a692358b99a464441', '68938fb428f2bcfef4ede62f7b29cd0f15f37ed59e94462c099f2ca64e36baa7', 'b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49', 'e546b10aee5edc754da4f9b0baace00392257121911f630145078eafad1e6bd9', '2d3c618b21a7e2f8bcd735da188e44a7609ff3f204832dd0781cd09e4388c9db')
| columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline, tgt.process.image.path, tgt.process.image.sha256
| sort -event.time
| limit 200Automated validation: static-passed
2 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeSHA256: Created image hash
Recommended fields
FileName: Created process name
Optional fields
SHA1: Hash. SHA256 is often unpopulated in this table; SHA1 is the safer pivot but the source publishes no SHA1 values.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, SHA256 | take 5
// HUNT: Published Plex2Shell sample hashes executed
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1496, T1090, T1105
// CONF: high | FP: low
// FALSE POSITIVES: none expected; hashes are single-sourced so ship as detect only
// EQUIVALENCE: telemetry-gap - Partial pivot: the published values are SHA-256 only, and Microsoft documents SHA256 as usually unpopulated in DeviceProcessEvents. The query is correct where the column is populated and silent where it is not, so a clean result proves little.
// REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) with SHA256 populated
// TUNING: Check SHA256 population before trusting an empty result, and pivot on the CQL or PowerQuery tab where the hash is attested.
// LOOKBACK: 14d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: SHA256 is usually unpopulated in DeviceProcessEvents; the source has no SHA1 or MD5 values to pivot on instead.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(14d)
| where SHA256 in~ ("5712ef510481f15fe8b18fdd10b73026d57cb3d9d151878a692358b99a464441", "68938fb428f2bcfef4ede62f7b29cd0f15f37ed59e94462c099f2ca64e36baa7", "b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49", "e546b10aee5edc754da4f9b0baace00392257121911f630145078eafad1e6bd9", "2d3c618b21a7e2f8bcd735da188e44a7609ff3f204832dd0781cd09e4388c9db")
| project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine, FolderPath, SHA256
| sort by Timestamp desc
| take 200Looks for: Outbound connections to the three exploitation source addresses and the payload host named by the honeypot report, and DNS lookups of proxystream.st. The addresses are likely short-lived, so treat a hit as confirmed contact and a miss as inconclusive. FP: Shared hosting reuse of an address after the campaign moves on; check the connection date against 2026-10-17 onward.
Known limitations and deployment notes
- PowerQuery: A DNS lookup answered from a resolver cache the agent did not observe is not captured.
- PowerQuery assumes: event.dns.request is the populated DNS name field; some schema versions populate dns.request instead.
- PowerQuery assumes: dst.ip.address holds the remote address on IP Connect events.
- KQL – Defender XDR: A DNS lookup with no following connection is not captured; DeviceNetworkEvents is not a DNS log.
- KQL – Defender XDR assumes: RemoteUrl is populated for the connection when a host name was resolved.
// HUNT: Contact with published attacker addresses or proxystream.st // MITRE: T1105, T1090, T1496 // CONF: high FP: low COST: low // REQUIRES: Outbound network connection with process and DNS lookup (any platform) // FALSE POSITIVES: address reuse by unrelated tenants after the campaign ends // LOOKBACK: 30d #event_simpleName=/^(NetworkConnectIP4|DnsRequest)$/ | (RemoteAddressIP4=/^(45\.194\.92\.222|91\.92\.40\.202|176\.65\.139\.228|217\.60\.194\.116)$/ or DomainName=/(^|\.)proxystream\.st$/i) | table([@timestamp, ComputerName, aid, ContextBaseFileName, RemoteAddressIP4, RemotePort, DomainName, ContextProcessId])
4 required fields · View requirements
Required telemetry
- IP Connect
- DNS Resolved
Required fields
event.time: Event timeevent.type: Event typedst.ip.address: Remote addressevent.dns.request: DNS name
Recommended fields
src.process.name: Owning processdst.port.number: Remote port
Readiness test
event.type in ('DNS Resolved', 'IP Connect') event.dns.request = *
| limit 5
| columns event.time, event.type, dst.ip.address, event.dns.request// HUNT: Contact with published attacker addresses or proxystream.st
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1105, T1090, T1496
// CONF: high | FP: low
// FALSE POSITIVES: address reuse by unrelated tenants after the campaign ends
// EQUIVALENCE: equivalent-with-schema-assumptions - Same indicator set: an outbound connection to one of four published addresses, or a DNS lookup of proxystream.st or a subdomain. The DNS name is read from event.dns.request; the vendor field docs name dns.request, so the readiness test prints both.
// REQUIRES: SentinelOne agent network (IP Connect) and DNS (DNS Resolved) telemetry
// TUNING: Review address reuse by unrelated tenants before acting once the campaign ends.
// LOOKBACK: 30d (set in the time picker)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A DNS lookup answered from a resolver cache the agent did not observe is not captured.
// VALIDATION LEVEL: static-reviewed
event.type in ('DNS Resolved', 'IP Connect') (event.dns.request in:anycase ('proxystream.st') or event.dns.request matches '\\.proxystream\\.st$' or dst.ip.address in ('45.194.92.222', '91.92.40.202', '176.65.139.228', '217.60.194.116'))
| columns event.time, endpoint.name, event.type, src.process.name, dst.ip.address, dst.port.number, event.dns.request
| sort -event.time
| limit 1000Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceNetworkEvents
Required fields
Timestamp: Event timeRemoteIP: Remote addressRemoteUrl: Remote host name
Recommended fields
InitiatingProcessFileName: Initiating processRemotePort: Remote port
Readiness test
DeviceNetworkEvents | where Timestamp > ago(1d) | project Timestamp, RemoteIP, RemoteUrl | take 5
// HUNT: Contact with published attacker addresses or proxystream.st
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1105, T1090, T1496
// CONF: high | FP: low
// FALSE POSITIVES: address reuse by unrelated tenants after the campaign ends
// EQUIVALENCE: approximate-analytic - DeviceNetworkEvents carries the published addresses and the destination host name when the connection had one, but it is not a DNS log: a lookup that was never followed by a connection is invisible. The hunt therefore covers connections to the four addresses and to proxystream.st by name, not every lookup.
// REQUIRES: Defender for Endpoint network telemetry (DeviceNetworkEvents)
// TUNING: Review address reuse by unrelated tenants before acting once the campaign ends.
// LOOKBACK: 30d (Advanced Hunting retention; the CQL tab covers the same 30d window)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A DNS lookup with no following connection is not captured; DeviceNetworkEvents is not a DNS log.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceNetworkEvents
| where Timestamp >= ago(30d)
| where RemoteIP in ("45.194.92.222", "91.92.40.202", "176.65.139.228", "217.60.194.116") or RemoteUrl =~ "proxystream.st" or RemoteUrl endswith ".proxystream.st"
| project Timestamp, ReportId, DeviceId, DeviceName, ActionType, InitiatingProcessFileName, RemoteIP, RemotePort, RemoteUrl
| sort by Timestamp desc
| take 1000Looks for: A lookup of a hostname that begins with 127.0.0.1. and ends in a wildcard-DNS service such as sslip.io. The chain relies on the server attaching the owner token to any request whose URL starts with that prefix while DNS sends it to an attacker address. Legitimate software has no reason to resolve such a name. Observed in the honeypot's first attack. FP: Developers testing local wildcard-DNS setups.
Known limitations and deployment notes
- PowerQuery: A lookup the agent did not observe, such as one resolved by an in-process resolver without a system call, is not captured.
- PowerQuery assumes: event.dns.request is the populated DNS name field; some schema versions populate dns.request instead.
- PowerQuery assumes: DNS Resolved coverage on Linux agents varies by agent version.
- KQL – Defender XDR: Linux DNS coverage by the sensor varies by version; a missing event type returns nothing.
- KQL – Defender XDR assumes: AdditionalFields carries the queried name under DnsQueryString or QueryName; the readiness test prints AdditionalFields so you can see which key your tenant populates.
- KQL – Defender XDR assumes: DnsQueryResponse is recorded for the Plex host's platform.
// HUNT: Loopback-prefixed wildcard DNS lookup (admin token theft step) // MITRE: T1528, T1190 // CONF: high FP: low COST: low // REQUIRES: DNS lookup telemetry on the Plex host (Linux DnsRequest coverage varies by sensor version) // FALSE POSITIVES: developers testing local wildcard-DNS setups // LOOKBACK: 14d #event_simpleName=DnsRequest | DomainName=/^127\.0\.0\.1\..+\.(sslip|nip)\.io$/i | table([@timestamp, ComputerName, aid, DomainName, ContextProcessId])
3 required fields · View requirements
Required telemetry
- DNS Resolved
Required fields
event.time: Event timeevent.type: Event typeevent.dns.request: DNS name
Recommended fields
src.process.name: Owning process
Readiness test
event.type = 'DNS Resolved' event.dns.request = * | limit 5 | columns event.time, endpoint.name, event.dns.request
// HUNT: Loopback-prefixed wildcard DNS lookup (admin token theft step) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1528, T1190 // CONF: high | FP: low // FALSE POSITIVES: developers testing local wildcard-DNS setups // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: a DNS lookup whose name starts with 127.0.0.1 and ends in a sslip.io or nip.io wildcard zone. The match is anchored and case-insensitive by default, which reproduces the CQL /i intent. // REQUIRES: SentinelOne agent DNS (DNS Resolved) telemetry on the Plex host // TUNING: Developers testing local wildcard DNS setups are the expected false positive; exclude their hosts by endpoint.name. // LOOKBACK: 14d (set in the time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A lookup the agent did not observe, such as one resolved by an in-process resolver without a system call, is not captured. // VALIDATION LEVEL: static-reviewed event.type = 'DNS Resolved' event.dns.request matches '^127\\.0\\.0\\.1\\..+\\.(sslip|nip)\\.io$' | columns event.time, endpoint.name, src.process.name, event.dns.request | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceEvents
Required fields
Timestamp: Event timeActionType: Action typeAdditionalFields: Event details
Recommended fields
InitiatingProcessFileName: Initiating process
Readiness test
DeviceEvents | where Timestamp > ago(1d) | where ActionType == "DnsQueryResponse" | project Timestamp, ActionType, AdditionalFields | take 5
// HUNT: Loopback-prefixed wildcard DNS lookup (admin token theft step) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1528, T1190 // CONF: high | FP: low // FALSE POSITIVES: developers testing local wildcard-DNS setups // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate on DNS responses: Defender has no endpoint DNS table, so the name is read from the DnsQueryResponse action's AdditionalFields. The regex is anchored and case-insensitive. // REQUIRES: Defender for Endpoint DNS telemetry (DeviceEvents, ActionType DnsQueryResponse) // TUNING: Developers testing local wildcard DNS setups are the expected false positive; exclude their hosts by DeviceName. // LOOKBACK: 14d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Linux DNS coverage by the sensor varies by version; a missing event type returns nothing. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceEvents | where Timestamp >= ago(14d) | where ActionType == "DnsQueryResponse" | extend QueryName = tostring(coalesce(parse_json(AdditionalFields)["DnsQueryString"], parse_json(AdditionalFields)["QueryName"])) | where QueryName matches regex @"(?i)^127\.0\.0\.1\..+\.(sslip|nip)\.io$" | project Timestamp, ReportId, DeviceId, DeviceName, ActionType, InitiatingProcessFileName, QueryName | sort by Timestamp desc | take 200
Looks for: A shared object (Linux) or DLL (Windows) written under a Codecs path by anything other than the Plex Media Server binary. CVE-2026-96656 makes the transcoder itself write the file, so the writer is Plex Transcoder. This is the write half of the write-then-load pattern; Q1 is the load half. FP: Plex's own codec download and updates, package-manager upgrades and container image refreshes. Baseline which process writes legitimate codecs in your fleet before alerting. ElfFileWritten is documented in the crowdstrike-logscale-v3 event reference; the syntax linter lists the same event as unknown, which is a linter defect, not a bad event name.
Known limitations and deployment notes
- PowerQuery: The sensor-side executable classification is not reproduced, so non-executable writes under a Codecs path also match.
- PowerQuery: A very small or malformed file may not be reported by the agent.
- PowerQuery assumes: tgt.file.path holds the full path of the written file.
- PowerQuery assumes: src.process.name is the writing process on file events.
- KQL – Defender XDR: The sensor-side executable classification is not reproduced, so non-executable writes under a Codecs path also match.
- KQL – Defender XDR assumes: FolderPath holds the directory of the written file.
- KQL – Defender XDR assumes: FileCreated and FileModified are the recorded write action types.
// HUNT: Executable written into a Plex Codecs directory by a non-server writer // MITRE: T1129, T1190 // CONF: medium FP: medium COST: low // REQUIRES: Executable-file-written telemetry (Linux ElfFileWritten, Windows PeFileWritten). Sensor classifies by file content, so a very small or malformed file may not be reported. // FALSE POSITIVES: Plex codec downloads and updates; package or image upgrades // TUNING: baseline the legitimate writer for 30 days, then exclude it with | ContextBaseFileName!=/^approved_updater$/i. Keep Plex Transcoder as a writer you do NOT exclude. // LOOKBACK: 14d #event_simpleName=/^(ElfFileWritten|PeFileWritten)$/ | TargetFileName=/Codecs/i | ContextBaseFileName!=/^(plex media serv|plexmediaserver)/i | table([@timestamp, ComputerName, aid, ContextBaseFileName, TargetFileName, SHA256HashData])
4 required fields · View requirements
Required telemetry
- File Creation
- File Modification
Required fields
event.time: Event timeevent.type: Event typetgt.file.path: Written file pathsrc.process.name: Writing process
Recommended fields
src.process.user: Writing accounttgt.file.sha256: File hash
Readiness test
event.type in ('File Creation', 'File Modification') tgt.file.path = *
| limit 5
| columns event.time, endpoint.name, src.process.name, tgt.file.path// HUNT: Executable written into a Plex Codecs directory by a non-server writer
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1129, T1190
// CONF: medium | FP: medium
// FALSE POSITIVES: Plex codec downloads and updates; package or image upgrades
// EQUIVALENCE: approximate-analytic - The CQL reads an executable-file-written event that the sensor classifies by file content. SentinelOne has no content-classified executable write event, so this hunts any file creation or modification under a Codecs path by a writer other than the Plex server, which is broader and noisier.
// REQUIRES: SentinelOne agent file telemetry (File Creation, File Modification)
// TUNING: Baseline the legitimate writer for 30 days, then exclude it by src.process.name. Keep Plex Transcoder as a writer you do not exclude.
// LOOKBACK: 14d (set in the time picker)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The sensor-side executable classification is not reproduced, so non-executable writes under a Codecs path also match. A very small or malformed file may not be reported by the agent.
// VALIDATION LEVEL: static-reviewed
event.type in ('File Creation', 'File Modification') tgt.file.path contains 'Codecs' !(src.process.name matches '^(plex media serv|plexmediaserver)')
| columns event.time, endpoint.name, event.type, src.process.user, src.process.name, tgt.file.path, tgt.file.sha256
| sort -event.time
| limit 200Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceFileEvents
Required fields
Timestamp: Event timeActionType: Action typeFolderPath: Written file directoryInitiatingProcessFileName: Writing process
Recommended fields
FileName: Written file name
Readiness test
DeviceFileEvents | where Timestamp > ago(1d) | project Timestamp, ActionType, FolderPath, InitiatingProcessFileName | take 5
// HUNT: Executable written into a Plex Codecs directory by a non-server writer
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1129, T1190
// CONF: medium | FP: medium
// FALSE POSITIVES: Plex codec downloads and updates; package or image upgrades
// EQUIVALENCE: approximate-analytic - The CQL reads an executable-file-written event that the sensor classifies by file content. Defender has no content-classified executable write, so this hunts any file creation or modification under a Codecs path by a writer other than the Plex server, which is broader and noisier.
// REQUIRES: Defender for Endpoint file telemetry (DeviceFileEvents)
// TUNING: Baseline the legitimate writer for 30 days, then exclude it by InitiatingProcessFileName. Keep Plex Transcoder as a writer you do not exclude.
// LOOKBACK: 14d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The sensor-side executable classification is not reproduced, so non-executable writes under a Codecs path also match.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceFileEvents
| where Timestamp >= ago(14d)
| where ActionType in ("FileCreated", "FileModified")
| where FolderPath contains "Codecs"
| where not(InitiatingProcessFileName matches regex @"(?i)^(plex media serv|plexmediaserver)")
| project Timestamp, ReportId, DeviceId, DeviceName, ActionType, InitiatingProcessAccountName, InitiatingProcessFileName, FolderPath, FileName, SHA1
| sort by Timestamp desc
| take 200Looks for: Frequency of public destinations contacted by Plex server, script host or transcoder on ports other than 80 and 443. The chain makes the server call out to an attacker-chosen host and port, and the proxy payload adds new destinations. A hunt, not an alert: remote streaming and relay make this noisy. FP: Remote streaming, relay and DLNA traffic; metadata providers on non-standard ports.
Known limitations and deployment notes
- PowerQuery: Remote streaming, relay and DLNA traffic on unusual ports is a known false positive; this is a hunt, not a detection.
- PowerQuery assumes: src.process.name is the connecting process on IP Connect events.
- PowerQuery assumes: dst.port.number compares as a number.
- KQL – Defender XDR: Remote streaming, relay and DLNA traffic on unusual ports is a known false positive; this is a hunt, not a detection.
- KQL – Defender XDR assumes: RemoteIPType is populated for outbound connections.
- KQL – Defender XDR assumes: ConnectionSuccess is the recorded action type for a completed outbound connection.
// HUNT: Plex process outbound to public addresses on unusual ports // MITRE: T1090, T1105 // CONF: low FP: high COST: medium // REQUIRES: Outbound network connection with owning process name (validate ContextBaseFileName on your sensor) // FALSE POSITIVES: remote streaming, relay and DLNA; metadata providers on non-standard ports // TUNING: rank by rarity and review destinations seen on one host only; exclude your own approved relay ranges with | !cidr(RemoteAddressIP4, subnet="203.0.113.0/24") // LOOKBACK: 14d #event_simpleName=NetworkConnectIP4 | ContextBaseFileName=/^plex (media serv|script hos|transcoder)/i | !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16","127.0.0.0/8","169.254.0.0/16"]) | RemotePort!=443 RemotePort!=80 | groupBy([aid, ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort], function=count(as=conns)) | sort(conns, order=desc, limit=200) | table([ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort, conns])
5 required fields · View requirements
Required telemetry
- IP Connect
Required fields
event.time: Event timeevent.type: Event typesrc.process.name: Owning processdst.ip.address: Remote addressdst.port.number: Remote port
Recommended fields
endpoint.name: Host
Readiness test
event.type = 'IP Connect' src.process.name = * | limit 5 | columns event.time, endpoint.name, src.process.name, dst.ip.address, dst.port.number
// HUNT: Plex process outbound to public addresses on unusual ports // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1090, T1105 // CONF: low | FP: high // FALSE POSITIVES: remote streaming, relay and DLNA; metadata providers on non-standard ports // EQUIVALENCE: approximate-analytic - Same hunt: a Plex process connecting out to a non-private address on a port other than 80 or 443, ranked by connection count. RFC 1918, loopback and link-local destinations are excluded with the same ranges as the CQL. // REQUIRES: SentinelOne agent network (IP Connect) telemetry with the owning process name // TUNING: Rank by rarity and review destinations seen on one host only. Exclude your own approved relay ranges with !(net_ipsubnet(dst.ip.address, '203.0.113.0/24')). // LOOKBACK: 14d (set in the time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Remote streaming, relay and DLNA traffic on unusual ports is a known false positive; this is a hunt, not a detection. // VALIDATION LEVEL: static-reviewed event.type = 'IP Connect' src.process.name matches '^plex (media serv|script hos|transcoder)' dst.ip.address = * !(net_rfc1918(dst.ip.address)) !(net_ipsubnet(dst.ip.address, '127.0.0.0/8')) !(net_ipsubnet(dst.ip.address, '169.254.0.0/16')) dst.port.number != 443 dst.port.number != 80 | group conns=count() by endpoint.name, src.process.name, dst.ip.address, dst.port.number | sort -conns | limit 200
Automated validation: static-passed
5 required fields · View requirements
Required telemetry
- DeviceNetworkEvents
Required fields
Timestamp: Event timeActionType: Action typeInitiatingProcessFileName: Initiating processRemoteIPType: Remote address typeRemotePort: Remote port
Recommended fields
RemoteIP: Remote address
Readiness test
DeviceNetworkEvents | where Timestamp > ago(1d) | project Timestamp, ActionType, InitiatingProcessFileName, RemoteIPType, RemotePort | take 5
// HUNT: Plex process outbound to public addresses on unusual ports // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1090, T1105 // CONF: low | FP: high // FALSE POSITIVES: remote streaming, relay and DLNA; metadata providers on non-standard ports // EQUIVALENCE: approximate-analytic - Same hunt: a Plex process connecting out to a public address on a port other than 80 or 443, ranked by connection count. RemoteIPType equal to Public stands in for the CQL's explicit private, loopback and link-local exclusions and also drops a few other reserved ranges. // REQUIRES: Defender for Endpoint network telemetry (DeviceNetworkEvents) // TUNING: Rank by rarity and review destinations seen on one host only. Exclude your own approved relay ranges by RemoteIP after baselining. // LOOKBACK: 14d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Remote streaming, relay and DLNA traffic on unusual ports is a known false positive; this is a hunt, not a detection. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0 DeviceNetworkEvents | where Timestamp >= ago(14d) | where ActionType == "ConnectionSuccess" | where InitiatingProcessFileName matches regex @"(?i)^plex (media serv|script hos|transcoder)" | where RemoteIPType == "Public" | where RemotePort !in (80, 443) | summarize Conns = count() by DeviceName, InitiatingProcessFileName, RemoteIP, RemotePort | sort by Conns desc | take 200
Detection Rules by Platform
CrowdStrike: Custom IOA
| IOA name | Promotes | Pattern | Exclusions | Deployment path |
|---|---|---|---|---|
| Plex service spawns shell or downloader | Promote Q1 | Parent image matches Plex Media Server, Plex Transcoder or Plex Script Host; child is a shell, interpreter or downloader | Approved DVR post-processing script names after a 30 day baseline. Never exclude Plex Transcoder as parent. | Custom IOA, process creation rule on Linux and Windows host groups containing Plex. Detect first. Do not set to prevent until baselined. Confirm rule-type availability for Linux in your tenant. |
| Executable written into Plex Codecs by non-server writer | Promote Q8 | Executable file written, path contains Codecs, writer is not the Plex Media Server binary | The one legitimate updater found in your baseline | Custom IOA on file-write events where the sensor supports it. Detect only. |
| Named Plex2Shell payload or miner arguments | Promote Q4 | Process name myawesomeproxy, mylovelyminer or x12345; command line with proxystream.st, stratum URL, donate-level or rig-id | None expected | Custom IOA, detect. Names are attacker-chosen, so do not rely on this alone. |
SentinelOne: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| Plex service spawns shell or downloader | Q1, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device". This pack's own text mentions a baseline or pilot; follow it as written |
| Executable written into Plex Codecs by non-server writer | Q8, PowerQuery tab | Hunt only until the gap is closed | Q8 is only an approximation here. The CQL reads an executable-file-written event that the sensor classifies by file content. SentinelOne has no content-classified executable write event, so this hunts any file creation or modification under a Codecs path by a writer other than the Plex server, which is broader and noisier.. This pack's own text mentions a baseline or pilot; follow it as written |
| Named Plex2Shell payload or miner arguments | Q4, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
Create the rule with POST /web/api/v2.1/cloud-detection/rules, queryType: "scheduled" and queryLang: "2.0", with the query in data.scheduledParams.query. Use 2.0 operator spellings: a rule written with the 1.0 token ContainsCIS was accepted and never fired on a tenant. The API does not warn, so lint the body before deploying.
Microsoft Defender: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| Plex service spawns shell or downloader | Q1, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself. This pack's own text mentions a baseline or pilot; follow it as written |
| Executable written into Plex Codecs by non-server writer | Q8, KQL – Defender XDR tab | Hunt only until the gap is closed | Q8 is only an approximation here. The CQL reads an executable-file-written event that the sensor classifies by file content. Defender has no content-classified executable write, so this hunts any file creation or modification under a Codecs path by a writer other than the Plex server, which is broader and noisier.. This pack's own text mentions a baseline or pilot; follow it as written |
| Named Plex2Shell payload or miner arguments | Q4, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself |
Q2, Q3, Q5, Q6, Q7 and Q9 stay a hunt in this pack, not a rule. A rule can generate only 150 alerts per run. Continuous (NRT) frequency needs a single table and no comment lines, so delete the generated comment header from the query first.
Machine-Readable IOC Appendix
type,value,action,severity,expiration,description,tags ipv4,45.194.92.222,detect,high,2027-01-22,Plex2Shell exploiting source,campaign:Plex2Shell ipv4,91.92.40.202,detect,high,2027-01-22,Plex2Shell exploiting source,campaign:Plex2Shell ipv4,176.65.139.228,detect,high,2027-01-22,Plex2Shell exploiting source,campaign:Plex2Shell ipv4,217.60.194.116,detect,high,2027-01-22,Plex2Shell payload host,campaign:Plex2Shell domain,proxystream.st,prevent,high,2027-04-22,Plex2Shell miner and proxy infrastructure named by vendor and honeypot report,campaign:Plex2Shell domain,ota.proxystream.st,detect,high,2027-04-22,Plex2Shell mining server named by press report,campaign:Plex2Shell sha256,5712ef510481f15fe8b18fdd10b73026d57cb3d9d151878a692358b99a464441,detect,high,2027-04-22,Plex2Shell docker dropper file,campaign:Plex2Shell sha256,68938fb428f2bcfef4ede62f7b29cd0f15f37ed59e94462c099f2ca64e36baa7,detect,high,2027-04-22,Plex2Shell x miner (UPX packed),campaign:Plex2Shell sha256,b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49,detect,high,2027-04-22,Plex2Shell x miner (unpacked),campaign:Plex2Shell sha256,e546b10aee5edc754da4f9b0baace00392257121911f630145078eafad1e6bd9,detect,high,2027-04-22,Plex2Shell myawesomeproxy binary,campaign:Plex2Shell sha256,2d3c618b21a7e2f8bcd735da188e44a7609ff3f204832dd0781cd09e4388c9db,detect,high,2027-04-22,Plex2Shell XMRig sample,campaign:Plex2Shell
indicatorType,indicatorValue,action,title,description,expirationTime,severity,recommendedActions,rbacGroups,category,mitretechniques,GenerateAlert IpAddress,45.194.92.222,Audit,Plex2Shell exploiting source,Plex2Shell exploiting source,2027-01-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True IpAddress,91.92.40.202,Audit,Plex2Shell exploiting source,Plex2Shell exploiting source,2027-01-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True IpAddress,176.65.139.228,Audit,Plex2Shell exploiting source,Plex2Shell exploiting source,2027-01-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True IpAddress,217.60.194.116,Audit,Plex2Shell payload host,Plex2Shell payload host,2027-01-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True DomainName,proxystream.st,Block,Plex2Shell miner and proxy infrastructure named by vendor an,Plex2Shell miner and proxy infrastructure named by vendor and honeypot report,2027-04-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True DomainName,ota.proxystream.st,Audit,Plex2Shell mining server named by press report,Plex2Shell mining server named by press report,2027-04-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,5712ef510481f15fe8b18fdd10b73026d57cb3d9d151878a692358b99a464441,Audit,Plex2Shell docker dropper file,Plex2Shell docker dropper file,2027-04-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,68938fb428f2bcfef4ede62f7b29cd0f15f37ed59e94462c099f2ca64e36baa7,Audit,Plex2Shell x miner,Plex2Shell x miner (UPX packed),2027-04-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49,Audit,Plex2Shell x miner,Plex2Shell x miner (unpacked),2027-04-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,e546b10aee5edc754da4f9b0baace00392257121911f630145078eafad1e6bd9,Audit,Plex2Shell myawesomeproxy binary,Plex2Shell myawesomeproxy binary,2027-04-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,2d3c618b21a7e2f8bcd735da188e44a7609ff3f204832dd0781cd09e4388c9db,Audit,Plex2Shell XMRig sample,Plex2Shell XMRig sample,2027-04-22T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True
Column names and allowed values are from Microsoft's documented parameter table. Prevent rows import as Block, without remediation; network indicators cannot use BlockAndRemediate. Check each expirationTime before importing: an indicator whose date has passed is already expired. Download the sample CSV from the Import page to confirm the column order before a bulk import. Each file takes at most 500 indicators. Learn describes the import per entity type, so if a mixed file is rejected, split it by indicatorType. An imported indicator does nothing unless the matching Advanced features are on (for example Custom network indicators and Allow or block file).
This pack does not ship a SentinelOne import block: the import format could not be verified against SentinelOne's own documentation. Export a sample from your console and the block can be added with confidence.
5712ef510481f15fe8b18fdd10b73026d57cb3d9d151878a692358b99a464441 68938fb428f2bcfef4ede62f7b29cd0f15f37ed59e94462c099f2ca64e36baa7 b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49 e546b10aee5edc754da4f9b0baace00392257121911f630145078eafad1e6bd9 2d3c618b21a7e2f8bcd735da188e44a7609ff3f204832dd0781cd09e4388c9db
45.194.92.222 91.92.40.202 176.65.139.228 217.60.194.116 proxystream.st ota.proxystream.st
# Process names (attacker-chosen, perishable) myawesomeproxy mylovelyminer x12345 # Command-line tokens proxystream.st stratum+tcp / stratum+ssl --donate-level --rig-id # Lineage Plex Transcoder -> sh / bash / curl / wget Plex Media Server -> sh -> any child # DNS pattern 127.0.0.1.<attacker-ip>.sslip.io # Request shape (server or proxy log) player timeline subscribe request with protocol=http://... user agent Python-urllib/3.10 (weak) # File shape shared object or DLL under a Codecs directory written by Plex Transcoder
# Fixed build: 1.43.3.10861 or later
v=$(curl -s http://127.0.0.1:32400/identity | sed -n 's/.*version="\([^"]*\)".*/\1/p')
v=${v%%-*}
if [ "$(printf '%s\n1.43.3.10861\n' "$v" | sort -V | head -1)" = "1.43.3.10861" ]; then echo "OK: $v"; else echo "UPDATE NEEDED: $v"; fi
dpkg-query -W plexmediaserver
rpm -q plexmediaserver
docker ps --format '{{.Names}}\t{{.Image}}' | grep -i plex
# Exposure
ss -ltnp | grep 32400Detection Validation Gates
| Gate | Criteria |
|---|---|
| Telemetry ready | Confirm the sensor reports process creation with parent and grandparent, DNS lookup, network connection and executable-file-written events from at least one Plex host (CrowdStrike: ProcessRollup2, DnsRequest, NetworkConnectIP4, ElfFileWritten. SentinelOne: Process Creation, DNS Resolved, IP Connect, File Creation. Defender: DeviceProcessEvents, DeviceEvents, DeviceNetworkEvents, DeviceFileEvents). Validate that parent names arrive untruncated or prefix-matched, and whether Linux DNS lookups are present. |
| Benign baseline | Run Q1, Q2, Q8 and Q9 for 30 days on Plex hosts. List legitimate script launches and the legitimate codec writer. Add them as exclusions with owner and date. |
| Positive tests (lab host only) | Q1: create a harmless executable named Plex Transcoder that starts a shell running id. Q4: copy a benign binary to a file named mylovelyminer and run it. Q7: resolve a lab name of the form 127.0.0.1.192-0-2-1.sslip.io. Q8: have a non-server process write a copy of a harmless library into a lab directory named Codecs. Do not use exploit code. |
| Promotion | Promote Q1, Q8 and Q4 to Custom IOA at detect after a clean baseline. Prevent only for proxystream.st at the network layer. Re-review indicators at expiry. |
Hardening: Tiered
- Update every Plex Media Server to 1.43.3.10861 or later. Authority: MITRE M1051 Update Software; vendor advisory (S01). Verify: query the local identity endpoint (see Patch and exposure audit in section 10). For containers, pull the new image and recreate the container; a restart keeps the old build.
- Remove internet exposure of port 32400. Delete router port forwards, turn off UPnP if unused, switch off Remote Access if nobody streams from outside. Authority: M1030 Network Segmentation, M1035 Limit Access to Resource over Network; vendor advisory (S01). Verify: from an outside network, request the identity endpoint on the public address; it must time out.
- Deny the Plex service identity outbound access to internal ranges and cloud metadata. Authority: M1037 Filter Network Traffic; S06 workaround; ⚠ best-practice, no formal benchmark for Plex. Verify:
iptables -S OUTPUTlists the rules; Q9 shows no new internal destinations. - Treat unpatched servers that were reachable after 2026-10-17 as compromised. Check Codecs, Preferences.xml and the process tree (section 7), then follow section 14.
- Put Plex behind a VPN or an access proxy. The researcher's repository recommends keeping the server off the open internet (S09). Authority: M1035. Where a reverse proxy is used, strip or validate the protocol parameter on the player timeline path (S06). Verify: external scan of port 32400 returns nothing.
- Inventory Plex everywhere, including NAS and containers, and record sensor status. Authority: ⚠ best-practice, no formal benchmark. This closes the unsensored gap that no query can. Verify: the inventory lists owner, version, exposure and whether a sensor reports in.
- Audit-watch the Codecs directory and Preferences.xml for writes. Authority: M1047 Audit; CIS Linux benchmark auditd guidance (general). Verify:
auditctl -lshows the watch, and a test file creates an event. - Container hardening for the Plex container: unprivileged user, no new privileges, dropped capabilities where the image allows, default-deny egress network. Authority: M1048 Application Isolation and Sandboxing; CIS Docker Benchmark (container runtime section). Verify:
docker inspectshows the settings.
- Scheduled updates where auto-update does not exist (Linux packages, Docker, NAS). Windows and macOS default to prompting, not installing (S10). Authority: M1051. Verify: update job log and a weekly version report.
- Isolate media servers on a dedicated segment with default-deny egress and an allow-list. The chain needs the server to call out; a media server rarely needs arbitrary destinations (S09). Authority: M1030, M1037. Verify: flow logs show only approved destinations.
- Rule for chained CVEs: escalate when several CVEs fixed in one release include an unauthenticated entry and a code-execution end (S09). Authority: ⚠ best-practice, no formal benchmark.
- Make Plex hosts rebuildable. The Codecs directory is regenerable (S01), so a clean rebuild is the eviction path. Keep configuration and libraries backed up separately.
Deployable Playbooks
Every step lists prerequisites, whether a reboot is needed and how to undo it. Pilot on one host first.
Playbook 1: Patch and verify a Docker or package install
# Prerequisites: backup of the Plex config volume or Library directory; maintenance window; shell on the host. # Reboot required: no (service restart only). # Rollback: re-pin the previous image tag or reinstall the previous package ONLY if the new build breaks playback. Rolling back reintroduces the vulnerability, so never roll back on an internet-exposed server. # Docker: pull and recreate (do not just restart) docker compose pull plex && docker compose up -d plex docker exec plex curl -s http://127.0.0.1:32400/identity # Debian or Ubuntu sudo dpkg -i plexmediaserver_*.deb # Fedora or CentOS sudo dnf install ./plexmediaserver-*.rpm # Verify (must print 1.43.3.10861 or later) curl -s http://127.0.0.1:32400/identity
Playbook 2: Egress restriction for the Plex service account (Linux)
# Prerequisites: root; the service account is named plex (confirm with: id plex); iptables available. Pilot first: this blocks Plex from reaching internal media shares reached by user-space clients and some DLNA behavior. # Reboot required: no. Rules are runtime only until saved with your distro's persistence tool. # Rollback: delete each rule by repeating it with -D instead of -I, or reload the saved ruleset. iptables -I OUTPUT 1 -m owner --uid-owner plex -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -I OUTPUT 2 -m owner --uid-owner plex -d 169.254.169.254 -j DROP iptables -I OUTPUT 3 -m owner --uid-owner plex -d 10.0.0.0/8 -j DROP iptables -I OUTPUT 4 -m owner --uid-owner plex -d 172.16.0.0/12 -j DROP # Add an ACCEPT for your own LAN client range before the next rule if clients are on 192.168.0.0/16 iptables -I OUTPUT 5 -m owner --uid-owner plex -d 192.168.0.0/16 -j DROP # Verify iptables -S OUTPUT | grep plex
Playbook 3: Audit watch on Codecs and Preferences.xml (Linux, auditd)
# Prerequisites: auditd installed and running; default Linux data path. If auditctl rejects a path containing spaces on your build, watch through a path without spaces (a bind mount) instead. # Reboot required: no for runtime rules; persisting in rules.d is loaded at the next audit service restart. # Rollback: auditctl -W with the same path and keys, or remove the rule file and restart auditd. PMS="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" auditctl -w "$PMS/Codecs" -p wa -k plex_codecs auditctl -w "$PMS/Preferences.xml" -p wa -k plex_prefs # Verify auditctl -l | grep plex_ ausearch -k plex_codecs -ts recent
Playbook 4: Close the internet path
# Prerequisites: router or firewall admin access; a VPN or access proxy ready for remote viewers. # Reboot required: no. # Rollback: re-add the forward only after the server is on 1.43.3.10861 or later and behind access control. # 1. Router: delete any manual port forward to TCP 32400; disable UPnP and NAT-PMP if not needed. # 2. Plex Web: Settings, Remote Access, disable if unused. # 3. Host firewall (ufw example): allow 32400 only from the VPN or LAN range ufw status numbered ufw delete <rule-number-for-32400-from-anywhere> ufw allow from 192.0.2.0/24 to any port 32400 proto tcp # Verify from a network outside yours (expect a timeout) curl -m 5 -s http://<public-ip>:32400/identity
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | Cut the host from the network at the switch or firewall, keeping power on. Remove any port forward to 32400. For a container, disconnect it from its network rather than deleting it. | IR lead | Time of isolation, firewall change record |
| Isolate: by platform | CrowdStrike: network-contain the host. SentinelOne: network quarantine (Disconnect from Network; confirm the label in your console version). Microsoft Defender: Isolate device (full network isolation). | IR lead | Time of isolation, firewall change record |
| Preserve | Before cleanup, copy the Plex log files (few rotations are kept), Preferences.xml, a listing of Codecs with hashes, the process tree and open connections. | IR analyst | Copies with hashes, command output |
| Scope | Run Q1 to Q9 across the fleet for 2026-10-17 onward. Check what the Plex service account could read: mounted shares, SSH keys, cloud sync tokens, stored credentials. | IR lead | Query results, access inventory |
| Eradicate | Rebuild from a clean install rather than deleting files one by one (vendor guidance, S01). If a rebuild is impossible, empty the Codecs directory, remove transcoder options you did not set, and remove the miner and proxy. Killing the miner alone is not enough because the planted file reruns on the next transcode. | Service owner | Rebuild record, new image digest |
| Recover | Update to 1.43.3.10861 or later before reconnecting. Sign out of all devices, change the Plex account password, rotate every credential the Plex account could read, and re-enable access only through a VPN. | Service owner + identity | Credential rotation record, version check |
| Re-hunt | Run section 7 checks and Q1 to Q9 again after 7 days and when indicators change. | Detection engineering | Query results, closure decision |
Escalate immediately: a shell child of Plex Transcoder, any published hash on a host, or contact with the payload host from a host that holds other credentials.
Detection Coverage Map
| Technique | Behavior | CQL | IOA | CrowdStrike coverage | SentinelOne | Defender XDR |
|---|---|---|---|---|---|---|
| T1190 | SSRF chain requests to port 32400 | none | none | GAP outside endpoint telemetry; server, proxy and network logs only (section 7) | GAP | GAP |
| T1528 | Token theft through loopback-prefixed wildcard DNS | Q7 | none | Partial depends on Linux DNS telemetry | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1129 | Shared object written into Codecs then loaded | Q8 | Promote Q8 | Partial file classification and baseline needed | Partial: Q8 approximate | Partial: Q8 approximate |
| T1059.004 | Shell spawned under Plex | Q1, Q2, Q3 | Promote Q1 | Good on sensored hosts only | Good, with assumptions | Good, with assumptions |
| T1105 | Payload download by curl or wget | Q1, Q6 | none | Partial network indicators are perishable | Partial: same limit as CrowdStrike | Partial: Q6 approximate |
| T1496 | XMRig-based miner | Q4, Q5 | Promote Q4 | Partial names and hashes are attacker-controlled | Partial: same limit as CrowdStrike | Partial: Q5 telemetry gap |
| T1090 | Residential proxy tool | Q4, Q5, Q6, Q9 | none | Partial Q9 is a noisy hunt | Partial: Q9 approximate | Partial: Q5 telemetry gap, Q6 approximate, Q9 approximate |
| Host asset | Plex on a NAS, router or home host with no sensor | none | none | GAP outside endpoint telemetry; network controls and asset inventory carry this part | GAP | GAP |
| Resource use | Sustained CPU with no playback | none | none | GAP no endpoint event carries CPU; native check only | GAP | GAP |
| Persistence by position | Planted file reruns on later transcodes | Q1, Q8 | Promote Q1 | Partial file removal needs a rebuild, not a query | Partial: Q8 approximate | Partial: Q8 approximate |
Validation gates are in section 11. Plex servers on NAS appliances and unmanaged hosts that carry no sensor are outside endpoint visibility. Network egress and ingress controls and asset inventory are the controls for that part; this pack does not claim coverage there.
Hunt Summary Ticket
TITLE: Plex2Shell: Plex Media Server unauthenticated RCE chain, miner and proxy payloads SEVERITY: HIGH (exploited in the wild, CISA KEV 2026-10-20). Endpoint coverage limited to sensored hosts. SCOPE: Plex Media Server before 1.43.3.10861 on Linux, Docker, Windows, macOS. Unsensored NAS and home hosts outside endpoint telemetry. HYPOTHESIS: Exploited Plex servers show a shell under a Plex process and an executable written into Codecs by a non-server writer, then miner or proxy execution. QUERIES: Q1 to Q9 (Q1, Q8 first; Q9 is a noisy hunt) DO FIRST: Inventory Plex installs and versions. Run Q1 and Q8 over 14 days. Patch and remove port 32400 exposure. FINDINGS: (fill in) GAPS: Request-stage exploitation (server, proxy and network logs, outside endpoint telemetry); unsensored NAS and home hosts; CPU use; miner wallet and pool port not published. ACTIONS: Patch to 1.43.3.10861 or later, rebuild any exposed old build, rotate credentials the Plex account could read, block proxystream.st at the network layer. OWNER: SOC / Detection Engineering / Infrastructure VERSION: v0.1 Draft · 2026-10-22
Changelog
References
| ID | Tier | Source | Used for | Accessed |
|---|---|---|---|---|
| S01 | 1 | Plex forum: Important Security Update for Plex Media Server v1.43.2 and earlier (updated 2026-10-19) | Vendor advisory, indicators, remediation | 2026-10-22 |
| S02 | 1 | zmain: Plex2Shell write-up (2026-10-16) | Chain description for defensive hunt logic | 2026-10-22 |
| S03 | 1 | BoredHackerBlog: Plex vulnerabilities being exploited in the wild (2026-10-17) | Honeypot observations and hashes | 2026-10-22 |
| S04 | 1 | CISA: Adds Six Known Exploited Vulnerabilities to Catalog (2026-10-20) | KEV confirmation | 2026-10-22 |
| S05 | 1/2 | code-zm/plex2shell detection: hunt script and YARA rules | Defender triage logic (data only, not executed) | 2026-10-22 |
| S06 | 2 | SentinelOne Vulnerability Database: CVE-2026-96652 | SSRF detail and workarounds | 2026-10-22 |
| S07 | 2 | The Hacker News: Hackers Exploit Plex2Shell Flaws (2026-10-19) | CVE roles, payload names, pool host | 2026-10-22 |
| S08 | 2 | SecurityOnline: Plex2Shell Plex Media Server exploited | Chain summary, IP list | 2026-10-22 |
| S09 | 2 | Safeguard: Plex2Shell explained (2026-10-22) | Timeline, mitigations, egress guidance | 2026-10-22 |
| S10 | 2 | Gizmodo (2026-10-19) | Shadowserver exposure, auto-update defaults | 2026-10-22 |
| S11 | 2 | Security Affairs (2026-10-19) | Loader behavior, investigation guidance | 2026-10-22 |
| S12 | 2 | Cyber Security News | Honeypot details, request shape | 2026-10-22 |
| S13 | 2 | Daily Security Review (2026-10-19) | Corroboration, single-honeypot caveat | 2026-10-22 |
| S14 | 2 | Cord Cutters News | Indicator list restatement | 2026-10-22 |
Source snapshots are saved in Plex2Shell-CVE-2026-96652-Hunt-sources/. No fetched page contained instruction-like text aimed at the analyst.