Research

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

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:

CategoryDescription
Botnet C2sCommand and control infrastructure
ScannersAggressive reconnaissance actors
Exploit hostsActive exploitation attempts
Malware distributionPayload 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:
FieldValue
NameDugganUSA-Threat-Intel
URLhttp://your-server:8080/checkpoint/ioc-feed.csv
Feed TypeCSV
Update Interval60 minutes
ActionPrevent

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 CaseMinimum ConfidenceNotes
Auto-block (production)90+Low false positive risk
Auto-block (aggressive)80+Slightly higher FP risk
Alert only70+Review before action
Logging/enrichmentAnyContext 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

ProblemSolution
Empty responseCheck internet connectivity, verify URL
TimeoutIncrease timeout to 30s, check firewall egress
JSON parse errorVerify jq/Python version, check response content
API auth failureRegenerate API key, check permissions
Objects not appearingCheck 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.

Look up an IOC → · Audit your brand on AIPM → · See pricing →


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