Detection rules › Elastic
Suspicious Terraform Provider Execution and Network Connection
Identifies an unsigned or untrusted Terraform provider binary launched by Terraform during init or plan from a user writable path outside the official registry.terraform.io provider cache, followed by an outbound network connection. Attackers may supply malicious provider binaries via typosquatted provider registries to establish access and download additional tooling, as observed in the KelpDAO incident.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Command & Control |
Telemetry coverage
Rule body
[rule]
description = """
Identifies an unsigned or untrusted Terraform provider binary launched by Terraform during init or plan from a user
writable path outside the official registry.terraform.io provider cache, followed by an outbound network connection.
Attackers may supply malicious provider binaries via typosquatted provider registries to establish access and download
additional tooling, as observed in the KelpDAO incident.
"""
id = "b4e8a1c3-5d6f-4a7b-9e0c-2d3f8a1b6c7e"
license = "Elastic License v2"
name = "Suspicious Terraform Provider Execution and Network Connection"
os_list = ["macos", "windows"]
reference = [
"https://layerzero.network/publications/kelpdao-incident-report.pdf",
"https://developer.hashicorp.com/terraform/cli/plugins/about-plugins",
]
version = "1.0.2"
query = '''
sequence by process.entity_id with maxspan=1m
[process where event.action == "exec" and
process.name like "terraform-provider-*" and process.parent.name in ("terraform", "terraform.exe") and
process.executable like ("/Users/*", "/tmp/*", "/private/tmp/*", "?:\\Users\\*", "?:\\Users\\ProgramData\\*") and
(process.code_signature.trusted == false or process.code_signature.exists == false) and
process.parent.command_line like ("* init*", "*plan*") and
not process.command_line like (".terraform/providers/registry.terraform.io/*", ".terraform\\providers\\registry.terraform.io\\*") and
not process.executable like ("*/registry.terraform.io/*",
"*/registry.opentofu.org/*",
"*.terraform.d/plugins/terraform.local/Mastercard/*",
"*\\registry.terraform.io\\*",
"*\\registry.opentofu.org\\*",
"*.terraform.d\\plugins\\terraform.local\\Mastercard\\*")]
[any where
(event.category == "network" and event.action == "connection_attempted" and destination.domain != null and
not (process.name == "terraform-provider-aws" and destination.domain like "*.amazonaws.com") and
not (process.name == "terraform-provider-elasticstate" and destination.domain like "*.elastic.cloud")) or
(event.category == "dns" and dns.question.name != null and
not (process.name == "terraform-provider-aws.exe" and dns.question.name like "*.amazonaws.com") and
not (process.name == "terraform-provider-elasticstate.exe" and dns.question.name like "*.elastic.cloud"))
]
'''
min_endpoint_version = "8.16.0"
optional_actions = []
[[actions]]
action = "kill_process"
field = "process.entity_id"
state = 0
[[threat]]
framework = "MITRE ATT&CK"
[[threat.technique]]
id = "T1195"
name = "Supply Chain Compromise"
reference = "https://attack.mitre.org/techniques/T1195/"
[[threat.technique.subtechnique]]
id = "T1195.002"
name = "Compromise Software Supply Chain"
reference = "https://attack.mitre.org/techniques/T1195/002/"
[threat.tactic]
id = "TA0001"
name = "Initial Access"
reference = "https://attack.mitre.org/tactics/TA0001/"
[[threat]]
framework = "MITRE ATT&CK"
[[threat.technique]]
id = "T1071"
name = "Application Layer Protocol"
reference = "https://attack.mitre.org/techniques/T1071/"
[[threat.technique.subtechnique]]
id = "T1071.001"
name = "Web Protocols"
reference = "https://attack.mitre.org/techniques/T1071/001/"
[threat.tactic]
id = "TA0011"
name = "Command and Control"
reference = "https://attack.mitre.org/tactics/TA0011/"
[internal]
min_endpoint_version = "8.16.0"
Stages and Predicates
Ordered sequence: each step below must occur in order within 1m, correlated by process.entity_id.
Stage 1: process
[process where event.action == "exec" and
process.name like "terraform-provider-*" and process.parent.name in ("terraform", "terraform.exe") and
process.executable like ("/Users/*", "/tmp/*", "/private/tmp/*", "?:\\Users\\*", "?:\\Users\\ProgramData\\*") and
(process.code_signature.trusted == false or process.code_signature.exists == false) and
process.parent.command_line like ("* init*", "*plan*") and
not process.command_line like (".terraform/providers/registry.terraform.io/*", ".terraform\\providers\\registry.terraform.io\\*") and
not process.executable like ("*/registry.terraform.io/*",
"*/registry.opentofu.org/*",
"*.terraform.d/plugins/terraform.local/Mastercard/*",
"*\\registry.terraform.io\\*",
"*\\registry.opentofu.org\\*",
"*.terraform.d\\plugins\\terraform.local\\Mastercard\\*")]
Stage 2: any
[any where
(event.category == "network" and event.action == "connection_attempted" and destination.domain != null and
not (process.name == "terraform-provider-aws" and destination.domain like "*.amazonaws.com") and
not (process.name == "terraform-provider-elasticstate" and destination.domain like "*.elastic.cloud")) or
(event.category == "dns" and dns.question.name != null and
not (process.name == "terraform-provider-aws.exe" and dns.question.name like "*.amazonaws.com") and
not (process.name == "terraform-provider-elasticstate.exe" and dns.question.name like "*.elastic.cloud"))
]
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
process.command_line | starts_with | .terraform/providers/registry.terraform.io/, .terraform\providers\registry.terraform.io\ | excludes:process.command_line field:"process.command_line" value:".terraform/providers/registry.terraform.io/" field:"process.command_line" value:".terraform\providers\registry.terraform.io\" |
process.executable | match | /registry.terraform.io/, /registry.opentofu.org/, .terraform.d/plugins/terraform.local/Mastercard/, \registry.terraform.io\, \registry.opentofu.org\, .terraform.d\plugins\terraform.local\Mastercard\ | excludes:process.executable |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
destination.domain | is_not_null | field:"DestinationHostname" kind:is_not_null | |
dns.question.name | is_not_null | field:"QueryName" kind:is_not_null | |
event.action | eq |
| field:"EventType" kind:eq |
event.category | eq |
| field:"event.category" kind:eq |
process.code_signature.exists | eq |
| field:"process.code_signature.exists" kind:eq value:"false" |
process.code_signature.trusted | eq |
| field:"process.code_signature.trusted" kind:eq value:"false" |
process.executable | wildcard |
| field:"Image" kind:wildcard |
process.name | wildcard |
| field:"process_name" kind:wildcard value:"terraform-provider-*" |
process.parent.command_line | wildcard |
| field:"ParentCommandLine" kind:wildcard |
process.parent.name | in |
| field:"parent_process_name" kind:in |