From d5d9541cc8a6ea3d3dc168cf5d338a53c176ffc7 Mon Sep 17 00:00:00 2001 From: Ruben Groenewoud Date: Wed, 19 Nov 2025 15:33:24 +0100 Subject: [PATCH 1/7] [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/7] 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/7] 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", From ac2bd281286c7b5c57207cfa9c476090087d2eac Mon Sep 17 00:00:00 2001 From: Ruben Groenewoud <78494512+Aegrah@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:03:11 +0100 Subject: [PATCH 4/7] Update persistence_web_server_potential_command_injection.toml --- ...eb_server_potential_command_injection.toml | 117 +++++++++++------- 1 file changed, 73 insertions(+), 44 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 7bfc6d4b8a9..09bd6b70e51 100644 --- a/rules/cross-platform/persistence_web_server_potential_command_injection.toml +++ b/rules/cross-platform/persistence_web_server_potential_command_injection.toml @@ -13,8 +13,8 @@ applications to inject and execute arbitrary commands on the server, often using 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" +from = "now-9m" +interval = "10m" language = "esql" license = "Elastic License v2" name = "Web Server Potential Command Injection Request" @@ -22,12 +22,8 @@ risk_score = 21 rule_id = "f3ac6734-7e52-4a0d-90b7-6847bf4308f2" severity = "low" tags = [ - "Domain Scope: Single", "Domain: Web", "Domain: Network", - "OS: Linux", - "OS: macOS", - "OS: Windows", "Use Case: Threat Detection", "Tactic: Reconnaissance", "Tactic: Credential Access", @@ -48,53 +44,86 @@ from 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) + (url.original is not null or url.full is not null) and + // Limit to 200 response code to reduce noise + http.response.status_code == 200 + +| eval Esql.url_lower = case(url.original is not null, url.original, url.full) +| eval Esql.url_lower = to_lower(Esql.url_lower) + +| eval Esql.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 Esql.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 Esql.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 Esql.contains_devtcp = case(Esql.url_lower like "*/dev/tcp/*" or Esql.url_lower like "*/dev/udp/*", 1, 0) +| eval Esql.contains_helpers = case((Esql.url_lower like "*/bin/*" or Esql.url_lower like "*/usr/bin/*") and (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 Esql.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 Esql.contains_privileges = case(Esql.url_lower like "*chmod*+x", 1, 0) +| eval Esql.contains_downloader = case(Esql.url_lower like "*curl *" or Esql.url_lower like "*wget *" , 1, 0) +| eval Esql.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 Esql.contains_base64_cmd = case(Esql.url_lower like "*base64*-d*" or Esql.url_lower like "*echo*|*base64*", 1, 0) +| eval Esql.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.any_payload_keyword = case( + Esql.contains_interpreter == 1 or Esql.contains_shell == 1 or Esql.contains_nc == 1 or Esql.contains_devtcp == 1 or + Esql.contains_helpers == 1 or Esql.contains_sus_cli == 1 or Esql.contains_privileges == 1 or Esql.contains_downloader == 1 or + Esql.contains_file_read_keywords == 1 or Esql.contains_base64_cmd == 1 or Esql.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, + Esql.url_lower, + Esql.any_payload_keyword, + Esql.contains_interpreter, + Esql.contains_shell, + Esql.contains_nc, + Esql.contains_devtcp, + Esql.contains_helpers, + Esql.contains_sus_cli, + Esql.contains_privileges, + Esql.contains_downloader, + Esql.contains_file_read_keywords, + Esql.contains_base64_cmd, + Esql.contains_suspicious_path, source.ip, destination.ip, agent.id, http.request.method, http.response.status_code, - user_agent.original + user_agent.original, + host.name, + event.dataset + +| stats + Esql.event_count = count(), + Esql.url_path_count_distinct = count_distinct(Esql.url_lower), + + // General fields + + Esql.host_name_values = values(host.name), + Esql.agent_id_values = values(agent.id), + Esql.url_path_values = values(Esql.url_lower), + Esql.http.response.status_code_values = values(http.response.status_code), + Esql.user_agent_original_values = values(user_agent.original), + Esql.event_dataset_values = values(event.dataset), + + // Rule Specific fields + Esql.any_payload_keyword_max = max(Esql.any_payload_keyword), + Esql.contains_interpreter_values = values(Esql.contains_interpreter), + Esql.contains_shell_values = values(Esql.contains_shell), + Esql.contains_nc_values = values(Esql.contains_nc), + Esql.contains_devtcp_values = values(Esql.contains_devtcp), + Esql.contains_helpers_values = values(Esql.contains_helpers), + Esql.contains_sus_cli_values = values(Esql.contains_sus_cli), + Esql.contains_privileges_values = values(Esql.contains_privileges), + Esql.contains_downloader_values = values(Esql.contains_downloader), + Esql.contains_file_read_keywords_values = values(Esql.contains_file_read_keywords), + Esql.contains_base64_cmd_values = values(Esql.contains_base64_cmd), + Esql.contains_suspicious_path_values = values(Esql.contains_suspicious_path) + + by source.ip, agent.id -| where any_payload_keyword == 1 -| limit 100 +| where + // Filter for potential command injection attempts with low event counts to reduce false positives + Esql.any_payload_keyword_max == 1 and Esql.event_count < 5 ''' [[rule.threat]] From 2163d2491f7f64b70d3d6cf538767fa289a2cc6a Mon Sep 17 00:00:00 2001 From: terrancedejesus Date: Mon, 24 Nov 2025 12:47:53 -0500 Subject: [PATCH 5/7] adding missing tags --- .../persistence_web_server_potential_command_injection.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 09bd6b70e51..1e2eee330b1 100644 --- a/rules/cross-platform/persistence_web_server_potential_command_injection.toml +++ b/rules/cross-platform/persistence_web_server_potential_command_injection.toml @@ -26,7 +26,10 @@ tags = [ "Domain: Network", "Use Case: Threat Detection", "Tactic: Reconnaissance", + "Tactic: Persistence", + "Tactic: Execution", "Tactic: Credential Access", + "Tactic: Command and Control", "Data Source: Network Packet Capture", "Data Source: Nginx", "Data Source: Apache", @@ -118,7 +121,7 @@ from Esql.contains_file_read_keywords_values = values(Esql.contains_file_read_keywords), Esql.contains_base64_cmd_values = values(Esql.contains_base64_cmd), Esql.contains_suspicious_path_values = values(Esql.contains_suspicious_path) - + by source.ip, agent.id | where From b84217e71f2c7ba911c2fa21b023ad353657bd0d Mon Sep 17 00:00:00 2001 From: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:01:16 -0500 Subject: [PATCH 6/7] Update rules/cross-platform/persistence_web_server_potential_command_injection.toml Co-authored-by: shashank-elastic <91139415+shashank-elastic@users.noreply.github.com> --- .../persistence_web_server_potential_command_injection.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1e2eee330b1..4a365a1c6c3 100644 --- a/rules/cross-platform/persistence_web_server_potential_command_injection.toml +++ b/rules/cross-platform/persistence_web_server_potential_command_injection.toml @@ -39,7 +39,7 @@ tags = [ timestamp_override = "event.ingested" type = "esql" query = ''' -from +from logs-network_traffic.http-*, logs-network_traffic.tls-*, logs-nginx.access-*, logs-apache.access-*, logs-apache_tomcat.access-*, logs-iis.access-* logs-network_traffic.http-*, logs-network_traffic.tls-*, logs-nginx.access-*, From 8f9f8c6f52a56ea96fc010b6cbdf5ce542153281 Mon Sep 17 00:00:00 2001 From: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:10:25 -0500 Subject: [PATCH 7/7] Update rules/cross-platform/persistence_web_server_potential_command_injection.toml Co-authored-by: shashank-elastic <91139415+shashank-elastic@users.noreply.github.com> --- .../persistence_web_server_potential_command_injection.toml | 6 ------ 1 file changed, 6 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 4a365a1c6c3..7620e70b652 100644 --- a/rules/cross-platform/persistence_web_server_potential_command_injection.toml +++ b/rules/cross-platform/persistence_web_server_potential_command_injection.toml @@ -40,12 +40,6 @@ 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-* - logs-network_traffic.http-*, - logs-network_traffic.tls-*, - logs-nginx.access-*, - logs-apache.access-*, - logs-apache_tomcat.access-*, - logs-iis.access-* | where (url.original is not null or url.full is not null) and // Limit to 200 response code to reduce noise