Automate Your Firewall Block Lists with Free Threat Intelligence: Integration Guide for Top 5 Vendors
- Patrick Duggan
- Jan 23
- 9 min read
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'])
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.




Comments