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|&#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;)'
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|&#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;)'
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"
to-top