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


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'])


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.


Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page