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