In this article:
The Sender Policy Framework (SPF) is a simple but effective email-validation technique designed to detect the forgery of email (also called email spoofing). An SPF record is a mechanism that allows a receiving email server to validate that inbound email from a particular domain comes from a server that is authorized to send email on behalf of that particular domain. The list of authorized sending hosts for a domain is published as a Domain Name System (DNS) record for that domain in the form of a specially formatted TXT record. An SPF record is required for spoofed e-mail prevention and anti-spam control.
RECOMMENDATION
A malformed SPF record can occur as the result of different conditions including: creating multiple SPF records per domain, invalid modifiers, and reaching maximum number of modifiers. The SPF standard can be found at https://tools.ietf.org/html/rfc7208. Additionally, there are tools available at the SPF Project, http://www.openspf.org/Tools.
SPF Record Maximum Validated DNS Lookups
SPF records have a maximum cap of 10 DNS lookups to be considered a valid SPF Record. Any records who exceed over 10 DNS lookups will be considered malformed.
Why is there a cap of 10 DNS lookups? What even adds up to the allowed cap of 10?
SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check, including any lookups caused by the use of the "include" mechanism or the "redirect" modifier. If this number is exceeded during a check, a PermError MUST be returned. The "include", "a", "mx", "ptr", and "exists" mechanisms as well as the "redirect" modifier do count against this limit. The "all", "ip4", and "ip6" mechanisms do not require DNS lookups and therefore do not count against this limit. The "exp" modifier does not count against this limit because the DNS lookup to fetch the explanation string occurs after the SPF record has been evaluated.
This limit is in place to prevent SPF lookups from being a useful avenue for Denial of Service attacks.
Malformed Records and what to check for?
Ok now you have a general idea of the make up of a standard SPF Record but how does one tell if a record is malformed?
Lets look at the SPF record of abcfinancial.us:
v=spf1 include:us._netblocks.mimecast.com include:spf.protection.outlook.com include:_spf.messagegears.net include:azuresend.com include:messagereach.com ip4:173.187.254.0/24 ip4:192.54.252.0/24 ip4:199.255.191.0/24 ip4:199.127.232.0/22 ip4:54.240.0.0/18 include:docebosaas.com include:spf05.hubspotemail.net ~all
Seems correct right? Most third party SPF Record checkers would return a green light on this record but unfortunately it is malformed.
Lets take a look at how many DNS lookups this record has. Remember every "include", "a", "mx", "ptr", and "exists" count towards the total lookup count.
For abcfinancial.us, it has a total of 8 DNS lookups. How did we get this number? This will be explained below this section.
Now lets look at the last part of the SPF record, on the tail end you see include:spf05.hubspotemail.net
If you run a SPF check on that record, no TXT or record will return. This is the reason for the malformed flag on abcfinancial.us’s SPF Record.
This can easily be resolved by adding a SPF Record on spf05.hubspotemail.net or removing that section from the overall SPF Record will resolve the issue as a whole.
Why does that need a SPF Record you may ask? Please refer to the SPF Record article in the knowledge base if you have further questions on the need of SPF Records on all owned domains.
DNS Query / Lookup counting
Remember a SPF Record is allowed a maximum of 10 dns queries or lookups to be considered a valid SPF Record. Every "include", "a", "mx", "ptr", and "exists" count towards the total lookup count.
The basics of reading a SPF record includes all of the mechanisms presented which will make the base count for the record itself.
From there, every include: you see in the record will be broken down and traced to its tail end. Remember there can be multiple SPF records linked together under one common record. For every include: there will be a SPF Record, and within that record it will be broken down further until you get to the bottom level of that record.
SPF Record breakdown:
Here are some examples of malformed records and what to look for:
securityscorecard.com
v=spf1 include:securityscorecard.com._nspf.vali.email include:%{i}._ip.%{h}._ehlo.%{d}._spf.vali.email -all
Total number of lookups - 2
Lets break it down:
1st lookup - include:%{i}._ip.%{h}._ehlo.%{d}._spf.vali.email
v=spf1 -all
2nd lookup- include:securityscorecard.com._nspf.vali.email
v=spf1 -all
affinnova.com
v=spf1 mx a ip4:208.65.144.0/21 ip4:208.81.64.0/21 ip4:167.89.48.27 a:spf.protection.outlook.com mx:mail.affinnova.com include:aspmx.pardot.com include:clearslide.com include:_spf.google.com ~all
Total number of lookups- 14
*We disregard listed IP’s towards the count
1st lookup - mx
2nd lookup - a
3rd lookup - include:aspmx.pardot.com
v=spf1 include:et._spf.pardot.com -all
(We are now breaking down the third lookup, as you can see, inside that SPF Record, there is an include)
4th lookup - include:et._spf.pardot.com
v=spf1 ip4:198.245.81.0/24 ip4:136.147.176.0/24 ip4:13.111.0.0/24 ip4:13.111.1.0/24 ip4:13.111.2.0/24 ip4:13.111.3.0/24 ip4:13.111.53.0/24 ip4:13.111.54.0/24 ip4:13.111.63.0/24 ip4:13.111.92.0/24 ip4:13.111.111.0/24 ip4:136.147.182.0/24 ip4:136.147.135.0/24 ip4:199.122.123.188/30 ip4:199.122.123.192 -all
(Going back to the top of the recent branch)
5th lookup - include:clearslide.com
v=spf1 include:_spf.google.com include:spf.clearslide.com ip4:206.169.182.100 ip4:69.25.103.2 ip4:207.218.90.122 ip4:64.79.155.192 include:stspg-customer.com -all
(3 includes)
6th lookup - include:_spf.google.com
v=spf1 include:_netblocks.google.com include:_netblocks2.google.com include:_netblocks3.google.com ~all
7th lookup - include:spf.clearslide.com
v=spf1 ip4:52.39.3.199 ip4:52.38.223.125 ip4:52.36.28.206 ip4:52.15.113.145 ip4:52.27.54.133 ip4:52.10.147.24 ip4:52.15.128.20 ip4:54.148.167.160 ip4:52.33.207.216 ~all
8th lookup- include:stspg-customer.com
v=spf1 ip4:23.253.182.103 ip4:23.253.183.145 ip4:23.253.183.146 ip4:23.253.183.147 ip4:23.253.183.148 ip4:23.253.183.150 ip4:166.78.68.221 ip4:166.78.69.146 ip4:167.89.46.159 ip4:167.89.64.9 ip4:167.89.65.0 ip4:167.89.65.53 ip4:167.89.65.100 ip4:167.89.74 .233 ip4:167.89.75.33 ip4:167.89.75.126 ip4:167.89.75.136 ip4:167.89.75.164 ip4:192.237.159.42 ip4:192.237.159.43 -all
9th lookup (breaking include:_spf.google.com as there are 3 lookups present) - include:_netblocks.google.com
v=spf1 ip4:35.190.247.0/24 ip4:64.233.160.0/19 ip4:66.102.0.0/20 ip4:66.249.80.0/20 ip4:72.14.192.0/18 ip4:74.125.0.0/16 ip4:108.177.8.0/21 ip4:173.194.0.0/16 ip4:209.85.128.0/17 ip4:216.58.192.0/19 ip4:216.239.32.0/19 ~all
10th lookup - include:_netblocks2.google.com
v=spf1 ip6:2001:4860:4000::/36 ip6:2404:6800:4000::/36 ip6:2607:f8b0:4000::/36 ip6:2800:3f0:4000::/36 ip6:2a00:1450:4000::/36 ip6:2c0f:fb50:4000::/36 ~all
11th lookup - include:_netblocks3.google.com
v=spf1 ip4:172.217.0.0/19 ip4:172.217.32.0/20 ip4:172.217.128.0/19 ip4:172.217.160.0/20 ip4:172.217.192.0/19 ip4:108.177.96.0/19 ip4:35.191.0.0/16 ip4:130.211.0.0/22 ~all
(back to the top)
12th lookup (tail end) - include:_spf.google.com
v=spf1 include:_netblocks.google.com include:_netblocks2.google.com include:_netblocks3.google.com ~all
*The count here stops as we already went through the lookups earlier. If the company wishes to trim their record down, taking this section off the tail end will remove 1 lookup.
13th lookup - a:spf.protection.outlook.com
14th lookup - mx:mail.affinnova.com
*A mx record should be present here, in this case as it is missing, the record is malformed
Another note worthy example (typos):
sams.com
v=spf1 ip4:161.168.202.62 ip4:161.168.202.15 ip4:161.168.202.17 ip4:161.165.225.65 ip4:161.165.225.14 ip4:161.165.225.16 ip4:161.168.202.61 ip4:161.165.225.64 ip4:161.167.229.0/24 ip4:174.36.102.125 ip4:161.170.244.233 ip4:161.170.248.233” “include:delivery.net include:bstocksamsclub.com ?all
Lookups - 2
1st lookup - include:bstocksamsclub.com
v=spf1 include:174.36.102.125 ~all
2nd lookup - include:delivery.net
*This lookup never finishes as there is a typo in the record. The ““ combined the IP’s and the include which is already a syntax error having the ““ there.
Other notable malformed issues you may see include:
-
Missing DNS A record on a domain
-
IP’s out of range / bound
-
Syntax / typos on formatting of the SPF Record
How can this issue be resolved?
- I have fixed this
- I have a compensating control
- Customers frequently ask us to remove findings because a domain does not send email. However, the fact that you do not use the domain to send legitimate email does not prevent third parties and bad actors from abusing your domain to send email. As per the description above, every domain that you own and do not use for email should have a defensive SPF record applied to it to prevent abuse that appears to come from your organization.
- There is no compensating control that absolves a domain owner from the responsibility of publishing an SPF policy to declare whether the domain will send email, and if so, the networks and entities that will send that email.
- This is not my IP or domain
- The IP does not belong to our company, the DNS entry has been corrected on the IP
- The IP does not belong to our company, the DNS entry has been corrected on the IP
- I cannot reproduce this issue and I think it’s incorrect
- There are independent tools that you can use to validate an SPF record. For example, SPF Checker is a common online tool that can be used to validate your SPF Records.