Detection rules › Panther
Panther rules: teleport
A long-lived cert was created
#An unusually long-lived Teleport certificate was created
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Detection logic
from datetime import datetime, timedelta
from typing import Dict, Tuple
from panther_base_helpers import (
golang_nanotime_to_python_datetime,
panther_nanotime_to_python_datetime,
)
PANTHER_TIME_FORMAT = r"%Y-%m-%d %H:%M:%S.%f"
# Tune this to be some Greatest Common Denominator of session TTLs for your
# environment
MAXIMUM_NORMAL_VALIDITY_INTERVAL = timedelta(hours=12)
# To allow some time in between when a request is submitted and authorized
# vs when the certificate actually gets generated. In practice, this is much
# less than 5 seconds.
ISSUANCE_GRACE_PERIOD = timedelta(seconds=5)
# You can audit your logs in Panther to try and understand your role/validity
# patterns from a known-good period of access.
# A query example:
# ```sql
# SELECT
# cluster_name,
# identity:roles,
# DATEDIFF('HOUR', time, identity:expires) AS validity
# FROM
# panther_logs.public.gravitational_teleportaudit
# WHERE
# p_occurs_between('2023-09-01 00:00:00','2023-10-06 21:00:00Z')
# AND event = 'cert.create'
# GROUP BY cluster_name, identity:roles, validity
# ORDER BY validity DESC
# ```
# A dictionary of:
# cluster names: to a dictionary of:
# role names: mapping to a tuple of:
# ( maximum usual validity, expiration datetime for this rule )
CLUSTER_ROLE_MAX_VALIDITIES: Dict[str, Dict[str, Tuple[timedelta, datetime]]] = {
# "teleport.example.com": {
# "example_role": (timedelta(hours=720), datetime(2023, 12, 01, 01, 02, 03)),
# "other_example_role": (timedelta(hours=720), datetime.max),
# },
}
def rule(event):
if not event.get("event") == "cert.create":
return False
max_validity = MAXIMUM_NORMAL_VALIDITY_INTERVAL + ISSUANCE_GRACE_PERIOD
for role in event.deep_get("identity", "roles", default=[]):
validity, expiration = CLUSTER_ROLE_MAX_VALIDITIES.get(event.get("cluster_name"), {}).get(
role, (None, None)
)
if validity and expiration:
# Ignore exceptions that have passed their expiry date
if datetime.utcnow() < expiration:
max_validity = max(max_validity, validity)
return validity_interval(event) > max_validity
def validity_interval(event):
event_time = panther_nanotime_to_python_datetime(event.get("time"))
expires = golang_nanotime_to_python_datetime(
event.deep_get("identity", "expires", default=None)
)
if not event_time and expires:
return False
interval = expires - event_time
return interval
def title(event):
identity = event.deep_get("identity", "user", default="<Cert with no User!?>")
return (
f"A Certificate for [{identity}] "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}] "
f"has been issued for an unusually long time: {validity_interval(event)!r} "
)
Rule specification
AnalysisType: rule
Filename: teleport_long_lived_certs.py
RuleID: Teleport.LongLivedCerts
DisplayName: A long-lived cert was created
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- Teleport
Severity: Medium
Description: An unusually long-lived Teleport certificate was created
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0003:T1098
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
Teleport certificates are usually issued for a short period of time. Alert if long-lived certificates were created.
SummaryAttributes:
- event
- code
- time
- identity
Stages and Predicates
Fires on Gravitational.TeleportAudit events when the condition below holds.
Condition
eventiscert.create
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"cert.create" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
user | identity.user |
cluster_name |
Response runbook
Teleport certificates are usually issued for a short period of time. Alert if long-lived certificates were created.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"cert_type": "user",
"cluster_name": "teleport.example.com",
"code": "TC000I",
"ei": 0,
"event": "cert.create",
"identity": {
"disallow_reissue": true,
"expires": "2043-09-17T22:00:00.444444428Z",
"impersonator": "bot-application",
"kubernetes_cluster": "staging",
"kubernetes_groups": [
"application"
],
"logins": [
"-teleport-nologin-88888888-4444-4444-4444-222222222222",
"-teleport-internal-join"
],
"prev_identity_expires": "0001-01-01T00:00:00Z",
"roles": [
"application"
],
"route_to_cluster": "teleport.example.com",
"teleport_cluster": "teleport.example.com",
"traits": {},
"user": "bot-application"
},
"time": "2023-09-17 21:00:00.000000",
"uid": "88888888-4444-4444-4444-222222222222"
}
A SAML Connector was created or modified
#A SAML connector was created or modified
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Resource Development |
Detection logic
def rule(event):
return event.get("event") == "saml.created"
def title(event):
return (
f"A SAML connector was created or updated by User [{event.get('user', '<UNKNOWN_USER>')}] "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Rule specification
AnalysisType: rule
Filename: teleport_saml_created.py
RuleID: Teleport.SAMLCreated
DisplayName: A SAML Connector was created or modified
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- Teleport
Severity: High
Description: A SAML connector was created or modified
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0042:T1585
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
When a SAML connector is modified, it can potentially change the trust model of the Teleport Cluster. Validate that these changes were expected and correct.
SummaryAttributes:
- event
- code
- user
- name
Stages and Predicates
Fires on Gravitational.TeleportAudit events when the condition below holds.
Condition
eventissaml.created
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"saml.created" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
cluster_name |
Response runbook
When a SAML connector is modified, it can potentially change the trust model of the Teleport Cluster. Validate that these changes were expected and correct.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"cluster_name": "teleport.example.com",
"code": "T8200I",
"ei": 0,
"event": "saml.created",
"name": "okta",
"time": "2023-09-19 18:00:00",
"uid": "88888888-4444-4444-4444-222222222222",
"user": "max.mustermann@zumbeispiel.example"
}
A Teleport Lock was created
#A Teleport Lock was created
Detection logic
def rule(event):
return event.get("event") == "lock.created"
def title(event):
return (
f"A Teleport Lock was created by {event.get('updated_by', '<UNKNOWN_UPDATED_BY>')} "
f"to Lock out user {event.get('target', {}).get('user', '<UNKNOWN_USER>')} "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Rule specification
AnalysisType: rule
Filename: teleport_lock_created.py
RuleID: Teleport.LockCreated
DisplayName: A Teleport Lock was created
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- Teleport
Severity: Info
Description: A Teleport Lock was created
DedupPeriodMinutes: 60
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
A Teleport Lock was created; this is an unusual administrative action. Investigate to understand why a Lock was created.
SummaryAttributes:
- event
- code
- time
- identity
Stages and Predicates
Fires on Gravitational.TeleportAudit events when the condition below holds.
Condition
eventislock.created
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"lock.created" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
updated_by | |
user | target.user |
cluster_name |
Response runbook
A Teleport Lock was created; this is an unusual administrative action. Investigate to understand why a Lock was created.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"cluster_name": "teleport.example.com",
"code": "TLK00I",
"ei": 0,
"event": "lock.created",
"expires": "0001-01-01T00:00:00Z",
"name": "88888888-4444-4444-4444-222222222222",
"target": {
"user": "user-to-disable"
},
"time": "2023-09-21T00:00:00.000000Z",
"uid": "88888888-4444-4444-4444-222222222222",
"updated_by": "max.mustermann@example.com",
"user": "max.mustermann@example.com"
}
A Teleport Role was modified or created
#A Teleport Role was modified or created
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Detection logic
def rule(event):
return event.get("event") == "role.created"
def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] created Role "
f"[{event.get('name', '<UNKNOWN_NAME>')}] "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Rule specification
AnalysisType: rule
Filename: teleport_role_created.py
RuleID: Teleport.RoleCreated
DisplayName: A Teleport Role was modified or created
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- Teleport
Severity: Medium
Description: A Teleport Role was modified or created
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0003:T1098.001
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
A Teleport Role was modified or created. Validate its legitimacy.
SummaryAttributes:
- event
- code
- user
- name
Stages and Predicates
Fires on Gravitational.TeleportAudit events when the condition below holds.
Condition
eventisrole.created
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"role.created" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
name |
cluster_name |
Response runbook
A Teleport Role was modified or created. Validate its legitimacy.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"cluster_name": "teleport.example.com",
"code": "T9000I",
"ei": 0,
"event": "role.created",
"expires": "0001-01-01T00:00:00Z",
"name": "teleport-event-handler",
"time": "2023-09-20T23:00:000.000000Z",
"uid": "88888888-4444-4444-4444-222222222222",
"user": "max.mustermann@example.com"
}
A user authenticated with SAML, but from an unknown company domain
#A user authenticated with SAML, but from an unknown company domain
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Detection logic
def rule(event):
cluster = event.get("cluster_name", "")
user_domain = event.get("user", "@").split("@")[-1]
return (
event.get("event") == "user.login"
and event.get("success") is True
and event.get("method") == "saml"
and not cluster.endswith(user_domain)
)
def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] logged into "
f"[{event.get('cluster_name', '<UNNAMED_CLUSTER>')}] using SAML from a different domain"
)
Rule specification
AnalysisType: rule
Filename: teleport_saml_login_not_company_domain.py
RuleID: Teleport.SAMLLoginWithoutCompanyDomain
DisplayName: "A user authenticated with SAML, but from an unknown company domain"
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- Teleport
Severity: Medium
Description: "A user authenticated with SAML, but from an unknown company domain"
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0003:T1098
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
A user authenticated with SAML, but from an unknown company domain
SummaryAttributes:
- event
- code
- user
- method
- mfa_device
Stages and Predicates
Fires on Gravitational.TeleportAudit events when all of the conditions below hold.
Condition
eventisuser.loginsuccessistruemethodissaml
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"user.login" |
method | eq |
| field:"method" kind:eq value:"saml" |
success | eq |
| field:"success" kind:eq value:"true" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
cluster_name |
Response runbook
A user authenticated with SAML, but from an unknown company domain
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"cluster_name": "teleport.example.com",
"code": "T1001I",
"ei": 0,
"event": "user.login",
"method": "saml",
"success": true,
"time": "2023-09-18 00:00:00",
"uid": "88888888-4444-4444-4444-222222222222",
"user": "wtf.how@omghax.gravitational.io"
}
A User from the company domain(s) Logged in without SAML
#A User from the company domain(s) Logged in without SAML
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Detection logic
def rule(event):
user_domain = event.get("user", "@").split("@")[-1]
cluster = event.get("cluster_name", "")
return bool(
event.get("event") == "user.login"
and event.get("success") is True
and cluster.endswith(user_domain)
and event.get("method") != "saml"
)
def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] logged into "
f"[{event.get('cluster_name', '<UNNAMED_CLUSTER>')}] without using SAML"
)
Rule specification
AnalysisType: rule
Filename: teleport_company_domain_login_without_saml.py
RuleID: Teleport.CompanyDomainLoginWithoutSAML
DisplayName: "A User from the company domain(s) Logged in without SAML"
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- Teleport
Severity: Medium
Description: "A User from the company domain(s) Logged in without SAML"
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0005:T1562
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
A User from the company domain(s) Logged in without SAML
SummaryAttributes:
- event
- code
- user
- method
- mfa_device
Stages and Predicates
Fires on Gravitational.TeleportAudit events when all of the conditions below hold.
Condition
eventisuser.loginsuccessistruemethodis notsaml
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"user.login" |
method | ne |
| field:"method" kind:ne value:"saml" |
success | eq |
| field:"success" kind:eq value:"true" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
cluster_name |
Response runbook
A User from the company domain(s) Logged in without SAML
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"cluster_name": "teleport.example.com",
"code": "T1001I",
"ei": 0,
"event": "user.login",
"method": "local",
"success": true,
"time": "2023-09-18 00:00:00",
"uid": "88888888-4444-4444-4444-222222222222",
"user": "jane.doe@example.com"
}
Teleport Create User Accounts
#A user has been manually created, modified, or deleted
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Detection logic
from panther_base_helpers import pattern_match_list
USER_CREATE_PATTERNS = [
"chage", # user password expiry
"passwd", # change passwords for users
"user*", # create, modify, and delete users
]
def rule(event):
# Filter the events
if event.get("event") != "session.command":
return False
# Check that the program matches our list above
return pattern_match_list(event.get("program", ""), USER_CREATE_PATTERNS)
def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] has manually modified system users "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Rule specification
AnalysisType: rule
Filename: teleport_create_user_accounts.py
RuleID: "Teleport.CreateUserAccounts"
DisplayName: "Teleport Create User Accounts"
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- SSH
- Persistence:Create Account
Reports:
MITRE ATT&CK:
- TA0003:T1136
Severity: High
Description: A user has been manually created, modified, or deleted
DedupPeriodMinutes: 15
Reference: https://goteleport.com/docs/management/admin/
Runbook: Analyze why it was manually created and delete it if necessary.
SummaryAttributes:
- event
- code
- user
- program
- path
- return_code
- login
- server_id
- sid
Stages and Predicates
Fires on Gravitational.TeleportAudit events when all of the conditions below hold.
Condition
eventissession.commandany of:
programmatches the patternchageprogrammatches the patternpasswdprogrammatches the patternuser*
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"session.command" |
program | wildcard |
| field:"program" kind:wildcard |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
cluster_name |
Response runbook
Analyze why it was manually created and delete it if necessary.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"argv": [
"jacknew"
],
"cgroup_id": 4294967567,
"code": "T4000I",
"ei": 105,
"event": "session.command",
"login": "root",
"namespace": "default",
"path": "/sbin/userdel",
"pid": 8931,
"ppid": 8930,
"program": "userdel",
"return_code": 0,
"server_id": "e75992b4-9e27-456f-b1c9-7a32da83c661",
"sid": "4244c271-8069-4679-a27e-f7c18f88ce45",
"time": "2020-08-17T18:39:26.192Z",
"uid": "346d3f61-a010-4871-84de-897f50b18118",
"user": "panther"
}
Teleport Network Scan Initiated
#A user has invoked a network scan that could potentially indicate enumeration of the network.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Detection logic
SCAN_COMMANDS = {"arp", "arp-scan", "fping", "nmap"}
def rule(event):
# Filter out commands
if event.get("event") == "session.command" and not event.get("argv"):
return False
# Check that the program is in our watch list
return event.get("program") in SCAN_COMMANDS
def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] has issued a network scan with "
f"[{event.get('program', '<UNKNOWN_PROGRAM>')}] "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Rule specification
AnalysisType: rule
Filename: teleport_network_scanning.py
RuleID: "Teleport.NetworkScanning"
DisplayName: "Teleport Network Scan Initiated"
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- SSH
- Discovery:Network Service Discovery
Severity: Medium
Description: A user has invoked a network scan that could potentially indicate enumeration of the network.
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0007:T1046
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
Find related commands within the time window and determine if the command was invoked legitimately. Examine the arguments to determine how the command was used.
SummaryAttributes:
- event
- code
- user
- program
- path
- return_code
- login
- server_id
- sid
Stages and Predicates
Fires on Gravitational.TeleportAudit events when all of the conditions below hold.
Condition
any of:
eventis notsession.commandargvis present
programis one ofarp,arp-scan,fping,nmap
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
argv | is_null | excludes:argv | |
event | eq | session.command | excludes:event field:"event" value:"session.command" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
program | in |
| field:"program" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
program |
cluster_name |
Response runbook
Find related commands within the time window and determine if the command was invoked legitimately. Examine the arguments to determine how the command was used.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"argv": [
"-v",
"-iR",
"100000",
"-Pn",
"-p",
"80"
],
"cgroup_id": 4294967672,
"code": "T4000I",
"ei": 16,
"event": "session.command",
"login": "root",
"namespace": "default",
"path": "/bin/nmap",
"pid": 13555,
"ppid": 13525,
"program": "nmap",
"return_code": 0,
"server_id": "e75992b4-9e27-456f-b1c9-7a32da83c661",
"sid": "a3562a0e-e57f-4273-9f69-eedb6cd029cb",
"time": "2020-08-17T21:13:47.117Z",
"uid": "c7f6367b-04bb-4b1d-9a3a-0497e8f4a650",
"user": "panther"
}
Teleport Scheduled Jobs
#A user has manually edited the Linux crontab
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution |
Detection logic
def rule(event):
# Filter the events
if event.get("event") != "session.command":
return False
# Ignore list/read events
if "-l" in event.get("argv", []):
return False
return event.get("program") == "crontab"
def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] has modified scheduled jobs"
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Rule specification
AnalysisType: rule
Filename: teleport_scheduled_jobs.py
RuleID: "Teleport.ScheduledJobs"
DisplayName: "Teleport Scheduled Jobs"
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- SSH
- Execution:Scheduled Task/Job
Severity: Medium
Reports:
MITRE ATT&CK:
- TA0002:T1053
Description: A user has manually edited the Linux crontab
Threshold: 10
DedupPeriodMinutes: 15
Reference: https://goteleport.com/docs/management/admin/
Runbook: Validate the user behavior and rotate the host if necessary.
SummaryAttributes:
- event
- code
- user
- program
- path
- return_code
- login
- server_id
- sid
Stages and Predicates
Fires on Gravitational.TeleportAudit events when all of the conditions below hold.
Condition
eventissession.commandargvdoes not contain-lprogramiscrontab
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
argv | contains | -l | excludes:argv field:"argv" value:"-l" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"session.command" |
program | eq |
| field:"program" kind:eq value:"crontab" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
cluster_name |
Response runbook
Validate the user behavior and rotate the host if necessary.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"argv": [],
"cgroup_id": 4294967717,
"code": "T4000I",
"ei": 39,
"event": "session.command",
"login": "root",
"namespace": "default",
"path": "/bin/crontab",
"pid": 18415,
"ppid": 18413,
"program": "crontab",
"return_code": 0,
"server_id": "e073ecab-6091-45da-83e4-80196e7bc659",
"sid": "29a3d18c-2c05-453d-979a-2ed888a14788",
"time": "2020-08-18T00:05:12.465Z",
"uid": "83e88438-efbc-41a2-8135-b0157e0d14c0",
"user": "panther"
}
Teleport SSH Auth Errors
#A high volume of SSH errors could indicate a brute-force attack
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Detection logic
def rule(event):
return bool(event.get("error")) and event.get("event") == "auth"
def title(event):
return (
f"A high volume of SSH errors was detected from user "
f"[{event.get('user', '<UNKNOWN_USER>')}] "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Rule specification
AnalysisType: rule
Filename: teleport_auth_errors.py
RuleID: "Teleport.AuthErrors"
DisplayName: "Teleport SSH Auth Errors"
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- SSH
- Credential Access:Brute Force
Severity: Medium
Reports:
MITRE ATT&CK:
- TA0006:T1110
Description: A high volume of SSH errors could indicate a brute-force attack
Threshold: 10
DedupPeriodMinutes: 15
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
Check that the user making the failed requests legitimately tried logging in that many times.
SummaryAttributes:
- event
- code
- user
- program
- path
- return_code
- login
- server_id
- sid
Stages and Predicates
Fires on Gravitational.TeleportAudit events when all of the conditions below hold.
Condition
erroris presenteventisauth
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
error | is_not_null | field:"error" kind:is_not_null | |
event | eq |
| field:"event" kind:eq value:"auth" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
cluster_name |
Response runbook
Check that the user making the failed requests legitimately tried logging in that many times.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"code": "T3007W",
"error": "ssh: principal \"jack\" not in the set of valid principals for given certificate: [\"ec2-user\"]",
"event": "auth",
"success": false,
"time": "2020-08-13T18:39:42Z",
"uid": "53e474cc-db1c-45f1-a60d-b31239e20098",
"user": "panther"
}
Teleport Suspicious Commands Executed
#A user has invoked a suspicious command that could lead to a host compromise
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution |
Detection logic
SUSPICIOUS_COMMANDS = {"nc", "wget"}
def rule(event):
if event.get("event") != "session.command":
return False
# Ignore commands without arguments
if not event.get("argv"):
return False
return event.get("program") in SUSPICIOUS_COMMANDS
def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] has executed the command "
f"[{event.get('program', '<UNKNOWN_PROGRAM>')}] "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Rule specification
AnalysisType: rule
Filename: teleport_suspicious_commands.py
RuleID: "Teleport.SuspiciousCommands"
DisplayName: "Teleport Suspicious Commands Executed"
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- SSH
- Execution:Command and Scripting Interpreter
Severity: Medium
Description: A user has invoked a suspicious command that could lead to a host compromise
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0002:T1059
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
Find related commands within the time window and determine if the command was invoked legitimately. Examine the arguments to determine how the command was used and reach out to the user to verify the intentions.
SummaryAttributes:
- event
- code
- user
- program
- path
- return_code
- login
- server_id
- sid
Stages and Predicates
Fires on Gravitational.TeleportAudit events when all of the conditions below hold.
Condition
eventissession.commandargvis presentprogramis one ofnc,wget
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
argv | is_not_null | field:"argv" kind:is_not_null | |
event | eq |
| field:"event" kind:eq value:"session.command" |
program | in |
| field:"program" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
program |
cluster_name |
Response runbook
Find related commands within the time window and determine if the command was invoked legitimately. Examine the arguments to determine how the command was used and reach out to the user to verify the intentions.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"argv": [
"-l",
"-p",
"11434"
],
"cgroup_id": 4294967537,
"code": "T4000I",
"ei": 15,
"event": "session.command",
"login": "root",
"namespace": "default",
"path": "/bin/nc",
"pid": 7143,
"ppid": 7115,
"program": "nc",
"return_code": 0,
"server_id": "e75992b4-9e27-456f-b1c9-7a32da83c661",
"sid": "8a3fc038-785b-43f3-8737-827b3e25fe5b",
"time": "2020-08-17T17:40:37.491Z",
"uid": "8eaf8f39-09d4-4a42-a22a-65163d2af702",
"user": "panther"
}
User Logged in as root
#A User logged in as root
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution |
Detection logic
def rule(event):
return event.get("event") == "session.start" and event.get("login") == "root"
def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] logged into "
f"[{event.get('server_hostname', '<UNKNOWN_HOSTNAME>')}] as root "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Rule specification
AnalysisType: rule
Filename: teleport_root_login.py
RuleID: Teleport.RootLogin
DisplayName: User Logged in as root
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- SSH
- Execution:Command and Scripting Interpreter
- Teleport
Severity: Medium
Description: A User logged in as root
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0002:T1059
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
Use user accounts and policies, rather than root when logging in. With access to root, it is possible to evade auditing and logging.
SummaryAttributes:
- event
- code
- user
- login
- server_hostname
- server_labels
Stages and Predicates
Fires on Gravitational.TeleportAudit events when all of the conditions below hold.
Condition
eventissession.startloginisroot
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"session.start" |
login | eq |
| field:"login" kind:eq value:"root" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
server_hostname |
cluster_name |
Response runbook
Use user accounts and policies, rather than root when logging in. With access to root, it is possible to evade auditing and logging.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"addr_remote": "192.0.2.1:35194",
"cluster_name": "teleport.example.com",
"code": "t2000i",
"ei": 0,
"event": "session.start",
"initial_command": [
""
],
"login": "root",
"namespace": "default",
"proto": "ssh",
"server_hostname": "ip-10-0-0-1.us-west-2.compute.internal",
"server_id": "12345678-1111-2222-3333-54e9467ff0e6",
"server_labels": {
"env": "prod",
"role": "project123"
},
"session_recording": "node-sync",
"sid": "12345678-1111-2222-3333-54e9467ff0e6",
"size": "80:25",
"time": "2023-09-18 11:22:33",
"uid": "12345678-1111-2222-3333-54e9467ff0e6",
"user": "max.mustermann@zumbeispiel.example"
}
User Logged in wihout MFA
#A local User logged in without MFA
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Detection logic
SENSITIVE_LOCAL_USERS = ["breakglass"]
def rule(event):
return (
event.get("event") == "user.login"
and event.get("success") == "true"
and event.get("method") == "local"
and not event.get("mfa_device")
)
def severity(event):
if event.get("user") in SENSITIVE_LOCAL_USERS:
return "HIGH"
return "MEDIUM"
def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] logged into "
f"[{event.get('cluster_name', '<UNNAMED_CLUSTER>')}] locally "
f"without using MFA"
)
Rule specification
AnalysisType: rule
Filename: teleport_local_user_login_without_mfa.py
RuleID: Teleport.LocalUserLoginWithoutMFA
DisplayName: User Logged in wihout MFA
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- Teleport
Severity: High
Description: A local User logged in without MFA
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0001:T1078
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
A local user logged in without Multi-Factor Authentication
SummaryAttributes:
- event
- code
- user
- success
- mfa_device
Stages and Predicates
Fires on Gravitational.TeleportAudit events when all of the conditions below hold.
Condition
eventisuser.loginsuccessistruemethodislocalmfa_deviceis empty
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"user.login" |
method | eq |
| field:"method" kind:eq value:"local" |
mfa_device | is_null | field:"mfa_device" kind:is_null | |
success | eq |
| field:"success" kind:eq value:"true" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
cluster_name |
Response runbook
A local user logged in without Multi-Factor Authentication