Attachment: HTML Attachment with Login Portal Indicators

Recursively scans files and archives to detect indicators of login portals implemented in HTML files. This is a known credential theft technique used by threat actors.

Sublime rule (View on GitHub)

  1name: "Attachment: HTML Attachment with Login Portal Indicators"
  2description: |
  3    Recursively scans files and archives to detect indicators of login portals implemented in HTML files. This is a known credential theft technique used by threat actors.
  4references:
  5  - "https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/html-file-attachments-still-a-threat/"
  6  - "https://app.any.run/tasks/6bd34bda-91ef-4d13-847c-81d7787dc763/"
  7  - "https://playground.sublimesecurity.com?id=6cd813e4-085b-4229-ad15-d2194cdbb91b"
  8type: "rule"
  9severity: "medium"
 10authors:
 11  - twitter: "ajpc500"
 12source: |
 13  type.inbound
 14  and any(attachments,
 15          (
 16            .file_extension in~ ("html", "htm", "shtml", "dhtml")
 17            or .file_extension in~ $file_extensions_common_archives
 18            or .file_type == "html"
 19          )
 20          and any(file.explode(.),
 21                  // suspicious strings found in javascript
 22                  (
 23                    length(filter(.scan.javascript.strings, strings.ilike(., "*password*", ))) >= 2
 24                    and 2 of (
 25                      any(.scan.javascript.strings, strings.ilike(., "*incorrect*")),
 26                      any(.scan.javascript.strings, strings.ilike(., "*invalid*")),
 27                      any(.scan.javascript.strings, strings.ilike(., "*login*")),
 28                      any(.scan.javascript.strings, regex.icontains(., "sign.in")),
 29                    )
 30                  )
 31                  or (
 32                    // suspicious strings found outside of javascript, but binexplode'd file still of HTML type
 33                    length(filter(.scan.strings.strings, strings.ilike(., "*password*", ))) >= 2
 34                    and 2 of (
 35                      any(.scan.strings.strings, strings.ilike(., "*incorrect*")),
 36                      any(.scan.strings.strings, strings.ilike(., "*invalid*")),
 37                      any(.scan.strings.strings, strings.ilike(., "*login*")),
 38                      any(.scan.strings.strings, strings.ilike(., "*<script>*")),
 39                      any(.scan.strings.strings, regex.icontains(., "sign.in")),
 40                      any(.scan.strings.strings,
 41                          regex.icontains(.,
 42                                          '<title>.[^<]+(Payment|Invoice|Statement|Login|Microsoft|Email|Excel)'
 43                          )
 44                      )
 45                    )
 46                  )
 47                  or
 48                  //Known phishing obfuscation
 49                  2 of (
 50                    // Enter password
 51                    any(.scan.strings.strings,
 52                        strings.ilike(.,
 53                                      "*&#69;&#110;&#116;&#101;&#114;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*"
 54                        )
 55                    ),
 56
 57                    // Forgotten my password
 58                    any(.scan.strings.strings,
 59                        strings.ilike(.,
 60                                      "*&#70;&#111;&#114;&#103;&#111;&#116;&#116;&#101;&#110;&#32;&#109;&#121;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*"
 61                        )
 62                    ),
 63
 64                    // Sign in
 65                    any(.scan.strings.strings,
 66                        strings.ilike(., "*&#83;&#105;&#103;&#110;&#32;&#105;&#110*")
 67                    )
 68                  )
 69          )
 70  )
 71
 72  and (
 73    (
 74      // exclude internal mailers where there is no SPF configured.
 75      // if the sender's root domain is an org domain, we
 76      // ensure there's no SPF failures to protect against spoofs.
 77      // we use root_domain because it's typically subdomains that are misconfigured
 78      sender.email.domain.root_domain in $org_domains
 79      and not any(distinct(headers.hops, .received_spf.verdict is not null),
 80                  strings.ilike(.received_spf.verdict, "*fail")
 81      )
 82    )
 83    or sender.email.domain.root_domain not in $org_domains
 84  )
 85
 86  // negate highly trusted sender domains unless they fail DMARC authentication
 87  and
 88  (
 89    (
 90      sender.email.domain.root_domain in $high_trust_sender_root_domains
 91      and (
 92        any(distinct(headers.hops, .authentication_results.dmarc is not null),
 93            strings.ilike(.authentication_results.dmarc, "*fail")
 94        )
 95      )
 96    )
 97    or sender.email.domain.root_domain not in $high_trust_sender_root_domains
 98  )
 99
100
101  and (
102    (
103      not profile.by_sender().solicited
104      and profile.by_sender().prevalence in ("new", "outlier")
105    )
106    or (
107      profile.by_sender().any_messages_malicious_or_spam
108      and not profile.by_sender().any_false_positives
109    )
110  )  
111attack_types:
112  - "Credential Phishing"
113tactics_and_techniques:
114  - "HTML smuggling"
115  - "Scripting"
116detection_methods:
117  - "Archive analysis"
118  - "File analysis"
119  - "HTML analysis"
120  - "Javascript analysis"
121  - "Sender analysis"
122id: "3aabf4a7-fefa-5266-83fe-012002c9db4a"
to-top