```html ```
top of page

Automate Your Firewall Block Lists with Free Threat Intelligence: Integration Guide for Top 5 Vendors

  • Writer: Patrick Duggan
    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 -u



Full 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 > Add


Name: 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 > Add


Name: 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-Intel





2. 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
end


config 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 status





4. Check Point


Check Point supports External IOC Feeds via SmartConsole or API.



Option A: External IOC Feed


Step 1: Configure Feed in SmartConsole


  1. Security Policies > Threat Prevention > Indicators

  2. Click "Add External Feed"

  3. 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 hourly


0 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.timer



Windows 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 -DontStopIfGoingOnBatteries


Register-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 logging


logging.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:


  1. Week 1: min_confidence=95 - Only highest confidence

  2. Week 2: min_confidence=90 - Expand if no issues

  3. Week 3: min_confidence=85 - Continue tuning

  4. 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 -5





Why Free?


Commercial threat feeds cost $20,000-$100,000/year. We publish ours for free because:


  1. Zero marginal cost - Bits are free to copy

  2. Network effect - More users = better detection

  3. Transparency - Open data builds trust

  4. 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

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page