@@ -5,7 +5,40 @@ severity: "medium"
55source : |
66 type.inbound
77 and 0 < length(body.current_thread.links) < 10
8- and any(body.current_thread.links, regex.icount(.href_url.url, 'http(s)?(%)?[^a-z]') >= 5 and .visible)
8+ and any(body.current_thread.links,
9+ .visible
10+ // no ability to loop query_params_decoded, so create the non-decoded equivlent
11+ and any(regex.extract(.href_url.query_params,
12+ '[?&](?P<name>[^=&]+)(?:=(?P<value>[^&]*))?'
13+ ),
14+
15+ // filter down to query params that start with a url
16+ regex.contains(.named_groups['value'],
17+ '^(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)'
18+ )
19+ // the number of unique domains in the URL query param is greater or equal to 2
20+ and length(distinct(map(filter(regex.iextract(.named_groups['value'],
21+ '(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)(?P<domain>[^/\s&%]+)'
22+ ),
23+ // sometimes URLs have // and produce entries we want to skip
24+ // so ensure it's a valid domain first
25+ strings.parse_domain(.named_groups['domain']).error is null
26+ and strings.parse_domain(.named_groups['domain']).valid
27+ // remove domain that are the same as the sender root domain
28+ and strings.parse_domain(.named_groups['domain']).root_domain != sender.email.domain.root_domain
29+ ),
30+ // return just the root domian
31+ strings.parse_domain(.named_groups['domain']).root_domain
32+ ), .)
33+ ) >= 3
34+
35+ // there are five or more total URLs in that query param
36+ and regex.count(.named_groups['value'],
37+ '(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)'
38+ ) >= 3
39+ )
40+ )
41+
942tags :
1043 - " Attack surface reduction"
1144attack_types :
@@ -19,4 +52,4 @@ detection_methods:
1952id : " dea82f37-8cfd-5233-9deb-bc436aba8182"
2053og_id : " 92f9d241-ebd2-53b8-9c67-6f9ec3e263b8"
2154testing_pr : 3027
22- testing_sha : e460c3468bf1452d7ead1d4c28082a53831cd909
55+ testing_sha : c54242faa3be72f6990dc70be2dba12603690f97
0 commit comments