Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pulling updates from main #51

Merged
merged 8 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Project specific
config.json
config/
state/

# Redis
dump.rdp
Expand Down
54 changes: 49 additions & 5 deletions respotter.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#!/usr/bin/env python3

import argparse
from copy import deepcopy
from datetime import datetime, timedelta
from ipaddress import ip_network
import json
from multiprocessing import Process
from multiprocessing import Process, Lock
from pathlib import Path
from scapy.all import *
from scapy.layers.dns import DNS, DNSQR
from scapy.layers.inet import IP, UDP
Expand Down Expand Up @@ -39,6 +41,7 @@ def __init__(self,
teams_webhook="",
syslog_address="",
):
# initialize logger
self.log = logging.getLogger('respotter')
formatter = logging.Formatter('')
handler = logging.StreamHandler()
Expand All @@ -50,15 +53,35 @@ def __init__(self,
formatter = logging.Formatter('Respotter {processName}[{process}]: {message}', style='{')
handler.setFormatter(formatter)
self.log.addHandler(handler)
conf.checkIPaddr = False # multicast/broadcast responses won't come from dst IP
# import configuration
self.delay = delay
self.excluded_protocols = excluded_protocols
self.hostname = hostname
self.is_daemon = False
self.timeout = timeout
self.verbosity = verbosity
self.responder_alerts = {}
self.vulnerable_alerts = {}
# state persistence
self.state_lock = Lock()
try:
with open("state/state.json", "r+") as state_file:
try:
previous_state = json.load(state_file)
self.responder_alerts = previous_state["responder_alerts"]
self.vulnerable_alerts = previous_state["vulnerable_alerts"]
for ip in self.responder_alerts:
self.responder_alerts[ip] = datetime.fromisoformat(self.responder_alerts[ip])
for ip in self.vulnerable_alerts:
for protocol in self.vulnerable_alerts[ip]:
self.vulnerable_alerts[ip][protocol] = datetime.fromisoformat(self.vulnerable_alerts[ip][protocol])
except json.JSONDecodeError:
raise FileNotFoundError
except FileNotFoundError:
self.responder_alerts = {}
self.vulnerable_alerts = {}
Path("state").mkdir(parents=True, exist_ok=True)
with open("state/state.json", "w") as state_file:
json.dump({"responder_alerts": {}, "vulnerable_alerts": {}}, state_file)
# get broadcast IP for Netbios
if subnet:
try:
network = ip_network(subnet)
Expand All @@ -68,6 +91,7 @@ def __init__(self,
elif "nbns" not in self.excluded_protocols:
self.log.error(f"[!] ERROR: subnet CIDR not configured. Netbios protocol will be disabled.")
self.excluded_protocols.append("nbns")
# setup webhooks
self.webhooks = {}
for service in ["teams", "slack", "discord"]:
webhook = eval(f"{service}_webhook")
Expand All @@ -89,6 +113,15 @@ def webhook_responder_alert(self, responder_ip):
send_discord_message(self.webhooks["discord"], title=title, details=details)
self.log.info(f"[+] Alert sent to Discord for {responder_ip}")
self.responder_alerts[responder_ip] = datetime.now()
with self.state_lock:
with open("state/state.json", "r+") as state_file:
state = json.load(state_file)
new_state = deepcopy(self.responder_alerts)
for ip in new_state:
new_state[ip] = new_state[ip].isoformat()
state["responder_alerts"] = new_state
state_file.seek(0)
json.dump(state, state_file)

def webhook_sniffer_alert(self, protocol, requester_ip, requested_hostname):
if requester_ip in self.vulnerable_alerts:
Expand All @@ -107,7 +140,16 @@ def webhook_sniffer_alert(self, protocol, requester_ip, requested_hostname):
self.vulnerable_alerts[requester_ip][protocol] = datetime.now()
else:
self.vulnerable_alerts[requester_ip] = {protocol: datetime.now()}

with self.state_lock:
with open("state/state.json", "r+") as state_file:
state = json.load(state_file)
new_state = deepcopy(self.vulnerable_alerts)
for ip in new_state:
for protocol in new_state[ip]:
new_state[ip][protocol] = new_state[ip][protocol].isoformat()
state["vulnerable_alerts"] = new_state
state_file.seek(0)
json.dump(state, state_file)

def send_llmnr_request(self):
# LLMNR uses the multicast IP 224.0.0.252 and UDP port 5355
Expand Down Expand Up @@ -180,6 +222,8 @@ def daemon(self):

def responder_scan(self):
self.log.info("[*] Responder scans started")
# Scapy setting -- multicast/broadcast responses won't come from dst IP
conf.checkIPaddr = False
while True:
if "llmnr" not in self.excluded_protocols:
self.send_llmnr_request()
Expand Down
Loading