Query Details

Reverse Shell Spawned From Vulnerable React Server Components

Query

let Lookback = 70m;
let CorrelationWindow = 5m;
let ServerDeviceIds =
    DeviceInfo
    | summarize arg_max(Timestamp, DeviceType) by DeviceId
    | where DeviceType =~ "Server"
    | project DeviceId;
let RscServerDeviceIds =
    DeviceTvmSoftwareInventory
    | where DeviceId in (ServerDeviceIds)
    | extend PackageName = replace_string(tolower(SoftwareName), "_", "-")
    | where PackageName in (
        "react-server-dom-webpack",
        "react-server-dom-parcel",
        "react-server-dom-turbopack"
    )
    | where SoftwareVersion matches regex @"^19\.[0-3]\.[0-9]+$"
    | extend Version = parse_version(SoftwareVersion)
    | where
        Version between (parse_version("19.0.0") .. parse_version("19.0.8"))
        or Version between (parse_version("19.1.0") .. parse_version("19.1.9"))
        or Version between (parse_version("19.2.0") .. parse_version("19.2.8"))
        or Version == parse_version("19.3.0")
    | distinct DeviceId;
let Shells =
    DeviceProcessEvents
    | where Timestamp >= ago(Lookback)
    | where DeviceId in (RscServerDeviceIds)
    | where ActionType == "ProcessCreated"
    | where InitiatingProcessFileName in~ ("node", "node.exe")
    | where FileName in~ (
        "sh", "bash", "dash", "ash", "zsh", "ksh",
        "cmd.exe", "powershell.exe", "pwsh.exe", "pwsh"
    )
    | extend Cmd = tolower(ProcessCommandLine)
    | extend
        DownloadPipeToShell =
            Cmd matches regex
            @"\b(curl|wget)\b[^|;\r\n]*\|\s*(/[^\s|;]+/)?(bash|sh|dash|ash|zsh|ksh)\b",
        BashReverseShell =
            Cmd contains "/dev/tcp/"
            and Cmd matches regex @"[012]?[<>]&[012]",
        PowerShellDownloadExecute =
            Cmd matches regex @"\b(iex|invoke-expression)\b"
            and (
                Cmd contains "downloadstring"
                or Cmd contains "downloadfile"
                or Cmd contains "invoke-webrequest"
                or Cmd contains "invoke-restmethod"
            ),
        PowerShellReverseShell =
            Cmd contains "net.sockets.tcpclient"
            and Cmd contains "getstream"
    | where DownloadPipeToShell
        or BashReverseShell
        or PowerShellDownloadExecute
        or PowerShellReverseShell
    | extend DetectionReason = case(
        BashReverseShell, "Node -> shell: /dev/tcp reverse-shell pattern",
        PowerShellReverseShell, "Node -> PowerShell: TCP reverse-shell pattern",
        DownloadPipeToShell, "Node -> shell: download piped into interpreter",
        "Node -> PowerShell: download and Invoke-Expression"
    )
    | project
        Timestamp,
        DeviceId,
        DeviceName,
        ReportId,
        AccountName,
        ShellPid = ProcessId,
        ShellStart = ProcessCreationTime,
        ShellCommandLine = ProcessCommandLine,
        NodeCommandLine = InitiatingProcessCommandLine,
        DetectionReason;
let Egress = materialize(
    DeviceNetworkEvents
    | where Timestamp >= ago(Lookback)
    | where DeviceId in (RscServerDeviceIds)
    | where ActionType == "ConnectionSuccess"
    | where RemoteIPType == "Public"
    | project
        DeviceId,
        NetworkTime = Timestamp,
        RemoteIP,
        RemotePort,
        RemoteUrl,
        InitiatingProcessId,
        InitiatingProcessCreationTime,
        InitiatingProcessParentId,
        InitiatingProcessParentCreationTime
);
let EgressByProcess =
    union
        (
            Egress
            | project
                DeviceId,
                NetworkTime,
                RemoteIP,
                RemotePort,
                RemoteUrl,
                MatchPid = InitiatingProcessId,
                MatchStart = InitiatingProcessCreationTime
        ),
        (
            Egress
            | project
                DeviceId,
                NetworkTime,
                RemoteIP,
                RemotePort,
                RemoteUrl,
                MatchPid = InitiatingProcessParentId,
                MatchStart = InitiatingProcessParentCreationTime
        );
Shells
| join kind=inner EgressByProcess on
    DeviceId,
    $left.ShellPid == $right.MatchPid,
    $left.ShellStart == $right.MatchStart
| where NetworkTime >= ShellStart
    and NetworkTime <= (ShellStart + CorrelationWindow)
| summarize
    FirstNetworkTime = min(NetworkTime),
    RemoteIPs = make_set(RemoteIP, 20),
    RemotePorts = make_set(RemotePort, 20),
    RemoteUrls = make_set(RemoteUrl, 20)
    by
        Timestamp,
        DeviceId,
        DeviceName,
        ReportId,
        AccountName,
        ShellPid,
        ShellStart,
        NodeCommandLine,
        ShellCommandLine,
        DetectionReason

About this query

Explanation

This query is designed to detect potential security threats on Node.js servers running vulnerable versions of React Server Components. Here's a simplified breakdown of what the query does:

  1. Identify Target Servers: It first identifies devices classified as servers that have specific vulnerable React Server Component packages installed. These packages are within certain version ranges known to be vulnerable.

  2. Monitor for Suspicious Activity: The query then looks for processes on these servers that might indicate a reverse shell attack. It checks for shell or interpreter processes (like bash, PowerShell, etc.) that were started by a Node.js process within the last 70 minutes.

  3. Pattern Matching: It examines the command lines of these processes for patterns typical of malicious activity, such as:

    • Using curl or wget to download a script and pipe it into a shell.
    • Bash-style reverse shells using TCP.
    • PowerShell commands that download and execute scripts.
    • PowerShell reverse shells using TCP connections.
  4. Correlate with Network Activity: For each suspicious shell process, the query checks for any outbound network connections to public IP addresses that occurred within five minutes of the shell process starting. This helps confirm whether the suspicious process is part of an actual attack.

  5. Output: If a match is found, the query outputs details about the suspicious command lines, along with the remote IPs, ports, and URLs involved. This information helps analysts confirm the exploitation and take action to contain it.

  6. Tuning and Adjustments: The query includes notes on how to adjust the time windows for different environments and suggests reviewing version ranges when new vulnerabilities are disclosed. It also recommends allowlisting known internal IPs to reduce false positives.

Overall, this query helps security teams detect and respond to potential reverse shell attacks on vulnerable Node.js servers by correlating process and network activity.