Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
[metadata]
creation_date = "2026/06/20"
integration = ["endpoint"]
maturity = "production"
updated_date = "2026/06/20"

[rule]
author = ["Aryu-RU"]
description = """
Identifies the Obsidian note-taking application spawning a command or script interpreter, such as PowerShell or a Unix
shell. Obsidian has no built-in need to launch interpreters on its own, and this behavior is characteristic of the
Obsidian "Shell Commands" community plugin (`obsidian-shellcommands`) being abused to auto-execute attacker-supplied
commands when a weaponized vault is opened, as observed in the PhantomPulse (REF6598) campaign.
"""
false_positives = [
"""
The Obsidian "Shell Commands" community plugin can be used legitimately to run user-defined commands, which causes
Obsidian to spawn an interpreter as designed. PowerShell, pwsh, and osascript children are matched directly;
validate the commands configured in the plugin's data.json and add exceptions for trusted users or hosts.
""",
"""
A legitimate Shell Commands configuration that shells out to a download or scripting utility (for example, fetching
notes over HTTP) can match the argument-based conditions on cmd or a Unix shell. Review the child process command
line and the destination, and exclude known-good plugin activity.
""",
]
from = "now-9m"
index = ["logs-endpoint.events.process-*"]
language = "eql"
license = "Elastic License v2"
name = "Command Interpreter Spawned by Obsidian"
note = """## Triage and analysis

### Investigating Command Interpreter Spawned by Obsidian

Obsidian is a Markdown note-taking application that does not natively execute shell commands. The only common way for
Obsidian to spawn an interpreter is through a community plugin, most notably the "Shell Commands" plugin
(`obsidian-shellcommands`), which can be configured to run arbitrary commands automatically when a vault is opened. In
the PhantomPulse campaign, a victim was socially engineered into logging into an attacker-controlled Obsidian Sync vault
and enabling community plugin sync, which pulled down a weaponized configuration that launched a PowerShell download
cradle on vault open.

### Possible investigation steps

- Confirm the parent is the legitimate, signed Obsidian binary by reviewing its code signature and hash, and identify
the user and host context of the execution.
- Review the child process command line for download-and-execute behavior, such as `Invoke-WebRequest`/`iwr`,
`Start-BitsTransfer`, `curl`, base64 or `-EncodedCommand`, `-ExecutionPolicy Bypass`, and `-WindowStyle Hidden` on
Windows, or `do shell script` and base64-decoded payloads launched via `osascript` on macOS.
- Inspect the vault's plugin configuration at `.obsidian/plugins/obsidian-shellcommands/data.json` and
`.obsidian/community-plugins.json` for attacker-defined commands and recently enabled plugins.
- Correlate surrounding network telemetry from the child process to identify connections to staging or command and
control infrastructure, and assess the reputation of any contacted IPs or domains.
- On macOS, check `~/Library/LaunchAgents/` for newly created property list files that establish persistence.

### False positive analysis

- The Obsidian "Shell Commands" community plugin can run user-defined commands legitimately, which will cause Obsidian
to spawn an interpreter. Validate the commands configured in the plugin's `data.json` and add exceptions for trusted
users or hosts.
- Other community plugins that shell out to local tooling can spawn an interpreter during normal use. Review the child
process command line and exclude known-good plugin activity.

### Response and remediation

- Terminate the interpreter process and any follow-on processes, and isolate the host if a payload was retrieved or
executed.
- Disable or remove the malicious Obsidian plugin configuration (`obsidian-shellcommands` data.json) and any unexpected
community plugins, and turn off community plugin sync for the affected vault.
- Remove dropped artifacts and persistence (for example, files staged in the user temp directory or macOS LaunchAgents),
and block the observed staging/command and control indicators at the firewall and DNS layers.
- Rotate any credentials that were entered into, or accessible from, the compromised host.
"""
references = [
"https://www.elastic.co/security-labs/phantom-in-the-vault",
"https://github.com/Taitava/obsidian-shellcommands",
"https://attack.mitre.org/techniques/T1059/",
"https://attack.mitre.org/techniques/T1204/002/",
]
risk_score = 47
rule_id = "8fed0d43-cf06-47c3-a5b8-11f1179d5000"
setup = """## Setup

This rule is designed for data generated by [Elastic Defend](https://www.elastic.co/security/endpoint-security), which
provides native endpoint detection and response, along with event enrichments designed to work with our detection
rules.

Setup instructions: https://ela.st/install-elastic-defend
"""
severity = "medium"
tags = [
"Domain: Endpoint",
"OS: Windows",
"OS: macOS",
"Use Case: Threat Detection",
"Tactic: Execution",
"Data Source: Elastic Defend",
"Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "eql"

query = '''
process where event.type == "start" and
(
/* Windows: Obsidian launching PowerShell is the documented PhantomPulse behavior and is
strongly anomalous on its own; cmd.exe is gated behind download/execution arguments to
suppress benign community-plugin shell-outs. */
(
host.os.type == "windows" and
process.parent.name : "Obsidian.exe" and
(
process.name : ("powershell.exe", "pwsh.exe") or
(
process.name : "cmd.exe" and
process.command_line : (
"*http*", "*curl*", "*certutil*", "*bitsadmin*", "*powershell*",
"*mshta*", "*rundll32*", "*regsvr32*", "*wscript*", "*cscript*",
"*-enc*", "*base64*"
)
)
)
) or
/* macOS: Obsidian launching osascript is the documented AppleScript vector; bare shells are
gated behind download/decode arguments. */
(
host.os.type == "macos" and
process.parent.name : "Obsidian" and
(
process.name : "osascript" or
(
process.name : ("sh", "bash", "zsh") and
process.command_line : (
"*http*", "*curl*", "*wget*", "*nscurl*", "*base64*", "*osascript*", "*/dev/tcp*"
)
)
)
)
)
'''


[[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.001"
name = "PowerShell"
reference = "https://attack.mitre.org/techniques/T1059/001/"

[[rule.threat.technique.subtechnique]]
id = "T1059.002"
name = "AppleScript"
reference = "https://attack.mitre.org/techniques/T1059/002/"

[[rule.threat.technique.subtechnique]]
id = "T1059.003"
name = "Windows Command Shell"
reference = "https://attack.mitre.org/techniques/T1059/003/"

[[rule.threat.technique.subtechnique]]
id = "T1059.004"
name = "Unix Shell"
reference = "https://attack.mitre.org/techniques/T1059/004/"


[[rule.threat.technique]]
id = "T1204"
name = "User Execution"
reference = "https://attack.mitre.org/techniques/T1204/"
[[rule.threat.technique.subtechnique]]
id = "T1204.002"
name = "Malicious File"
reference = "https://attack.mitre.org/techniques/T1204/002/"



[rule.threat.tactic]
id = "TA0002"
name = "Execution"
reference = "https://attack.mitre.org/tactics/TA0002/"

[rule.investigation_fields]
field_names = [
"host.os.type",
"user.name",
"process.parent.name",
"process.parent.executable",
"process.name",
"process.executable",
"process.command_line",
]

Loading