{
  "id": "php2shell",
  "hunts": [
    {
      "id": "shell",
      "title": "A Web Server or PHP Worker Starting a Shell or Download Tool",
      "tools": [
        {
          "tool": "defender",
          "name": "Defender XDR",
          "reads": "DeviceProcessEvents",
          "note": "This table fills only on Linux servers onboarded to Defender for Endpoint. It names the parent and the grandparent, so a download started through a shell still shows.",
          "query": [
            "// Shells and download tools that a web server or PHP worker started, directly or through a shell",
            "DeviceProcessEvents",
            "| where Timestamp > ago(30d)",
            "| where FileName in~ (\"sh\", \"bash\", \"dash\", \"zsh\", \"curl\", \"wget\")",
            "| extend Parent = tolower(InitiatingProcessFileName), GrandParent = tolower(InitiatingProcessParentFileName)",
            "| where Parent startswith \"php\" or Parent in (\"apache2\", \"httpd\", \"nginx\")",
            "    or GrandParent startswith \"php\" or GrandParent in (\"apache2\", \"httpd\", \"nginx\")",
            "| project Timestamp, DeviceName, AccountName, GrandParent, Parent, InitiatingProcessCommandLine, FileName, ProcessCommandLine",
            "| order by Timestamp desc"
          ]
        },
        {
          "tool": "sentinel",
          "name": "Sentinel",
          "reads": "ASIM _Im_ProcessCreate (Sysmon for Linux event 1 and Defender XDR)",
          "note": "It needs Sysmon for Linux process creation events (event 1) or the Microsoft Defender XDR connector in the workspace. A source that leaves the grandparent empty shows only the programs the worker started itself.",
          "query": [
            "// Shells and download tools that a web server or PHP worker started, from every Linux source ASIM reads",
            "_Im_ProcessCreate(starttime = ago(30d), targetprocess_has_any = dynamic([\"sh\", \"bash\", \"dash\", \"zsh\", \"curl\", \"wget\"]))",
            "| extend Child = tolower(extract(@\"[^/]+$\", 0, TargetProcessName))",
            "| extend Parent = tolower(extract(@\"[^/]+$\", 0, ActingProcessName)), GrandParent = tolower(extract(@\"[^/]+$\", 0, ParentProcessName))",
            "| where Child in (\"sh\", \"bash\", \"dash\", \"zsh\", \"curl\", \"wget\")",
            "| where Parent startswith \"php\" or Parent in (\"apache2\", \"httpd\", \"nginx\")",
            "    or GrandParent startswith \"php\" or GrandParent in (\"apache2\", \"httpd\", \"nginx\")",
            "| project TimeGenerated, DvcHostname, TargetUsername, GrandParent, Parent, ActingProcessCommandLine, Child, TargetProcessCommandLine",
            "| order by TimeGenerated desc"
          ]
        },
        {
          "tool": "velociraptor",
          "name": "Velociraptor",
          "reads": "Generic.System.Pstree",
          "note": "Velociraptor reads the processes on each server you hunt, so nothing has to be forwarded first. It is a live view, so a shell that has already exited does not show.",
          "query": [
            "// Shells and download tools running now whose chain of parents runs through a web server or PHP",
            "SELECT Pid, Name, Exe, CommandLine, Username, StartTime, CallChain",
            "FROM Artifact.Generic.System.Pstree(CallChainFilter='(?i)(php|apache2|httpd|nginx)')",
            "WHERE Name =~ '(?i)^(sh|bash|dash|zsh|curl|wget)$'"
          ]
        },
        {
          "tool": "splunk",
          "name": "Splunk",
          "reads": "CIM Endpoint.Processes (Sysmon for Linux event 1)",
          "note": "It needs Linux process starts in the Endpoint data model, such as Sysmon for Linux event 1 through the Splunk Add-on for Sysmon for Linux. The model holds the parent only, so check what started each shell it lists.",
          "query": [
            "```Shells and download tools that a web server or PHP worker started, from the CIM Endpoint data model```",
            "| tstats summariesonly=false count min(_time) AS first_seen max(_time) AS last_seen",
            "    from datamodel=Endpoint.Processes",
            "    where earliest=-30d Processes.process_name IN (\"sh\", \"bash\", \"dash\", \"zsh\", \"curl\", \"wget\")",
            "      (Processes.parent_process_name=\"php*\" OR Processes.parent_process_name IN (\"apache2\", \"httpd\", \"nginx\"))",
            "    by Processes.dest Processes.parent_process_name Processes.parent_process Processes.process_name Processes.process",
            "| sort 0 - last_seen",
            "| convert ctime(first_seen) ctime(last_seen)"
          ]
        }
      ]
    },
    {
      "id": "web-root",
      "title": "New or Changed Scripts in the Web Root",
      "tools": [
        {
          "tool": "defender",
          "name": "Defender XDR",
          "reads": "DeviceFileEvents",
          "note": "This table fills only on Linux servers onboarded to Defender for Endpoint. Change the two folders to your own web roots.",
          "query": [
            "// Script files created, changed or renamed under the web roots, with the program that wrote each one",
            "DeviceFileEvents",
            "| where Timestamp > ago(30d)",
            "| where ActionType in (\"FileCreated\", \"FileModified\", \"FileRenamed\")",
            "| where FolderPath startswith \"/var/www/\" or FolderPath startswith \"/srv/www/\"",
            "| where FileName endswith \".php\" or FileName endswith \".phtml\" or FileName endswith \".phar\"",
            "| extend Writer = tolower(InitiatingProcessFileName)",
            "| extend ByWebProcess = Writer startswith \"php\" or Writer in (\"apache2\", \"httpd\", \"nginx\")",
            "| project Timestamp, DeviceName, ActionType, FolderPath, FileName, Writer, ByWebProcess, InitiatingProcessCommandLine, InitiatingProcessAccountName, SHA1",
            "| order by ByWebProcess desc, Timestamp desc"
          ]
        },
        {
          "tool": "sentinel",
          "name": "Sentinel",
          "reads": "ASIM _Im_FileEvent (Sysmon for Linux event 11 and Defender XDR)",
          "note": "It needs Sysmon for Linux file events or the Microsoft Defender XDR connector in the workspace. The Sysmon for Linux parser reads new files (event 11), so a change to an existing file shows only from Defender data.",
          "query": [
            "// Script files created, changed or renamed under the web roots, from every Linux source ASIM reads",
            "_Im_FileEvent(starttime = ago(30d), eventtype_in = dynamic([\"FileCreated\", \"FileModified\", \"FileRenamed\"]), targetfilepath_has_any = dynamic([\"www\"]))",
            "| where TargetFilePath startswith \"/var/www/\" or TargetFilePath startswith \"/srv/www/\"",
            "| where TargetFilePath endswith \".php\" or TargetFilePath endswith \".phtml\" or TargetFilePath endswith \".phar\"",
            "| extend Writer = tolower(extract(@\"[^/]+$\", 0, ActingProcessName))",
            "| extend ByWebProcess = Writer startswith \"php\" or Writer in (\"apache2\", \"httpd\", \"nginx\")",
            "| project TimeGenerated, DvcHostname, EventType, TargetFilePath, Writer, ByWebProcess, ActingProcessCommandLine, ActorUsername",
            "| order by ByWebProcess desc, TimeGenerated desc"
          ]
        },
        {
          "tool": "velociraptor",
          "name": "Velociraptor",
          "reads": "Linux.Search.FileFinder",
          "note": "Velociraptor reads the files on each server you hunt, so it also finds scripts that no log recorded. Change the folder to your own web root, and remember that a file time can be set back.",
          "query": [
            "// Script files under the web root changed in the last 30 days, newest first",
            "SELECT OSPath, Size, Mode, MTime, CTime",
            "FROM Artifact.Linux.Search.FileFinder(",
            "    SearchFilesGlob='/var/www/**10/*.{php,phtml,phar}',",
            "    MoreRecentThan=timestamp(epoch=now() - 30 * 86400))",
            "ORDER BY MTime DESC"
          ]
        },
        {
          "tool": "splunk",
          "name": "Splunk",
          "reads": "CIM Endpoint.Filesystem (Sysmon for Linux event 11)",
          "note": "It needs Linux file events in the Endpoint data model, such as Sysmon for Linux event 11 through the Splunk Add-on for Sysmon for Linux. The dataset does not name the program that wrote the file, so read the raw event for it.",
          "query": [
            "```Script files created or changed under the web roots, from the CIM Endpoint data model```",
            "| tstats summariesonly=false count min(_time) AS first_seen max(_time) AS last_seen",
            "    from datamodel=Endpoint.Filesystem",
            "    where earliest=-30d Filesystem.action IN (\"created\", \"modified\")",
            "      (Filesystem.file_path=\"/var/www/*\" OR Filesystem.file_path=\"/srv/www/*\")",
            "      (Filesystem.file_name=\"*.php\" OR Filesystem.file_name=\"*.phtml\" OR Filesystem.file_name=\"*.phar\")",
            "    by Filesystem.dest Filesystem.file_path Filesystem.action",
            "| sort 0 - last_seen",
            "| convert ctime(first_seen) ctime(last_seen)"
          ]
        }
      ]
    }
  ]
}
