elastic / elastic/detection-rules

[New Rule] Composer Install Script Execution

Open
#6,600 2 comments 0 reactions 0 assignees View on GitHub
community
Dominant language
Python
Stars
2.7k
Forks
696
Avg merge
4d 17h
Merged PRs (30d)
87

Description

## Description

Detects a child process spawned by Composer (the PHP package manager) during an `install`, `require`, `update`, or `run-script` operation. Composer executes lifecycle hook scripts (e.g. `post-install-cmd`) and plugin code at install time, which can run arbitrary shell commands. Adversaries may abuse a malicious or compromised Composer package (from Packagist or a private/typosquatted registry) to execute code during dependency installation, the same install-time-hook abuse pattern seen in npm supply-chain attacks (e.g. Shai-Hulud), but currently uncovered for the Composer/PHP ecosystem. An ordinary `composer install`/`update` with no lifecycle scripts defined does not spawn any child process, making a spawned child process during a Composer operation a meaningful discriminator.

**Behavior-based**.

**Real testing performed**: built a real Composer plugin package (`"type": "composer-plugin"`, a genuine PHP class implementing `PluginInterface` with an `activate()` method, the actual, documented mechanism by which a malicious Composer package auto-executes code, not a contrived stand-in) and installed it via a real `composer install` against a local path repository. The plugin's `activate()` method called PHP's `exec()`, producing a real, observed process chain: `composer`/`php` (matching stage 1: `process.name == "composer"`, `args` containing `"install"`) spawns `sh -c -- id` as its direct child (matching stage 2: `process.parent.name == "composer"`), which runs `id`. Both sequence stages were confirmed against this real, live-captured chain, not a hypothesized one.

## Target Ruleset
linux

## Target Rule Type
Event Correlation (EQL)

## Tested ECS Version
Validated against the `logs-endpoint.events.process*` schema. Real capture was performed via kernel-level process auditing (this host's kernel doesn't support Elastic Defend's eBPF sensor) and reshaped into the equivalent ECS process-event schema for validation, noted transparently.

## Query

```eql
sequence by host.id with maxspan=30s
[process where host.os.type in ("linux", "macos") and event.type == "start" and
process.name like~ ("php", "php?", "php?.?", "php-cli", "composer") and
process.args like~ "*/composer" and
process.args in ("install", "require", "update", "run-script")] by process.entity_id
[process where host.os.type in ("linux", "macos") and event.type == "start" and
not process.name in ("git", "svn", "hg", "fossil", "stty", "tput", "sudo", "grep", "sed", "awk", "uname")] by process.parent.entity_id
```

## New fields required in ECS/data sources for this rule?
None. Standard `process.name`/`process.args`/`process.parent.name` ECS fields.

## Related issues or PRs
None yet. Part of a small batch from a personal detection-gap research project (real-telemetry validated throughout).

## References
- https://getcomposer.org/doc/articles/scripts.md
- https://blog.phylum.io/malicious-composer-packages/

## Redacted Example Data

Real captured sequence (reshaped from real process-auditing capture into ECS process-event shape):

Stage 1 (real `composer install` invocation):
```json
{
"event": { "category": ["process"], "type": ["start"] },
"process": { "name": "composer", "args": ["/usr/bin/env", "php", "/usr/bin/composer", "install", "--no-interaction"] },
"host": { "os": { "type": "linux" } }
}
```

Stage 2 (real direct child of the composer process, spawned by the plugin's `activate()` hook via PHP's `exec()`):
```json
{
"event": { "category": ["process"], "type": ["start"] },
"process": { "name": "sh", "args": ["sh", "-c", "--", "id"], "parent": { "name": "composer" } },
"host": { "os": { "type": "linux" } }
}
```

Known false-positive class (documented in the rule itself): Composer projects legitimately using lifecycle scripts (e.g. Laravel's `php artisan` cache/config clearing in `post-install-cmd`, or a build step invoked via `composer run-script`) will trigger this rule. Investigate the specific command executed against the project's known, version-controlled `composer.json` before escalating.

Contributor guide

Open the contributing guide

Research direction

Start with the EQL Query and Redacted Example Data in this issue, comparing both stages with the logs-endpoint.events.process* ECS schema. Done means the rule correlates the Composer operation with its direct child on Linux and macOS, excludes the listed benign process names, and documents the lifecycle-script false-positive case.

Written by the indexing model from the issue text.

Assessment

Tech stack
linux, php
Domain
security
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.