Skip to content

Commit 527fa51

Browse files
authored
Merge branch 'main' into gitleaks-secret-scanning
2 parents 7c64938 + 2abd3de commit 527fa51

File tree

34 files changed

+1128
-168
lines changed

34 files changed

+1128
-168
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
[metadata]
2+
creation_date = "2025/11/27"
3+
integration = ["endpoint", "auditd_manager", "crowdstrike", "sentinel_one_cloud_funnel"]
4+
maturity = "production"
5+
updated_date = "2025/11/27"
6+
7+
[rule]
8+
author = ["Elastic"]
9+
description = """
10+
This rule detects the creation of privileged containers that mount host directories into the container's filesystem.
11+
Such configurations can be exploited by attackers to escape the container isolation and gain access to the host system,
12+
potentially leading to privilege escalation and lateral movement within the environment.
13+
"""
14+
from = "now-9m"
15+
index = [
16+
"auditbeat-*",
17+
"endgame-*",
18+
"logs-auditd_manager.auditd-*",
19+
"logs-crowdstrike.fdr*",
20+
"logs-endpoint.events.process*",
21+
"logs-sentinel_one_cloud_funnel.*",
22+
]
23+
language = "eql"
24+
license = "Elastic License v2"
25+
name = "Privileged Container Creation with Host Directory Mount"
26+
note = """## Triage and analysis
27+
28+
> **Disclaimer**:
29+
> This investigation guide was created using generative AI technology and has been reviewed to improve its accuracy and relevance. While every effort has been made to ensure its quality, we recommend validating the content and adapting it to suit your specific environment and operational needs.
30+
31+
### Investigating Privileged Container Creation with Host Directory Mount
32+
33+
This rule flags creation of a privileged container that bind-mounts host directories into the container filesystem, breaking isolation and granting direct access to host files and devices. An attacker on a compromised node starts a privileged container via the runtime, bind-mounts the host root (/:) inside it, then chroots and alters critical files or services to seize host control, persist, and pivot.
34+
35+
### Possible investigation steps
36+
37+
- Retrieve the full process tree and execution context (user, controlling TTY, parent, remote source IP) to identify who initiated the container and whether it aligns with a sanctioned change.
38+
- Run runtime introspection to confirm the container’s mounts and privileges (e.g., docker ps/inspect or CRI equivalents), capturing the container ID, exact hostPath mappings, image, entrypoint, and start time.
39+
- If orchestrated, correlate with Kubernetes events and kubelet logs at the same timestamp to find any Pod using privileged: true with hostPath volumes, recording the namespace, service account, controller, and requestor.
40+
- Review audit and file integrity telemetry after container start for host-impacting actions such as chroot into the mount, nsenter into host namespaces, or writes to critical paths (/etc/passwd, /etc/sudoers, /root/.ssh, /var/lib/kubelet, and systemd unit directories).
41+
- Assess image provenance and intent by resolving the image digest and registry, reviewing history/entrypoint for post-start tooling, and validating with service owners or allowlists whether this privileged host-mount is expected on this node.
42+
43+
### False positive analysis
44+
45+
- An administrator uses docker run --privileged with -v /:/host during a break-glass or troubleshooting session to edit host files or restart services, matching the pattern but aligned with an approved maintenance procedure.
46+
- Automated provisioning or upgrade workflows intentionally start a privileged container that bind-mounts / to apply system configuration, install packages, or manage kernel modules during node bootstrap, producing an expected event.
47+
48+
### Response and remediation
49+
50+
- Immediately stop and remove the privileged container by its ID using docker, cordon the node to prevent scheduling, and temporarily disable the Docker/CRI socket to block further privileged runs.
51+
- Preserve forensic artifacts (docker inspect output, container image and filesystem, bash history, /var/log) and remediate by diffing and restoring critical paths (/etc, /root/.ssh, /etc/systemd/system, /var/lib/kubelet), removing rogue users, SSH keys, and systemd units.
52+
- Rotate credentials potentially exposed by the host mount (SSH keys, kubelet certs, cloud tokens under /var/lib/kubelet or /root), patch the container runtime, and uncordon or rejoin the node only after verifying privileged runs and host-root mounts are blocked.
53+
- Escalate to incident response if the mount included "/" or if evidence shows chroot or nsenter into host namespaces or writes to /etc/passwd, /etc/sudoers, or systemd unit files, and initiate host reimage and broader fleet scoping.
54+
- Enforce controls that deny --privileged and hostPath of "/" via admission policy (Pod Security Standards, Kyverno, OPA Gatekeeper), drop CAP_SYS_ADMIN with seccomp/AppArmor, enable Docker userns-remap and no-new-privileges, and restrict membership in the docker group.
55+
- Establish a break-glass approval workflow and an allowlist for legitimate host mounts, enable file integrity monitoring on /etc and kubelet directories, and add runtime rules to alert and block future docker run -v /:/host attempts."""
56+
references = [
57+
"https://www.elastic.co/blog/shai-hulud-worm-npm-supply-chain-compromise",
58+
"https://socket.dev/blog/shai-hulud-strikes-again-v2",
59+
"https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack",
60+
"https://unit42.paloaltonetworks.com/container-escape-techniques/",
61+
]
62+
risk_score = 73
63+
rule_id = "d1f310cb-5921-4d37-bbdf-cfdab7a6df9c"
64+
setup = """## Setup
65+
This rule requires data coming in from Elastic Defend.
66+
### Elastic Defend Integration Setup
67+
Elastic Defend is integrated into the Elastic Agent using Fleet. Upon configuration, the integration allows the Elastic Agent to monitor events on your host and send data to the Elastic Security app.
68+
#### Prerequisite Requirements:
69+
- Fleet is required for Elastic Defend.
70+
- To configure Fleet Server refer to the [documentation](https://www.elastic.co/guide/en/fleet/current/fleet-server.html).
71+
#### The following steps should be executed in order to add the Elastic Defend integration on a Linux System:
72+
- Go to the Kibana home page and click "Add integrations".
73+
- In the query bar, search for "Elastic Defend" and select the integration to see more details about it.
74+
- Click "Add Elastic Defend".
75+
- Configure the integration name and optionally add a description.
76+
- Select the type of environment you want to protect, either "Traditional Endpoints" or "Cloud Workloads".
77+
- Select a configuration preset. Each preset comes with different default settings for Elastic Agent, you can further customize these later by configuring the Elastic Defend integration policy. [Helper guide](https://www.elastic.co/guide/en/security/current/configure-endpoint-integration-policy.html).
78+
- We suggest selecting "Complete EDR (Endpoint Detection and Response)" as a configuration setting, that provides "All events; all preventions"
79+
- Enter a name for the agent policy in "New agent policy name". If other agent policies already exist, you can click the "Existing hosts" tab and select an existing policy instead.
80+
For more details on Elastic Agent configuration settings, refer to the [helper guide](https://www.elastic.co/guide/en/fleet/8.10/agent-policy.html).
81+
- Click "Save and Continue".
82+
- To complete the integration, select "Add Elastic Agent to your hosts" and continue to the next section to install the Elastic Agent on your hosts.
83+
For more details on Elastic Defend refer to the [helper guide](https://www.elastic.co/guide/en/security/current/install-endpoint.html).
84+
"""
85+
severity = "high"
86+
tags = [
87+
"Domain: Endpoint",
88+
"Domain: Container",
89+
"OS: Linux",
90+
"OS: macOS",
91+
"Use Case: Threat Detection",
92+
"Tactic: Execution",
93+
"Data Source: Elastic Defend",
94+
"Data Source: Elastic Endgame",
95+
"Data Source: Auditd Manager",
96+
"Data Source: Crowdstrike",
97+
"Data Source: SentinelOne",
98+
"Resources: Investigation Guide",
99+
]
100+
timestamp_override = "event.ingested"
101+
type = "eql"
102+
query = '''
103+
process where event.type == "start" and event.action in ("exec", "exec_event", "start", "ProcessRollup2", "executed", "process_started") and
104+
process.name == "docker" and process.args == "--privileged" and process.args == "run" and
105+
process.args == "-v" and process.args like "/:/*" and
106+
not (
107+
(process.args == "aktosecurity/mirror-api-logging:k8s_ebpf" and process.args == "akto-api-security-traffic-collector") or
108+
(process.args like "goharbor/prepare:*" and process.args in ("/:/hostfs", "/:/hostfs/"))
109+
)
110+
'''
111+
112+
[[rule.threat]]
113+
framework = "MITRE ATT&CK"
114+
115+
[[rule.threat.technique]]
116+
id = "T1059"
117+
name = "Command and Scripting Interpreter"
118+
reference = "https://attack.mitre.org/techniques/T1059/"
119+
120+
[[rule.threat.technique.subtechnique]]
121+
id = "T1059.004"
122+
name = "Unix Shell"
123+
reference = "https://attack.mitre.org/techniques/T1059/004/"
124+
125+
[[rule.threat.technique]]
126+
id = "T1609"
127+
name = "Container Administration Command"
128+
reference = "https://attack.mitre.org/techniques/T1609/"
129+
130+
[rule.threat.tactic]
131+
id = "TA0002"
132+
name = "Execution"
133+
reference = "https://attack.mitre.org/tactics/TA0002/"
134+
135+
[[rule.threat]]
136+
framework = "MITRE ATT&CK"
137+
138+
[[rule.threat.technique]]
139+
id = "T1611"
140+
name = "Escape to Host"
141+
reference = "https://attack.mitre.org/techniques/T1611/"
142+
143+
[rule.threat.tactic]
144+
id = "TA0004"
145+
name = "Privilege Escalation"
146+
reference = "https://attack.mitre.org/tactics/TA0004/"

rules/cross-platform/multiple_alerts_elastic_defend_netsecurity_by_host.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2025/11/18"
33
integration = ["endpoint", "panw", "fortinet_fortigate", "suricata"]
44
maturity = "production"
5-
updated_date = "2025/11/18"
5+
updated_date = "2025/11/28"
66

77
[rule]
88
author = ["Elastic"]
@@ -65,6 +65,9 @@ FROM logs-* metadata _id
6565
Esql.destination_ip_values = VALUES(destination.ip)
6666
by Esql.source_ip
6767
| where Esql.event_module_distinct_count >= 2
68+
| eval concat_module_values = MV_CONCAT(Esql.event_module_values, ",")
69+
// Make sure an endpoint alert is present along one of the network ones
70+
| where concat_module_values like "*endpoint*"
6871
| keep Esql.alerts_count, Esql.source_ip, Esql.destination_ip_values, Esql.host_id_values, Esql.user_name_values, Esql.event_module_values, Esql.message_values, Esql.process_executable_values
6972
'''
7073
note = """## Triage and analysis

rules/cross-platform/persistence_web_server_potential_command_injection.toml

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[metadata]
22
creation_date = "2025/11/19"
3-
integration = ["nginx", "apache", "apache_tomcat", "iis", "network_traffic"]
3+
integration = ["nginx", "apache", "apache_tomcat", "iis"]
44
maturity = "production"
5-
updated_date = "2025/11/24"
5+
updated_date = "2025/12/01"
66

77
[rule]
88
author = ["Elastic"]
@@ -54,14 +54,12 @@ rule_id = "f3ac6734-7e52-4a0d-90b7-6847bf4308f2"
5454
severity = "low"
5555
tags = [
5656
"Domain: Web",
57-
"Domain: Network",
5857
"Use Case: Threat Detection",
5958
"Tactic: Reconnaissance",
6059
"Tactic: Persistence",
6160
"Tactic: Execution",
6261
"Tactic: Credential Access",
6362
"Tactic: Command and Control",
64-
"Data Source: Network Packet Capture",
6563
"Data Source: Nginx",
6664
"Data Source: Apache",
6765
"Data Source: Apache Tomcat",
@@ -71,26 +69,24 @@ tags = [
7169
timestamp_override = "event.ingested"
7270
type = "esql"
7371
query = '''
74-
from logs-network_traffic.http-*, logs-network_traffic.tls-*, logs-nginx.access-*, logs-apache.access-*, logs-apache_tomcat.access-*, logs-iis.access-*
72+
from logs-nginx.access-*, logs-apache.access-*, logs-apache_tomcat.access-*, logs-iis.access-*
7573
| where
76-
(url.original is not null or url.full is not null) and
7774
// Limit to 200 response code to reduce noise
7875
http.response.status_code == 200
7976
80-
| eval Esql.url_lower = case(url.original is not null, url.original, url.full)
81-
| eval Esql.url_lower = to_lower(Esql.url_lower)
82-
83-
| 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)
84-
| 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)
85-
| 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)
86-
| eval Esql.contains_devtcp = case(Esql.url_lower like "*/dev/tcp/*" or Esql.url_lower like "*/dev/udp/*", 1, 0)
87-
| 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)
88-
| 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)
89-
| eval Esql.contains_privileges = case(Esql.url_lower like "*chmod*+x", 1, 0)
90-
| eval Esql.contains_downloader = case(Esql.url_lower like "*curl *" or Esql.url_lower like "*wget *" , 1, 0)
91-
| 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)
92-
| eval Esql.contains_base64_cmd = case(Esql.url_lower like "*base64*-d*" or Esql.url_lower like "*echo*|*base64*", 1, 0)
93-
| 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)
77+
| eval Esql.url_original_to_lower = to_lower(url.original)
78+
79+
| eval Esql.contains_interpreter = case(Esql.url_original_to_lower like "*python* -c*" or Esql.url_original_to_lower like "*perl* -e*" or Esql.url_original_to_lower like "*ruby* -e*" or Esql.url_original_to_lower like "*ruby* -rsocket*" or Esql.url_original_to_lower like "*lua* -e*" or Esql.url_original_to_lower like "*php* -r*" or Esql.url_original_to_lower like "*node* -e*", 1, 0)
80+
| eval Esql.contains_shell = case(Esql.url_original_to_lower like "*/bin/bash*" or Esql.url_original_to_lower like "*bash*-c*" or Esql.url_original_to_lower like "*/bin/sh*" or Esql.url_original_to_lower rlike "*sh.{1,2}-c*", 1, 0)
81+
| eval Esql.contains_nc = case(Esql.url_original_to_lower like "*netcat*" or Esql.url_original_to_lower like "*ncat*" or Esql.url_original_to_lower rlike """.*nc.{1,2}[0-9]{1,3}(\.[0-9]{1,3}){3}.{1,2}[0-9]{1,5}.*""" or Esql.url_original_to_lower like "*nc.openbsd*" or Esql.url_original_to_lower like "*nc.traditional*" or Esql.url_original_to_lower like "*socat*", 1, 0)
82+
| eval Esql.contains_devtcp = case(Esql.url_original_to_lower like "*/dev/tcp/*" or Esql.url_original_to_lower like "*/dev/udp/*", 1, 0)
83+
| eval Esql.contains_helpers = case((Esql.url_original_to_lower like "*/bin/*" or Esql.url_original_to_lower like "*/usr/bin/*") and (Esql.url_original_to_lower like "*mkfifo*" or Esql.url_original_to_lower like "*nohup*" or Esql.url_original_to_lower like "*setsid*" or Esql.url_original_to_lower like "*busybox*"), 1, 0)
84+
| eval Esql.contains_sus_cli = case(Esql.url_original_to_lower like "*import*pty*spawn*" or Esql.url_original_to_lower like "*import*subprocess*call*" or Esql.url_original_to_lower like "*tcpsocket.new*" or Esql.url_original_to_lower like "*tcpsocket.open*" or Esql.url_original_to_lower like "*io.popen*" or Esql.url_original_to_lower like "*os.execute*" or Esql.url_original_to_lower like "*fsockopen*", 1, 0)
85+
| eval Esql.contains_privileges = case(Esql.url_original_to_lower like "*chmod*+x", 1, 0)
86+
| eval Esql.contains_downloader = case(Esql.url_original_to_lower like "*curl *" or Esql.url_original_to_lower like "*wget *" , 1, 0)
87+
| eval Esql.contains_file_read_keywords = case(Esql.url_original_to_lower like "*/etc/shadow*" or Esql.url_original_to_lower like "*/etc/passwd*" or Esql.url_original_to_lower like "*/root/.ssh/*" or Esql.url_original_to_lower like "*/home/*/.ssh/*" or Esql.url_original_to_lower like "*~/.ssh/*" or Esql.url_original_to_lower like "*/proc/self/environ*", 1, 0)
88+
| eval Esql.contains_base64_cmd = case(Esql.url_original_to_lower like "*base64*-d*" or Esql.url_original_to_lower like "*echo*|*base64*", 1, 0)
89+
| eval Esql.contains_suspicious_path = case(Esql.url_original_to_lower like "*/tmp/*" or Esql.url_original_to_lower like "*/var/tmp/*" or Esql.url_original_to_lower like "*/dev/shm/*" or Esql.url_original_to_lower like "*/root/*" or Esql.url_original_to_lower like "*/home/*/*" or Esql.url_original_to_lower like "*/var/www/*" or Esql.url_original_to_lower like "*/etc/cron.*/*", 1, 0)
9490
9591
| eval Esql.any_payload_keyword = case(
9692
Esql.contains_interpreter == 1 or Esql.contains_shell == 1 or Esql.contains_nc == 1 or Esql.contains_devtcp == 1 or
@@ -99,7 +95,7 @@ from logs-network_traffic.http-*, logs-network_traffic.tls-*, logs-nginx.access-
9995
10096
| keep
10197
@timestamp,
102-
Esql.url_lower,
98+
Esql.url_original_to_lower,
10399
Esql.any_payload_keyword,
104100
Esql.contains_interpreter,
105101
Esql.contains_shell,
@@ -123,13 +119,13 @@ from logs-network_traffic.http-*, logs-network_traffic.tls-*, logs-nginx.access-
123119
124120
| stats
125121
Esql.event_count = count(),
126-
Esql.url_path_count_distinct = count_distinct(Esql.url_lower),
122+
Esql.url_path_count_distinct = count_distinct(Esql.url_original_to_lower),
127123
128124
// General fields
129125
130126
Esql.host_name_values = values(host.name),
131127
Esql.agent_id_values = values(agent.id),
132-
Esql.url_path_values = values(Esql.url_lower),
128+
Esql.url_path_values = values(Esql.url_original_to_lower),
133129
Esql.http.response.status_code_values = values(http.response.status_code),
134130
Esql.user_agent_original_values = values(user_agent.original),
135131
Esql.event_dataset_values = values(event.dataset),

0 commit comments

Comments
 (0)