This hunt detects adversaries establishing PHP webshells that process dynamic inputs and payloads within both small and large file sizes containing suspicious strings, indicating potential command execution or data exfiltration activities. The SOC team should proactively hunt for this behavior in Azure Sentinel to identify early-stage post-exploitation persistence mechanisms that often evade standard signature-based detection due to their variable file characteristics.
rule EXT_WEBSHELL_PHP_Generic {
meta:
description = "php webshell having some kind of input and some kind of payload. restricted to small files or big ones including suspicious strings"
license = "Detection Rule License 1.1 https://github.com/Neo23x0/signature-base/blob/master/LICENSE"
author = "Arnim Rupp (https://github.com/ruppde)"
reference = "Internal Research"
score = 70
date = "2021-01-14"
modified = "2026-03-09"
hash = "bee1b76b1455105d4bfe2f45191071cf05e83a309ae9defcf759248ca9bceddd"
hash = "6bf351900a408120bee3fc6ea39905c6a35fe6efcf35d0a783ee92062e63a854"
hash = "e3b4e5ec29628791f836e15500f6fdea19beaf3e8d9981c50714656c50d3b365"
hash = "00813155bf7f5eb441e1619616a5f6b21ae31afc99caa000c4aafd54b46c3597"
hash = "e31788042d9cdeffcb279533b5a7359b3beb1144f39bacdd3acdef6e9b4aff25"
hash = "36b91575a08cf40d4782e5aebcec2894144f1e236a102edda2416bc75cbac8dd"
hash = "a34154af7c0d7157285cfa498734cfb77662edadb1a10892eb7f7e2fb5e2486c"
hash = "791a882af2cea0aa8b8379791b401bebc235296858266ddb7f881c8923b7ea61"
hash = "9a8ab3c225076a26309230d7eac7681f85b271d2db22bf5a190adbf66faca2e6"
hash = "0d3ee83adc9ebf8fb1a8c449eed5547ee5e67e9a416cce25592e80963198ae23"
hash = "3d8708609562a27634df5094713154d8ca784dbe89738e63951e12184ff07ad6"
hash = "70d64d987f0d9ab46514abcc868505d95dbf458387f858b0d7580e4ee8573786"
hash = "259b3828694b4d256764d7d01b0f0f36ca0526d5ee75e134c6a754d2ab0d1caa"
hash = "04d139b48d59fa2ef24fb9347b74fa317cb05bd8b7389aeb0a4d458c49ea7540"
hash = "58d0e2ff61301fe0c176b51430850239d3278c7caf56310d202e0cdbdde9ac3f"
hash = "731f36a08b0e63c63b3a2a457667dfc34aa7ff3a2aee24e60a8d16b83ad44ce2"
hash = "e4ffd4ec67762fe00bb8bd9fbff78cffefdb96c16fe7551b5505d319a90fa18f"
hash = "fa00ee25bfb3908808a7c6e8b2423c681d7c52de2deb30cbaea2ee09a635b7d4"
hash = "98c1937b9606b1e8e0eebcb116a784c9d2d3db0039b21c45cba399e86c92c2fa"
hash = "e9423ad8e51895db0e8422750c61ef4897b3be4292b36dba67d42de99e714bff"
hash = "7a16311a371f03b29d5220484e7ecbe841cfaead4e73c17aa6a9c23b5d94544d"
hash = "7ca5dec0515dd6f401cb5a52c313f41f5437fc43eb62ea4bcc415a14212d09e9"
hash = "3de8c04bfdb24185a07f198464fcdd56bb643e1d08199a26acee51435ff0a99f"
hash = "63297f8c1d4e88415bc094bc5546124c9ed8d57aca3a09e36ae18f5f054ad172"
hash = "a09dcf52da767815f29f66cb7b03f3d8c102da5cf7b69567928961c389eac11f"
hash = "d9ae762b011216e520ebe4b7abcac615c61318a8195601526cfa11bbc719a8f1"
hash = "dd5d8a9b4bb406e0b8f868165a1714fe54ffb18e621582210f96f6e5ae850b33"
id = "ce3c93a5-3088-5e7e-a0d4-8bea18cf9cc3"
strings:
$wfp_tiny1 = "escapeshellarg" fullword
$wfp_tiny2 = "addslashes" fullword
//strings from private rule php_false_positive_tiny
// try to use only strings which would be flagged by themselves as suspicious by other rules, e.g. eval
//$gfp_tiny1 = "addslashes" fullword
//$gfp_tiny2 = "escapeshellarg" fullword
$gfp_tiny3 = "include \"./common.php\";" // xcache
$gfp_tiny4 = "assert('FALSE');"
$gfp_tiny5 = "assert(false);"
$gfp_tiny6 = "assert(FALSE);"
$gfp_tiny7 = "assert('array_key_exists("
$gfp_tiny8 = "echo shell_exec($aspellcommand . ' 2>&1');"
$gfp_tiny9 = "throw new Exception('Could not find authentication source with id ' . $sourceId);"
$gfp_tiny10 = "return isset( $_POST[ $key ] ) ? $_POST[ $key ] : ( isset( $_REQUEST[ $key ] ) ? $_REQUEST[ $key ] : $default );"
$gfp_tiny11 = "; This is the recommended, PHP 4-style version of the php.ini-dist file"
//strings from private rule capa_php_old_safe
$php_short = "<?" wide ascii
// prevent xml and asp from hitting with the short tag
$no_xml1 = "<?xml version" nocase wide ascii
$no_xml2 = "<?xml-stylesheet" nocase wide ascii
$no_asp1 = "<%@LANGUAGE" nocase wide ascii
$no_asp2 = /<script language="(vb|jscript|c#)/ nocase wide ascii
$no_pdf = "<?xpacket"
// of course the new tags should also match
// already matched by "<?"
$php_new1 = /<\?=[^?]/ wide ascii
$php_new2 = "<?php" nocase wide ascii
$php_new3 = "<script language=\"php" nocase wide ascii
//strings from private rule capa_php_input
$inp1 = "php://input" wide ascii
$inp2 = /_GET\s?\[/ wide ascii
// for passing $_GET to a function
$inp3 = /\(\s?\$_GET\s?\)/ wide ascii
$inp4 = /_POST\s?\[/ wide ascii
$inp5 = /\(\s?\$_POST\s?\)/ wide ascii
$inp6 = /_REQUEST\s?\[/ wide ascii
$inp7 = /\(\s?\$_REQUEST\s?\)/ wide ascii
$inp8 = /\(\s?\$_HEADERS\s?[\)\[]/ wide ascii
// PHP automatically adds all the request headers into the $_SERVER global array, prefixing each header name by the "HTTP_" string, so e.g. @eval($_SERVER['HTTP_CMD']) will run any code in the HTTP header CMD
$inp15 = "_SERVER['HTTP_" wide ascii
$inp16 = "_SERVER[\"HTTP_" wide ascii
$inp17 = /getenv[\t ]{0,20}\([\t ]{0,20}['"]HTTP_/ wide ascii
$inp18 = "array_values($_SERVER)" wide ascii
$inp19 = /file_get_contents\("https?:\/\// wide ascii
$inp20 = "TSOP_" wide ascii
//strings from private rule capa_php_payload
// \([^)] to avoid matching on e.g. eval() in comments
$cpayload1 = /\beval[\n\t ]{0,500}(\([^)]|\/\*)/ nocase wide ascii
$cpayload2 = /\bexec[\n\t ]{0,500}(\([^)]|\/\*)/ nocase wide ascii
$cpayload3 = /\bshell_exec[\n\t ]{0,500}(\([^)]|\/\*)/ nocase wide ascii
$cpayload4 = /\bpassthru[\n\t ]{0,500}(\([^)]|\/\*)/ nocase wide ascii
$cpayload5 = /\bsystem[\n\t ]{0,500}(\([^)]|\/\*)/ nocase wide ascii
$cpayload6 = /\bpopen[\n\t ]{0,500}(\([^)]|\/\*)/ nocase wide ascii
$cpayload7 = /\bproc_open[\n\t ]{0,500}(\([^)]|\/\*)/ nocase wide ascii
$cpayload8 = /\bpcntl_exec[\n\t ]{0,500}(\([^)]|\/\*)/ nocase wide ascii
$cpayload9 = /\bassert[\n\t ]{0,500}\([^)0]/ nocase wide ascii
$cpayload10 = /\bpreg_replace[\n\t ]{0,500}\([^\)]{1,100}\/[ismxADSUXju]{0,11}(e|\\x65)/ nocase wide ascii
$cpayload12 = /\bmb_ereg_replace[\t ]{0,500}\([^\)]{1,100}'e'/ nocase wide ascii
$cpayload13 = /\bmb_eregi_replace[\t ]{0,500}\([^\)]{1,100}'e'/ nocase wide ascii
$cpayload20 = /\bcreate_function[\n\t ]{0,500}(\([^)]|\/\*)/ nocase wide ascii
$cpayload21 = /\bReflectionFunction[\n\t ]{0,500}(\([^)]|\/\*)/ nocase wide ascii
$cpayload22 = /fetchall\(PDO::FETCH_FUNC[\n\t ]{0,500}[,}\)]/ nocase wide ascii
$m_cpayload_preg_filter1 = /\bpreg_filter[\n\t ]{0,500}(\([^\)]|\/\*)/ nocase wide ascii
$m_cpayload_preg_filter2 = "'|.{0,500}|e'" nocase wide ascii
// TODO backticks
//strings from private rule capa_gen_sus
// these strings are just a bit suspicious, so several of them are needed, depending on filesize
$gen_bit_sus1 = /:\s{0,20}eval}/ nocase wide ascii
$gen_bit_sus2 = /\.replace\(\/\w\/g/ nocase wide ascii
$gen_bit_sus6 = "self.delete"
$gen_bit_sus9 = "\"cmd /c" nocase
$gen_bit_sus10 = "\"cmd\"" nocase
$gen_bit_sus11 = "\"cmd.exe" nocase
$gen_bit_sus12 = "%comspec%" wide ascii
$gen_bit_sus13 = "%COMSPEC%" wide ascii
//TODO:$gen_bit_sus12 = ".UserName" nocase
$gen_bit_sus18 = "Hklm.GetValueNames();" nocase
// bonus string for proxylogon exploiting webshells
$gen_bit_sus19 = "http://schemas.microsoft.com/exchange/" wide ascii
$gen_bit_sus21 = "\"upload\"" wide ascii
$gen_bit_sus22 = "\"Upload\"" wide ascii
$gen_bit_sus23 = "UPLOAD" fullword wide ascii
$gen_bit_sus24 = "fileupload" wide ascii
$gen_bit_sus25 = "file_upload" wide ascii
// own base64 or base32 func
$gen_bit_sus29 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" fullword wide ascii
$gen_bit_sus29b = "abcdefghijklmnopqrstuvwxyz234567" fullword wide ascii
$gen_bit_sus30 = "serv-u" wide ascii
$gen_bit_sus31 = "Serv-u" wide ascii
$gen_bit_sus32 = "Army" fullword wide ascii
// single letter paramweter
$gen_bit_sus33 = /\$_(GET|POST|REQUEST)\["\w"\]/ fullword wide ascii
$gen_bit_sus34 = "Content-Transfer-Encoding: Binary" wide ascii
$gen_bit_sus35 = "crack" fullword wide ascii
$gen_bit_sus44 = "<pre>" wide ascii
$gen_bit_sus45 = "<PRE>" wide ascii
$gen_bit_sus46 = "shell_" wide ascii
//fp: $gen_bit_sus47 = "Shell" fullword wide ascii
$gen_bit_sus50 = "bypass" wide ascii
$gen_bit_sus52 = " ^ $" wide ascii
$gen_bit_sus53 = ".ssh/authorized_keys" wide ascii
$gen_bit_sus55 = /\w'\.'\w/ wide ascii
$gen_bit_sus56 = /\w\"\.\"\w/ wide ascii
$gen_bit_sus57 = "dumper" wide ascii
$gen_bit_sus59 = "'cmd'" wide ascii
$gen_bit_sus60 = "\"execute\"" wide ascii
$gen_bit_sus61 = "/bin/sh" wide ascii
$gen_bit_sus62 = "Cyber" wide ascii
$gen_bit_sus63 = "portscan" fullword wide ascii
//$gen_bit_sus64 = "\"command\"" fullword wide ascii
//$gen_bit_sus65 = "'command'" fullword wide ascii
$gen_bit_sus66 = "whoami" fullword wide ascii
$gen_bit_sus67 = "$password='" fullword wide ascii
$gen_bit_sus68 = "$password=\"" fullword wide ascii
$gen_bit_sus69 = "$cmd" fullword wide ascii
$gen_bit_sus70 = "\"?>\"." fullword wide ascii
$gen_bit_sus71 = "Hacking" fullword wide ascii
$gen_bit_sus72 = "hacking" fullword wide ascii
$gen_bit_sus73 = ".htpasswd" wide ascii
$gen_bit_sus74 = /\btouch\(\$[^,]{1,30},/ wide ascii
$gen_bit_sus75 = "uploaded" fullword wide ascii
// very suspicious strings, one is enough
$gen_much_sus7 = "Web Shell" nocase
$gen_much_sus8 = "WebShell" nocase
$gen_much_sus3 = "hidded shell"
$gen_much_sus4 = "WScript.Shell.1" nocase
$gen_much_sus5 = "AspExec"
$gen_much_sus14 = "\\pcAnywhere\\" nocase
$gen_much_sus15 = "antivirus" nocase
$gen_much_sus16 = "McAfee" nocase
$gen_much_sus17 = "nishang"
$gen_much_sus18 = "\"unsafe" fullword wide ascii
$gen_much_sus19 = "'unsafe" fullword wide ascii
$gen_much_sus24 = "exploit" fullword wide ascii
$gen_much_sus25 = "Exploit" fullword wide ascii
$gen_much_sus26 = "TVqQAAMAAA" wide ascii
$gen_much_sus30 = "Hacker" wide ascii
$gen_much_sus31 = "HACKED" fullword wide ascii
$gen_much_sus32 = "hacked" fullword wide ascii
$gen_much_sus33 = "hacker" wide ascii
$gen_much_sus34 = "grayhat" nocase wide ascii
$gen_much_sus35 = "Microsoft FrontPage" wide ascii
$gen_much_sus36 = "Rootkit" wide ascii
$gen_much_sus37 = "rootkit" wide ascii
$gen_much_sus38 = "/*-/*-*/" wide ascii
$gen_much_sus39 = "u\"+\"n\"+\"s" wide ascii
$gen_much_sus40 = "\"e\"+\"v" wide ascii
$gen_much_sus41 = "a\"+\"l\"" wide ascii
$gen_much_sus42 = "\"+\"(\"+\"" wide ascii
$gen_much_sus43 = "q\"+\"u\"" wide ascii
$gen_much_sus44 = "\"u\"+\"e" wide ascii
$gen_much_sus45 = "/*//*/" wide ascii
$gen_much_sus46 = "(\"/*/\"" wide ascii
$gen_much_sus47 = "eval(eval(" wide ascii
// self remove
$gen_much_sus48 = "unlink(__FILE__)" wide ascii
$gen_much_sus49 = "Shell.Users" wide ascii
$gen_much_sus50 = "PasswordType=Regular" wide ascii
$gen_much_sus51 = "-Expire=0" wide ascii
$gen_much_sus60 = "_=$$_" wide ascii
$gen_much_sus62 = "++;$" wide ascii
$gen_much_sus63 = "++; $" wide ascii
$gen_much_sus64 = "_.=$_" wide ascii
$gen_much_sus70 = "-perm -04000" wide ascii
$gen_much_sus71 = "-perm -02000" wide ascii
$gen_much_sus72 = "grep -li password" wide ascii
$gen_much_sus73 = "-name config.inc.php" wide ascii
// touch without parameters sets the time to now, not malicious and gives fp
$gen_much_sus75 = "password crack" wide ascii
$gen_much_sus76 = "mysqlDll.dll" wide ascii
$gen_much_sus77 = "net user" wide ascii
$gen_much_sus80 = "fopen(\".htaccess\",\"w" wide ascii
$gen_much_sus81 = /strrev\(['"]/ wide ascii
$gen_much_sus82 = "PHPShell" fullword wide ascii
$gen_much_sus821 = "PHP Shell" fullword wide ascii
$gen_much_sus83 = "phpshell" fullword wide ascii
$gen_much_sus84 = "PHPshell" fullword wide ascii
$gen_much_sus87 = "deface" wide ascii
$gen_much_sus88 = "Deface" wide ascii
$gen_much_sus89 = "backdoor" wide ascii
$gen_much_sus90 = "r00t" fullword wide ascii
$gen_much_sus91 = "xp_cmdshell" fullword wide ascii
$gen_much_sus92 = "str_rot13" fullword wide ascii
//strings from private rule capa_php_payload_multiple
// \([^)] to avoid matching on e.g. eval() in comments
$cmpayload1 = /\beval[\t ]{0,500}\([^)]/ nocase wide ascii
$cmpayload2 = /\bexec[\t ]{0,500}\([^)]/ nocase wide ascii
$cmpayload3 = /\bshell_exec[\t ]{0,500}\([^)]/ nocase wide ascii
$cmpayload4 = /\bpassthru[\t ]{0,500}\([^)]/ nocase wide ascii
$cmpayload5 = /\bsystem[\t ]{0,500}\([^)]/ nocase wide ascii
$cmpayload6 = /\bpopen[\t ]{0,500}\([^)]/ nocase wide ascii
$cmpayload7 = /\bproc_open[\t ]{0,500}\([^)]/ nocase wide ascii
$cmpayload8 = /\bpcntl_exec[\t ]{0,500}\([^)]/ nocase wide ascii
$cmpayload9 = /\bassert[\t ]{0,500}\([^)0]/ nocase wide ascii
$cmpayload10 = /\bpreg_replace[\t ]{0,500}\([^\)]{1,100}\/e/ nocase wide ascii
$cmpayload11 = /\bpreg_filter[\t ]{0,500}\([^\)]{1,100}\/e/ nocase wide ascii
$cmpayload12 = /\bmb_ereg_replace[\t ]{0,500}\([^\)]{1,100}'e'/ nocase wide ascii
$cmpayload20 = /\bcreate_function[\t ]{0,500}\([^)]/ nocase wide ascii
$cmpayload21 = /\bReflectionFunction[\t ]{0,500}\([^)]/ nocase wide ascii
$fp1 = "# Some examples from obfuscated malware:" ascii
$fp2 = "{@see TFileUpload} for further details." ascii
condition:
filesize < 500KB
and (
any of ($inp*)
)
and (
any of ($cpayload*) or
all of ($m_cpayload_preg_filter*)
)
and (
(
(
$php_short in (0..100) or
$php_short in (filesize - 1000..filesize)
)
and not any of ($no_*)
)
or any of ($php_new*)
)
and
((filesize < 1000 and not any of ($wfp_tiny*)) or
((
uint32be(0) == 0x47494638 // GIF8
or (
filesize < 4KB and
(
1 of ($gen_much_sus*) or
2 of ($gen_bit_sus*)
)
) or (
filesize < 20KB and
(
2 of ($gen_much_sus*) or
3 of ($gen_bit_sus*)
)
) or (
filesize < 50KB and
(
2 of ($gen_much_sus*) or
4 of ($gen_bit_sus*)
)
) or (
filesize < 100KB and
(
2 of ($gen_much_sus*) or
6 of ($gen_bit_sus*)
)
) or (
filesize < 150KB and
(
3 of ($gen_much_sus*) or
7 of ($gen_bit_sus*)
)
) or (
filesize < 500KB and
(
4 of ($gen_much_sus*) or
8 of ($gen_bit_sus*)
)
)
)
and
(filesize > 5KB or not any of ($wfp_tiny*))) or
(filesize < 500KB and (
4 of ($cmpayload*)
)
)
)
and not (
any of ($gfp_tiny*)
or 1 of ($fp*)
)
}
This YARA rule can be deployed in the following contexts:
This rule contains 181 string patterns in its detection logic.
Here are 5 specific false positive scenarios for the PHP Webshell Detection Rule, tailored for an enterprise environment:
Scenario: Automated Backup and Archiving via Cron Jobs
cron jobs running rsync or custom PHP backup utilities) often generate temporary .php files containing compressed data payloads. These files may be unusually large due to embedded JSON logs or binary blobs, triggering the “big file” and “payload” logic while appearing as a webshell.backup-service or cron-runner) and restrict detection to files created within specific maintenance windows (e.g., 02:00–04:00 UTC). Alternatively, exclude file paths matching /var/backups/ or /tmp/maintenance_*.php.Scenario: CMS Plugin Updates via Admin Dashboard
*/wp-content/plugins/*, */components/com_*). Additionally, filter out events where the HTTP User-Agent header matches known admin clients or internal IP ranges associated with the Content Management System’s update server.Scenario: CI/CD Pipeline Deployment Artifacts