Reconnaissance: All recipients cc/bcc'd or undisclosed
Recon messages, a form of deliverability testing, are used to validate whether a recipient address is valid or not, potentially preceding an attack.
All recipients are bcc'd or undisclosed, with no links or attachments, and a short body and subject from an unknown sender.
Sublime rule (View on GitHub)
1name: "Reconnaissance: All recipients cc/bcc'd or undisclosed"
2description: |
3 Recon messages, a form of deliverability testing, are used to validate whether a recipient address is valid or not, potentially preceding an attack.
4
5 All recipients are bcc'd or undisclosed, with no links or attachments, and a short body and subject from an unknown sender.
6type: "rule"
7severity: "low"
8source: |
9 type.inbound
10 and (
11 length(recipients.bcc) > 0
12 or length(recipients.cc) > 0
13 or any(recipients.to, strings.ilike(.display_name, "undisclosed?recipients"))
14 )
15 and (
16 length(subject.subject) <= 10
17 // extract the "real subject" (strip off any external warning keywords)
18 or any(regex.iextract(subject.subject,
19 '(?:^\[?(?:EXT|EXTERNAL)\]?[: ]\s*){0,3} ?(?P<real_subject>.*)'
20 ),
21 length(.named_groups['real_subject']) <= 10
22 )
23 or (
24 strings.ilike(subject.subject, "*checking*", "*testing*")
25 and length(subject.subject) <= 25
26 )
27 )
28 and length(attachments) == 0
29 // and there are no links. Or all the links are to aka.ms or an extraction from a warning banner that match the senders domain
30 and (
31 length(body.links) == 0
32 or length(filter(body.links,
33 (
34 .display_text is null
35 and .display_url.url == sender.email.domain.root_domain
36 )
37 or .href_url.domain.domain == "aka.ms"
38 )
39 ) == length(body.links)
40 )
41 and (
42 body.current_thread.text is null
43 or length(body.current_thread.text) < 50
44 or (
45 length(body.current_thread.text) < 900
46 // or body is most likely all warning banner ending with a generic greeting
47 and regex.imatch(body.current_thread.text, '.*(hi|hello)')
48 )
49 // body length without disclaimer is shorter than 50 characters
50 or (
51 any(map(filter(ml.nlu_classifier(body.current_thread.text).entities,
52 .name == "disclaimer"
53 ),
54 .text
55 ),
56 (length(body.current_thread.text) - length(.)) < 50
57 )
58 )
59 )
60 and profile.by_sender().prevalence != "common"
61 and not profile.by_sender().solicited
62 and not profile.by_sender().any_false_positives
63
64 // negate highly trusted sender domains unless they fail DMARC authentication
65 and (
66 (
67 sender.email.domain.root_domain in $high_trust_sender_root_domains
68 and not headers.auth_summary.dmarc.pass
69 )
70 or sender.email.domain.root_domain not in $high_trust_sender_root_domains
71 )
72
73tags:
74 - "Attack surface reduction"
75 - "Deliverability testing"
76attack_types:
77 - "Reconnaissance"
78detection_methods:
79 - "Content analysis"
80 - "Header analysis"
81 - "Sender analysis"
82id: "420f60d3-5d10-5384-9253-9521a758e799"