This detection identifies adversaries who establish a persistent foothold by uploading and writing malicious PHP files to disk without triggering immediate execution alerts, often indicating the initial phase of a web shell deployment. Proactively hunting for this behavior in Azure Sentinel is critical because it uncovers stealthy reconnaissance activities that traditional execution-based rules might miss, allowing analysts to isolate compromised endpoints before attackers escalate their access or deploy additional payloads.
rule WEBSHELL_PHP_Writer
{
meta:
description = "PHP webshell which only writes an uploaded file to disk"
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"
date = "2021/04/17"
modified = "2023-07-05"
score = 50
hash = "ec83d69512aa0cc85584973f5f0850932fb1949fb5fb2b7e6e5bbfb121193637"
hash = "407c15f94a33232c64ddf45f194917fabcd2e83cf93f38ee82f9720e2635fa64"
hash = "988b125b6727b94ce9a27ea42edc0ce282c5dfeb"
hash = "0ce760131787803bbef216d0ee9b5eb062633537"
hash = "20281d16838f707c86b1ff1428a293ed6aec0e97"
id = "05bb3e0c-69b2-5176-a3eb-e6ba2d72a205"
strings:
$sus3 = "'upload'" wide ascii
$sus4 = "\"upload\"" wide ascii
$sus5 = "\"Upload\"" wide ascii
$sus6 = "gif89" wide ascii
//$sus13= "<textarea " wide ascii
$sus16= "Army" fullword wide ascii
$sus17= "error_reporting( 0 )" wide ascii
$sus18= "' . '" wide ascii
//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
// 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
//strings from private rule capa_php_write_file
$php_multi_write1 = "fopen(" wide ascii
$php_multi_write2 = "fwrite(" wide ascii
$php_write1 = "move_uploaded_file" fullword wide ascii
$php_write2 = "copy" fullword wide ascii
condition:
//any of them or
(
(
(
$php_short in (0..100) or
$php_short in (filesize-1000..filesize)
)
and not any of ( $no_* )
)
or any of ( $php_new* )
)
and (
any of ( $inp* )
)
and (
any of ( $php_write* ) or
all of ( $php_multi_write* )
)
and
(
filesize < 400 or
(
filesize < 4000 and 1 of ( $sus* )
)
)
}
This YARA rule can be deployed in the following contexts:
This rule contains 33 string patterns in its detection logic.
Here are 5 specific false positive scenarios for the “PHP webshell which only writes an uploaded file to disk” detection rule, including suggested filters and exclusions:
Automated Backup Script Execution
backup_handler.php) running via cron on the application server. The script connects to an internal database, extracts logs or configuration snapshots, and writes them as .zip or .tar.gz files into a dedicated /var/backups/ directory without executing any code within those files.^/var/backups/.+ and restrict the rule to only trigger on directories containing executable permissions (e.g., exclude directories where files have mode 0644 or less).CMS Media Library Uploads
upload.php) receives the request and saves the file directly to the /wp-content/uploads/ directory. Since these are static assets (images/PDFs) intended for viewing rather than execution, they do not constitute an active webshell threat.jpg, jpeg, png, gif, pdf, docx) and the destination directory matches known media paths (e.g., /wp-content/uploads/ or /sites/default/files/).CI/CD Pipeline Artifact Deployment