Querying Spamhaus for IP reputation

Published: 2021-04-16
Last Updated: 2021-04-17 03:07:21 UTC
by Rick Wanner (Version: 1)
0 comment(s)

Way back in 2018 I posted a diary describing how I have been using the Neutrino API to do IP reputation checks.  In the subsequent 2+ years that python script has evolved some which hopefully I can go over at some point in the future, but for now I would like to show you the most recent capability I added into that script.

As most of you know, The Spamhaus Project has been forefront in the fight against Spam for over 20 years. But did you know they provide a DNS query based api that can be used, for low volume non-commercial use, to query all of the Spamhaus blocklists at once. The interface is zen.spamhaus.org. Because it is DNS query based you can perform the query using nslookup or dig and the returned IP address is the return code.

For example say we want to test whether or not 196.16.11.222 is on a Spamhaus list.  First because the interface takes a DNS query we would need to reverse the IP address and then add .zen.spamhaus.org.  i.e. the DNS query would look like 222.11.16.196.zen.spamhaus.org

$ nslookup 222.11.16.196.zen.spamhaus.org

Non-authoritative answer:
Name:  222.11.16.196.zen.spamhaus.org
Address: 127.0.0.2
Name:  222.11.16.196.zen.spamhaus.org
Address: 127.0.0.9

or with dig...

$ dig 222.11.16.196.zen.spamhaus.org

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.4 <<>> 222.11.16.196.zen.spamhaus.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64622
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;222.11.16.196.zen.spamhaus.org.    IN   A

;; ANSWER SECTION:
222.11.16.196.zen.spamhaus.org. 41 IN  A    127.0.0.2
222.11.16.196.zen.spamhaus.org. 41 IN  A    127.0.0.9

As you can see in both cases the DNS response returned two results. 127.0.0.2 and 127.0.0.9.  In practicality just the fact that you receive return codes tells you that this IP is on Spamhaus's lists, and has recently been involved in naughty behavior. However to know which Spamhaus lists in particular the return codes apply to:

Return Code	Zone	Description
127.0.0.2	SBL	    Spamhaus SBL Data
127.0.0.3	SBL	    Spamhaus SBL CSS Data
127.0.0.4	XBL	    CBL Data
127.0.0.9	SBL	    Spamhaus DROP/EDROP Data
127.0.0.10	PBL	    ISP Maintained
127.0.0.11	PBL	    Spamhaus Maintained

If you query an IP which is not on any Spamhaus lists the result will be Non-Existent Domain (NXDOMAIN)

nslookup 222.11.16.1.zen.spamhaus.org

** server can't find 222.11.16.1.zen.spamhaus.org: NXDOMAIN

I have created a Python script which performs this lookup and have integrated this code into my ip reputation script. 

$ python3 queryspamhaus.py 196.16.11.222
196.16.11.222 127.0.0.2 ['SBL']
    
$ python3 queryspamhaus.py 1.16.11.222
1.16.11.222 0 ['Not Found']

The script does have a bug.  The socket.gethostbyname() function only returns one result, so is returning an incomplete result for IPs which are on multiple Spamhaus lists. Since usually all I am looking for is if the IP is on any list I have never bothered to research how to fix this bug.

For those of you who are interested, the script is below.  As usual, I only build these scripts for my own use/research, so a real python programmer could very likely code something better.

#!/usr/bin/env/python3
#
# queryspamhaus.py

import os
import sys, getopt, argparse
import socket

def check_spamhaus(ip):
    hostname = ".".join(ip.split(".")[::-1]) + ".zen.spamhaus.org"
    try:
       result  = socket.gethostbyname(hostname)
    except socket.error:
       result = 0

    rdict = {"127.0.0.2": ["SBL"],
             "127.0.0.3": ["SBL CSS"],
             "127.0.0.4": ["XBL"],
             "127.0.0.6": ["XBL"],
             "127.0.0.7": ["XBL"],
             "127.0.0.9": ["SBL"],
             "127.0.0.10": ["PBL"],
             "127.0.0.11": ["PBL"],
             0 : ["Not Found"]
            }

    return result, rdict[result]

def main():
   parser = argparse.ArgumentParser()
   parser.add_argument('IP', help="IP address")
   args=parser.parse_args()

   ip=args.IP
   result,tresult  = check_spamhaus(ip)
   print('{} {} {}'.format(ip, result, tresult))

main()

-- Rick Wanner MSISE - rwanner at isc dot sans dot edu - Twitter:namedeplume (Protected)

Keywords: Spamhaus
0 comment(s)

Comments


Diary Archives