Brand impersonation: DocuSign
Attack impersonating a DocuSign request for signature.
Sublime rule (View on GitHub)
1name: "Brand impersonation: DocuSign"
2description: |
3 Attack impersonating a DocuSign request for signature.
4references:
5 - "https://playground.sublimesecurity.com?id=2d2c6472-fabb-4952-b902-573a6294aa2f"
6type: "rule"
7severity: "high"
8source: |
9 type.inbound
10 and (
11 // orgs can have docusign.company.com
12 strings.ilike(sender.email.email, '*docusign.net*', '*docusign.com*')
13
14 // if the above is true, you'll see a "via Docusign"
15 or strings.ilike(sender.display_name, '*docusign*')
16
17 // detects 1 character variations,
18 // such as DocuSlgn (with an "L" instead of an "I")
19 or strings.ilevenshtein(sender.display_name, "docusign") == 1
20 or strings.ilike(sender.display_name, "*docuonline*", "*via *signature*")
21 or (
22 strings.istarts_with(body.html.inner_text, "docusign")
23 and not strings.istarts_with(body.current_thread.text, "docusign")
24 )
25 // docusign is found in current thread AND contains docusign wording within current_thread or subject
26 or (
27 regex.icontains(body.current_thread.text, '\bdocu.?sign\b')
28 and (
29 // additional context from body.current_thread.text
30 strings.istarts_with(body.current_thread.text, "DOCUSIGN\n", )
31 or regex.icontains(body.current_thread.text,
32 'You have received a ([^\s]+\s)?document',
33 )
34 or strings.icontains(body.current_thread.text,
35 'a document to review and sign',
36 )
37 or strings.icontains(body.current_thread.text,
38 'A document is available for you',
39 )
40 or strings.icontains(body.current_thread.text,
41 'a document ready for you',
42 )
43 or strings.icontains(body.current_thread.text,
44 'This email contains a secure link to DocuSign'
45 )
46 or strings.icontains(body.current_thread.text,
47 'All parties have completed with Docusign'
48 )
49 or strings.icontains(body.current_thread.text,
50 'the signing of this document has been completed'
51 )
52 or strings.icontains(body.current_thread.text,
53 'Please use the link above to Docusign'
54 )
55 or strings.icontains(body.current_thread.text, 'Review on Docusign')
56 or strings.icontains(body.current_thread.text, 'Completed with Docusign')
57 or strings.icontains(body.current_thread.text, 'Completed on Docusign')
58 or strings.icontains(body.current_thread.text, 'Complete with Docusign')
59 or strings.icontains(body.current_thread.text,
60 'please review and complete with DocuSign'
61 )
62 or strings.icontains(body.current_thread.text,
63 'We appreciate you choosing DocuSign'
64 )
65 or strings.icontains(body.current_thread.text,
66 'A document has been sent to you for'
67 )
68 or strings.icontains(body.current_thread.text, 'Please Sign docusign')
69 or strings.icontains(body.current_thread.text,
70 'This email was sent via DocuSign'
71 )
72 or strings.icontains(body.current_thread.text,
73 'This email was sent to you via DocuSign'
74 )
75 or strings.icontains(body.current_thread.text,
76 'This message was sent via DocuSign'
77 )
78 or strings.icontains(body.current_thread.text,
79 'This message was sent to you via DocuSign'
80 )
81 or strings.icontains(body.current_thread.text,
82 'review via DocuSign Electronic Signature'
83 )
84 or strings.icontains(body.current_thread.text, 'sent to you by DocuSign')
85 or strings.icontains(body.current_thread.text, 'Processed by DocuSign')
86 or strings.icontains(body.current_thread.text,
87 'Please read and sign the document'
88 )
89 or strings.icontains(body.current_thread.text,
90 'Please kindly review and sign the '
91 )
92 or strings.icontains(body.current_thread.text,
93 'Your document is pending review and signature'
94 )
95 or strings.icontains(body.current_thread.text,
96 'pending document for your signature'
97 )
98 or strings.icontains(body.current_thread.text,
99 'your review and signature'
100 )
101 or strings.icontains(body.current_thread.text, 'a pending document for')
102 or strings.icontains(body.current_thread.text, 'Your document is ready')
103 or strings.icontains(body.current_thread.text,
104 'This email is automatically generated by DocuSign'
105 )
106 or strings.icontains(body.current_thread.text,
107 'Your document has been completed'
108 )
109 // docusign is "near" review and sign or sign and return
110 or regex.icontains(body.current_thread.text,
111 'Review\s*(?:and\s*|&\s*)Sign.{0,40}docusign',
112 'docusign.{0,40}Review\s*(?:and\s*|&\s*)Sign',
113 'Sign\s*(?:and\s*|&\s*)Return.{0,40}docusign',
114 'Sign\s*(?:and\s*|&\s*)Return.docusign.{0,40}'
115 )
116
117 // additional context from subject.subject
118 or strings.icontains(subject.subject, 'complete with docusign')
119 or strings.icontains(subject.subject, 'signature request')
120 or regex.icontains(subject.subject, 'Review\s*(?:and\s*|&\s*)Sign')
121 or regex.icontains(subject.subject, 'Sign\s*(?:and\s*|&\s*)Return')
122 or strings.icontains(subject.subject, 'Please Docusign')
123 or strings.icontains(subject.subject, 'Docusign has sent')
124 )
125 )
126 or (
127 // negate replies/forwards which involve a legit docusign message-id format
128 not any(headers.references,
129 strings.iends_with(., 'docusign.net')
130 and regex.imatch(., '[0-9a-f]{32}@(?:[^\.]+\.)?docusign.net')
131 )
132 and (
133 (
134 sender.display_name is not null
135 and regex.icontains(sender.display_name, '\bdocu\b')
136 and strings.icontains(sender.display_name, 'sign')
137 )
138 or (
139 subject.subject is not null
140 and regex.icontains(subject.subject, '\bdocu\b')
141 and strings.icontains(subject.subject, 'sign')
142 )
143 or (
144 (
145 regex.icontains(body.html.raw,
146 'Powered by.{0,6}(?:\s*<\/?[^\>]+\>\s*)+<img[^\>]+(?:src="https:\/\/docucdn-a\.akamaihd\.net\/[^\"]+email-logo.png"|alt="DocuSign")'
147 )
148 or regex.icontains(body.current_thread.text, 'Powered by\s*DocuSign')
149 )
150 // limit it to where the powered by is within the current thread
151 and strings.icontains(body.current_thread.text, 'Powered by')
152 )
153 // footer disclaimers
154 or strings.icontains(body.current_thread.text,
155 'using the Docusign Electronic Signature Service'
156 )
157 or strings.icontains(body.current_thread.text,
158 'who uses the DocuSign Electronic Signature Service'
159 )
160 or strings.icontains(body.current_thread.text,
161 'Thank you for choosing DocuSign'
162 )
163 or (
164 (
165 strings.icontains(body.current_thread.text,
166 'Alternate Signing Method'
167 )
168 or strings.icontains(body.current_thread.text, 'Alternative Access')
169 )
170 and regex.icontains(body.current_thread.text,
171 '(?:Click|Select) ''Access Documents'', and enter '
172 )
173 )
174 or (
175 strings.icontains(body.current_thread.text,
176 'Please do not share this email, link, or access code with others'
177 )
178 and not sender.email.domain.root_domain in (
179 "insuresign.com",
180 "clixsign.com",
181 "esignlive.com",
182 "clickcontracts.com",
183 "sadq.sa",
184 "vasion.com",
185 "chubb.com", // insurance company
186 )
187 )
188 or (
189 strings.icontains(body.current_thread.text, 'Docusign provides a ')
190 and strings.icontains(body.current_thread.text,
191 'solution for Digital Transaction Management'
192 )
193 )
194 or strings.icontains(body.current_thread.text,
195 'a secure link to DocuSign'
196 )
197
198 // footer links
199 or (
200 length(filter(body.links,
201 (
202 .href_url.domain.domain == "support.docusign.com"
203 and strings.contains(.href_url.path, '/articles/')
204 )
205 or .href_url.domain.domain == "community.docusign.com"
206 or .href_url.domain.domain == "protect.docusign.com"
207 or .href_url.domain.domain == "app.esign.docusign.com"
208 )
209 ) >= 2
210 // and the display_text for these links are within the current thread
211 and (
212 strings.icontains(body.current_thread.text, 'Declining to sign')
213 or strings.icontains(body.current_thread.text,
214 'Managing notifications'
215 )
216 or strings.icontains(body.current_thread.text,
217 'How to Sign a Document'
218 )
219 or strings.icontains(body.current_thread.text,
220 'Docusign Support Center'
221 )
222 or strings.icontains(body.current_thread.text, 'Report this email')
223 or strings.icontains(body.current_thread.text, 'Docusign Community')
224 or strings.icontains(body.current_thread.text,
225 'Connect with our support team'
226 )
227 or strings.icontains(body.current_thread.text, 'Unsubscribe')
228 or strings.icontains(body.current_thread.text, 'Manage Preferences')
229 )
230 )
231 )
232 )
233 or (
234 (
235 regex.icontains(body.html.raw,
236 '<font size="?[0-9]"?[^\>]*>DocuSign</font>'
237 )
238 or regex.icontains(body.html.raw, '\nDocu(?:<[^\>]+>\s*)+Sign<')
239 or regex.icontains(body.html.raw,
240 '<span[^>]*style="[^"]*">Docu.?Sign<\/span>'
241 )
242 or any(html.xpath(body.html, '//h1').nodes,
243 regex.icontains(.display_text, 'Docu.?Sign')
244 )
245 or regex.icontains(body.html.raw,
246 '<span[^>]*style="[^"]*">(Docu|D(?:ocu?)?)<\/span>(?:<[^\>]+\>){0,2}<span[^>]*style="[^"]*">(Sign|S(?:ign?)?)<\/span>'
247 )
248 // any bold text contains docusign
249 or any(html.xpath(body.html, '//strong').nodes,
250 regex.imatch(.display_text, 'Docu.?Sign')
251 )
252 // title starts with Docusign
253 or any(html.xpath(body.html, '//title').nodes,
254 regex.icontains(.display_text, '^docu.?sign')
255 )
256 // a div with a class of logo contains the display text of docusign
257 or any(html.xpath(body.html, '//div[@class="logo"]').nodes,
258 strings.icontains(.display_text, 'Docusign')
259 )
260 // image contains an alt text of docusign
261 or any(html.xpath(body.html, '//img/@alt').nodes, .raw =~ "docusign")
262
263 // Basic variations with HTML encoding
264 // use of regex extract allows
265 or any(regex.iextract(body.html.raw,
266 '(?:D|D|D)(?:&#?[0-9a-fA-F]{2,6};|\s|o|о|о|о)(?:&#?[0-9a-fA-F]{2,6};|\s|c|с|с|с)u(?:&#?[0-9a-fA-F]{2,6};|\s)?S(?:&#?[0-9a-fA-F]{2,6};|\s|i|і|і|і)(?:&#?[0-9a-fA-F]{2,6};|\s|g|ɡ|ɡ|ɡ)(?:n|n|n)'
267 ),
268 .full_match !~ "docusign"
269 )
270 // Common homograph patterns
271 or any(regex.iextract(body.html.raw,
272 '(?:[DⅮᎠᗞᗡ𝐃𝐷𝑫𝒟𝓓𝔇𝔻𝕯𝖣])\s*(?:[oοоօ0Ооʘ◯])\s*(?:[cсçҫ¢ϲС])\s*u\s*(?:[sѕЅ5$])\s*(?:[iіІ1l!|])\s*(?:[gǵġģ9ɡ])\s*(?:[nոռℼη𝐧𝑛𝒏𝓃𝓷𝔫𝕟𝖓])'
273 ),
274 .full_match !~ "docusign"
275 )
276
277 // Look for HTML entities for each letter in sequence
278 or any(regex.iextract(body.html.raw,
279 '(?:D|D|D)(?:o|о|o|o|о|о|ο|ο)(?:c|с|c|c|с|с|ϲ|ç|g|ĉ|ĉ)(?:u|u|u|у|у|υ|υ)(?:s|s|s|ѕ|ѕ)(?:i|і|i|i|і|і|ı|ı)(?:g|g|g|ɡ|ɡ|ğ|ğ)(?:n|n|n|н|н|η|η)'
280 ),
281 .full_match !~ "docusign"
282 )
283
284 // Handle repeated HTML entities and variation selectors (using Unicode class)
285 or any(regex.iextract(body.html.raw,
286 'D(?:&#[0-9]{1,7};)*\p{Mn}*o(?:&#[0-9]{1,7};)*\p{Mn}*c(?:&#[0-9]{1,7};)*\p{Mn}*u(?:&#[0-9]{1,7};)*\p{Mn}*[Ss](?:&#[0-9]{1,7};)*\p{Mn}*i(?:&#[0-9]{1,7};)*\p{Mn}*g(?:&#[0-9]{1,7};)*\p{Mn}*n'
287 ),
288 .full_match !~ "docusign"
289 )
290 )
291 and (
292 regex.icontains(body.html.raw,
293 'b(?:ackground(?:-color)?|g?color):\s*rgb\(30,\s*76,\s*161\)',
294 'b(?:ackground(?:-color)?|g?color):\s*rgb\(61,\s*170,\s*73\)'
295 )
296 or regex.icontains(body.html.raw,
297 '<(?:div|td|table)[^>]*b(?:ackground(?:-color)?|g?color)(?::|=)\s*\"?#1e4ca1[^>]*>',
298 )
299 or regex.icontains(body.html.raw,
300 'b(?:ackground(?:-color)?|g?color)(?::|=)\s*\"?#(?:214e9f|3260a7|0056b3|1e4ca1|214395|325bb8|3c60ad)'
301 )
302 )
303 )
304 )
305
306 // identifies the main CTA in the email, eg "Review now" or "Review document"
307 // this should always be a known docusign domain,
308 // even with branded docusign subdomains
309 and (
310 any(
311 // filter links that match docusign wording
312 filter(body.links,
313 // we've observed invisible characters in the display name
314 // such as U+034F: "Revi\x{034F}ew Now"
315 (
316 strings.ilevenshtein(.display_text, "Review Now") <= 3
317 or strings.ilevenshtein(.display_text, "Review and Sign") <= 3
318 or (
319 strings.icontains(.display_text, "Review")
320 // negate benign uses of the "review" term
321 and not (
322 strings.icontains(.display_text, "Review Us")
323 or strings.icontains(.display_text, "leave us a review")
324 or regex.icontains(.display_text, '\bReviews\b')
325 // don't match microsoft quarantine messages
326 or (
327 strings.icontains(.display_text, "Review Message")
328 and (
329 .href_url.domain.domain == "security.microsoft.com"
330 and .href_url.path == "/quarantine"
331 )
332 )
333 )
334 )
335 or strings.icontains(.display_text, "document")
336 or strings.icontains(.display_text, "docusign")
337 or strings.icontains(.display_text, "Review on Docusign")
338 or (
339 strings.icontains(.display_text, "Sign")
340 and strings.icontains(.display_text, "Now")
341 )
342 )
343 ),
344 // ensure those links aren't legit
345 not .href_url.domain.root_domain in (
346 "docusign.com",
347 "docusign.net",
348 'docusign.co.uk',
349 'docusign.com.br',
350 'docusign.fr',
351 // other e-signature companies which use simliar wording
352 "insuresign.com",
353 "clixsign.com",
354 "esignlive.com",
355 "clickcontracts.com",
356 "adobesign.com",
357 "hellosign.com",
358 )
359 and not (
360 .href_url.domain.root_domain == "mimecastprotect.com"
361 and (
362 .href_url.query_params is not null
363 and regex.icontains(.href_url.query_params,
364 'domain=(?:\w+\.)?docusign.(?:net|com|co\.uk|com\.br|fr)',
365 // other e-signature companies
366 'domain=(?:\w+\.)?(?:insuresign\.com|clixsign\.com|esignlive\.com|clickcontracts\.com|adobesign\.com|hellosign\.com)'
367 )
368 )
369 )
370 )
371 // Suspicious attachment
372 or any(attachments,
373 (
374 .file_extension in~ ("html", "htm", "shtml", "dhtml")
375 or .file_extension in~ $file_extensions_common_archives
376 or .file_type == "html"
377 or .content_type == "text/html"
378 )
379 and 1 of (
380 (
381 regex.icontains(file.parse_html(.).raw, '\s{0,}<script.*')
382 and regex.icontains(file.parse_html(.).raw, "</script>")
383 ),
384 strings.ilike(file.parse_html(.).raw,
385 "*createElement*",
386 "*appendChild*",
387 "*createObjectURL*"
388 ),
389 strings.icount(file.parse_html(.).raw, "/*") > 10,
390 any($free_subdomain_hosts, strings.icontains(..file_name, .))
391 )
392 )
393 )
394
395 // negate highly trusted sender domains if they pass DMARC authentication
396 and not (
397 sender.email.domain.root_domain in $high_trust_sender_root_domains
398 and coalesce(headers.auth_summary.dmarc.pass, false)
399 )
400
401 // negation for messages traversing docusign.net
402 // happens with custom sender domains
403 and not (
404 any(headers.domains, .root_domain == "docusign.net")
405 and headers.auth_summary.spf.pass
406 and headers.auth_summary.dmarc.pass
407 )
408
409 // adding negation for messages originating from docusigns api
410 // and the sender.display.name contains "via"
411 and not (
412 any(headers.hops,
413 any(.fields,
414 .name == "X-Api-Host" and strings.ends_with(.value, "docusign.net")
415 )
416 )
417 and strings.contains(sender.display_name, "via")
418 )
419attack_types:
420 - "Credential Phishing"
421tactics_and_techniques:
422 - "Impersonation: Brand"
423 - "Lookalike domain"
424 - "Social engineering"
425 - "Spoofing"
426detection_methods:
427 - "Header analysis"
428 - "Sender analysis"
429 - "URL analysis"
430id: "4d29235c-08b9-5f9b-950e-60b05c4691fb"