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|&#68;|&#x44;)(?:&#?[0-9a-fA-F]{2,6};|\s|o|о|&#1086;|&#x43e;)(?:&#?[0-9a-fA-F]{2,6};|\s|c|с|&#1089;|&#x441;)u(?:&#?[0-9a-fA-F]{2,6};|\s)?S(?:&#?[0-9a-fA-F]{2,6};|\s|i|і|&#1110;|&#x456;)(?:&#?[0-9a-fA-F]{2,6};|\s|g|ɡ|&#609;|&#x261;)(?:n|&#110;|&#x6e;)'
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|&#68;|&#x44;)(?:o|о|&#111;|&#x6f;|&#1086;|&#x43e;|&#959;|&#x3bf;)(?:c|с|&#99;|&#x63;|&#1089;|&#x441;|&#1010;|&#231;|&#x67;|&#265;|&#x109;)(?:u|&#117;|&#x75;|&#1091;|&#x443;|&#965;|&#x3c5;)(?:s|&#115;|&#x73;|&#1109;|&#x455;)(?:i|і|&#105;|&#x69;|&#1110;|&#x456;|&#305;|&#x131;)(?:g|&#103;|&#x67;|&#609;|&#x261;|&#287;|&#x11f;)(?:n|&#110;|&#x6e;|&#1085;|&#x43d;|&#951;|&#x3b7;)'
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"
to-top