Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New Hunt] Persistence via Web Shells #4320

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions hunting/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Here are the queries currently available:
- [Persistence via System V Init](./linux/docs/persistence_via_sysv_init.md) (ES|QL)
- [Persistence via Systemd (Timers)](./linux/docs/persistence_via_systemd_timers.md) (ES|QL)
- [Persistence via Udev](./linux/docs/persistence_via_udev.md) (ES|QL)
- [Persistence via Web Shell](./linux/docs/persistence_via_web_shell.md) (ES|QL)
- [Persistence via rc.local/rc.common](./linux/docs/persistence_via_rc_local.md) (ES|QL)
- [Potential Defense Evasion via Multi-Dot Process Execution](./linux/docs/defense_evasion_via_multi_dot_process_execution.md) (ES|QL)
- [Privilege Escalation Identification via Existing Sudoers File](./linux/docs/privilege_escalation_via_existing_sudoers.md) (ES|QL)
Expand Down
5 changes: 5 additions & 0 deletions hunting/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ linux:
mitre:
- T1037.004
- T1546.003
e2e4a1ad-5e03-4968-927c-9ef13c49a3b8:
name: Persistence via Web Shell
path: ./linux/queries/persistence_via_web_shell.toml
mitre:
- T1505.003
okta:
0b936024-71d9-11ef-a9be-f661ea17fbcc:
name: Failed OAuth Access Token Retrieval via Public Client App
Expand Down
61 changes: 61 additions & 0 deletions hunting/linux/docs/persistence_via_web_shell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Persistence via Web Shell

---

## Metadata

- **Author:** Elastic
- **Description:** This hunt identifies potential persistence mechanisms leveraging web shells on Linux systems. Web shells are malicious scripts or executables that attackers deploy to provide remote access, execute arbitrary commands, or maintain persistence on compromised systems. This hunt focuses on detecting suspicious file creation events and anomalous network activity associated with known web shell behaviors.

- **UUID:** `e2e4a1ad-5e03-4968-927c-9ef13c49a3b8`
- **Integration:** [endpoint](https://docs.elastic.co/integrations/endpoint)
- **Language:** `[ES|QL]`
- **Source File:** [Persistence via Web Shell](../queries/persistence_via_web_shell.toml)

## Query

```sql
from logs-endpoint.events.file-*
| keep @timestamp, host.os.type, event.action, file.extension, process.name, agent.id, file.name, process.executable
| where @timestamp > now() - 30 days
| where host.os.type == "linux" and event.action in ("rename", "creation") and
file.extension in ("php", "py", "pl", "rb", "rs", "lua", "jsp") and not (
// Add your noisy exclusions here
process.name in ("dnf", "dpkg", "pip3", "pip", "yum", "tar", "code", "vmtoolsd")
)
| stats cc = count(), agent_count = count_distinct(agent.id) by file.name, process.executable
| where agent_count <= 3
| sort cc asc
| limit 100
```

```sql
from logs-endpoint.events.network-*
| keep @timestamp, host.os.type, event.type, event.action, process.name, source.ip, agent.id, process.executable, process.command_line
| where @timestamp > now() - 30 days
| where host.os.type == "linux" and event.type == "end" and event.action == "disconnect_received" and
(
process.name like "ruby*" or
process.name like "perl*" or
process.name like "python*" or
process.name like "php*"
) and source.ip IS NOT null and not CIDR_MATCH(source.ip, "127.0.0.0/8", "169.254.0.0/16", "224.0.0.0/4", "::1", "172.18.0.0/16")
| stats cc = count(), agent_count = count_distinct(agent.id) by process.executable, process.command_line, source.ip
| where agent_count <= 3
| sort cc asc
| limit 100
```

## Notes

- Monitors for the creation or renaming of files with extensions commonly associated with web shells, such as PHP, Python, Perl, Ruby, Lua, and JSP scripts.
- Analyzes network disconnect events to identify anomalous connections initiated by scripting engines, indicating potential use of web shells for remote access.
- Provides statistics and counts to detect rare or unusual activity related to file modifications or network events, helping prioritize investigation efforts.

## MITRE ATT&CK Techniques

- [T1505.003](https://attack.mitre.org/techniques/T1505/003)

## License

- `Elastic License v2`
49 changes: 49 additions & 0 deletions hunting/linux/queries/persistence_via_web_shell.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[hunt]
author = "Elastic"
description = """
This hunt identifies potential persistence mechanisms leveraging web shells on Linux systems. Web shells are malicious scripts or executables that attackers deploy to provide remote access, execute arbitrary commands, or maintain persistence on compromised systems. This hunt focuses on detecting suspicious file creation events and anomalous network activity associated with known web shell behaviors.
"""
integration = ["endpoint"]
uuid = "e2e4a1ad-5e03-4968-927c-9ef13c49a3b8"
name = "Persistence via Web Shell"
language = ["ES|QL"]
license = "Elastic License v2"
notes = [
"Monitors for the creation or renaming of files with extensions commonly associated with web shells, such as PHP, Python, Perl, Ruby, Lua, and JSP scripts.",
"Analyzes network disconnect events to identify anomalous connections initiated by scripting engines, indicating potential use of web shells for remote access.",
"Provides statistics and counts to detect rare or unusual activity related to file modifications or network events, helping prioritize investigation efforts."
]
mitre = ["T1505.003"]

query = [
'''
from logs-endpoint.events.file-*
| keep @timestamp, host.os.type, event.action, file.extension, process.name, agent.id, file.name, process.executable
| where @timestamp > now() - 30 days
| where host.os.type == "linux" and event.action in ("rename", "creation") and
file.extension in ("php", "py", "pl", "rb", "rs", "lua", "jsp") and not (
// Add your noisy exclusions here
process.name in ("dnf", "dpkg", "pip3", "pip", "yum", "tar", "code", "vmtoolsd")
)
| stats cc = count(), agent_count = count_distinct(agent.id) by file.name, process.executable
| where agent_count <= 3
| sort cc asc
| limit 100
''',
'''
from logs-endpoint.events.network-*
| keep @timestamp, host.os.type, event.type, event.action, process.name, source.ip, agent.id, process.executable, process.command_line
| where @timestamp > now() - 30 days
| where host.os.type == "linux" and event.type == "end" and event.action == "disconnect_received" and
(
process.name like "ruby*" or
process.name like "perl*" or
process.name like "python*" or
process.name like "php*"
) and source.ip IS NOT null and not CIDR_MATCH(source.ip, "127.0.0.0/8", "169.254.0.0/16", "224.0.0.0/4", "::1", "172.18.0.0/16")
| stats cc = count(), agent_count = count_distinct(agent.id) by process.executable, process.command_line, source.ip
| where agent_count <= 3
| sort cc asc
| limit 100
'''
]
Loading