This detection identifies active ASP webshells that actively scan for writable directories to establish persistence and conceal additional malicious payloads within the application layer. Proactively hunting for this behavior in Azure Sentinel is critical because it reveals early-stage lateral movement attempts where adversaries exploit directory permissions to expand their foothold before executing secondary attacks.
rule WEBSHELL_ASP_Scan_Writable
{
meta:
description = "ASP webshell searching for writable directories (to hide more webshells ...)"
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 = 75
date = "2021/03/14"
modified = "2023-04-05"
hash = "2409eda9047085baf12e0f1b9d0b357672f7a152"
hash = "af1c00696243f8b062a53dad9fb8b773fa1f0395631ffe6c7decc42c47eedee7"
id = "1766e081-0591-59ab-b546-b13207764b4d"
strings:
$scan1 = "DirectoryInfo" nocase fullword wide ascii
$scan2 = "GetDirectories" nocase fullword wide ascii
$scan3 = "Create" nocase fullword wide ascii
$scan4 = "File" nocase fullword wide ascii
$scan5 = "System.IO" nocase fullword wide ascii
// two methods: check permissions or write and delete:
$scan6 = "CanWrite" nocase fullword wide ascii
$scan7 = "Delete" nocase fullword wide ascii
$sus1 = "upload" nocase fullword wide ascii
$sus2 = "shell" nocase wide ascii
$sus3 = "orking directory" nocase fullword wide ascii
$sus4 = "scan" nocase wide ascii
//strings from private rule capa_asp
$tagasp_short1 = /<%[^"]/ wide ascii
// also looking for %> to reduce fp (yeah, short atom but seldom since special chars)
$tagasp_short2 = "%>" wide ascii
// classids for scripting host etc
$tagasp_classid1 = "72C24DD5-D70A-438B-8A42-98424B88AFB8" nocase wide ascii
$tagasp_classid2 = "F935DC22-1CF0-11D0-ADB9-00C04FD58A0B" nocase wide ascii
$tagasp_classid3 = "093FF999-1EA0-4079-9525-9614C3504B74" nocase wide ascii
$tagasp_classid4 = "F935DC26-1CF0-11D0-ADB9-00C04FD58A0B" nocase wide ascii
$tagasp_classid5 = "0D43FE01-F093-11CF-8940-00A0C9054228" nocase wide ascii
$tagasp_long10 = "<%@ " wide ascii
// <% eval
$tagasp_long11 = /<% \w/ nocase wide ascii
$tagasp_long12 = "<%ex" nocase wide ascii
$tagasp_long13 = "<%ev" nocase wide ascii
// <%@ LANGUAGE = VBScript.encode%>
// <%@ Language = "JScript" %>
// <%@ WebHandler Language="C#" class="Handler" %>
// <%@ WebService Language="C#" Class="Service" %>
// <%@Page Language="Jscript"%>
// <%@ Page Language = Jscript %>
// <%@PAGE LANGUAGE=JSCRIPT%>
// <%@ Page Language="Jscript" validateRequest="false" %>
// <%@ Page Language = Jscript %>
// <%@ Page Language="C#" %>
// <%@ Page Language="VB" ContentType="text/html" validaterequest="false" AspCompat="true" Debug="true" %>
// <script runat="server" language="JScript">
// <SCRIPT RUNAT=SERVER LANGUAGE=JSCRIPT>
// <SCRIPT RUNAT=SERVER LANGUAGE=JSCRIPT>
// <msxsl:script language="JScript" ...
$tagasp_long20 = /<(%|script|msxsl:script).{0,60}language="?(vb|jscript|c#)/ nocase wide ascii
$tagasp_long32 = /<script\s{1,30}runat=/ wide ascii
$tagasp_long33 = /<SCRIPT\s{1,30}RUNAT=/ wide ascii
// avoid hitting php
$php1 = "<?php"
$php2 = "<?="
// avoid hitting jsp
$jsp1 = "=\"java." wide ascii
$jsp2 = "=\"javax." wide ascii
$jsp3 = "java.lang." wide ascii
$jsp4 = "public" fullword wide ascii
$jsp5 = "throws" fullword wide ascii
$jsp6 = "getValue" fullword wide ascii
$jsp7 = "getBytes" fullword wide ascii
$perl1 = "PerlScript" fullword
//strings from private rule capa_asp_input
// Request.BinaryRead
// Request.Form
$asp_input1 = "request" fullword nocase wide ascii
$asp_input2 = "Page_Load" fullword nocase wide ascii
// base64 of Request.Form(
$asp_input3 = "UmVxdWVzdC5Gb3JtK" fullword wide ascii
$asp_xml_http = "Microsoft.XMLHTTP" fullword nocase wide ascii
$asp_xml_method1 = "GET" fullword wide ascii
$asp_xml_method2 = "POST" fullword wide ascii
$asp_xml_method3 = "HEAD" fullword wide ascii
// dynamic form
$asp_form1 = "<form " wide ascii
$asp_form2 = "<Form " wide ascii
$asp_form3 = "<FORM " wide ascii
$asp_asp = "<asp:" wide ascii
$asp_text1 = ".text" wide ascii
$asp_text2 = ".Text" wide ascii
condition:
filesize < 10KB and (
(
any of ( $tagasp_long* ) or
// TODO : yara_push_private_rules.py doesn't do private rules in private rules yet
any of ( $tagasp_classid* ) or
(
$tagasp_short1 and
$tagasp_short2 in ( filesize-100..filesize )
) or (
$tagasp_short2 and (
$tagasp_short1 in ( 0..1000 ) or
$tagasp_short1 in ( filesize-1000..filesize )
)
)
) and not (
(
any of ( $perl* ) or
$php1 at 0 or
$php2 at 0
) or (
( #jsp1 + #jsp2 + #jsp3 ) > 0 and ( #jsp4 + #jsp5 + #jsp6 + #jsp7 ) > 0
)
)
)
and (
any of ( $asp_input* ) or
(
$asp_xml_http and
any of ( $asp_xml_method* )
) or
(
any of ( $asp_form* ) and
any of ( $asp_text* ) and
$asp_asp
)
)
and 6 of ( $scan* ) and any of ( $sus* )
}
This YARA rule can be deployed in the following contexts:
This rule contains 48 string patterns in its detection logic.
Here are 4 specific false positive scenarios for the ASP webshell searching for writable directories detection rule, including suggested filters and exclusions:
Scenario: Automated Backup or Deployment Scripts
App_Data or specific sub-directories of the IIS website during a nightly build window.NT SERVICE\Octopus.Deploy) and filter out file creation events occurring within standard deployment windows (e.g., 02:00–04:00 UTC) where the parent process is a recognized CI/CD agent.Scenario: Application Health Checks by Load Balancers
Temp folders) to ensure the web server is responsive and capable of writing session data before routing traffic.F5-BIG-IP, AWS-HealthCheck) accessing standard probe URLs (e.g., /healthcheck.aspx or /status.aspx).Scenario: Scheduled Maintenance and Log Rotation Jobs
.aspx page to archive old logs from the Logs directory or rotate database backups, requiring write permissions in those specific directories