new file: .gitignore

modified:   README.md
	new file:   app/dashboard.py
	new file:   app/main.py
	new file:   docker-compose.yml
	new file:   snort/local.rules
	new file:   snort/snort-logs/soc_actions.log
	new file:   snort/snort.lua
	new file:   snort/snort3-community.rules
This commit is contained in:
2026-05-29 21:22:34 -04:00
parent 03544b828d
commit 19c23117b9
9 changed files with 4923 additions and 2 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# .gitignore
.env
__pycache__/
snort/logs/*.log
snort/logs/*.txt
!snort/logs/.gitkeep

View File

@@ -1,3 +1,80 @@
# firewall-roulette
# Firewall Roulette
Because every time the LLM triggers, you might lose access to your own network!
> An experimental AI-powered Intrusion Prevention System (IPS) that occasionally tries to ban your own router.
**Firewall Roulette** is a Proof of Concept (PoC) research project that integrates **Snort 3**, a custom Python middleware, and a Large Language Model (**Claude 3.5 Haiku** via OpenRouter) to automatically generate and apply firewall block rules in real-time.
Spoiler alert: Giving an LLM direct write access to your local firewall rules is a terrible, hilarious idea. This repository serves as empirical proof of why deterministic security tools are still the gold standard.
---
## The "Why You Shouldn't Use This in Production" Findings
During the development and testing of this automated SOC, the LLM repeatedly demonstrated why AI is currently unfit to handle raw network perimeter defense without massive amounts of hardcoded "babysitting":
1. **Friendly Fire (Lack of Spatial Awareness):** Despite strict prompt constraints defining the local subnet (`192.168.1.0/24`), the LLM would frequently panic at standard internal background noise (like ICMP pings or mDNS broadcasts) and attempt to quarantine the local gateway or isolate the host machine.
2. **Fatal Syntactic Hallucinations:** The LLM would occasionally invent nonexistent network protocols (e.g., `drop eth 0 -> 0 0`). Because Snort strictly validates rules on reload, a single AI hallucination would crash the entire intrusion detection engine.
3. **Ghost Hunting (The Ephemeral Port Problem):** Rather than blocking an attacker's IP broadly or targeting the compromised service port, the LLM would often hardcode the attacker's randomized ephemeral source port. By the time the rule was deployed, the attacker was already using a different port, rendering the block useless.
**Conclusion:** We had to write so much static Python logic to prevent the AI from destroying the network that the AI itself became redundant. Use [CrowdSec](https://www.crowdsec.net/) or [Suricata](https://suricata.io/) instead.
---
## Architecture
1. **Snort 3 Engine:** Listens to the network interface, detects anomalies via community rulesets, and outputs JSON alerts.
2. **Webhook Shipper (`main.py`):** A Python daemon that tails the Snort log, aggressively filters out local noise to save API tokens, alerts Discord, and passes external threat payloads to the LLM via tool-calling.
3. **The LLM (Claude 3.5 Haiku):** Analyzes the payload and triggers a Python tool to write a strictly formatted Snort `drop` rule.
4. **FastAPI Dashboard (`dashboard.py`):** A local web UI for simulating attacks, visualizing the active rule ledger, and reading the LLM's internal thought process in real-time.
---
## Installation & Usage
### 1. Requirements
* Docker & Docker Compose (or a manager like Portainer)
* An [OpenRouter API Key](https://openrouter.ai/)
* A Discord Webhook URL (optional, for alerts)
### 2. Setup
Clone the repository and set up the required placeholder files:
```bash
git clone [https://gitea.sekidesu.xyz/SekiDesu01/firewall-roulette.git](https://gitea.sekidesu.xyz/SekiDesu01/firewall-roulette.git)
cd firewall-roulette
mkdir snort-logs
touch local.rules
touch snort-logs/soc_actions.log
```
Create a `.env` file for your API keys:
```env
WEBHOOK_URL="[https://discord.com/api/webhooks/your-webhook](https://discord.com/api/webhooks/your-webhook)"
OPENROUTER_API_KEY="sk-or-v1-your-key-here"
```
### 3. Deploy
Start the stack using Docker Compose:
```bash
docker compose up -d
```
### 4. Access the Dashboard
Open your browser and navigate to:
```text
http://localhost:5050
```
From here, you can use **Chaos Mode** to inject random external attacks into the pipeline and watch the LLM try (and sometimes fail) to write valid blocking rules in real-time.
---
## 🛠️ Tech Stack
* **Engine:** Snort 3
* **Middleware/Dashboard:** Python 3.11, FastAPI, Uvicorn, Requests
* **Frontend:** HTML5, TailwindCSS (via CDN), Vanilla JS
* **AI:** Claude-3.5-Haiku via OpenRouter API
---
## 📝 License
AGPL License. Do whatever you want with this, but please do not deploy it on a corporate network unless you want to get fired.

218
app/dashboard.py Normal file
View File

@@ -0,0 +1,218 @@
import json
import os
import random
from fastapi import FastAPI, Form, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
app = FastAPI(title="Duct-Tape SOC Dashboard")
LOG_FILE = "/var/log/snort/alert_json.txt"
RULES_FILE = "/etc/snort/rules/local.rules"
ACTION_LOG = "/var/log/snort/soc_actions.log"
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Duct-Tape SOC Dashboard v3</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-900 text-gray-100 font-sans min-h-screen p-6">
<div class="max-w-7xl mx-auto space-y-6">
<header class="border-b border-gray-800 pb-4 flex justify-between items-center">
<div>
<h1 class="text-3xl font-bold text-indigo-400">Duct-Tape SOC</h1>
<p class="text-sm text-gray-400">Advanced Snort 3 & LLM Firewall Automation Simulator</p>
</div>
<div class="flex gap-4 items-center">
<div class="text-right mr-4 text-sm text-gray-400">
Active Rules: <span id="ruleCount" class="font-bold text-green-400">0</span><br>
Alerts Logged: <span id="alertCount" class="font-bold text-blue-400">0</span>
</div>
<button onclick="clearRules()" class="bg-red-600 hover:bg-red-700 text-white font-medium py-2 px-4 rounded transition">
Wipe Active Rules
</button>
</div>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="bg-gray-800 p-6 rounded-lg border border-gray-700 space-y-6">
<div>
<h2 class="text-xl font-semibold text-indigo-300 mb-4">Quick Scenarios</h2>
<select id="scenarioSelect" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-white mb-3">
<option value="ssh_attack">External SSH Brute Force</option>
<option value="nmap_scan">External Nmap Scan</option>
<option value="ssdp_noise">Internal SSDP Noise</option>
<option value="mdns_noise">Internal mDNS Noise</option>
</select>
<button onclick="injectStandard()" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 rounded transition">
Fire Quick Payload
</button>
</div>
<hr class="border-gray-700">
<div>
<h2 class="text-xl font-semibold text-orange-400 mb-4">Chaos Mode</h2>
<button onclick="injectRandom()" class="w-full bg-orange-600 hover:bg-orange-700 text-white font-medium py-2 rounded transition">
Generate Random Attack
</button>
</div>
</div>
<div class="bg-gray-800 p-6 rounded-lg border border-gray-700 flex flex-col h-[350px]">
<h2 class="text-xl font-semibold text-pink-400 mb-4">Python & LLM Action Stream</h2>
<pre id="actionBox" class="bg-gray-950 p-4 rounded border border-gray-800 overflow-y-auto text-xs font-mono flex-1 text-gray-300 whitespace-pre-wrap">Awaiting actions...</pre>
</div>
<div class="bg-gray-800 p-6 rounded-lg border border-gray-700 flex flex-col h-[350px]">
<h2 class="text-xl font-semibold text-indigo-300 mb-4">Active local.rules</h2>
<pre id="rulesBox" class="bg-gray-950 p-4 rounded border border-gray-800 overflow-y-auto text-xs font-mono flex-1 text-green-400">Loading rules...</pre>
</div>
</div>
<div class="bg-gray-800 p-6 rounded-lg border border-gray-700">
<h2 class="text-xl font-semibold text-indigo-300 mb-4">Live Snort JSON Tail</h2>
<div id="alertsContainer" class="space-y-2 max-h-[300px] overflow-y-auto">
<p class="text-sm text-gray-500 italic">Streaming logs...</p>
</div>
</div>
</div>
<script>
async function updateDashboardData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
document.getElementById('rulesBox').textContent = data.rules || "No rules active.";
document.getElementById('ruleCount').textContent = data.rule_count;
document.getElementById('alertCount').textContent = data.total_alerts;
// Update new Action Log Box
const actionBox = document.getElementById('actionBox');
actionBox.textContent = data.actions || "No actions logged.";
const container = document.getElementById('alertsContainer');
if (data.alerts && data.alerts.length > 0) {
container.innerHTML = data.alerts.map(alert =>
`<pre class="bg-gray-950 p-3 rounded border border-gray-800 text-xs font-mono text-gray-300 overflow-x-auto mb-2">${JSON.stringify(alert, null, 2)}</pre>`
).join('');
} else {
container.innerHTML = '<p class="text-sm text-gray-500 italic">No alerts seen yet.</p>';
}
} catch (err) {
console.error("Dashboard poll failed:", err);
}
}
async function injectStandard() {
const formData = new FormData();
formData.append('scenario', document.getElementById('scenarioSelect').value);
await fetch('/inject-standard', { method: 'POST', body: formData });
updateDashboardData();
}
async function injectRandom() {
await fetch('/inject-random', { method: 'POST' });
updateDashboardData();
}
async function clearRules() {
await fetch('/clear-rules', { method: 'POST' });
updateDashboardData();
}
setInterval(updateDashboardData, 2000);
window.onload = updateDashboardData;
</script>
</body>
</html>
"""
def write_alert(payload):
try:
with open(LOG_FILE, "a") as f:
f.write(json.dumps(payload) + "\n")
f.flush()
os.fsync(f.fileno())
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to write to log: {e}")
@app.get("/", response_class=HTMLResponse)
def index():
return HTML_TEMPLATE
@app.get("/api/data")
def get_data():
rules_content = "File empty or missing."
rule_count = 0
if os.path.exists(RULES_FILE):
with open(RULES_FILE, "r") as f:
rules_content = f.read().strip()
rule_count = rules_content.count("drop ")
actions_content = ""
if os.path.exists(ACTION_LOG):
with open(ACTION_LOG, "r") as f:
# Grab the last 25 lines of the action stream
actions_content = "".join(f.readlines()[-25:])
parsed_alerts = []
total_alerts = 0
if os.path.exists(LOG_FILE):
with open(LOG_FILE, "r") as f:
lines = f.readlines()
total_alerts = len(lines)
for line in reversed(lines[-10:]):
try:
parsed_alerts.append(json.loads(line))
except:
continue
return JSONResponse(content={
"rules": rules_content,
"alerts": parsed_alerts,
"actions": actions_content.strip(),
"rule_count": rule_count,
"total_alerts": total_alerts
})
@app.post("/inject-standard")
def inject_standard(scenario: str = Form(...)):
scenarios = {
"ssh_attack": {"proto": "TCP", "src_ap": "185.220.101.5:43210", "dst_ap": "192.168.1.50:22", "rule": "1:2000123:1", "msg": "SSH Brute Force Attempt"},
"nmap_scan": {"proto": "TCP", "src_ap": "45.33.32.156:59832", "dst_ap": "192.168.1.50:80", "rule": "1:2000456:1", "msg": "Nmap Port Scan"},
"ssdp_noise": {"proto": "UDP", "src_ap": "192.168.1.121:1900", "dst_ap": "239.255.255.250:1900", "rule": "116:6:1", "msg": "SSDP Broadcast"},
"mdns_noise": {"proto": "UDP", "src_ap": "192.168.1.83:5353", "dst_ap": "224.0.0.251:5353", "rule": "116:6:1", "msg": "mDNS Multicast"}
}
if scenario not in scenarios:
raise HTTPException(status_code=400, detail="Invalid scenario")
write_alert(scenarios[scenario])
return {"status": "success"}
@app.post("/inject-random")
def inject_random():
src_ip = f"{random.randint(1, 223)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(1, 254)}"
src_port = random.randint(1024, 65535)
dst_ip = f"192.168.1.{random.randint(2, 254)}"
dst_port = random.choice([22, 80, 443, 3389, 8080])
payload = {
"proto": random.choice(["TCP", "UDP"]),
"src_ap": f"{src_ip}:{src_port}",
"dst_ap": f"{dst_ip}:{dst_port}",
"rule": f"1:{random.randint(10000, 99999)}:1",
"msg": "Simulated Random Attack"
}
write_alert(payload)
return {"status": "success"}
@app.post("/clear-rules")
def clear_rules():
try:
with open(RULES_FILE, "w") as f:
f.write("")
return {"status": "success"}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to clear rules: {e}")

233
app/main.py Normal file
View File

@@ -0,0 +1,233 @@
import time
import os
import json
import requests
import re
LOG_FILE = "/var/log/snort/alert_json.txt"
RULES_FILE = "/app/local.rules"
ACTION_LOG = "/var/log/snort/soc_actions.log"
WEBHOOK = os.environ.get("WEBHOOK_URL")
OPENROUTER_KEY = os.environ.get("OPENROUTER_API_KEY")
LLM_MODEL = "anthropic/claude-3.5-haiku"
INTERNAL_PREFIXES = ("192.168.", "10.", "172.")
def log_msg(msg):
"""Writes to standard output and the shared action log for the dashboard."""
print(msg)
try:
with open(ACTION_LOG, "a") as f:
f.write(msg + "\n")
f.flush()
os.fsync(f.fileno())
except Exception:
pass
def append_snort_rule(rule_string):
rule_string = rule_string.strip()
valid_starts = ["drop tcp", "drop udp", "drop icmp", "drop ip"]
if not any(rule_string.startswith(prefix) for prefix in valid_starts):
log_msg(f"CRITICAL: Blocked invalid protocol syntax: {rule_string}")
return "Rejected: Rule must start with drop tcp, udp, icmp, or ip."
if "any any -> any any" in rule_string:
log_msg("CRITICAL: Blocked LLM attempt to deploy a nuclear 'any any' drop rule.")
return "Rejected: Rule would cause total network blackout."
dangerous_targets = ["255.255.255.255", "224.0.0", "239.255.255", "ff02::"]
if any(target in rule_string for target in dangerous_targets):
log_msg(f"CRITICAL: Blocked LLM attempt to block broadcast/multicast traffic.")
return "Rejected: Rule targets critical local infrastructure noise."
if "->" not in rule_string or "sid:" not in rule_string:
log_msg("CRITICAL: Blocked malformed rule structural syntax.")
return "Rejected: Malformed Snort syntax."
parts = rule_string.split()
if len(parts) > 2:
src_ip = parts[2]
if src_ip.lower() == "any":
log_msg("CRITICAL: Blocked rule. LLM failed to identify a specific attacker IP.")
return "Rejected: Source IP cannot be 'any'."
if src_ip.startswith(INTERNAL_PREFIXES):
log_msg(f"CRITICAL: Blocked LLM attempt to ban internal subnet IP: {src_ip}")
return "Rejected: Cannot block internal network IPs."
if os.path.exists(RULES_FILE):
with open(RULES_FILE, "r") as f:
if src_ip in f.read():
log_msg(f"Skipping: Attacker {src_ip} is already blocked in local.rules")
return "Rejected: IP already blocked."
try:
with open(RULES_FILE, "a") as f:
f.write(f"\n# Auto-generated by LLM\n{rule_string}\n")
return "Rule successfully appended to local.rules."
except Exception as e:
return f"Failed to write rule: {e}"
def get_next_sid():
highest_sid = 1000000
if os.path.exists(RULES_FILE):
with open(RULES_FILE, "r") as f:
sids = re.findall(r'sid:(\d+);', f.read())
if sids:
highest_sid = max([int(s) for s in sids])
return highest_sid + 1
def ask_llm_for_rule(alert_data):
if not OPENROUTER_KEY:
return
next_sid = get_next_sid()
headers = {
"Authorization": f"Bearer {OPENROUTER_KEY}",
"Content-Type": "application/json"
}
prompt = (
"You are an automated SOC analyst generating Snort 3 block rules.\n"
f"Analyze this alert payload:\n{json.dumps(alert_data)}\n\n"
"CRITICAL REQUIREMENTS:\n"
f"1. Use EXACTLY this syntax: drop [proto] [src] any -> [dst] [dst_port] (msg:\"LLM Block\"; sid:{next_sid}; rev:1;)\n"
"2. The SOURCE port MUST ALWAYS be 'any'. Attackers use random ephemeral ports.\n"
"3. If the alert does not specify a clear, non-local external IP address as the attacker, you MUST NOT generate a rule.\n"
"4. NEVER target 255.255.255.255, multicast ranges, or loopback addresses.\n"
"5. CONTEXT: The protected internal network is 192.168.1.0/24. The attacker is ALWAYS external. NEVER use an IP starting with 192.168. as the source.\n"
"6. The source IP must be the specific external attacker. Never use 'any' for the source IP.\n"
"7. THOUGHT PROCESS: Briefly explain your reasoning in 1-2 sentences in the text response before deciding whether to call the tool or do nothing."
)
payload = {
"model": LLM_MODEL,
"messages": [{"role": "user", "content": prompt}],
"tools": [{
"type": "function",
"function": {
"name": "append_snort_rule",
"description": "Appends a new Snort rule.",
"parameters": {
"type": "object",
"properties": {
"rule_string": {
"type": "string",
"description": "The exact valid Snort 3 rule string."
}
},
"required": ["rule_string"]
}
}
}]
}
log_msg("Asking LLM for a block rule...")
try:
response = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload)
response.raise_for_status()
response_data = response.json()
message = response_data["choices"][0]["message"]
content = message.get("content", "")
if content:
log_msg(f"LLM Reasoning: {content.strip()}")
if "tool_calls" in message and message["tool_calls"]:
for tool_call in message["tool_calls"]:
if tool_call["function"]["name"] == "append_snort_rule":
args = json.loads(tool_call["function"]["arguments"])
rule_string = args.get("rule_string")
log_msg(f"LLM generated rule: {rule_string}")
result = append_snort_rule(rule_string)
log_msg(result)
else:
log_msg("LLM opted not to generate a rule.")
except Exception as e:
log_msg(f"Failed to communicate with LLM or parse response: {e}")
def send_discord_alert(data, raw_json_str, proto, src, dst, rule_id):
embed = {
"title": f"[SNORT] {proto} Traffic Detected",
"description": f"**Raw Payload:**\n```json\n{raw_json_str}\n```",
"color": 16711680,
"fields": [
{"name": "Source", "value": f"`{src}`", "inline": True},
{"name": "Destination", "value": f"`{dst}`", "inline": True},
{"name": "Rule ID", "value": f"`{rule_id}`", "inline": True}
],
"footer": {"text": "Duct-Tape SOC"}
}
while True:
try:
response = requests.post(WEBHOOK, json={"embeds": [embed]})
if response.status_code == 429:
wait_time = response.json().get("retry_after", 1)
log_msg(f"Rate limited by Discord. Sleeping for {wait_time}s...")
time.sleep(wait_time)
continue
elif response.status_code in [200, 204]:
log_msg("Sent alert to Discord successfully.")
break
else:
log_msg(f"Discord responded: {response.status_code} - {response.text}")
break
except Exception as e:
log_msg(f"Failed to send to Discord: {e}")
break
def tail_and_ship():
log_msg(f"Waiting for Snort to create {LOG_FILE}...")
while not os.path.exists(LOG_FILE):
time.sleep(1)
log_msg("Log found. Tailing for alerts...")
with open(LOG_FILE, "r") as f:
f.seek(0, 2)
while True:
line = f.readline()
if not line:
time.sleep(0.5)
continue
try:
data = json.loads(line)
proto = data.get("proto", "UNKNOWN")
src = data.get("src_ap", "Unknown")
dst = data.get("dst_ap", "Unknown")
src_ip = src.split(':')[0] if ':' in src else src
dst_ip = dst.split(':')[0] if ':' in dst else dst
if dst_ip == "255.255.255.255" or dst_ip.startswith("224.") or dst_ip.startswith("239.") or dst_ip.startswith("ff02:"):
continue
if src_ip.startswith(INTERNAL_PREFIXES) and dst_ip.startswith(INTERNAL_PREFIXES):
continue
rule_id = data.get("rule", "Unknown")
raw_json_str = json.dumps(data, indent=2)
send_discord_alert(data, raw_json_str, proto, src, dst, rule_id)
ask_llm_for_rule(data)
except Exception as e:
log_msg(f"Failed to process alert: {e}")
if __name__ == "__main__":
if not WEBHOOK:
log_msg("Error: WEBHOOK_URL environment variable is missing.")
exit(1)
# Initialize the log file
with open(ACTION_LOG, "w") as f:
f.write("SOC Action Log Initialized.\n")
tail_and_ship()

52
docker-compose.yml Normal file
View File

@@ -0,0 +1,52 @@
version: '3.8'
services:
snort:
image: ciscotalos/snort3:latest
user: "root"
network_mode: host
privileged: true
volumes:
# Pushed all Snort-related mounts into the ./snort directory
- ./snort/snort-logs:/var/log/snort
- ./snort/local.rules:/etc/snort/rules/local.rules
- ./snort/snort3-community.rules:/etc/snort/rules/snort3-community.rules:ro
- ./snort/snort.lua:/home/snorty/snort3/etc/snort/snort.lua:ro
entrypoint: ["/home/snorty/snort3/bin/snort"]
command: [
"-c", "/home/snorty/snort3/etc/snort/snort.lua",
"-R", "/etc/snort/rules/local.rules",
# NOTE: Users cloning this must change the interface to match their machine
"-i", "enp1s0f0",
"-l", "/var/log/snort",
"--lua", "alert_json = { file = true }",
"--bpf", "not broadcast and not multicast"
]
webhook-shipper:
image: python:3.11-slim
restart: unless-stopped
volumes:
# Updated paths for logs, rules, and the python script
- ./snort/snort-logs:/var/log/snort
- ./app/main.py:/app/main.py:ro
- ./snort/local.rules:/app/local.rules
environment:
- WEBHOOK_URL=${WEBHOOK_URL}
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
working_dir: /app
command: sh -c "pip install --no-cache-dir requests && python -u main.py"
soc-dashboard:
image: python:3.11-slim
restart: unless-stopped
ports:
- "5050:5050"
volumes:
# Updated paths for logs, rules, and the dashboard script
- ./snort/snort-logs:/var/log/snort
- ./snort/local.rules:/etc/snort/rules/local.rules
- ./app/dashboard.py:/app/dashboard.py:ro
working_dir: /app
command: >
sh -c "pip install fastapi uvicorn python-multipart && uvicorn dashboard:app --host 0.0.0.0 --port 5050"

9
snort/local.rules Normal file
View File

@@ -0,0 +1,9 @@
# Auto-generated by LLM
drop tcp 77.247.97.72 any -> 192.168.1.225 8080 (msg:"LLM Block"; sid:1000001; rev:1;)
# Auto-generated by LLM
drop tcp 45.33.32.156 any -> 192.168.1.50 80 (msg:"LLM Block"; sid:1000002; rev:1;)
# Auto-generated by LLM
drop udp 21.154.103.61 any -> 192.168.1.53 80 (msg:"LLM Block"; sid:1000003; rev:1;)

View File

@@ -0,0 +1,30 @@
SOC Action Log Initialized.
Waiting for Snort to create /var/log/snort/alert_json.txt...
Log found. Tailing for alerts...
Sent alert to Discord successfully.
Asking LLM for a block rule...
LLM Reasoning: Analysis:
The alert shows an external IP (77.247.97.72) attacking an internal IP (192.168.1.225) on port 8080 via TCP. The source IP is a valid external address, and the destination is within the protected 192.168.1.0/24 network. All conditions are met to generate a block rule.
I'll create a Snort 3 drop rule that precisely matches the specified syntax:
LLM generated rule: drop tcp 77.247.97.72 any -> 192.168.1.225 8080 (msg:"LLM Block"; sid:1000001; rev:1;)
Rule successfully appended to local.rules.
Sent alert to Discord successfully.
Asking LLM for a block rule...
LLM Reasoning: Analysis of the alert payload:
The alert shows an external IP (45.33.32.156) scanning an internal host (192.168.1.50) on port 80 using TCP. The source IP is a clear, non-local external IP address, and it's targeting a specific internal network host. This meets all the criteria for generating a blocking Snort rule. I will create a rule to drop TCP traffic from this specific external attacker IP to the internal destination.
LLM generated rule: drop tcp 45.33.32.156 any -> 192.168.1.50 80 (msg:"LLM Block"; sid:1000002; rev:1;)
Rule successfully appended to local.rules.
Sent alert to Discord successfully.
Asking LLM for a block rule...
LLM Reasoning: Analysis of the alert payload:
- The source IP (21.154.103.61) is a valid external IP address
- The destination is within the protected 192.168.1.0/24 network
- The protocol is UDP
- Destination port is 80
- This meets all the criteria for generating a block rule
I will generate a Snort 3 drop rule to block UDP traffic from this specific external attacker to the internal network:
LLM generated rule: drop udp 21.154.103.61 any -> 192.168.1.53 80 (msg:"LLM Block"; sid:1000003; rev:1;)
Rule successfully appended to local.rules.

279
snort/snort.lua Normal file
View File

@@ -0,0 +1,279 @@
---------------------------------------------------------------------------
-- Snort++ configuration
---------------------------------------------------------------------------
-- there are over 200 modules available to tune your policy.
-- many can be used with defaults w/o any explicit configuration.
-- use this conf as a template for your specific configuration.
-- 1. configure defaults
-- 2. configure inspection
-- 3. configure bindings
-- 4. configure performance
-- 5. configure detection
-- 6. configure filters
-- 7. configure outputs
-- 8. configure tweaks
---------------------------------------------------------------------------
-- 1. configure defaults
---------------------------------------------------------------------------
-- HOME_NET and EXTERNAL_NET must be set now
-- setup the network addresses you are protecting
HOME_NET = '192.168.1.0/24'
-- set up the external network addresses.
-- (leave as "any" in most situations)
EXTERNAL_NET = 'any'
include 'snort_defaults.lua'
---------------------------------------------------------------------------
-- 2. configure inspection
---------------------------------------------------------------------------
-- mod = { } uses internal defaults
-- you can see them with snort --help-module mod
-- mod = default_mod uses external defaults
-- you can see them in snort_defaults.lua
-- the following are quite capable with defaults:
stream = { }
stream_ip = { }
stream_icmp = { }
stream_tcp = { }
stream_udp = { }
stream_user = { }
stream_file = { }
arp_spoof = { }
back_orifice = { }
dns = { }
imap = { }
netflow = {}
normalizer = { }
pop = { }
rpc_decode = { }
sip = { }
ssh = { }
ssl = { }
telnet = { }
cip = { }
dnp3 = { }
iec104 = { }
mms = { }
modbus = { }
opcua = { }
s7commplus = { }
dce_smb = { }
dce_tcp = { }
dce_udp = { }
dce_http_proxy = { }
dce_http_server = { }
-- see snort_defaults.lua for default_*
gtp_inspect = default_gtp
port_scan = default_med_port_scan
smtp = default_smtp
ftp_server = default_ftp_server
ftp_client = { }
ftp_data = { }
http_inspect = { }
http2_inspect = { }
-- see file_magic.rules for file id rules
file_id = { rules_file = 'file_magic.rules' }
file_policy = { }
js_norm = default_js_norm
-- the following require additional configuration to be fully effective:
appid =
{
-- appid requires this to use appids in rules
--app_detector_dir = 'directory to load appid detectors from'
}
--[[
reputation =
{
-- configure one or both of these, then uncomment reputation
-- (see also related path vars at the top of snort_defaults.lua)
--blacklist = 'blacklist file name with ip lists'
--whitelist = 'whitelist file name with ip lists'
}
--]]
---------------------------------------------------------------------------
-- 3. configure bindings
---------------------------------------------------------------------------
wizard = default_wizard
binder =
{
-- port bindings required for protocols without wizard support
{ when = { proto = 'udp', ports = '53', role='server' }, use = { type = 'dns' } },
{ when = { proto = 'tcp', ports = '53', role='server' }, use = { type = 'dns' } },
{ when = { proto = 'tcp', ports = '111', role='server' }, use = { type = 'rpc_decode' } },
{ when = { proto = 'tcp', ports = '502', role='server' }, use = { type = 'modbus' } },
{ when = { proto = 'tcp', ports = '2123 2152 3386', role='server' }, use = { type = 'gtp_inspect' } },
{ when = { proto = 'tcp', ports = '2404', role='server' }, use = { type = 'iec104' } },
{ when = { proto = 'udp', ports = '2222', role = 'server' }, use = { type = 'cip' } },
{ when = { proto = 'tcp', ports = '44818', role = 'server' }, use = { type = 'cip' } },
{ when = { proto = 'tcp', service = 'dcerpc' }, use = { type = 'dce_tcp' } },
{ when = { proto = 'udp', service = 'dcerpc' }, use = { type = 'dce_udp' } },
{ when = { proto = 'udp', service = 'netflow' }, use = { type = 'netflow' } },
{ when = { service = 'netbios-ssn' }, use = { type = 'dce_smb' } },
{ when = { service = 'dce_http_server' }, use = { type = 'dce_http_server' } },
{ when = { service = 'dce_http_proxy' }, use = { type = 'dce_http_proxy' } },
{ when = { service = 'cip' }, use = { type = 'cip' } },
{ when = { service = 'dnp3' }, use = { type = 'dnp3' } },
{ when = { service = 'dns' }, use = { type = 'dns' } },
{ when = { service = 'ftp' }, use = { type = 'ftp_server' } },
{ when = { service = 'ftp-data' }, use = { type = 'ftp_data' } },
{ when = { service = 'gtp' }, use = { type = 'gtp_inspect' } },
{ when = { service = 'imap' }, use = { type = 'imap' } },
{ when = { service = 'http' }, use = { type = 'http_inspect' } },
{ when = { service = 'http2' }, use = { type = 'http2_inspect' } },
{ when = { service = 'iec104' }, use = { type = 'iec104' } },
{ when = { service = 'mms' }, use = { type = 'mms' } },
{ when = { service = 'modbus' }, use = { type = 'modbus' } },
{ when = { service = 'opcua' }, use = { type = 'opcua' } },
{ when = { service = 'pop3' }, use = { type = 'pop' } },
{ when = { service = 'ssh' }, use = { type = 'ssh' } },
{ when = { service = 'sip' }, use = { type = 'sip' } },
{ when = { service = 'smtp' }, use = { type = 'smtp' } },
{ when = { service = 'ssl' }, use = { type = 'ssl' } },
{ when = { service = 'sunrpc' }, use = { type = 'rpc_decode' } },
{ when = { service = 's7commplus' }, use = { type = 's7commplus' } },
{ when = { service = 'telnet' }, use = { type = 'telnet' } },
{ use = { type = 'wizard' } }
}
---------------------------------------------------------------------------
-- 4. configure performance
---------------------------------------------------------------------------
-- use latency to monitor / enforce packet and rule thresholds
--latency = { }
-- use these to capture perf data for analysis and tuning
--profiler = { }
--perf_monitor = { }
---------------------------------------------------------------------------
-- 5. configure detection
---------------------------------------------------------------------------
references = default_references
classifications = default_classifications
ips =
{
-- use this to enable decoder and inspector alerts
enable_builtin_rules = false,
include = "/etc/snort/rules/snort3-community.rules",
-- use include for rules files; be sure to set your path
-- note that rules files can include other rules files
-- (see also related path vars at the top of snort_defaults.lua)
variables = default_variables
}
-- use these to configure additional rule actions
-- react = { }
-- reject = { }
-- use this to enable payload injection utility
-- payload_injector = { }
---------------------------------------------------------------------------
-- 6. configure filters
---------------------------------------------------------------------------
-- below are examples of filters
-- each table is a list of records
--[[
suppress =
{
-- don't want to any of see these
{ gid = 1, sid = 1 },
-- don't want to see anything for a given host
{ track = 'by_dst', ip = '1.2.3.4' }
-- don't want to see these for a given host
{ gid = 1, sid = 2, track = 'by_dst', ip = '1.2.3.4' },
}
--]]
--[[
event_filter =
{
-- reduce the number of events logged for some rules
{ gid = 1, sid = 1, type = 'limit', track = 'by_src', count = 2, seconds = 10 },
{ gid = 1, sid = 2, type = 'both', track = 'by_dst', count = 5, seconds = 60 },
}
--]]
--[[
rate_filter =
{
-- alert on connection attempts from clients in SOME_NET
{ gid = 135, sid = 1, track = 'by_src', count = 5, seconds = 1,
new_action = 'alert', timeout = 4, apply_to = '[$SOME_NET]' },
-- alert on connections to servers over threshold
{ gid = 135, sid = 2, track = 'by_dst', count = 29, seconds = 3,
new_action = 'alert', timeout = 1 },
}
--]]
---------------------------------------------------------------------------
-- 7. configure outputs
---------------------------------------------------------------------------
-- event logging
-- you can enable with defaults from the command line with -A <alert_type>
-- uncomment below to set non-default configs
--alert_csv = { }
--alert_fast = { }
--alert_full = { }
--alert_sfsocket = { }
--alert_syslog = { }
--unified2 = { }
-- packet logging
-- you can enable with defaults from the command line with -L <log_type>
--log_codecs = { }
--log_hext = { }
--log_pcap = { }
-- additional logs
--packet_capture = { }
--file_log = { }
---------------------------------------------------------------------------
-- 8. configure tweaks
---------------------------------------------------------------------------
if ( tweaks ~= nil ) then
include(tweaks .. '.lua')
end

4017
snort/snort3-community.rules Normal file

File diff suppressed because it is too large Load Diff