Detection rules › Sublime MQL
Attachment: ICS calendar with embedded file from internal sender with SPF failure
Detects calendar invitations (ICS files) from internal domains that fail SPF authentication and contain embedded attachments, with single attendee and organizer both from organizational domains.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | Credential Phishing |
| Tactics and techniques | Spoofing, Evasion, ICS Phishing |
Event coverage
| Message attribute |
|---|
| attachments (collection) |
| headers.auth_summary |
| sender.email |
| type |
Rule body MQL
type.inbound
and sender.email.domain.domain in $org_domains
and not coalesce(headers.auth_summary.dmarc.pass, false)
// has an ICS file
and any(attachments,
.file_extension == "ics" or .content_type == "text/calendar"
)
// and a single other attachment that isn't an ICS but is embedded in the ICS
and length(filter(attachments,
not (
.file_extension == "ics"
or .content_type == "text/calendar"
or .file_type in $file_types_images
)
and any(filter(attachments,
.file_extension == "ics"
or .content_type == "text/calendar"
),
strings.contains(file.parse_text(.).text,
..content_id
)
)
)
) == 1
// exlode the ics file and look at the VEVENT file
and any(filter(attachments,
.file_extension == "ics" or .content_type == "text/calendar"
),
any(file.explode(.),
// attendees and org are both within org_domains
any(.scan.ics.calendars,
any(.components,
.type == "VEVENT"
and length(.attendees) == 1
and all(.attendees,
.mailbox.email.domain.domain in $org_domains
)
and any(.organizers,
.mailbox.email.domain.domain in $org_domains
)
and length(.attachments) > 0
and all(.attachments,
.type == "uri" and strings.starts_with(.uri, "CID:")
)
)
)
)
)
Detection logic
Scope: inbound message.
Detects calendar invitations (ICS files) from internal domains that fail SPF authentication and contain embedded attachments, with single attendee and organizer both from organizational domains.
- inbound message
- sender.email.domain.domain in $org_domains
not:
- coalesce(headers.auth_summary.dmarc.pass)
any of
attachmentswhere any holds:- .file_extension is 'ics'
- .content_type is 'text/calendar'
- length(filter(attachments, not .file_extension == 'ics' or .content_type == 'text/calendar' or .file_type in $file_types_images and any(filter(attachments, .file_extension == 'ics' or .content_type == 'text/calendar'), strings.contains(file.parse_text(.).text, ..content_id)))) is 1
any of
filter(attachments)where:any of
file.explode(.)where:any of
.scan.ics.calendarswhere:any of
.componentswhere all hold:- .type is 'VEVENT'
- length(.attendees) is 1
all of
.attendeeswhere:- .mailbox.email.domain.domain in $org_domains
any of
.organizerswhere:- .mailbox.email.domain.domain in $org_domains
- length(.attachments) > 0
all of
.attachmentswhere all hold:- .type is 'uri'
- .uri starts with 'CID:'
Inspects: attachments[].content_id, attachments[].content_type, attachments[].file_extension, attachments[].file_type, headers.auth_summary.dmarc.pass, sender.email.domain.domain, type.inbound. Sensors: file.explode, file.parse_text, strings.contains, strings.starts_with. Reference lists: $file_types_images, $org_domains.
Indicators matched (5)
| Field | Match | Value |
|---|---|---|
attachments[].file_extension | equals | ics |
attachments[].content_type | equals | text/calendar |
file.explode(filter(attachments)[])[].scan.ics.calendars[].components[].type | equals | VEVENT |
file.explode(filter(attachments)[])[].scan.ics.calendars[].components[].attachments[].type | equals | uri |
strings.starts_with | prefix | CID: |