threat hunting

Hunting For Webshell Activity

Hunt for web shell deployments on internet-facing servers by analyzing file creation in web directories, suspicious process spawning from web servers, and anomalous HTTP patterns.

mitre-attackpersistenceproactive-detectiont1505threat-huntingweb-serverwebshell
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When proactively hunting for indicators of hunting for webshell activity in the environment
  • After threat intelligence indicates active campaigns using these techniques
  • During incident response to scope compromise related to these techniques
  • When EDR or SIEM alerts trigger on related indicators
  • During periodic security assessments and purple team exercises

Prerequisites

  • EDR platform with process and network telemetry (CrowdStrike, MDE, SentinelOne)
  • SIEM with relevant log data ingested (Splunk, Elastic, Sentinel)
  • Sysmon deployed with comprehensive configuration
  • Windows Security Event Log forwarding enabled
  • Threat intelligence feeds for IOC correlation

Workflow

  1. Formulate Hypothesis: Define a testable hypothesis based on threat intelligence or ATT&CK gap analysis.
  2. Identify Data Sources: Determine which logs and telemetry are needed to validate or refute the hypothesis.
  3. Execute Queries: Run detection queries against SIEM and EDR platforms to collect relevant events.
  4. Analyze Results: Examine query results for anomalies, correlating across multiple data sources.
  5. Validate Findings: Distinguish true positives from false positives through contextual analysis.
  6. Correlate Activity: Link findings to broader attack chains and threat actor TTPs.
  7. Document and Report: Record findings, update detection rules, and recommend response actions.

Key Concepts

Concept Description
T1505.003 Web Shell
T1190 Exploit Public-Facing Application
T1059.001 PowerShell

Tools & Systems

Tool Purpose
CrowdStrike Falcon EDR telemetry and threat detection
Microsoft Defender for Endpoint Advanced hunting with KQL
Splunk Enterprise SIEM log analysis with SPL queries
Elastic Security Detection rules and investigation timeline
Sysmon Detailed Windows event monitoring
Velociraptor Endpoint artifact collection and hunting
Sigma Rules Cross-platform detection rule format

Common Scenarios

  1. Scenario 1: China Chopper web shell via IIS vulnerability
  2. Scenario 2: ASPXSpy through vulnerable upload
  3. Scenario 3: PHP shell hidden in image file
  4. Scenario 4: JSP shell via Tomcat manager console

Output Format

Hunt ID: TH-HUNTIN-[DATE]-[SEQ]
Technique: T1505.003
Host: [Hostname]
User: [Account context]
Evidence: [Log entries, process trees, network data]
Risk Level: [Critical/High/Medium/Low]
Confidence: [High/Medium/Low]
Recommended Action: [Containment, investigation, monitoring]
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.7 KB

API Reference: Hunting for Webshell Activity

Process Tree Detection

Parent Process Child Process Severity
w3wp.exe cmd.exe, powershell.exe CRITICAL
httpd/apache2 bash, sh, python CRITICAL
tomcat/java cmd.exe, bash CRITICAL
nginx bash, sh CRITICAL

Splunk SPL - Web Server Shell Spawn

index=sysmon EventCode=1
| where match(ParentImage, "(?i)(w3wp|httpd|apache2|nginx|tomcat)")
| where match(Image, "(?i)(cmd\.exe|powershell|bash|whoami|net\.exe)")
| table _time Computer ParentImage Image CommandLine User

KQL - Web Shell Process Chain

DeviceProcessEvents
| where InitiatingProcessFileName in~ ("w3wp.exe", "httpd", "apache2", "nginx")
| where FileName in~ ("cmd.exe", "powershell.exe", "bash", "whoami.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine

Web Access Log Patterns

webshell_patterns = [
    r"POST\s+.*\.(asp|aspx|php|jsp)\s+",   # POST to script files
    r"cmd=|exec=|command=|shell=",           # Command parameters
    r"c99shell|r57shell|b374k|weevely",      # Known webshell names
]

Sigma Rule - Webshell Detection

title: Webshell Spawning Shell Process
logsource:
  category: process_creation
  product: windows
detection:
  parent:
    ParentImage|endswith: '\w3wp.exe'
  child:
    Image|endswith:
      - '\cmd.exe'
      - '\powershell.exe'
  condition: parent and child
level: critical
tags:
  - attack.persistence
  - attack.t1505.003

References

standards.md1.5 KB

Standards and References - Hunting For Webshell Activity

MITRE ATT&CK Mappings

Technique Name Description
T1505.003 Web Shell See attack.mitre.org/techniques/T1505/003
T1190 Exploit Public-Facing Application See attack.mitre.org/techniques/T1190
T1059.001 PowerShell See attack.mitre.org/techniques/T1059/001

Detection Data Sources

Source Event ID Purpose
Sysmon 1 Process creation with command line
Sysmon 3 Network connection initiated
Sysmon 7 Image loaded (DLL)
Sysmon 10 Process access (LSASS)
Sysmon 11 File creation
Sysmon 12/13 Registry create/set
Sysmon 22 DNS query
Sysmon 25 Process tampering
Windows Security 4624 Successful logon
Windows Security 4625 Failed logon
Windows Security 4648 Explicit credential logon
Windows Security 4672 Special privileges assigned
Windows Security 4688 Process creation
Windows Security 4697 Service installed
Windows Security 4698 Scheduled task created
Windows Security 4769 Kerberos TGS requested
Windows Security 5140 Network share accessed

References

workflows.md2.9 KB

Detailed Hunting Workflow - Hunting For Webshell Activity

Phase 1: Data Collection and Querying

Splunk SPL Query

index=sysmon EventCode=1
| where match(ParentImage, "(?i)(w3wp|httpd|nginx|tomcat|java)\.exe")
| where match(Image, "(?i)(cmd|powershell|bash|whoami|net|ipconfig)\.exe")
| table _time Computer User ParentImage Image CommandLine

KQL Query (Microsoft Defender for Endpoint)

DeviceProcessEvents
| where InitiatingProcessFileName in~ ("w3wp.exe","httpd.exe","nginx.exe","java.exe")
| where FileName in~ ("cmd.exe","powershell.exe","whoami.exe","net.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine

Phase 2: Baseline and Anomaly Detection

Step 2.1 - Establish Normal Behavior Baseline

  • Collect 30 days of historical data for the targeted technique
  • Document expected patterns, frequencies, and legitimate use cases
  • Identify known false positive sources and document exceptions
  • Build statistical baseline (mean, standard deviation) for key metrics

Step 2.2 - Identify Anomalies

  • Compare current activity against the 30-day baseline
  • Flag events exceeding 3 standard deviations from normal
  • Prioritize anomalies by risk score and potential business impact
  • Cross-reference with threat intelligence for known IOCs

Phase 3: Investigation and Correlation

Step 3.1 - Deep Dive Analysis

  • For each anomaly, collect full process tree context
  • Correlate with network activity, file operations, and authentication events
  • Check binary signatures, file hashes, and certificate validity
  • Review user account context and access patterns

Step 3.2 - Attack Chain Reconstruction

  • Map findings to MITRE ATT&CK kill chain stages
  • Identify initial access vector if applicable
  • Trace lateral movement and privilege escalation paths
  • Determine data access and potential exfiltration

Phase 4: Validation and Response

Step 4.1 - True/False Positive Determination

  • Verify findings with system owners and IT operations
  • Check change management records for authorized activities
  • Validate user context (authorized actions vs. compromised account)
  • Document determination rationale for each finding

Step 4.2 - Response Actions

  • For confirmed threats: initiate incident response procedures
  • For detection gaps: create or update detection rules
  • For false positives: tune existing rules and update exclusions
  • Update threat hunting playbook with lessons learned

Phase 5: Documentation and Reporting

Step 5.1 - Hunt Report

  • Summarize hypothesis, methodology, and findings
  • Include all queries executed and their results
  • Document IOCs discovered and detection rules created
  • Provide recommendations for security improvements

Step 5.2 - Knowledge Base Update

  • Add findings to threat intelligence platform
  • Update MITRE ATT&CK coverage heatmap
  • Share detection rules via Sigma format
  • Schedule follow-up hunts for related techniques

Scripts 2

agent.py6.6 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for hunting web shell activity on web servers via process tree and log analysis."""

import json
import argparse
import re
from datetime import datetime


WEB_SERVER_PROCESSES = [
    "w3wp.exe", "httpd", "apache2", "nginx", "tomcat", "java",
    "php-cgi", "php-fpm", "node", "iisexpress",
]

SHELL_SPAWNS = [
    "cmd.exe", "powershell.exe", "pwsh.exe", "bash", "sh",
    "wscript.exe", "cscript.exe", "certutil.exe", "whoami.exe",
    "net.exe", "net1.exe", "ipconfig.exe", "systeminfo.exe",
    "tasklist.exe", "nslookup.exe",
]

WEBSHELL_HTTP_PATTERNS = [
    r"POST\s+.*\.(asp|aspx|php|jsp|jspx)\s+",
    r"cmd=", r"exec=", r"command=", r"shell=",
    r"c99shell", r"r57shell", r"b374k", r"weevely",
    r"china\s*chopper", r"antsword",
]


def load_process_logs(log_path):
    """Load process creation logs (JSON lines)."""
    entries = []
    with open(log_path) as f:
        for line in f:
            try:
                entries.append(json.loads(line))
            except json.JSONDecodeError:
                continue
    return entries


def detect_webserver_child_shells(process_logs):
    """Detect shell processes spawned by web server processes."""
    findings = []
    for entry in process_logs:
        parent = entry.get("ParentImage", entry.get("parent_process", "")).lower()
        child = entry.get("Image", entry.get("process_name", "")).lower()
        is_web_parent = any(ws in parent for ws in WEB_SERVER_PROCESSES)
        is_shell_child = any(sh in child for sh in SHELL_SPAWNS)
        if is_web_parent and is_shell_child:
            cmd = entry.get("CommandLine", entry.get("command_line", ""))
            findings.append({
                "timestamp": entry.get("UtcTime", entry.get("timestamp", "")),
                "hostname": entry.get("Computer", entry.get("hostname", "")),
                "parent_process": parent,
                "child_process": child,
                "command_line": cmd[:500],
                "user": entry.get("User", ""),
                "severity": "CRITICAL",
                "technique": "T1505.003",
            })
    return findings


def analyze_web_access_logs(access_log_path):
    """Analyze web access logs for webshell indicators."""
    findings = []
    with open(access_log_path) as f:
        for i, line in enumerate(f, 1):
            for pattern in WEBSHELL_HTTP_PATTERNS:
                if re.search(pattern, line, re.IGNORECASE):
                    ip_match = re.match(r"^(\S+)", line)
                    findings.append({
                        "line_number": i,
                        "source_ip": ip_match.group(1) if ip_match else "",
                        "log_entry": line.strip()[:500],
                        "pattern_matched": pattern,
                        "severity": "HIGH",
                    })
                    break
    return findings


def detect_file_creation_in_webroot(file_events, webroot_paths=None):
    """Detect new script files created in web server directories."""
    if webroot_paths is None:
        webroot_paths = [
            "/var/www", "/opt/lampp/htdocs", "inetpub/wwwroot",
            "/usr/share/nginx/html", "/srv/www",
        ]
    script_extensions = [".php", ".asp", ".aspx", ".jsp", ".jspx", ".cgi", ".cfm"]
    findings = []
    for event in file_events:
        filepath = event.get("TargetFilename", event.get("file_path", "")).lower()
        in_webroot = any(wr in filepath for wr in webroot_paths)
        is_script = any(filepath.endswith(ext) for ext in script_extensions)
        if in_webroot and is_script:
            findings.append({
                "timestamp": event.get("UtcTime", event.get("timestamp", "")),
                "file_path": filepath,
                "process": event.get("Image", event.get("process_name", "")),
                "hostname": event.get("Computer", event.get("hostname", "")),
                "severity": "CRITICAL",
                "reason": "script_created_in_webroot",
            })
    return findings


def detect_post_exploitation(process_logs):
    """Detect reconnaissance commands typically run through webshells."""
    recon_patterns = [
        (r"whoami", "user_discovery"),
        (r"ipconfig|ifconfig", "network_config"),
        (r"net\s+(user|group|localgroup)", "account_enum"),
        (r"systeminfo", "system_info"),
        (r"tasklist|ps\s+aux", "process_enum"),
        (r"netstat\s+-an", "connection_enum"),
        (r"dir\s+/s|find\s+/|ls\s+-la", "file_enum"),
    ]
    findings = []
    for entry in process_logs:
        parent = entry.get("ParentImage", entry.get("parent_process", "")).lower()
        if not any(ws in parent for ws in WEB_SERVER_PROCESSES):
            continue
        cmd = entry.get("CommandLine", entry.get("command_line", ""))
        for pattern, category in recon_patterns:
            if re.search(pattern, cmd, re.IGNORECASE):
                findings.append({
                    "timestamp": entry.get("UtcTime", entry.get("timestamp", "")),
                    "command": cmd[:300],
                    "category": category,
                    "parent": parent,
                    "severity": "HIGH",
                })
                break
    return findings


def main():
    parser = argparse.ArgumentParser(description="Webshell Activity Hunter")
    parser.add_argument("--process-log", help="JSON lines process creation log")
    parser.add_argument("--access-log", help="Web server access log")
    parser.add_argument("--file-events", help="JSON lines file creation events")
    parser.add_argument("--output", default="webshell_hunt_report.json")
    args = parser.parse_args()

    report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}}

    if args.process_log:
        logs = load_process_logs(args.process_log)
        shells = detect_webserver_child_shells(logs)
        report["findings"]["shell_spawns"] = shells
        print(f"[+] Web server shell spawns: {len(shells)}")
        recon = detect_post_exploitation(logs)
        report["findings"]["post_exploitation"] = recon
        print(f"[+] Post-exploitation commands: {len(recon)}")

    if args.access_log:
        hits = analyze_web_access_logs(args.access_log)
        report["findings"]["access_log_hits"] = hits
        print(f"[+] Access log webshell indicators: {len(hits)}")

    if args.file_events:
        events = load_process_logs(args.file_events)
        files = detect_file_creation_in_webroot(events)
        report["findings"]["webroot_files"] = files
        print(f"[+] Scripts created in webroot: {len(files)}")

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"[+] Report saved to {args.output}")


if __name__ == "__main__":
    main()
process.py4.4 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Web Shell Detection - Analyzes process and file events for web shell indicators on web servers."""

import json, csv, argparse, datetime, re
from collections import defaultdict
from pathlib import Path

WEB_SERVER_PROCESSES = {"w3wp.exe", "httpd.exe", "nginx.exe", "apache.exe", "tomcat.exe", "java.exe", "php-cgi.exe", "node.exe"}
SUSPICIOUS_CHILDREN = {"cmd.exe", "powershell.exe", "bash", "sh", "whoami.exe", "net.exe", "net1.exe", "ipconfig.exe", "systeminfo.exe", "tasklist.exe", "ping.exe", "nslookup.exe", "certutil.exe"}
WEBSHELL_EXTENSIONS = {".aspx", ".asp", ".php", ".jsp", ".jspx", ".cfm", ".ashx", ".asmx"}
WEB_DIRECTORIES = [r"\\inetpub\\", r"\\wwwroot\\", r"\\htdocs\\", r"\\webapps\\", r"\\www\\", r"\\html\\"]

def parse_logs(path):
    p = Path(path)
    if p.suffix == ".json":
        with open(p, encoding="utf-8") as f:
            data = json.load(f)
            return data if isinstance(data, list) else data.get("events", [])
    elif p.suffix == ".csv":
        with open(p, encoding="utf-8-sig") as f:
            return [dict(r) for r in csv.DictReader(f)]
    return []

def detect_webshell_process(event):
    eid = str(event.get("EventCode", event.get("EventID", "")))
    if eid != "1": return None
    parent = (event.get("ParentImage", event.get("InitiatingProcessFileName", ""))).lower()
    image = (event.get("Image", event.get("FileName", ""))).lower()
    parent_name = parent.split("\\")[-1].split("/")[-1]
    image_name = image.split("\\")[-1].split("/")[-1]
    if parent_name not in WEB_SERVER_PROCESSES: return None
    if image_name not in SUSPICIOUS_CHILDREN: return None
    return {
        "detection_type": "WEBSHELL_PROCESS_SPAWN",
        "technique": "T1505.003",
        "web_server": parent_name, "child_process": image_name,
        "command_line": event.get("CommandLine", event.get("ProcessCommandLine", "")),
        "hostname": event.get("Computer", event.get("DeviceName", "unknown")),
        "user": event.get("User", event.get("AccountName", "unknown")),
        "timestamp": event.get("UtcTime", event.get("Timestamp", "")),
        "risk_score": 80, "risk_level": "CRITICAL",
        "indicators": [f"Web server {parent_name} spawned {image_name}"],
    }

def detect_webshell_file(event):
    eid = str(event.get("EventCode", event.get("EventID", "")))
    if eid != "11": return None
    target = event.get("TargetFilename", event.get("FolderPath", "")).lower()
    if not any(re.search(p, target, re.IGNORECASE) for p in WEB_DIRECTORIES): return None
    ext = "." + target.split(".")[-1] if "." in target else ""
    if ext not in WEBSHELL_EXTENSIONS: return None
    return {
        "detection_type": "WEBSHELL_FILE_CREATION",
        "technique": "T1505.003",
        "file_path": target, "extension": ext,
        "creating_process": event.get("Image", event.get("InitiatingProcessFileName", "")),
        "hostname": event.get("Computer", event.get("DeviceName", "unknown")),
        "timestamp": event.get("UtcTime", event.get("Timestamp", "")),
        "risk_score": 70, "risk_level": "HIGH",
        "indicators": [f"Web shell file created: {ext} in web directory"],
    }

def run_hunt(input_path, output_dir):
    print(f"[*] Web Shell Hunt - {datetime.datetime.now().isoformat()}")
    events = parse_logs(input_path)
    findings = []
    for e in events:
        r = detect_webshell_process(e)
        if r: findings.append(r)
        r = detect_webshell_file(e)
        if r: findings.append(r)
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    with open(Path(output_dir) / "webshell_findings.json", "w", encoding="utf-8") as f:
        json.dump({"hunt_id": f"TH-WEBSHELL-{datetime.date.today()}", "findings": findings}, f, indent=2)
    print(f"[+] {len(findings)} findings written to {output_dir}")

def main():
    p = argparse.ArgumentParser(description="Web Shell Detection")
    sp = p.add_subparsers(dest="cmd")
    h = sp.add_parser("hunt"); h.add_argument("--input", "-i", required=True); h.add_argument("--output", "-o", default="./webshell_output")
    sp.add_parser("queries")
    args = p.parse_args()
    if args.cmd == "hunt": run_hunt(args.input, args.output)
    elif args.cmd == "queries":
        print('''index=sysmon EventCode=1
| where match(ParentImage, "(?i)(w3wp|httpd|nginx|tomcat|java)\\.exe")
| where match(Image, "(?i)(cmd|powershell|whoami|net|ipconfig)\\.exe")
| table _time Computer ParentImage Image CommandLine''')
    else: p.print_help()

if __name__ == "__main__": main()

Assets 1

template.mdtext/markdown · 2.6 KB
Keep exploring