From b7668b69b2de9d1ae55ee8e6a0d4c16ff3d13bf5 Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Mon, 26 Aug 2024 11:25:14 -0500 Subject: [PATCH 01/11] added option to query another hostname for each protocol --- respotter.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/respotter.py b/respotter.py index ac526d6..5c85ba9 100644 --- a/respotter.py +++ b/respotter.py @@ -170,12 +170,14 @@ def webhook_sniffer_alert(self, protocol, requester_ip, requested_hostname): state_file.seek(0) json.dump(state, state_file) - def send_llmnr_request(self): + def send_llmnr_request(self, hostname=""): # LLMNR uses the multicast IP 224.0.0.252 and UDP port 5355 - packet = IP(dst="224.0.0.252")/UDP(dport=5355)/LLMNRQuery(qd=DNSQR(qname=self.hostname)) + if not hostname: + hostname = self.hostname + packet = IP(dst="224.0.0.252")/UDP(dport=5355)/LLMNRQuery(qd=DNSQR(qname=hostname)) response = sr1(packet, timeout=1, verbose=0) if not response: - self.log.debug(f"[*] [LLMNR] No response for '{self.hostname}'") + self.log.debug(f"[*] [LLMNR] No response for '{hostname}'") return for p in response: self.log.debug(p) @@ -184,16 +186,18 @@ def send_llmnr_request(self): if sniffed_packet.haslayer(LLMNRResponse): for answer in sniffed_packet[LLMNRResponse].an: if answer.type == 1: # Type 1 is A record, which contains the IP address - self.log.critical(f"[!] [LLMNR] Responder detected at: {answer.rdata} - responded to name '{self.hostname}'") + self.log.critical(f"[!] [LLMNR] Responder detected at: {answer.rdata} - responded to name '{hostname}'") if self.is_daemon: self.webhook_responder_alert(answer.rdata) - def send_mdns_request(self): + def send_mdns_request(self, hostname=""): # mDNS uses the multicast IP 224.0.0.251 and UDP port 5353 - packet = IP(dst="224.0.0.251")/UDP(dport=5353)/DNS(rd=1, qd=DNSQR(qname=self.hostname)) + if not hostname: + hostname = self.hostname + packet = IP(dst="224.0.0.251")/UDP(dport=5353)/DNS(rd=1, qd=DNSQR(qname=hostname)) response = sr1(packet, timeout=1, verbose=0) if not response: - self.log.debug(f"[*] [MDNS] No response for '{self.hostname}'") + self.log.debug(f"[*] [MDNS] No response for '{hostname}'") return for p in response: self.log.debug(p) @@ -202,18 +206,21 @@ def send_mdns_request(self): if sniffed_packet is not None and sniffed_packet.haslayer(DNS): for answer in sniffed_packet[DNS].an: if answer.type == 1: - self.log.critical(f"[!] [MDNS] Responder detected at: {answer.rdata} - responded to name '{self.hostname}'") + self.log.critical(f"[!] [MDNS] Responder detected at: {answer.rdata} - responded to name '{hostname}'") if self.is_daemon: self.webhook_responder_alert(answer.rdata) - def send_nbns_request(self): + def send_nbns_request(self, hostname=""): try: self.broadcast_ip except AttributeError: self.log.error("[!] ERROR: broadcast IP not set. Skipping Netbios request.") return + if not hostname: + hostname = self.hostname # WORKAROUND: Scapy not matching long req to resp (secdev/scapy PR #4446) - hostname = self.hostname[:15] + if len(hostname) > 15: + hostname = hostname[:15] # Netbios uses the broadcast IP and UDP port 137 packet = IP(dst=self.broadcast_ip)/UDP(sport=137, dport=137)/NBNSHeader(OPCODE=0x0, NM_FLAGS=0x11, QDCOUNT=1)/NBNSQueryRequest(SUFFIX="file server service", QUESTION_NAME=hostname, QUESTION_TYPE="NB") response = sr1(packet, timeout=1, verbose=0) From a2a25d116601c7ba029735e27cf735d4fa5490a6 Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Mon, 26 Aug 2024 15:18:38 -0500 Subject: [PATCH 02/11] initial remediation alert implementation --- respotter.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/respotter.py b/respotter.py index 5c85ba9..410506b 100644 --- a/respotter.py +++ b/respotter.py @@ -72,19 +72,24 @@ def __init__(self, previous_state = json.load(state_file) self.responder_alerts = previous_state["responder_alerts"] self.vulnerable_alerts = previous_state["vulnerable_alerts"] + self.remediation_alerts = previous_state["remediation_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]) + for ip in self.remediation_alerts: + for protocol in self.remediation_alerts[ip]: + self.remediation_alerts[ip][protocol] = datetime.fromisoformat(self.remediation_alerts[ip][protocol]) except json.JSONDecodeError: raise FileNotFoundError except FileNotFoundError: self.responder_alerts = {} self.vulnerable_alerts = {} + self.remediation_alerts = {} Path("state").mkdir(parents=True, exist_ok=True) with open(self.state_file, "w") as state_file: - json.dump({"responder_alerts": {}, "vulnerable_alerts": {}}, state_file) + json.dump({"responder_alerts": {}, "vulnerable_alerts": {}, "remediation_alerts": {}}, state_file) # get broadcast IP for Netbios if subnet: try: @@ -169,6 +174,30 @@ def webhook_sniffer_alert(self, protocol, requester_ip, requested_hostname): state["vulnerable_alerts"] = new_state state_file.seek(0) json.dump(state, state_file) + + def webhook_remediation_alert(self, requester_ip, message): + with self.state_lock: + if requester_ip in self.remediation_alerts: + if self.remediation_alerts[requester_ip] > datetime.now() - timedelta(hours=1): + return + title = "Configuration issue detected!" + details = message + for service in ["teams", "discord", "slack"]: + if service in self.webhooks: + try: + eval(f"send_{service}_message")(self.webhooks[service], title=title, details=details) + self.log.info(f"[+] Remediation alert sent to {service.capitalize()} for {requester_ip}") + except WebhookException as e: + self.log.error(f"[!] {service.capitalize()} webhook failed: {e}") + self.remediation_alerts[requester_ip] = datetime.now() + with open(self.state_file, "r+") as state_file: + state = json.load(state_file) + new_state = deepcopy(self.remediation_alerts) + for ip in new_state: + new_state[ip] = new_state[ip].isoformat() + state["remediation_alerts"] = new_state + state_file.seek(0) + json.dump(state, state_file) def send_llmnr_request(self, hostname=""): # LLMNR uses the multicast IP 224.0.0.252 and UDP port 5355 @@ -322,6 +351,59 @@ def nbns_found(self, packet): if self.is_daemon: self.webhook_sniffer_alert("Netbios", packet[IP].src, requested_hostname) + def get_remediation_advice(self, protocol, requester_ip, requested_hostname): + if ip := self.dns_lookup(requested_hostname): + if ip == requester_ip: + # Host looking for itself + self.log.debug(f"[*] [{protocol}] {requester_ip} is looking for itself") + return None + else: + # Host looking for another device + self.log.info(f"[*] [{protocol}] {requester_ip} has incorrect DNS server for {requested_hostname}") + advice = f"{requester_ip} unable to find host '{requested_hostname}' in DNS so it used {protocol}. Update the DNS settings on {requester_ip} to point to the correct DNS server" + self.webhook_remediation_alert(requester_ip, advice) + else: + if self.device_exists(requested_hostname): + # We got a response -- DNS server is missing a record for the host + self.log.info(f"[*] [{protocol}] DNS record missing for '{requested_hostname}' - add record to DNS server") + advice = f"{requester_ip} unable to find host '{requested_hostname}' in DNS so it used {protocol}. Add a DNS record for '{requested_hostname}' to the DNS server" + self.webhook_remediation_alert(requester_ip, advice) + else: + # We got no response -- the device doesn't exist + self.log.debug(f"[*] [{protocol}] {requester_ip} is looking for non-existent device {requested_hostname}") + + def dns_lookup(self, hostname): + try: + return socket.gethostbyname(hostname) + except: + return None + + def device_exists(self, hostname): + # LLMNR + packet = IP(dst="224.0.0.252")/UDP(dport=5355)/LLMNRQuery(qd=DNSQR(qname=hostname)) + response = sr1(packet, timeout=1, verbose=0) + if response: + return True + # mDNS + packet = IP(dst="224.0.0.251")/UDP(dport=5353)/DNS(rd=1, qd=DNSQR(qname=hostname)) + response = sr1(packet, timeout=1, verbose=0) + if response: + return True + # Netbios + try: + self.broadcast_ip + except AttributeError: + return False + # WORKAROUND: Scapy not matching long req to resp (secdev/scapy PR #4446) + if len(hostname) > 15: + hostname = hostname[:15] + packet = IP(dst=self.broadcast_ip)/UDP(sport=137, dport=137)/NBNSHeader(OPCODE=0x0, NM_FLAGS=0x11, QDCOUNT=1)/NBNSQueryRequest(SUFFIX="file server service", QUESTION_NAME=hostname, QUESTION_TYPE="NB") + response = sr1(packet, timeout=1, verbose=0) + if response: + return True + return False + + def parse_options(): # add_help=False so it doesn't parse -h yet config_parser = argparse.ArgumentParser(add_help=False) From 70ee3feb2211976fa19a7173273a9d2ad98ce0cd Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Mon, 26 Aug 2024 15:22:06 -0500 Subject: [PATCH 03/11] remove vulnerability alert and replace with remediation advice --- respotter.py | 42 ++++-------------------------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/respotter.py b/respotter.py index 410506b..cf82232 100644 --- a/respotter.py +++ b/respotter.py @@ -71,13 +71,9 @@ def __init__(self, try: previous_state = json.load(state_file) self.responder_alerts = previous_state["responder_alerts"] - self.vulnerable_alerts = previous_state["vulnerable_alerts"] self.remediation_alerts = previous_state["remediation_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]) for ip in self.remediation_alerts: for protocol in self.remediation_alerts[ip]: self.remediation_alerts[ip][protocol] = datetime.fromisoformat(self.remediation_alerts[ip][protocol]) @@ -85,11 +81,10 @@ def __init__(self, raise FileNotFoundError except FileNotFoundError: self.responder_alerts = {} - self.vulnerable_alerts = {} self.remediation_alerts = {} Path("state").mkdir(parents=True, exist_ok=True) with open(self.state_file, "w") as state_file: - json.dump({"responder_alerts": {}, "vulnerable_alerts": {}, "remediation_alerts": {}}, state_file) + json.dump({"responder_alerts": {}, "remediation_alerts": {}}, state_file) # get broadcast IP for Netbios if subnet: try: @@ -145,35 +140,6 @@ def webhook_responder_alert(self, responder_ip): state["responder_alerts"] = new_state state_file.seek(0) json.dump(state, state_file) - - def webhook_sniffer_alert(self, protocol, requester_ip, requested_hostname): - with self.state_lock: - if requester_ip in self.vulnerable_alerts: - if protocol in self.vulnerable_alerts[requester_ip]: - if self.vulnerable_alerts[requester_ip][protocol] > datetime.now() - timedelta(days=1): - return - title = f"Vulnerable host detected!" - details = f"{protocol.upper()} query for '{requested_hostname}' from {requester_ip} - potentially vulnerable to Responder" - for service in ["teams", "discord", "slack"]: - if service in self.webhooks: - try: - eval(f"send_{service}_message")(self.webhooks[service], title=title, details=details) - self.log.info(f"[+] Alert sent to {service.capitalize()} for {requester_ip}") - except WebhookException as e: - self.log.error(f"[!] {service.capitalize()} webhook failed: {e}") - if requester_ip in self.vulnerable_alerts: - self.vulnerable_alerts[requester_ip][protocol] = datetime.now() - else: - self.vulnerable_alerts[requester_ip] = {protocol: datetime.now()} - with open(self.state_file, "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 webhook_remediation_alert(self, requester_ip, message): with self.state_lock: @@ -332,7 +298,7 @@ def llmnr_found(self, packet): return self.log.critical(f"[!] [LLMNR] LLMNR query for '{requested_hostname}' from {packet[IP].src} - potentially vulnerable to Responder") if self.is_daemon: - self.webhook_sniffer_alert("LLMNR", packet[IP].src, requested_hostname) + self.get_remediation_advice("LLMNR", packet[IP].src, requested_hostname) def mdns_found(self, packet): for dns_packet in packet[DNS].qd: @@ -341,7 +307,7 @@ def mdns_found(self, packet): return self.log.critical(f"[!] [MDNS] mDNS query for '{requested_hostname}' from {packet[IP].src} - potentially vulnerable to Responder") if self.is_daemon: - self.webhook_sniffer_alert("mDNS", packet[IP].src, requested_hostname) + self.get_remediation_advice("MDNS", packet[IP].src, requested_hostname) def nbns_found(self, packet): requested_hostname = packet[NBNSQueryRequest].QUESTION_NAME.decode() @@ -349,7 +315,7 @@ def nbns_found(self, packet): return self.log.critical(f"[!] [NBT-NS] NBT-NS query for '{requested_hostname}' from {packet[IP].src} - potentially vulnerable to Responder") if self.is_daemon: - self.webhook_sniffer_alert("Netbios", packet[IP].src, requested_hostname) + self.get_remediation_advice("NBT-NS", packet[IP].src, requested_hostname) def get_remediation_advice(self, protocol, requester_ip, requested_hostname): if ip := self.dns_lookup(requested_hostname): From a0a8459816d2b1c2995943c49bf3faf5d615d7ba Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Mon, 26 Aug 2024 15:29:19 -0500 Subject: [PATCH 04/11] fix teams return code check --- utils/teams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/teams.py b/utils/teams.py index a285e81..a3defbc 100644 --- a/utils/teams.py +++ b/utils/teams.py @@ -38,5 +38,5 @@ def send_teams_message(webhook_url, title, details): ] } response = requests.post(webhook_url, json=json_data, headers=headers) - if response.status_code != 202: + if response.status_code != 200: raise WebhookException(f"Failed to send message to Teams. Status code: {response.status_code}") \ No newline at end of file From bfba2567a4e15143cf5cd07b0a840bd1d849e684 Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Mon, 26 Aug 2024 15:30:10 -0500 Subject: [PATCH 05/11] use both the old and new webhook return codes --- utils/teams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/teams.py b/utils/teams.py index a3defbc..d19f9d0 100644 --- a/utils/teams.py +++ b/utils/teams.py @@ -38,5 +38,5 @@ def send_teams_message(webhook_url, title, details): ] } response = requests.post(webhook_url, json=json_data, headers=headers) - if response.status_code != 200: + if response.status_code not in [200, 202]: raise WebhookException(f"Failed to send message to Teams. Status code: {response.status_code}") \ No newline at end of file From 48440992468ca7ccace6c704e98de1835f847f87 Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Mon, 26 Aug 2024 15:39:28 -0500 Subject: [PATCH 06/11] don't sniff packets from ourself --- respotter.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/respotter.py b/respotter.py index cf82232..13db0cf 100644 --- a/respotter.py +++ b/respotter.py @@ -293,29 +293,38 @@ def sniffer_startup(self): def llmnr_found(self, packet): for dns_packet in packet[LLMNRQuery].qd: + requester_ip = packet[IP].src + if requester_ip == conf.iface: + return requested_hostname = dns_packet.qname.decode() if requested_hostname == self.hostname + ".": return - self.log.critical(f"[!] [LLMNR] LLMNR query for '{requested_hostname}' from {packet[IP].src} - potentially vulnerable to Responder") + self.log.critical(f"[!] [LLMNR] LLMNR query for '{requested_hostname}' from {requester_ip} - potentially vulnerable to Responder") if self.is_daemon: - self.get_remediation_advice("LLMNR", packet[IP].src, requested_hostname) + self.get_remediation_advice("LLMNR", requester_ip, requested_hostname) def mdns_found(self, packet): for dns_packet in packet[DNS].qd: + requester_ip = packet[IP].src + if requester_ip == conf.iface: + return requested_hostname = dns_packet.qname.decode() if requested_hostname == self.hostname + ".": return - self.log.critical(f"[!] [MDNS] mDNS query for '{requested_hostname}' from {packet[IP].src} - potentially vulnerable to Responder") + self.log.critical(f"[!] [MDNS] mDNS query for '{requested_hostname}' from {requester_ip} - potentially vulnerable to Responder") if self.is_daemon: - self.get_remediation_advice("MDNS", packet[IP].src, requested_hostname) + self.get_remediation_advice("MDNS", requester_ip, requested_hostname) def nbns_found(self, packet): + requester_ip = packet[IP].src + if requester_ip == conf.iface: + return requested_hostname = packet[NBNSQueryRequest].QUESTION_NAME.decode() if requested_hostname == self.hostname[:15]: return - self.log.critical(f"[!] [NBT-NS] NBT-NS query for '{requested_hostname}' from {packet[IP].src} - potentially vulnerable to Responder") + self.log.critical(f"[!] [NBT-NS] NBT-NS query for '{requested_hostname}' from {requester_ip} - potentially vulnerable to Responder") if self.is_daemon: - self.get_remediation_advice("NBT-NS", packet[IP].src, requested_hostname) + self.get_remediation_advice("NBT-NS", requester_ip, requested_hostname) def get_remediation_advice(self, protocol, requester_ip, requested_hostname): if ip := self.dns_lookup(requested_hostname): From d0d6b704cf93d13e658e9518f998508337cd9cd2 Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Mon, 26 Aug 2024 15:41:14 -0500 Subject: [PATCH 07/11] use the AsyncSniffer filter --- respotter.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/respotter.py b/respotter.py index 13db0cf..127cbe7 100644 --- a/respotter.py +++ b/respotter.py @@ -263,21 +263,21 @@ def vuln_sniff(self): """ llmnr_sniffer = AsyncSniffer( filter="udp port 5355", - lfilter=lambda pkt: pkt.haslayer(LLMNRQuery), # TODO: should this be DNSQR? + lfilter=lambda pkt: pkt.haslayer(LLMNRQuery) and pkt[IP].src != conf.iface, # TODO: should this be DNSQR? started_callback=self.sniffer_startup, prn=self.llmnr_found, store=0 ) mdns_sniffer = AsyncSniffer( filter="udp port 5353", - lfilter=lambda pkt: pkt.haslayer(DNS), # TODO: should this be DNSQR? + lfilter=lambda pkt: pkt.haslayer(DNS) and pkt[IP].src != conf.iface, # TODO: should this be DNSQR? started_callback=self.sniffer_startup, prn=self.mdns_found, store=0 ) nbns_sniffer = AsyncSniffer( filter="udp port 137", - lfilter=lambda pkt: pkt.haslayer(NBNSQueryRequest), + lfilter=lambda pkt: pkt.haslayer(NBNSQueryRequest) and pkt[IP].src != conf.iface, started_callback=self.sniffer_startup, prn=self.nbns_found, store=0 @@ -294,8 +294,6 @@ def sniffer_startup(self): def llmnr_found(self, packet): for dns_packet in packet[LLMNRQuery].qd: requester_ip = packet[IP].src - if requester_ip == conf.iface: - return requested_hostname = dns_packet.qname.decode() if requested_hostname == self.hostname + ".": return @@ -306,8 +304,6 @@ def llmnr_found(self, packet): def mdns_found(self, packet): for dns_packet in packet[DNS].qd: requester_ip = packet[IP].src - if requester_ip == conf.iface: - return requested_hostname = dns_packet.qname.decode() if requested_hostname == self.hostname + ".": return @@ -317,8 +313,6 @@ def mdns_found(self, packet): def nbns_found(self, packet): requester_ip = packet[IP].src - if requester_ip == conf.iface: - return requested_hostname = packet[NBNSQueryRequest].QUESTION_NAME.decode() if requested_hostname == self.hostname[:15]: return From a78bd987d9cb702e014e984f84e33db13ec47a6f Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Mon, 26 Aug 2024 15:55:06 -0500 Subject: [PATCH 08/11] fix iface IP reference --- respotter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/respotter.py b/respotter.py index 127cbe7..4fbb6e5 100644 --- a/respotter.py +++ b/respotter.py @@ -263,21 +263,21 @@ def vuln_sniff(self): """ llmnr_sniffer = AsyncSniffer( filter="udp port 5355", - lfilter=lambda pkt: pkt.haslayer(LLMNRQuery) and pkt[IP].src != conf.iface, # TODO: should this be DNSQR? + lfilter=lambda pkt: pkt.haslayer(LLMNRQuery) and pkt[IP].src != conf.iface.ip, # TODO: should this be DNSQR? started_callback=self.sniffer_startup, prn=self.llmnr_found, store=0 ) mdns_sniffer = AsyncSniffer( filter="udp port 5353", - lfilter=lambda pkt: pkt.haslayer(DNS) and pkt[IP].src != conf.iface, # TODO: should this be DNSQR? + lfilter=lambda pkt: pkt.haslayer(DNS) and pkt[IP].src != conf.iface.ip, # TODO: should this be DNSQR? started_callback=self.sniffer_startup, prn=self.mdns_found, store=0 ) nbns_sniffer = AsyncSniffer( filter="udp port 137", - lfilter=lambda pkt: pkt.haslayer(NBNSQueryRequest) and pkt[IP].src != conf.iface, + lfilter=lambda pkt: pkt.haslayer(NBNSQueryRequest) and pkt[IP].src != conf.iface.ip, started_callback=self.sniffer_startup, prn=self.nbns_found, store=0 From e1260705aa690754fcb83a3bb590997afb0cfe5e Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Mon, 26 Aug 2024 16:04:28 -0500 Subject: [PATCH 09/11] fix remediation alerts state file format --- respotter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/respotter.py b/respotter.py index 4fbb6e5..964b2b3 100644 --- a/respotter.py +++ b/respotter.py @@ -75,8 +75,7 @@ def __init__(self, for ip in self.responder_alerts: self.responder_alerts[ip] = datetime.fromisoformat(self.responder_alerts[ip]) for ip in self.remediation_alerts: - for protocol in self.remediation_alerts[ip]: - self.remediation_alerts[ip][protocol] = datetime.fromisoformat(self.remediation_alerts[ip][protocol]) + self.remediation_alerts[ip] = datetime.fromisoformat(self.remediation_alerts[ip]) except json.JSONDecodeError: raise FileNotFoundError except FileNotFoundError: From 4b6d3ba2f3573eac32c02a71f60dffb927a814ea Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Mon, 26 Aug 2024 16:14:29 -0500 Subject: [PATCH 10/11] removed redundant check for queries from respotter --- respotter.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/respotter.py b/respotter.py index 964b2b3..07255b1 100644 --- a/respotter.py +++ b/respotter.py @@ -294,8 +294,6 @@ def llmnr_found(self, packet): for dns_packet in packet[LLMNRQuery].qd: requester_ip = packet[IP].src requested_hostname = dns_packet.qname.decode() - if requested_hostname == self.hostname + ".": - return self.log.critical(f"[!] [LLMNR] LLMNR query for '{requested_hostname}' from {requester_ip} - potentially vulnerable to Responder") if self.is_daemon: self.get_remediation_advice("LLMNR", requester_ip, requested_hostname) @@ -304,8 +302,6 @@ def mdns_found(self, packet): for dns_packet in packet[DNS].qd: requester_ip = packet[IP].src requested_hostname = dns_packet.qname.decode() - if requested_hostname == self.hostname + ".": - return self.log.critical(f"[!] [MDNS] mDNS query for '{requested_hostname}' from {requester_ip} - potentially vulnerable to Responder") if self.is_daemon: self.get_remediation_advice("MDNS", requester_ip, requested_hostname) @@ -313,8 +309,6 @@ def mdns_found(self, packet): def nbns_found(self, packet): requester_ip = packet[IP].src requested_hostname = packet[NBNSQueryRequest].QUESTION_NAME.decode() - if requested_hostname == self.hostname[:15]: - return self.log.critical(f"[!] [NBT-NS] NBT-NS query for '{requested_hostname}' from {requester_ip} - potentially vulnerable to Responder") if self.is_daemon: self.get_remediation_advice("NBT-NS", requester_ip, requested_hostname) From a9f258ff81c1bbfcbc9465da75aeafcea7a6fdfd Mon Sep 17 00:00:00 2001 From: "C.J. May" Date: Thu, 5 Sep 2024 11:06:13 -0500 Subject: [PATCH 11/11] ignore netbios queries if DNS lookup succeeds --- respotter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/respotter.py b/respotter.py index 07255b1..2ed26b9 100644 --- a/respotter.py +++ b/respotter.py @@ -319,6 +319,9 @@ def get_remediation_advice(self, protocol, requester_ip, requested_hostname): # Host looking for itself self.log.debug(f"[*] [{protocol}] {requester_ip} is looking for itself") return None + elif protocol == "NBT-NS": + # Netbios sometimes is used before doing a DNS lookup + return None else: # Host looking for another device self.log.info(f"[*] [{protocol}] {requester_ip} has incorrect DNS server for {requested_hostname}")