Automate Your Firewall Block Lists with Free Threat Intelligence: Integration Guide for Top 5 Vendors
- Patrick Duggan
- Jan 23
- 9 min read
Updated: Aug 11
TL;DR
We publish free threat intelligence. You can automatically block malicious IPs on your firewall without paying $50K/year for a commercial feed.
# Get block-ready IP list right now
curl -s "https://analytics.dugganusa.com/api/v1/stix-feed/v2?min_confidence=90" | \
jq -r '.objects[] | select(.type=="indicator") | .pattern' | \
grep -oP "\d+\.\d+\.\d+\.\d+"This guide covers automated integration for Palo Alto, Cisco, Fortinet, Check Point, and Sophos.
What You Get
2,400+ actively malicious IPs updated hourly:
Category | Description |
Botnet C2s | Command and control infrastructure |
Scanners | Aggressive reconnaissance actors |
Exploit hosts | Active exploitation attempts |
Malware distribution | Payload delivery infrastructure |
AbuseIPDB confidence scores
VirusTotal detections
MITRE ATT&CK mapping
ISP and geolocation data
90+: Auto-block (high confidence, low false positive risk)
70-89: Alert + review
50-69: Log only
Feed Endpoints
Simple IP List (Firewall-Ready)
# High confidence IPs only (recommended for auto-blocking)
curl -s "https://analytics.dugganusa.com/api/v1/stix-feed/v2?min_confidence=90" | \
jq -r '.objects[] | select(.type=="indicator") | .pattern' | \
grep -oP "\d+\.\d+\.\d+\.\d+" | sort -uFull STIX Bundle (For Automation Scripts)
curl -s "https://analytics.dugganusa.com/api/v1/stix-feed/v2?min_confidence=90"Returns full context: confidence scores, threat types, MITRE techniques, enrichment data.
1. Palo Alto Networks (PAN-OS)
Palo Alto supports External Dynamic Lists (EDLs) - perfect for automated threat intel integration.
Option A: Direct EDL (Simplest)
Create a simple web-hosted IP list that PAN-OS fetches automatically.
Step 1: Create IP List Endpoint
Host this script on any web server (or use our hosted version):
#!/usr/bin/env python3
"""
EDL Generator for Palo Alto Networks
Host on your web server, PAN-OS fetches automatically
"""from flask import Flask, Response import requests
app = Flask(__name__)
STIX_URL = "https://analytics.dugganusa.com/api/v1/stix-feed/v2"
@app.route('/edl/dugganusa-blocklist.txt') def edl_blocklist(): """Return plain text IP list for PAN-OS EDL.""" response = requests.get(f"{STIX_URL}?min_confidence=90") bundle = response.json()
ips = set() for obj in bundle.get('objects', []): if obj.get('type') == 'indicator': import re match = re.search(r'\d+\.\d+\.\d+\.\d+', obj.get('pattern', '')) if match: ips.add(match.group())
if __name__ == '__main__': app.run(host='0.0.0.0', port=8080) ```
Step 2: Configure EDL in PAN-OS
Objects > External Dynamic Lists > AddName: DugganUSA-Threat-Intel Type: IP List Source: http://your-server:8080/edl/dugganusa-blocklist.txt Certificate Profile: None (or your CA if HTTPS) Update Interval: Hourly ```
Step 3: Create Security Policy
Policies > Security > AddName: Block-DugganUSA-Threats Source Zone: any Destination Zone: any Destination Address: DugganUSA-Threat-Intel (EDL) Action: Deny Log at Session End: Yes ```
Option B: PAN-OS API (Direct Integration)
#!/usr/bin/env python3
"""
Direct PAN-OS API integration for DugganUSA threat feed
Cron: 0 * * * * /opt/scripts/panos_threat_update.py
"""import requests import xml.etree.ElementTree as ET import re
def get_threat_ips(min_confidence=90): """Fetch IPs from STIX feed.""" response = requests.get(f"{STIX_URL}?min_confidence={min_confidence}") bundle = response.json()
ips = [] for obj in bundle.get('objects', []): if obj.get('type') == 'indicator': match = re.search(r'\d+\.\d+\.\d+\.\d+', obj.get('pattern', '')) if match: ips.append(match.group())
return list(set(ips))
def panos_api(action, xpath, element=None): """Execute PAN-OS API call.""" params = { 'type': 'config', 'action': action, 'key': PANOS_API_KEY, 'xpath': xpath } if element: params['element'] = element
response = requests.get( f"https://{PANOS_HOST}/api/", params=params, verify=False ) return ET.fromstring(response.text)
def update_address_objects(ips): """Create/update address objects for each IP.""" for ip in ips: name = f"threat-{ip.replace('.', '-')}" xpath = f"/config/devices/entry/vsys/entry[@name='vsys1']/address/entry[@name='{name}']" element = f"<ip-netmask>{ip}/32</ip-netmask><description>DugganUSA Threat Intel</description>"
panos_api('set', xpath, element)
print(f"Created/updated {len(ips)} address objects")
def update_address_group(ips): """Update address group with threat IPs.""" members = ''.join([f"<member>threat-{ip.replace('.', '-')}</member>" for ip in ips]) xpath = f"/config/devices/entry/vsys/entry[@name='vsys1']/address-group/entry[@name='{ADDRESS_GROUP}']" element = f"<static>{members}</static><description>Auto-updated from DugganUSA STIX feed</description>"
panos_api('set', xpath, element) print(f"Updated address group with {len(ips)} members")
def commit(): """Commit configuration changes.""" params = { 'type': 'commit', 'cmd': '<commit></commit>', 'key': PANOS_API_KEY } response = requests.get(f"https://{PANOS_HOST}/api/", params=params, verify=False) print("Commit initiated")
def main(): ips = get_threat_ips(min_confidence=90) print(f"Fetched {len(ips)} threat IPs")
update_address_objects(ips) update_address_group(ips) commit()
if __name__ == "__main__": main() ```
PAN-OS CLI Reference
# View EDL status
show object external-dynamic-list DugganUSA-Threat-Intel2. Cisco (ASA / Firepower / FTD)
Cisco ASA (Classic)
ASA supports network object groups populated via scripts.
Automated Update Script:
#!/usr/bin/env python3
"""
Cisco ASA Threat Intel Updater
Uses Paramiko for SSH-based configuration
pip install paramiko requests
"""import paramiko import requests import re import time
def get_threat_ips(min_confidence=90): """Fetch IPs from STIX feed.""" response = requests.get(f"{STIX_URL}?min_confidence={min_confidence}") bundle = response.json()
ips = [] for obj in bundle.get('objects', []): if obj.get('type') == 'indicator': match = re.search(r'\d+\.\d+\.\d+\.\d+', obj.get('pattern', '')) if match: ips.append(match.group())
return list(set(ips))
def update_asa(ips): """Update ASA network object group via SSH.""" ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(ASA_HOST, username=ASA_USER, password=ASA_PASS)
shell = ssh.invoke_shell() time.sleep(1)
def send_command(cmd): shell.send(cmd + '\n') time.sleep(0.5)
for ip in ips[:500]: # ASA limit considerations send_command(f' network-object host {ip}')
send_command('exit') send_command('write memory')
ssh.close() print(f"Updated ASA with {min(len(ips), 500)} IPs")
def main(): ips = get_threat_ips(min_confidence=90) print(f"Fetched {len(ips)} threat IPs") update_asa(ips)
if __name__ == "__main__": main() ```
ASA ACL Configuration:
! Create the object group (done by script)
object-group network DUGGANUSA-BLOCKLIST
description DugganUSA Threat Intel Feed
network-object host 1.2.3.4
network-object host 5.6.7.8! Apply to ACL access-list OUTSIDE-IN extended deny ip object-group DUGGANUSA-BLOCKLIST any log access-list OUTSIDE-IN extended deny ip any object-group DUGGANUSA-BLOCKLIST log
! Apply to interface access-group OUTSIDE-IN in interface outside ```
Cisco Firepower (FMC API)
#!/usr/bin/env python3
"""
Cisco FMC Network Group Updater
pip install requests
"""import requests import re import json
class FMCClient: def __init__(self, host, user, password): self.host = host self.base_url = f"https://{host}/api/fmc_config/v1" self.session = requests.Session() self.session.verify = False self.authenticate(user, password)
def authenticate(self, user, password): """Get authentication token.""" auth_url = f"https://{self.host}/api/fmc_platform/v1/auth/generatetoken" response = self.session.post(auth_url, auth=(user, password)) self.token = response.headers.get('X-auth-access-token') self.session.headers.update({ 'X-auth-access-token': self.token, 'Content-Type': 'application/json' })
def get_network_group(self, name): """Get network group by name.""" url = f"{self.base_url}/domain/{DOMAIN_UUID}/object/networkgroups" response = self.session.get(url, params={'filter': f'name:{name}'}) items = response.json().get('items', []) return items[0] if items else None
def create_host_object(self, ip): """Create host object.""" url = f"{self.base_url}/domain/{DOMAIN_UUID}/object/hosts" data = { 'name': f'threat-{ip.replace(".", "-")}', 'type': 'Host', 'value': ip, 'description': 'DugganUSA Threat Intel' } response = self.session.post(url, json=data) return response.json().get('id')
def update_network_group(self, group_id, host_ids): """Update network group with hosts.""" url = f"{self.base_url}/domain/{DOMAIN_UUID}/object/networkgroups/{group_id}" data = { 'id': group_id, 'type': 'NetworkGroup', 'objects': [{'type': 'Host', 'id': hid} for hid in host_ids] } self.session.put(url, json=data)
def main(): # Fetch threat IPs response = requests.get(f"{STIX_URL}?min_confidence=90") bundle = response.json()
ips = [] for obj in bundle.get('objects', []): if obj.get('type') == 'indicator': match = re.search(r'\d+\.\d+\.\d+\.\d+', obj.get('pattern', '')) if match: ips.append(match.group())
ips = list(set(ips))[:200] # Limit for performance print(f"Processing {len(ips)} IPs")
host_ids = [] for ip in ips: host_id = fmc.create_host_object(ip) if host_id: host_ids.append(host_id)
group = fmc.get_network_group('DugganUSA-Threats') if group: fmc.update_network_group(group['id'], host_ids) print(f"Updated network group with {len(host_ids)} hosts")
if __name__ == "__main__": main() ```
3. Fortinet FortiGate
FortiGate supports External Block Lists (Threat Feeds) natively.
Option A: External Threat Feed (Simplest)
Step 1: Host IP List
#!/usr/bin/env python3
"""
FortiGate Threat Feed Generator
Returns IPs in format FortiGate expects
"""from flask import Flask, Response import requests import re
app = Flask(__name__)
@app.route('/fortigate/blocklist.txt') def blocklist(): response = requests.get( "https://analytics.dugganusa.com/api/v1/stix-feed/v2?min_confidence=90" ) bundle = response.json()
ips = set() for obj in bundle.get('objects', []): if obj.get('type') == 'indicator': match = re.search(r'\d+\.\d+\.\d+\.\d+', obj.get('pattern', '')) if match: ips.add(match.group())
if __name__ == '__main__': app.run(host='0.0.0.0', port=8080) ```
Step 2: Configure FortiGate
config system external-resource
edit "DugganUSA-Threats"
set type address
set resource "http://your-server:8080/fortigate/blocklist.txt"
set refresh-rate 60
next
endconfig firewall policy edit 100 set name "Block-DugganUSA-Threats" set srcintf "wan1" set dstintf "any" set srcaddr "all" set dstaddr "DugganUSA-Threats" set action deny set schedule "always" set logtraffic all next end ```
Option B: FortiGate API
#!/usr/bin/env python3
"""
FortiGate API Integration
pip install requests
"""import requests import re
def get_threat_ips(min_confidence=90): response = requests.get(f"{STIX_URL}?min_confidence={min_confidence}") bundle = response.json()
ips = [] for obj in bundle.get('objects', []): if obj.get('type') == 'indicator': match = re.search(r'\d+\.\d+\.\d+\.\d+', obj.get('pattern', '')) if match: ips.append(match.group())
return list(set(ips))
def fgt_api(method, endpoint, data=None): """Make FortiGate API call.""" url = f"https://{FGT_HOST}/api/v2/{endpoint}" headers = {'Authorization': f'Bearer {FGT_API_KEY}'}
if method == 'GET': response = requests.get(url, headers=headers, verify=False) elif method == 'POST': response = requests.post(url, headers=headers, json=data, verify=False) elif method == 'PUT': response = requests.put(url, headers=headers, json=data, verify=False) elif method == 'DELETE': response = requests.delete(url, headers=headers, verify=False)
return response.json()
def update_address_group(ips): """Update FortiGate address group.""" # Create address objects for ip in ips: name = f"threat-{ip.replace('.', '-')}" fgt_api('POST', 'cmdb/firewall/address', { 'name': name, 'type': 'ipmask', 'subnet': f'{ip}/32', 'comment': 'DugganUSA Threat Intel' })
print(f"Updated address group with {len(ips)} members")
def main(): ips = get_threat_ips(min_confidence=90) print(f"Fetched {len(ips)} threat IPs") update_address_group(ips[:500]) # Limit for performance
if __name__ == "__main__": main() ```
FortiGate CLI Reference
# Check external resource status
diagnose autoupdate status4. Check Point
Check Point supports External IOC Feeds via SmartConsole or API.
Option A: External IOC Feed
Step 1: Configure Feed in SmartConsole
Security Policies > Threat Prevention > Indicators
Click "Add External Feed"
Configure:
Field | Value |
Name | DugganUSA-Threat-Intel |
URL | http://your-server:8080/checkpoint/ioc-feed.csv |
Feed Type | CSV |
Update Interval | 60 minutes |
Action | Prevent |
Step 2: Host Feed in Check Point Format
#!/usr/bin/env python3
"""
Check Point IOC Feed Generator
CSV format: indicator,type,confidence,description
"""from flask import Flask, Response import requests import re import io import csv
app = Flask(__name__)
@app.route('/checkpoint/ioc-feed.csv') def ioc_feed(): response = requests.get( "https://analytics.dugganusa.com/api/v1/stix-feed/v2?min_confidence=80" ) bundle = response.json()
output = io.StringIO() writer = csv.writer(output) writer.writerow(['indicator', 'type', 'confidence', 'description'])
Microsoft pulls this feed daily. AT&T pulls this feed daily. Starlink pulls this feed daily. Get the DugganUSA STIX feed — $9/mo →
for obj in bundle.get('objects', []): if obj.get('type') == 'indicator': match = re.search(r'\d+\.\d+\.\d+\.\d+', obj.get('pattern', '')) if match: writer.writerow([ match.group(), 'IP', obj.get('confidence', 80), obj.get('name', 'DugganUSA Threat')[:100] ])
return Response(output.getvalue(), mimetype='text/csv')
if __name__ == '__main__': app.run(host='0.0.0.0', port=8080) ```
Option B: Check Point Management API
#!/usr/bin/env python3
"""
Check Point Management API Integration
pip install cpapi requests
"""import requests import re import json
class CheckPointAPI: def __init__(self, host, user, password): self.host = host self.base_url = f"https://{host}/web_api" self.session = requests.Session() self.session.verify = False self.login(user, password)
def login(self, user, password): response = self.session.post(f"{self.base_url}/login", json={ 'user': user, 'password': password }) self.sid = response.json()['sid'] self.session.headers.update({'X-chkp-sid': self.sid})
def add_host(self, name, ip): return self.session.post(f"{self.base_url}/add-host", json={ 'name': name, 'ip-address': ip, 'comments': 'DugganUSA Threat Intel' }).json()
def set_group(self, name, members): return self.session.post(f"{self.base_url}/set-group", json={ 'name': name, 'members': members }).json()
def publish(self): return self.session.post(f"{self.base_url}/publish", json={}).json()
def logout(self): self.session.post(f"{self.base_url}/logout", json={})
def main(): # Fetch threats response = requests.get(f"{STIX_URL}?min_confidence=90") bundle = response.json()
ips = [] for obj in bundle.get('objects', []): if obj.get('type') == 'indicator': match = re.search(r'\d+\.\d+\.\d+\.\d+', obj.get('pattern', '')) if match: ips.append(match.group())
ips = list(set(ips))[:300] print(f"Processing {len(ips)} IPs")
host_names = [] for ip in ips: name = f"threat-{ip.replace('.', '-')}" cp.add_host(name, ip) host_names.append(name)
cp.set_group('DugganUSA-Threats', host_names) cp.publish() cp.logout()
print(f"Updated Check Point with {len(ips)} threat hosts")
if __name__ == "__main__": main() ```
5. Sophos XG / XGS Firewall
Sophos supports IP Host Lists via web admin or API.
Option A: Scheduled Script
#!/usr/bin/env python3
"""
Sophos XG API Integration
pip install requests
"""import requests import re import xml.etree.ElementTree as ET
def get_threat_ips(min_confidence=90): response = requests.get(f"{STIX_URL}?min_confidence={min_confidence}") bundle = response.json()
ips = [] for obj in bundle.get('objects', []): if obj.get('type') == 'indicator': match = re.search(r'\d+\.\d+\.\d+\.\d+', obj.get('pattern', '')) if match: ips.append(match.group())
return list(set(ips))
def sophos_api(request_xml): """Make Sophos API call.""" url = f"https://{SOPHOS_HOST}:4444/webconsole/APIController" response = requests.post(url, data={'reqxml': request_xml}, verify=False) return ET.fromstring(response.text)
def update_ip_host_group(ips): """Update Sophos IP Host Group.""" # Create IP hosts for ip in ips: name = f"threat-{ip.replace('.', '-')}" request = f""" <Request> <Login> <Username>{SOPHOS_USER}</Username> <Password>{SOPHOS_PASS}</Password> </Login> <Set operation="add"> <IPHost> <Name>{name}</Name> <IPFamily>IPv4</IPFamily> <HostType>IP</HostType> <IPAddress>{ip}</IPAddress> </IPHost> </Set> </Request> """ sophos_api(request)
def main(): ips = get_threat_ips(min_confidence=90) print(f"Fetched {len(ips)} threat IPs") update_ip_host_group(ips[:500])
if __name__ == "__main__": main() ```
Sophos Central (Cloud-Managed)
For Sophos Central managed firewalls, use the Central API:
#!/usr/bin/env python3
"""
Sophos Central API Integration
"""import requests
def get_sophos_token(): """Get OAuth token for Sophos Central.""" response = requests.post( "https://id.sophos.com/api/v2/oauth2/token", data={ 'grant_type': 'client_credentials', 'client_id': CENTRAL_CLIENT_ID, 'client_secret': CENTRAL_CLIENT_SECRET, 'scope': 'token' } ) return response.json()['access_token']
def update_blocklist(token, ips): """Update Sophos Central blocklist.""" headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' }
for ip in ips: requests.post( "https://api.central.sophos.com/endpoint/v1/settings/blocked-items", headers=headers, json={ 'type': 'ip', 'properties': {'ip': ip}, 'comment': 'DugganUSA Threat Intel' } )
print(f"Updated Sophos Central with {len(ips)} IPs") ```
Universal Cron Setup
Linux Cron Job
# /etc/cron.d/threat-intel-update
# Update firewall block lists hourly0 root /opt/scripts/firewall_threat_update.py >> /var/log/threat-update.log 2>&1 ```
Systemd Timer (Modern Linux)
# /etc/systemd/system/threat-intel-update.service
[Unit]
Description=Update firewall threat intelligence
After=network.target[Service] Type=oneshot ExecStart=/opt/scripts/firewall_threat_update.py User=root
[Timer] OnCalendar=hourly Persistent=true
[Install] WantedBy=timers.target ```
systemctl enable threat-intel-update.timer
systemctl start threat-intel-update.timerWindows Task Scheduler
# PowerShell: Create scheduled task
$action = New-ScheduledTaskAction -Execute "python.exe" -Argument "C:\Scripts\firewall_threat_update.py"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Hours 1)
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopIfGoingOnBatteriesRegister-ScheduledTask -TaskName "ThreatIntelUpdate" -Action $action -Trigger $trigger -Settings $settings -User "SYSTEM" ```
Best Practices
1. Confidence Thresholds
Use Case | Minimum Confidence | Notes |
Auto-block (production) | 90+ | Low false positive risk |
Auto-block (aggressive) | 80+ | Slightly higher FP risk |
Alert only | 70+ | Review before action |
Logging/enrichment | Any | Context only |
2. Rate Limiting
Recommended: Poll every 60 minutes
Maximum useful: Every 15 minutes
Wasteful: More than every 5 minutes
3. Allowlist Management
Always maintain an allowlist for false positive recovery:
ALLOWLIST = {
'8.8.8.8', # Google DNS
'1.1.1.1', # Cloudflare DNS
'208.67.222.222', # OpenDNS
}ips = [ip for ip in threat_ips if ip not in ALLOWLIST] ```
4. Logging
Log all automated blocks for audit:
import logginglogging.basicConfig( filename='/var/log/threat-blocks.log', format='%(asctime)s - %(message)s' )
for ip in blocked_ips: logging.info(f"AUTO-BLOCKED: {ip} (confidence: {confidence}%)") ```
5. Gradual Rollout
Start conservative, then tune:
Week 1: min_confidence=95 - Only highest confidence
Week 2: min_confidence=90 - Expand if no issues
Week 3: min_confidence=85 - Continue tuning
Ongoing: Monitor and adjust
Troubleshooting
Common Issues
Problem | Solution |
Empty response | Check internet connectivity, verify URL |
Timeout | Increase timeout to 30s, check firewall egress |
JSON parse error | Verify jq/Python version, check response content |
API auth failure | Regenerate API key, check permissions |
Objects not appearing | Check commit/publish step, verify object names |
Testing
# Verify feed is accessible
curl -sI "https://analytics.dugganusa.com/api/v1/stix-feed/v2" | head -5Why Free?
Commercial threat feeds cost $20,000-$100,000/year. We publish ours for free because:
Zero marginal cost - Bits are free to copy
Network effect - More users = better detection
Transparency - Open data builds trust
Competition - Forces commercial vendors to improve
We're a two-person operation in Minnesota. If a free feed from a small shop matches or beats your $50K enterprise subscription, that says something about the threat intel market.
Support
Email: [email protected]
Status: https://status.dugganusa.com
Feed docs: https://analytics.dugganusa.com/docs/stix-feed
Found a false positive? Email us the IP - we investigate every report.
License
CC0-1.0 (Public Domain)
Use however you want. Attribution appreciated but not required.
Published by DugganUSA LLC
"Democratic Sharing Law - publish threat intel openly because shared intelligence improves faster than hoarded intelligence."
Her name is Renee Nicole Good.
The cheapest, fastest, most accurate threat feed on the internet.
275+ enterprises pulling daily. 1M+ IOCs. 17.4M indexed documents. We beat Zscaler by 43 days on NrodeCodeRAT. Starter tier $9/mo — less than any competitor’s sales demo.
Was this useful? Thirty seconds, no cookies, no tracking, no third parties, your address hashed and never stored. If the box below does not load, the same question lives at https://analytics.dugganusa.com/nps.html?post=automate-your-firewall-block-lists-with-free-threat-intelligence-integration-guide-for-top-5-vendor




Comments