From d5d9541cc8a6ea3d3dc168cf5d338a53c176ffc7 Mon Sep 17 00:00:00 2001 From: Ruben Groenewoud Date: Wed, 19 Nov 2025 15:33:24 +0100 Subject: [PATCH 1/3] [New Rule] Web Server Potential Command Injection Request --- ...eb_server_potential_command_injection.toml | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 rules/cross-platform/persistence_web_server_potential_command_injection.toml diff --git a/rules/cross-platform/persistence_web_server_potential_command_injection.toml b/rules/cross-platform/persistence_web_server_potential_command_injection.toml new file mode 100644 index 00000000000..b3c35d8762a --- /dev/null +++ b/rules/cross-platform/persistence_web_server_potential_command_injection.toml @@ -0,0 +1,169 @@ +[metadata] +creation_date = "2025/11/19" +integration = ["nginx", "apache", "apache_tomcat", "iis", "network_traffic"] +maturity = "production" +updated_date = "2025/11/19" + +[rule] +author = ["Elastic"] +description = """ +This rule detects potential command injection attempts via web server requests by identifying URLs that contain +suspicious patterns commonly associated with command execution payloads. Attackers may exploit vulnerabilities in web +applications to inject and execute arbitrary commands on the server, often using interpreters like Python, Perl, Ruby, +PHP, or shell commands. By monitoring for these indicators in web traffic, security teams can identify and respond to +potential threats early. +""" +from = "now-61m" +interval = "1h" +language = "esql" +license = "Elastic License v2" +name = "Web Server Potential Command Injection Request" +risk_score = 21 +rule_id = "f3ac6734-7e52-4a0d-90b7-6847bf4308f2" +severity = "low" +tags = [ + "Domain Scope: Single", + "Domain: Web", + "OS: Linux", + "OS: macOS", + "OS: Windows", + "Use Case: Threat Detection", + "Tactic: Reconnaissance", + "Tactic: Credential Access", + "Data Source: Network Packet Capture", + "Data Source: Nginx", + "Data Source: Apache", + "Data Source: Apache Tomcat", + "Data Source: IIS", +] +timestamp_override = "event.ingested" +type = "esql" +query = ''' +from + logs-network_traffic.http-*, + logs-network_traffic.tls-*, + logs-nginx.access-*, + logs-apache.access-*, + logs-apache_tomcat.access-*, + logs-iis.access-* +| where + @timestamp > now() - 1d and + (url.original is not null or url.full is not null) + +| eval esql_url_text = case(url.original is not null, url.original, url.full) +| eval esql_url_lower = to_lower(esql_url_text) + +| eval contains_interpreter = case(esql_url_lower like "*python* -c*" or esql_url_lower like "*perl* -e*" or esql_url_lower like "*ruby* -e*" or esql_url_lower like "*ruby* -rsocket*" or esql_url_lower like "*lua* -e*" or esql_url_lower like "*php* -r*" or esql_url_lower like "*node* -e*", 1, 0) +| eval contains_shell = case(esql_url_lower like "*/bin/bash*" or esql_url_lower like "*bash*-c*" or esql_url_lower like "*/bin/sh*" or esql_url_lower rlike "*sh.{1,2}-c*", 1, 0) +| eval contains_nc = case(esql_url_lower like "*netcat*" or esql_url_lower like "*ncat*" or esql_url_lower rlike """.*nc.{1,2}[0-9]{1,3}(\.[0-9]{1,3}){3}.{1,2}[0-9]{1,5}.*""" or esql_url_lower like "*nc.openbsd*" or esql_url_lower like "*nc.traditional*" or esql_url_lower like "*socat*", 1, 0) +| eval contains_devtcp = case(esql_url_lower like "*/dev/tcp/*" or esql_url_lower like "*/dev/udp/*", 1, 0) +| eval contains_helpers = case(esql_url_lower like "*mkfifo*" or esql_url_lower like "*nohup*" or esql_url_lower like "*setsid*" or esql_url_lower like "*busybox*", 1, 0) +| eval contains_sus_cli = case(esql_url_lower like "*import*pty*spawn*" or esql_url_lower like "*import*subprocess*call*" or esql_url_lower like "*tcpsocket.new*" or esql_url_lower like "*tcpsocket.open*" or esql_url_lower like "*io.popen*" or esql_url_lower like "*os.execute*" or esql_url_lower like "*fsockopen*", 1, 0) +| eval contains_privileges = case(esql_url_lower like "*chmod*" or esql_url_lower like "*chown*", 1, 0) +| eval contains_downloader = case(esql_url_lower like "*curl *" or esql_url_lower like "*wget *" , 1, 0) +| eval contains_file_read_keywords = case(esql_url_lower like "*/etc/shadow*" or esql_url_lower like "*/etc/passwd*" or esql_url_lower like "*/root/.ssh/*" or esql_url_lower like "*/home/*/.ssh/*" or esql_url_lower like "*~/.ssh/*" or esql_url_lower like "*/proc/self/environ*", 1, 0) +| eval contains_base64_cmd = case(esql_url_lower like "*base64*-d*" or esql_url_lower like "*xxd*" or esql_url_lower like "*echo*|*base64*", 1, 0) +| eval contains_suspicious_path = case(esql_url_lower like "*/tmp/*" or esql_url_lower like "*/var/tmp/*" or esql_url_lower like "*/dev/shm/*" or esql_url_lower like "*/root/*" or esql_url_lower like "*/home/*/*" or esql_url_lower like "*/var/www/*" or esql_url_lower like "*/etc/cron.*/*", 1, 0) + +| eval any_payload_keyword = case( + contains_interpreter == 1 or contains_shell == 1 or contains_nc == 1 or contains_devtcp == 1 or + contains_helpers == 1 or contains_sus_cli == 1 or contains_privileges == 1 or contains_downloader == 1 or + contains_file_read_keywords == 1 or contains_base64_cmd == 1 or contains_suspicious_path == 1, 1, 0) + +| keep + @timestamp, + esql_url_lower, + any_payload_keyword, + contains_interpreter, + contains_shell, + contains_nc, + contains_devtcp, + contains_helpers, + contains_sus_cli, + contains_privileges, + contains_downloader, + contains_file_read_keywords, + contains_base64_cmd, + contains_suspicious_path, + source.ip, + destination.ip, + agent.id, + http.request.method, + http.response.status_code, + user_agent.original + +| where any_payload_keyword == 1 +| limit 100 +''' + +[[rule.threat]] +framework = "MITRE ATT&CK" + +[[rule.threat.technique]] +id = "T1505" +name = "Server Software Component" +reference = "https://attack.mitre.org/techniques/T1505/" + +[[rule.threat.technique.subtechnique]] +id = "T1505.003" +name = "Web Shell" +reference = "https://attack.mitre.org/techniques/T1505/003/" + +[rule.threat.tactic] +id = "TA0003" +name = "Persistence" +reference = "https://attack.mitre.org/tactics/TA0003/" + +[[rule.threat]] +framework = "MITRE ATT&CK" + +[[rule.threat.technique]] +id = "T1059" +name = "Command and Scripting Interpreter" +reference = "https://attack.mitre.org/techniques/T1059/" + +[[rule.threat.technique.subtechnique]] +id = "T1059.004" +name = "Unix Shell" +reference = "https://attack.mitre.org/techniques/T1059/004/" + +[rule.threat.tactic] +id = "TA0002" +name = "Execution" +reference = "https://attack.mitre.org/tactics/TA0002/" + +[[rule.threat]] +framework = "MITRE ATT&CK" + +[[rule.threat.technique]] +id = "T1071" +name = "Application Layer Protocol" +reference = "https://attack.mitre.org/techniques/T1071/" + +[rule.threat.tactic] +id = "TA0011" +name = "Command and Control" +reference = "https://attack.mitre.org/tactics/TA0011/" + +[[rule.threat]] +framework = "MITRE ATT&CK" + +[[rule.threat.technique]] +id = "T1595" +name = "Active Scanning" +reference = "https://attack.mitre.org/techniques/T1595/" + +[[rule.threat.technique.subtechnique]] +id = "T1595.002" +name = "Vulnerability Scanning" +reference = "https://attack.mitre.org/techniques/T1595/002/" + +[[rule.threat.technique.subtechnique]] +id = "T1595.003" +name = "Wordlist Scanning" +reference = "https://attack.mitre.org/techniques/T1595/003/" + +[rule.threat.tactic] +id = "TA0043" +name = "Reconnaissance" +reference = "https://attack.mitre.org/tactics/TA0043/" From 59dba87216daa3eee7215e1b696c98487614d1b3 Mon Sep 17 00:00:00 2001 From: Ruben Groenewoud <78494512+Aegrah@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:42:56 +0100 Subject: [PATCH 2/3] Update variable names to use consistent casing --- ...eb_server_potential_command_injection.toml | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/rules/cross-platform/persistence_web_server_potential_command_injection.toml b/rules/cross-platform/persistence_web_server_potential_command_injection.toml index b3c35d8762a..dc92bef89a0 100644 --- a/rules/cross-platform/persistence_web_server_potential_command_injection.toml +++ b/rules/cross-platform/persistence_web_server_potential_command_injection.toml @@ -50,20 +50,20 @@ from @timestamp > now() - 1d and (url.original is not null or url.full is not null) -| eval esql_url_text = case(url.original is not null, url.original, url.full) -| eval esql_url_lower = to_lower(esql_url_text) - -| eval contains_interpreter = case(esql_url_lower like "*python* -c*" or esql_url_lower like "*perl* -e*" or esql_url_lower like "*ruby* -e*" or esql_url_lower like "*ruby* -rsocket*" or esql_url_lower like "*lua* -e*" or esql_url_lower like "*php* -r*" or esql_url_lower like "*node* -e*", 1, 0) -| eval contains_shell = case(esql_url_lower like "*/bin/bash*" or esql_url_lower like "*bash*-c*" or esql_url_lower like "*/bin/sh*" or esql_url_lower rlike "*sh.{1,2}-c*", 1, 0) -| eval contains_nc = case(esql_url_lower like "*netcat*" or esql_url_lower like "*ncat*" or esql_url_lower rlike """.*nc.{1,2}[0-9]{1,3}(\.[0-9]{1,3}){3}.{1,2}[0-9]{1,5}.*""" or esql_url_lower like "*nc.openbsd*" or esql_url_lower like "*nc.traditional*" or esql_url_lower like "*socat*", 1, 0) -| eval contains_devtcp = case(esql_url_lower like "*/dev/tcp/*" or esql_url_lower like "*/dev/udp/*", 1, 0) -| eval contains_helpers = case(esql_url_lower like "*mkfifo*" or esql_url_lower like "*nohup*" or esql_url_lower like "*setsid*" or esql_url_lower like "*busybox*", 1, 0) -| eval contains_sus_cli = case(esql_url_lower like "*import*pty*spawn*" or esql_url_lower like "*import*subprocess*call*" or esql_url_lower like "*tcpsocket.new*" or esql_url_lower like "*tcpsocket.open*" or esql_url_lower like "*io.popen*" or esql_url_lower like "*os.execute*" or esql_url_lower like "*fsockopen*", 1, 0) -| eval contains_privileges = case(esql_url_lower like "*chmod*" or esql_url_lower like "*chown*", 1, 0) -| eval contains_downloader = case(esql_url_lower like "*curl *" or esql_url_lower like "*wget *" , 1, 0) -| eval contains_file_read_keywords = case(esql_url_lower like "*/etc/shadow*" or esql_url_lower like "*/etc/passwd*" or esql_url_lower like "*/root/.ssh/*" or esql_url_lower like "*/home/*/.ssh/*" or esql_url_lower like "*~/.ssh/*" or esql_url_lower like "*/proc/self/environ*", 1, 0) -| eval contains_base64_cmd = case(esql_url_lower like "*base64*-d*" or esql_url_lower like "*xxd*" or esql_url_lower like "*echo*|*base64*", 1, 0) -| eval contains_suspicious_path = case(esql_url_lower like "*/tmp/*" or esql_url_lower like "*/var/tmp/*" or esql_url_lower like "*/dev/shm/*" or esql_url_lower like "*/root/*" or esql_url_lower like "*/home/*/*" or esql_url_lower like "*/var/www/*" or esql_url_lower like "*/etc/cron.*/*", 1, 0) +| eval Esql_url_text = case(url.original is not null, url.original, url.full) +| eval Esql_url_lower = to_lower(Esql_url_text) + +| eval contains_interpreter = case(Esql_url_lower like "*python* -c*" or Esql_url_lower like "*perl* -e*" or Esql_url_lower like "*ruby* -e*" or Esql_url_lower like "*ruby* -rsocket*" or Esql_url_lower like "*lua* -e*" or Esql_url_lower like "*php* -r*" or Esql_url_lower like "*node* -e*", 1, 0) +| eval contains_shell = case(Esql_url_lower like "*/bin/bash*" or Esql_url_lower like "*bash*-c*" or Esql_url_lower like "*/bin/sh*" or Esql_url_lower rlike "*sh.{1,2}-c*", 1, 0) +| eval contains_nc = case(Esql_url_lower like "*netcat*" or Esql_url_lower like "*ncat*" or Esql_url_lower rlike """.*nc.{1,2}[0-9]{1,3}(\.[0-9]{1,3}){3}.{1,2}[0-9]{1,5}.*""" or Esql_url_lower like "*nc.openbsd*" or Esql_url_lower like "*nc.traditional*" or Esql_url_lower like "*socat*", 1, 0) +| eval contains_devtcp = case(Esql_url_lower like "*/dev/tcp/*" or Esql_url_lower like "*/dev/udp/*", 1, 0) +| eval contains_helpers = case(Esql_url_lower like "*mkfifo*" or Esql_url_lower like "*nohup*" or Esql_url_lower like "*setsid*" or Esql_url_lower like "*busybox*", 1, 0) +| eval contains_sus_cli = case(Esql_url_lower like "*import*pty*spawn*" or Esql_url_lower like "*import*subprocess*call*" or Esql_url_lower like "*tcpsocket.new*" or Esql_url_lower like "*tcpsocket.open*" or Esql_url_lower like "*io.popen*" or Esql_url_lower like "*os.execute*" or Esql_url_lower like "*fsockopen*", 1, 0) +| eval contains_privileges = case(Esql_url_lower like "*chmod*" or Esql_url_lower like "*chown*", 1, 0) +| eval contains_downloader = case(Esql_url_lower like "*curl *" or Esql_url_lower like "*wget *" , 1, 0) +| eval contains_file_read_keywords = case(Esql_url_lower like "*/etc/shadow*" or Esql_url_lower like "*/etc/passwd*" or Esql_url_lower like "*/root/.ssh/*" or Esql_url_lower like "*/home/*/.ssh/*" or Esql_url_lower like "*~/.ssh/*" or Esql_url_lower like "*/proc/self/environ*", 1, 0) +| eval contains_base64_cmd = case(Esql_url_lower like "*base64*-d*" or Esql_url_lower like "*xxd*" or Esql_url_lower like "*echo*|*base64*", 1, 0) +| eval contains_suspicious_path = case(Esql_url_lower like "*/tmp/*" or Esql_url_lower like "*/var/tmp/*" or Esql_url_lower like "*/dev/shm/*" or Esql_url_lower like "*/root/*" or Esql_url_lower like "*/home/*/*" or Esql_url_lower like "*/var/www/*" or Esql_url_lower like "*/etc/cron.*/*", 1, 0) | eval any_payload_keyword = case( contains_interpreter == 1 or contains_shell == 1 or contains_nc == 1 or contains_devtcp == 1 or @@ -72,7 +72,7 @@ from | keep @timestamp, - esql_url_lower, + Esql_url_lower, any_payload_keyword, contains_interpreter, contains_shell, From 7962ca6aafea9e36cbc46edb0a56de50011a19de Mon Sep 17 00:00:00 2001 From: Ruben Groenewoud <78494512+Aegrah@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:43:15 +0100 Subject: [PATCH 3/3] Add 'Domain: Network' tag to command injection rule --- .../persistence_web_server_potential_command_injection.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/rules/cross-platform/persistence_web_server_potential_command_injection.toml b/rules/cross-platform/persistence_web_server_potential_command_injection.toml index dc92bef89a0..7bfc6d4b8a9 100644 --- a/rules/cross-platform/persistence_web_server_potential_command_injection.toml +++ b/rules/cross-platform/persistence_web_server_potential_command_injection.toml @@ -24,6 +24,7 @@ severity = "low" tags = [ "Domain Scope: Single", "Domain: Web", + "Domain: Network", "OS: Linux", "OS: macOS", "OS: Windows",