Skip to main content
Beginner20 minNetwork Engineer / Security AdministratorTAXII feed: Pro plan and up · Free: bulk check

Blocking Malicious IPs with Firewall Automation

What you will achieve

A cron-driven pipeline that automatically blocks malicious IPs at the network edge, updated every 6 hours from observed threat activity intelligence.

isMalicious APIPythoniptablespfSenseAWS
Jean-Vincent QUILICHINIJean-Vincent QUILICHINI2026-03-05

What You Will Achieve

By the end of this playbook you will have:

  • A Python script that pulls malicious IPs from isMalicious and formats them for your firewall
  • iptables, pfSense, Palo Alto, and cloud implementation examples
  • A cron schedule that keeps blocklists fresh every 6 hours
  • Change detection so only new IPs are added (no duplicate rules)

Prerequisites

Requirement Details
isMalicious API key Free tier gives 250 requests/month; Pro for the TAXII export
Python 3.9+ Runs the sync script
Root/admin access Required to modify firewall rules

Step 1: Fetch Malicious IPs from isMalicious

The STIX/TAXII feed (Pro) is the cleanest way to pull IOCs in bulk. On the Free plan, check the addresses you already see (firewall logs, alerts) with the bulk check endpoint instead.

Using the TAXII feed (Pro)

python
import base64
import requests

TAXII_URL = "https://api.ismalicious.com/taxii/api-root/collections/malicious-ips/objects"
API_KEY = "YOUR_API_KEY"
API_SECRET = "YOUR_API_SECRET"
HDR = {"X-API-KEY": base64.b64encode(f"{API_KEY}:{API_SECRET}".encode()).decode()}

def fetch_malicious_ips():
    resp = requests.get(
        TAXII_URL,
        params={"limit": 1000},
        headers=HDR
    )
    resp.raise_for_status()
    bundle = resp.json()

    ips = []
    for obj in bundle.get("objects", []):
        if obj.get("type") == "indicator":
            pattern = obj.get("pattern", "")
            # Extract IPv4 from STIX pattern: [ipv4-addr:value = '1.2.3.4']
            if "ipv4-addr:value" in pattern:
                ip = pattern.split("'")[1]
                ips.append(ip)

    return ips

Step 2: iptables Integration

blocklist_update.sh

bash
#!/bin/bash
set -euo pipefail

CHAIN="ISMALICIOUS_BLOCK"
BLOCKLIST="/tmp/ismalicious_ips.txt"

# Fetch fresh blocklist
python3 /opt/ismalicious/fetch_ips.py > "$BLOCKLIST"

# Create chain if it doesn't exist
iptables -N "$CHAIN" 2>/dev/null || true
iptables -F "$CHAIN"

# Re-add all rules
while IFS= read -r ip; do
  [[ -z "$ip" ]] && continue
  iptables -A "$CHAIN" -s "$ip" -j DROP
  iptables -A "$CHAIN" -d "$ip" -j DROP
done < "$BLOCKLIST"

# Ensure chain is referenced from INPUT/OUTPUT/FORWARD
iptables -C INPUT   -j "$CHAIN" 2>/dev/null || iptables -I INPUT   1 -j "$CHAIN"
iptables -C OUTPUT  -j "$CHAIN" 2>/dev/null || iptables -I OUTPUT  1 -j "$CHAIN"
iptables -C FORWARD -j "$CHAIN" 2>/dev/null || iptables -I FORWARD 1 -j "$CHAIN"

echo "$(date): Updated $(wc -l < "$BLOCKLIST") blocked IPs"

Add to cron

bash
# Every 6 hours
0 */6 * * * /opt/ismalicious/blocklist_update.sh >> /var/log/ismalicious_blocklist.log 2>&#x26;1

Step 3: pfSense with pfBlockerNG

pfBlockerNG cannot send the X-API-KEY header. Use a cron job on the firewall (or a jump host) to download with curl and Base64 credentials, then point pfBlockerNG at the local file (see blocklist guide):

bash
curl -fsS -H "X-API-KEY: $(printf '%s' 'KEY:SECRET' | base64 | tr -d '\n')" \
  "https://api.ismalicious.com/blocklist/download/blocklist-ips-all.txt" \
  -o /var/db/pfblockerng/ismalicious-ips.txt

Step 4: AWS Security Group Automation

python
import boto3
from fetch_ips import fetch_malicious_ips

ec2 = boto3.client("ec2", region_name="us-east-1")
SG_ID = "sg-XXXXXXXXXXXXXXXXX"

def update_security_group():
    ips = fetch_malicious_ips()

    # Remove all existing deny rules
    sg = ec2.describe_security_groups(GroupIds=[SG_ID])["SecurityGroups"][0]
    if sg.get("IpPermissionsEgress"):
        ec2.revoke_security_group_egress(
            GroupId=SG_ID,
            IpPermissions=sg["IpPermissionsEgress"]
        )

    # Add new deny rules in batches of 50 (AWS limit)
    ip_ranges = [{"CidrIp": f"{ip}/32", "Description": "isMalicious block"} for ip in ips[:50]]
    if ip_ranges:
        ec2.authorize_security_group_egress(
            GroupId=SG_ID,
            IpPermissions=[{
                "IpProtocol": "-1",
                "IpRanges": ip_ranges
            }]
        )

    print(f"Updated {len(ip_ranges)} blocked IPs in {SG_ID}")

if __name__ == "__main__":
    update_security_group()

Step 5: Palo Alto Networks

PAN-OS EDL URLs cannot send X-API-KEY. Host the list yourself: cron downloads blocklist-ips-all.txt (or critical) via curl + Base64 credentials to an internal HTTPS server or object storage, then point the EDL at that URL.


Validation

After deploying, verify a known malicious IP is blocked:

bash
# Test connectivity to a known malicious IP (should timeout/be blocked)
curl --connect-timeout 3 http://185.220.101.1/ || echo "Blocked successfully"

# Check iptables hit count
iptables -L ISMALICIOUS_BLOCK -v -n | head -20

Monitoring & Alerting

Log every blocked connection for SOC review:

bash
# Log instead of silently drop (for SIEM ingestion)
iptables -A ISMALICIOUS_BLOCK -s "$ip" -j LOG --log-prefix "ISMALICIOUS_BLOCKED: "
iptables -A ISMALICIOUS_BLOCK -s "$ip" -j DROP

Route these logs to your SIEM and create an alert rule for high-frequency hits (potential active attack).


Did this playbook work for you?

Run this playbook today

Start with a free API key; move to Pro when you need more volume.

No credit card required · No monthly cap on website lookups · 250 API requests/month