Detection rules › Sublime MQL
Credential phishing: Suspicious e-sign agreement document notification
Detects phishing attempts disguised as e-signature requests, characterized by common document sharing phrases, unusual HTML padding, and suspicious link text.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | Credential Phishing |
| Tactics and techniques | Social engineering |
Event coverage
Rule body MQL
type.inbound
and any([subject.subject, sender.display_name],
regex.icontains(strings.replace_confusables(.),
"D[0o]cuLink",
"Agreement",
"Access.&.Appr[0o]ved",
"Agreement.{0,5}Review",
"Attend.and.Review",
"action.re?quired",
"AuthentiSign",
"Completed.File",
"D[0o]chsared",
"D[0o]cshared",
"D[0o]csPoint",
"D[0o]cument.Shared",
"D[0o]cuCentre",
"D[0o]cuCenter",
"D[0o]cCenter",
"D[0o]csOnline",
"D[0o]cSend",
"D[0o]cu?Send",
"d[0o]csign",
"D[0o]cu-eSin",
"D[0o]cu-management",
"\\beSign",
"e\\.sign",
"esign.[0o]nline",
"[SsZz][lL][GgSs][Nn].*D[0o]c",
"e-d[0o]c",
"e-signature",
"e-Verify Doc",
"eSignature",
"eSign&Return",
"eSign[0o]nline",
"Fileshare",
"Review.and.C[0o]mplete",
"Review.&.Sign",
"Sign[0o]nline",
"Signature.Request",
"Shared.C[0o]mpleted",
"Sign.and.Seal",
"viaSign",
"D[0o]cuSign",
"D[0o]csID",
"Complete.{0,10}D[0o]cuSign",
"Enroll & Sign",
"Review and Sign",
"Sign(?:Report|Now)",
"SignD[0o]c",
"D[0o]cxxx",
"d[0o]cufile",
'E\x{00AD}-\x{00AD}S\x{00AD}i\x{00AD}g\x{00AD}n\x{00AD}&Return',
"d[0o]cument.signature",
"Electr[0o]nic.?Signature",
"Complete: ",
"Please (?:Review|Sign)",
"^REVIEW$",
"requests your signature",
"signature on.*contract",
"Independent Contract",
"Contract.*signature",
"add your signature",
"signature needed",
"attn_task",
"DocReq\\b"
)
or (
regex.icontains(strings.replace_confusables(.), "action.re?quired")
and not (
sender.email.domain.root_domain == "sharepointonline.com"
and headers.auth_summary.dmarc.pass
and strings.icontains(subject.subject, "asked to edit")
)
)
)
and (
// unusual repeated patterns in HTML
regex.icontains(body.html.raw, '((<br\s*/?>\s*){20,}|\n{20,})')
or regex.icontains(body.html.raw, '(<p[^>]*>\s*<br\s*/?>\s*</p>\s*){30,}')
or regex.icontains(body.html.raw,
'(<p class=".*?"><span style=".*?"><o:p>&nbsp;</o:p></span></p>\s*){30,}'
)
or regex.icontains(body.html.raw, '(<p>&nbsp;</p>\s*){7,}')
or regex.icontains(body.html.raw, '(<p[^>]*>\s*&nbsp;<br>\s*</p>\s*){5,}')
or regex.icontains(body.html.raw, '(<p[^>]*>&nbsp;</p>\s*){7,}')
or strings.count(body.html.raw, "&nbsp;\u{200C}&nbsp;\u{200C}&nbsp") > 50
or regex.count(body.html.raw,
'<span\s*class\s*=\s*"[^\"]+"\s*>\s*[a-z]\s*<\/span><span\s*class\s*=\s*"[^\"]+"\s*>\s*[a-z]+\s*<\/span>'
) > 50
// lookalike docusign
or regex.icontains(body.html.raw, '>Docus[1l]gn<')
or strings.icontains(body.current_thread.text, 'completed by all parties')
or (
regex.icontains(body.html.inner_text, 'Document')
and length(body.html.inner_text) < 500
)
// common greetings via email.local_part
or any(recipients.to,
// use count to ensure the email address is not part of a disclaimer
strings.icount(body.current_thread.text, .email.local_part) >
// sum allows us to add more logic as needed
sum([
strings.icount(body.current_thread.text,
strings.concat('was sent to ', .email.email)
),
strings.icount(body.current_thread.text,
strings.concat('intended for ', .email.email)
)
]
)
)
// common greetings via mailbox display name
or strings.icount(body.current_thread.text, mailbox.display_name) >
// sum allows us to add more logic as needed
sum([
strings.icount(body.current_thread.text,
strings.concat('was sent to ', mailbox.display_name)
),
strings.icount(body.current_thread.text,
strings.concat('intended for ', mailbox.display_name)
)
]
)
// Abnormally high count of mailto links in raw html
or regex.count(body.html.raw,
'mailto:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
) > 50
// High count of empty elements (padding)
or regex.count(body.html.raw,
'<(?:p|div|span|td)[^>]*>\s*(?:&nbsp;|\s)*\s*</(?:p|div|span|td)>'
) > 30
// HR impersonation
or strings.ilike(sender.display_name, "HR", "H?R", "*Human Resources*")
// Sender display name contains a phone number
or regex.icontains(sender.display_name,
'\+?([ilo0-9]{1}.)?\(?[ilo0-9]{3}?\)?.[ilo0-9]{3}.?[ilo0-9]{4}'
)
)
and (
any(body.links,
// suspicious content within link display_text
regex.icontains(strings.replace_confusables(.display_text),
"activate",
"re-auth",
"verify",
"acknowledg",
"(keep|change).{0,20}(active|password|access)",
'((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
'use.same.pass',
'validate.{0,15}account',
'recover.{0,15}messages',
'(retry|update).{0,10}payment',
'check activity',
'(listen|play).{0,10}(vm|voice)',
'clarify.{0,20}(deposit|wallet|funds)',
'enter.{0,15}teams',
'Review and sign',
'REVIEW.*DOCUMENT',
'Open Document',
'Sign Now',
'complete tasks?'
)
// check that the display_text is all lowercase
or (
regex.contains(.display_text,
"\\bVIEW",
"DOWNLOAD",
"CHECK",
"KEEP.(SAME|MY)",
"VERIFY",
"ACCESS\\b",
"SIGN\\b",
"ENABLE\\b",
"RETAIN",
"PLAY",
"LISTEN",
)
and regex.match(.display_text, "^[^a-z]*[A-Z][^a-z]*$")
)
// the display text is _exactly_
or .display_text in~ ("Open")
// URL fragment containing recipient's address
or .href_url.fragment in map(recipients.to, .email.email)
)
// one hyperlinked image that's not a tracking pixel
or (
length(html.xpath(body.html,
"//a//img[(number(@width) > 5 or not(@width)) and (number(@height) > 5 or not(@height))]"
).nodes
) == 1
and length(body.current_thread.text) < 500
)
or (
length(attachments) > 0
and any(attachments,
(
regex.icontains(beta.ocr(.).text,
"activate",
"re-auth",
"verify",
"acknowledg",
"(keep|change).{0,20}(active|password|access)",
'((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
'use.same.pass',
'validate.{0,15}account',
'recover.{0,15}messages',
'(retry|update).{0,10}payment',
'check activity',
'(listen|play).{0,10}(vm|voice)',
'clarify.{0,20}(deposit|wallet|funds)',
'enter.{0,15}teams',
'Review and sign'
)
)
or (
any(file.explode(.),
regex.icontains(.scan.ocr.raw,
"activate",
"re-auth",
"verify",
"acknowledg",
"(keep|change).{0,20}(active|password|access)",
'((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
'use.same.pass',
'validate.{0,15}account',
'recover.{0,15}messages',
'(retry|update).{0,10}payment',
'check activity',
'(listen|play).{0,10}(vm|voice)',
'clarify.{0,20}(deposit|wallet|funds)',
'enter.{0,15}teams',
'Review and sign'
)
)
)
)
)
)
// the message is unsolicited and no false positives
and (
not profile.by_sender_email().solicited
or profile.by_sender_email().prevalence == "new"
or (
profile.by_sender_email().any_messages_malicious_or_spam
and not profile.by_sender_email().any_messages_benign
)
or (
profile.by_sender_email().any_messages_malicious_or_spam
and profile.by_sender_email().any_messages_benign
and (
not headers.auth_summary.dmarc.pass or not headers.auth_summary.spf.pass
)
)
)
// negate replies/fowards containing legitimate docs
and not (length(headers.references) > 0 or headers.in_reply_to is not null)
// negate highly trusted sender domains unless they fail DMARC authentication
and (
(
sender.email.domain.root_domain in $high_trust_sender_root_domains
and (
any(distinct(headers.hops, .authentication_results.dmarc is not null),
strings.ilike(.authentication_results.dmarc, "*fail")
)
)
)
or sender.email.domain.root_domain not in $high_trust_sender_root_domains
)
Detection logic
Scope: inbound message.
Detects phishing attempts disguised as e-signature requests, characterized by common document sharing phrases, unusual HTML padding, and suspicious link text.
- inbound message
any of
[subject.subject, sender.display_name]where any holds:strings.replace_confusables(.) matches any of 62 patterns
D[0o]cuLinkAgreementAccess.&.Appr[0o]vedAgreement.{0,5}ReviewAttend.and.Reviewaction.re?quiredAuthentiSignCompleted.FileD[0o]chsaredD[0o]csharedD[0o]csPointD[0o]cument.SharedD[0o]cuCentreD[0o]cuCenterD[0o]cCenterD[0o]csOnlineD[0o]cSendD[0o]cu?Sendd[0o]csignD[0o]cu-eSinD[0o]cu-management\\beSigne\\.signesign.[0o]nline[SsZz][lL][GgSs][Nn].*D[0o]ce-d[0o]ce-signaturee-Verify DoceSignatureeSign&ReturneSign[0o]nlineFileshareReview.and.C[0o]mpleteReview.&.SignSign[0o]nlineSignature.RequestShared.C[0o]mpletedSign.and.SealviaSignD[0o]cuSignD[0o]csIDComplete.{0,10}D[0o]cuSignEnroll & SignReview and SignSign(?:Report|Now)SignD[0o]cD[0o]cxxxd[0o]cufileE\x{00AD}-\x{00AD}S\x{00AD}i\x{00AD}g\x{00AD}n\x{00AD}&Returnd[0o]cument.signatureElectr[0o]nic.?SignatureComplete:Please (?:Review|Sign)^REVIEW$requests your signaturesignature on.*contractIndependent ContractContract.*signatureadd your signaturesignature neededattn_taskDocReq\\b
all of:
- strings.replace_confusables(.) matches 'action.re?quired'
not:
all of:
- sender.email.domain.root_domain is 'sharepointonline.com'
- headers.auth_summary.dmarc.pass
- subject.subject contains 'asked to edit'
any of:
- body.html.raw matches '((<br\\s*/?>\\s*){20,}|\\n{20,})'
- body.html.raw matches '(<p[^>]*>\\s*<br\\s*/?>\\s*</p>\\s*){30,}'
- body.html.raw matches '(<p class=".*?"><span style=".*?"><o:p>&nbsp;</o:p></span></p>\\s*){30,}'
- body.html.raw matches '(<p>&nbsp;</p>\\s*){7,}'
- body.html.raw matches '(<p[^>]*>\\s*&nbsp;<br>\\s*</p>\\s*){5,}'
- body.html.raw matches '(<p[^>]*>&nbsp;</p>\\s*){7,}'
- strings.count(body.html.raw, '&nbsp;\\u{200C}&nbsp;\\u{200C}&nbsp') > 50
- regex.count(body.html.raw, '<span\\s*class\\s*=\\s*"[^\\"]+"\\s*>\\s*[a-z]\\s*<\\/span><span\\s*class\\s*=\\s*"[^\\"]+"\\s*>\\s*[a-z]+\\s*<\\/span>') > 50
- body.html.raw matches '>Docus[1l]gn<'
- body.current_thread.text contains 'completed by all parties'
all of:
- body.html.inner_text matches 'Document'
- length(body.html.inner_text) < 500
any of
recipients.towhere:- strings.icount(body.current_thread.text) > sum([strings.icount(body.current_thread.text, strings.concat('was sent to ', .email.email)), strings.icount(body.current_thread.text, strings.concat('intended for ', .email.email))])
- strings.icount(body.current_thread.text) > sum([strings.icount(body.current_thread.text, strings.concat('was sent to ', mailbox.display_name)), strings.icount(body.current_thread.text, strings.concat('intended for ', mailbox.display_name))])
- regex.count(body.html.raw, 'mailto:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}') > 50
- regex.count(body.html.raw, '<(?:p|div|span|td)[^>]*>\\s*(?:&nbsp;|\\s)*\\s*</(?:p|div|span|td)>') > 30
sender.display_name matches any of 3 patterns
HRH?R*Human Resources*
- sender.display_name matches '\\+?([ilo0-9]{1}.)?\\(?[ilo0-9]{3}?\\)?.[ilo0-9]{3}.?[ilo0-9]{4}'
any of:
any of
body.linkswhere any holds:strings.replace_confusables(.display_text) matches any of 19 patterns
activatere-authverifyacknowledg(keep|change).{0,20}(active|password|access)((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)use.same.passvalidate.{0,15}accountrecover.{0,15}messages(retry|update).{0,10}paymentcheck activity(listen|play).{0,10}(vm|voice)clarify.{0,20}(deposit|wallet|funds)enter.{0,15}teamsReview and signREVIEW.*DOCUMENTOpen DocumentSign Nowcomplete tasks?
all of:
.display_text matches any of 11 patterns
\\bVIEWDOWNLOADCHECKKEEP.(SAME|MY)VERIFYACCESS\\bSIGN\\bENABLE\\bRETAINPLAYLISTEN
- .display_text matches '^[^a-z]*[A-Z][^a-z]*$'
- .display_text in ('Open')
- .href_url.fragment in map(recipients.to, .email.email)
all of:
- length(html.xpath(body.html, '//a//img[(number(@width) > 5 or not(@width)) and (number(@height) > 5 or not(@height))]').nodes) is 1
- length(body.current_thread.text) < 500
all of:
- length(attachments) > 0
any of
attachmentswhere any holds:beta.ocr(.).text matches any of 15 patterns
activatere-authverifyacknowledg(keep|change).{0,20}(active|password|access)((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)use.same.passvalidate.{0,15}accountrecover.{0,15}messages(retry|update).{0,10}paymentcheck activity(listen|play).{0,10}(vm|voice)clarify.{0,20}(deposit|wallet|funds)enter.{0,15}teamsReview and sign
any of
file.explode(.)where:.scan.ocr.raw matches any of 15 patterns
activatere-authverifyacknowledg(keep|change).{0,20}(active|password|access)((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)use.same.passvalidate.{0,15}accountrecover.{0,15}messages(retry|update).{0,10}paymentcheck activity(listen|play).{0,10}(vm|voice)clarify.{0,20}(deposit|wallet|funds)enter.{0,15}teamsReview and sign
any of:
not:
- profile.by_sender_email().solicited
- profile.by_sender_email().prevalence is 'new'
all of:
- profile.by_sender_email().any_messages_malicious_or_spam
not:
- profile.by_sender_email().any_messages_benign
all of:
- profile.by_sender_email().any_messages_malicious_or_spam
- profile.by_sender_email().any_messages_benign
any of:
not:
- headers.auth_summary.dmarc.pass
not:
- headers.auth_summary.spf.pass
none of:
- length(headers.references) > 0
- headers.in_reply_to is set
any of:
all of:
- sender.email.domain.root_domain in $high_trust_sender_root_domains
any of
distinct(headers.hops)where:- .authentication_results.dmarc matches '*fail'
- sender.email.domain.root_domain not in $high_trust_sender_root_domains
Inspects: body.current_thread.text, body.html, body.html.inner_text, body.html.raw, body.links, body.links[].display_text, body.links[].href_url.fragment, headers.auth_summary.dmarc.pass, headers.auth_summary.spf.pass, headers.hops, headers.hops[].authentication_results.dmarc, headers.in_reply_to, headers.references, mailbox.display_name, recipients.to, recipients.to[].email.email, recipients.to[].email.local_part, sender.display_name, sender.email.domain.root_domain, subject.subject, type.inbound. Sensors: beta.ocr, file.explode, html.xpath, profile.by_sender_email, regex.contains, regex.count, regex.icontains, regex.match, strings.concat, strings.count, strings.icontains, strings.icount, strings.ilike, strings.replace_confusables. Reference lists: $high_trust_sender_root_domains.
Indicators matched (113)
| Field | Match | Value |
|---|---|---|
regex.icontains | regex | D[0o]cuLink |
regex.icontains | regex | Agreement |
regex.icontains | regex | Access.&.Appr[0o]ved |
regex.icontains | regex | Agreement.{0,5}Review |
regex.icontains | regex | Attend.and.Review |
regex.icontains | regex | action.re?quired |
regex.icontains | regex | AuthentiSign |
regex.icontains | regex | Completed.File |
regex.icontains | regex | D[0o]chsared |
regex.icontains | regex | D[0o]cshared |
regex.icontains | regex | D[0o]csPoint |
regex.icontains | regex | D[0o]cument.Shared |
101 more
regex.icontains | regex | D[0o]cuCentre |
regex.icontains | regex | D[0o]cuCenter |
regex.icontains | regex | D[0o]cCenter |
regex.icontains | regex | D[0o]csOnline |
regex.icontains | regex | D[0o]cSend |
regex.icontains | regex | D[0o]cu?Send |
regex.icontains | regex | d[0o]csign |
regex.icontains | regex | D[0o]cu-eSin |
regex.icontains | regex | D[0o]cu-management |
regex.icontains | regex | \\beSign |
regex.icontains | regex | e\\.sign |
regex.icontains | regex | esign.[0o]nline |
regex.icontains | regex | [SsZz][lL][GgSs][Nn].*D[0o]c |
regex.icontains | regex | e-d[0o]c |
regex.icontains | regex | e-signature |
regex.icontains | regex | e-Verify Doc |
regex.icontains | regex | eSignature |
regex.icontains | regex | eSign&Return |
regex.icontains | regex | eSign[0o]nline |
regex.icontains | regex | Fileshare |
regex.icontains | regex | Review.and.C[0o]mplete |
regex.icontains | regex | Review.&.Sign |
regex.icontains | regex | Sign[0o]nline |
regex.icontains | regex | Signature.Request |
regex.icontains | regex | Shared.C[0o]mpleted |
regex.icontains | regex | Sign.and.Seal |
regex.icontains | regex | viaSign |
regex.icontains | regex | D[0o]cuSign |
regex.icontains | regex | D[0o]csID |
regex.icontains | regex | Complete.{0,10}D[0o]cuSign |
regex.icontains | regex | Enroll & Sign |
regex.icontains | regex | Review and Sign |
regex.icontains | regex | Sign(?:Report|Now) |
regex.icontains | regex | SignD[0o]c |
regex.icontains | regex | D[0o]cxxx |
regex.icontains | regex | d[0o]cufile |
regex.icontains | regex | E\x{00AD}-\x{00AD}S\x{00AD}i\x{00AD}g\x{00AD}n\x{00AD}&Return |
regex.icontains | regex | d[0o]cument.signature |
regex.icontains | regex | Electr[0o]nic.?Signature |
regex.icontains | regex | Complete: |
regex.icontains | regex | Please (?:Review|Sign) |
regex.icontains | regex | ^REVIEW$ |
regex.icontains | regex | requests your signature |
regex.icontains | regex | signature on.*contract |
regex.icontains | regex | Independent Contract |
regex.icontains | regex | Contract.*signature |
regex.icontains | regex | add your signature |
regex.icontains | regex | signature needed |
regex.icontains | regex | attn_task |
regex.icontains | regex | DocReq\\b |
sender.email.domain.root_domain | equals | sharepointonline.com |
strings.icontains | substring | asked to edit |
regex.icontains | regex | ((<br\s*/?>\s*){20,}|\n{20,}) |
regex.icontains | regex | (<p[^>]*>\s*<br\s*/?>\s*</p>\s*){30,} |
regex.icontains | regex | (<p class=".*?"><span style=".*?"><o:p>&nbsp;</o:p></span></p>\s*){30,} |
regex.icontains | regex | (<p>&nbsp;</p>\s*){7,} |
regex.icontains | regex | (<p[^>]*>\s*&nbsp;<br>\s*</p>\s*){5,} |
regex.icontains | regex | (<p[^>]*>&nbsp;</p>\s*){7,} |
regex.count | regex | <span\s*class\s*=\s*"[^\"]+"\s*>\s*[a-z]\s*<\/span><span\s*class\s*=\s*"[^\"]+"\s*>\s*[a-z]+\s*<\/span> |
regex.icontains | regex | >Docus[1l]gn< |
strings.icontains | substring | completed by all parties |
regex.icontains | regex | Document |
regex.count | regex | mailto:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,} |
regex.count | regex | <(?:p|div|span|td)[^>]*>\s*(?:&nbsp;|\s)*\s*</(?:p|div|span|td)> |
strings.ilike | substring | HR |
strings.ilike | substring | H?R |
strings.ilike | substring | *Human Resources* |
regex.icontains | regex | \+?([ilo0-9]{1}.)?\(?[ilo0-9]{3}?\)?.[ilo0-9]{3}.?[ilo0-9]{4} |
regex.icontains | regex | activate |
regex.icontains | regex | re-auth |
regex.icontains | regex | verify |
regex.icontains | regex | acknowledg |
regex.icontains | regex | (keep|change).{0,20}(active|password|access) |
regex.icontains | regex | ((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?) |
regex.icontains | regex | use.same.pass |
regex.icontains | regex | validate.{0,15}account |
regex.icontains | regex | recover.{0,15}messages |
regex.icontains | regex | (retry|update).{0,10}payment |
regex.icontains | regex | check activity |
regex.icontains | regex | (listen|play).{0,10}(vm|voice) |
regex.icontains | regex | clarify.{0,20}(deposit|wallet|funds) |
regex.icontains | regex | enter.{0,15}teams |
regex.icontains | regex | Review and sign |
regex.icontains | regex | REVIEW.*DOCUMENT |
regex.icontains | regex | Open Document |
regex.icontains | regex | Sign Now |
regex.icontains | regex | complete tasks? |
regex.contains | regex | \\bVIEW |
regex.contains | regex | DOWNLOAD |
regex.contains | regex | CHECK |
regex.contains | regex | KEEP.(SAME|MY) |
regex.contains | regex | VERIFY |
regex.contains | regex | ACCESS\\b |
regex.contains | regex | SIGN\\b |
regex.contains | regex | ENABLE\\b |
regex.contains | regex | RETAIN |
regex.contains | regex | PLAY |
regex.contains | regex | LISTEN |
regex.match | regex | ^[^a-z]*[A-Z][^a-z]*$ |
body.links[].display_text | member | Open |
strings.ilike | substring | *fail |