Detection rules › Sublime MQL
Attachment: HTML attachment with login portal indicators
Recursively scans files and archives to detect indicators of login portals implemented in HTML files. This is a known credential theft technique used by threat actors.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | Credential Phishing |
| Tactics and techniques | HTML smuggling, Scripting |
Event coverage
| Message attribute |
|---|
| attachments (collection) |
| headers.auth_summary |
| sender.email |
| type |
Rule body MQL
type.inbound
and any(attachments,
(
.file_extension in~ ("html", "htm", "shtml", "dhtml")
or .file_extension in~ $file_extensions_common_archives
or .file_type == "html"
)
and any(file.explode(.),
// suspicious strings found in javascript
(
length(filter(.scan.javascript.strings,
strings.ilike(., "*password*", )
)
) >= 2
and 2 of (
any(.scan.javascript.strings,
strings.ilike(., "*incorrect*")
),
any(.scan.javascript.strings, strings.ilike(., "*invalid*")),
any(.scan.javascript.strings, strings.ilike(., "*login*")),
any(.scan.javascript.strings, regex.icontains(., "sign.in")),
)
)
or (
// suspicious strings found outside of javascript, but binexplode'd file still of HTML type
length(filter(.scan.strings.strings,
strings.ilike(., "*password*", )
)
) >= 2
and 2 of (
any(.scan.strings.strings, strings.ilike(., "*incorrect*")),
any(.scan.strings.strings, strings.ilike(., "*invalid*")),
any(.scan.strings.strings, strings.ilike(., "*login*")),
any(.scan.strings.strings, strings.ilike(., "*<script>*")),
any(.scan.strings.strings, regex.icontains(., "sign.in")),
any(.scan.strings.strings,
regex.icontains(.,
'<title>.[^<]+(Payment|Invoice|Statement|Login|Microsoft|Email|Excel)'
)
)
)
)
or
// Known phishing obfuscation
2 of (
// Enter password
any(.scan.strings.strings,
strings.ilike(.,
"*&#69;&#110;&#116;&#101;&#114;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*"
)
),
// Forgotten my password
any(.scan.strings.strings,
strings.ilike(.,
"*&#70;&#111;&#114;&#103;&#111;&#116;&#116;&#101;&#110;&#32;&#109;&#121;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*"
)
),
// Sign in
any(.scan.strings.strings,
strings.ilike(.,
"*&#83;&#105;&#103;&#110;&#32;&#105;&#110*"
)
)
)
)
)
and (
(
// exclude internal mailers where there is no SPF configured.
// if the sender's root domain is an org domain, we
// ensure there's an SPF pass
// we use root_domain because it's typically subdomains that are misconfigured
sender.email.domain.root_domain in $org_domains
and headers.auth_summary.spf.pass
)
or sender.email.domain.root_domain not in $org_domains
)
// negate highly trusted sender domains unless they fail DMARC authentication
and (
(
sender.email.domain.root_domain in $high_trust_sender_root_domains
and not headers.auth_summary.dmarc.pass
)
or sender.email.domain.root_domain not in $high_trust_sender_root_domains
)
and (
(
not profile.by_sender().solicited
and profile.by_sender().prevalence in ("new", "outlier")
)
or (
profile.by_sender().any_messages_malicious_or_spam
and not profile.by_sender().any_messages_benign
)
)
Detection logic
Scope: inbound message.
Recursively scans files and archives to detect indicators of login portals implemented in HTML files. This is a known credential theft technique used by threat actors.
- inbound message
any of
attachmentswhere all hold:any of:
- .file_extension in ('html', 'htm', 'shtml', 'dhtml')
- .file_extension in $file_extensions_common_archives
- .file_type is 'html'
any of
file.explode(.)where any holds:all of:
- length(filter(.scan.javascript.strings, strings.ilike(., '*password*'))) ≥ 2
at least 2 of:
any of
.scan.javascript.stringswhere:- . matches '*incorrect*'
any of
.scan.javascript.stringswhere:- . matches '*invalid*'
any of
.scan.javascript.stringswhere:- . matches '*login*'
any of
.scan.javascript.stringswhere:- . matches 'sign.in'
all of:
- length(filter(.scan.strings.strings, strings.ilike(., '*password*'))) ≥ 2
at least 2 of:
any of
.scan.strings.stringswhere:- . matches '*incorrect*'
any of
.scan.strings.stringswhere:- . matches '*invalid*'
any of
.scan.strings.stringswhere:- . matches '*login*'
any of
.scan.strings.stringswhere:- . matches '*<script>*'
any of
.scan.strings.stringswhere:- . matches 'sign.in'
any of
.scan.strings.stringswhere:- . matches '<title>.[^<]+(Payment|Invoice|Statement|Login|Microsoft|Email|Excel)'
at least 2 of:
any of
.scan.strings.stringswhere:- . matches '*&#69;&#110;&#116;&#101;&#114;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*'
any of
.scan.strings.stringswhere:- . matches '*&#70;&#111;&#114;&#103;&#111;&#116;&#116;&#101;&#110;&#32;&#109;&#121;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*'
any of
.scan.strings.stringswhere:- . matches '*&#83;&#105;&#103;&#110;&#32;&#105;&#110*'
any of:
all of:
- sender.email.domain.root_domain in $org_domains
- headers.auth_summary.spf.pass
- sender.email.domain.root_domain not in $org_domains
any of:
all of:
- sender.email.domain.root_domain in $high_trust_sender_root_domains
not:
- headers.auth_summary.dmarc.pass
- sender.email.domain.root_domain not in $high_trust_sender_root_domains
any of:
all of:
not:
- profile.by_sender().solicited
- profile.by_sender().prevalence in ('new', 'outlier')
all of:
- profile.by_sender().any_messages_malicious_or_spam
not:
- profile.by_sender().any_messages_benign
Inspects: attachments[].file_extension, attachments[].file_type, headers.auth_summary.dmarc.pass, headers.auth_summary.spf.pass, sender.email.domain.root_domain, type.inbound. Sensors: file.explode, profile.by_sender, regex.icontains, strings.ilike. Reference lists: $file_extensions_common_archives, $high_trust_sender_root_domains, $org_domains.
Indicators matched (15)
| Field | Match | Value |
|---|---|---|
attachments[].file_extension | member | html |
attachments[].file_extension | member | htm |
attachments[].file_extension | member | shtml |
attachments[].file_extension | member | dhtml |
attachments[].file_type | equals | html |
strings.ilike | substring | *password* |
strings.ilike | substring | *incorrect* |
strings.ilike | substring | *invalid* |
strings.ilike | substring | *login* |
regex.icontains | regex | sign.in |
strings.ilike | substring | *<script>* |
regex.icontains | regex | <title>.[^<]+(Payment|Invoice|Statement|Login|Microsoft|Email|Excel) |
3 more
strings.ilike | substring | *&#69;&#110;&#116;&#101;&#114;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100* |
strings.ilike | substring | *&#70;&#111;&#114;&#103;&#111;&#116;&#116;&#101;&#110;&#32;&#109;&#121;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100* |
strings.ilike | substring | *&#83;&#105;&#103;&#110;&#32;&#105;&#110* |