Skip to content

Commit

Permalink
Merge pull request #53 from lawndoc/test-webhooks #minor
Browse files Browse the repository at this point in the history
Test webhooks
  • Loading branch information
lawndoc authored Jul 17, 2024
2 parents 6694938 + 75e94a5 commit 84b07ef
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 44 deletions.
3 changes: 2 additions & 1 deletion config.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"hostname": "Loremipsumdolorsitamet",
"slack_webhook": "",
"subnet": "",
"syslog_address": "",
"teams_webhook": "",
"test_webhooks": false,
"timeout": 1,
"verbosity": 2,
"syslog_address": "",
}
84 changes: 49 additions & 35 deletions respotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
from scapy.layers.llmnr import LLMNRQuery, LLMNRResponse
from scapy.layers.netbios import NBNSQueryRequest, NBNSQueryResponse, NBNSHeader
from time import sleep
from utils.teams import send_teams_message
from utils.discord import send_discord_message
from utils.errors import WebhookException
from utils.slack import send_slack_message
from utils.teams import send_teams_message
import logging
import logging.config
import logging.handlers
Expand All @@ -41,6 +42,7 @@ def __init__(self,
slack_webhook="",
teams_webhook="",
syslog_address="",
test_webhooks=False
):
# initialize logger
self.log = logging.getLogger('respotter')
Expand Down Expand Up @@ -100,24 +102,35 @@ def __init__(self,
self.webhooks[service] = webhook
else:
self.log.warning(f"[-] WARNING: {service} webhook URL not set")
if test_webhooks:
self.webhook_test()

def webhook_test(self):
title = "Test message"
details = "Respotter is starting up... This is a test 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"[+] {service.capitalize()} webhook test successful")
except WebhookException as e:
self.log.error(f"[!] {service.capitalize()} webhook test failed: {e}")

def webhook_responder_alert(self, responder_ip):
if responder_ip in self.responder_alerts:
if self.responder_alerts[responder_ip] > datetime.now() - timedelta(hours=1):
return
title = "Responder instance found"
details = f"Responder instance found at {responder_ip}"
if "teams" in self.webhooks:
send_teams_message(self.webhooks["teams"], title=title, details=details)
self.log.info(f"[+] Alert sent to Teams for {responder_ip}")
if "discord" in self.webhooks:
send_discord_message(self.webhooks["discord"], title=title, details=details)
self.log.info(f"[+] Alert sent to Discord for {responder_ip}")
if "slack" in self.webhooks:
send_slack_message(self.webhooks["slack"], title=title, details=details)
self.log.info(f"[+] Alert sent to Slack for {responder_ip}")
self.responder_alerts[responder_ip] = datetime.now()
with self.state_lock:
if responder_ip in self.responder_alerts:
if self.responder_alerts[responder_ip] > datetime.now() - timedelta(hours=1):
return
title = "Responder detected!"
details = f"Responder instance found at {responder_ip}"
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 {responder_ip}")
except WebhookException as e:
self.log.error(f"[!] {service.capitalize()} webhook failed: {e}")
self.responder_alerts[responder_ip] = datetime.now()
with open("state/state.json", "r+") as state_file:
state = json.load(state_file)
new_state = deepcopy(self.responder_alerts)
Expand All @@ -128,26 +141,24 @@ def webhook_responder_alert(self, responder_ip):
json.dump(state, state_file)

def webhook_sniffer_alert(self, protocol, requester_ip, requested_hostname):
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"{protocol.upper()} query detected"
details = f"{protocol.upper()} query for '{requested_hostname}' from {requester_ip} - potentially vulnerable to Responder"
if "teams" in self.webhooks:
send_teams_message(self.webhooks["teams"], title=title, details=details)
self.log.info(f"[+] Alert sent to Teams for {requester_ip}")
if "discord" in self.webhooks:
send_discord_message(self.webhooks["discord"], title=title, details=details)
self.log.info(f"[+] Alert sent to Discord for {requester_ip}")
if "slack" in self.webhooks:
send_slack_message(self.webhooks["slack"], title=title, details=details)
self.log.info(f"[+] Alert sent to Slack for {requester_ip}")
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 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("state/state.json", "r+") as state_file:
state = json.load(state_file)
new_state = deepcopy(self.vulnerable_alerts)
Expand Down Expand Up @@ -319,6 +330,7 @@ def parse_options():
"subnet": "",
"syslog_address": "",
"teams_webhook": "",
"test_webhooks": False,
"timeout": 1,
"verbosity": 2,
}
Expand All @@ -339,6 +351,7 @@ def parse_options():
parser.add_argument("-n", "--hostname", help="Hostname to scan for")
parser.add_argument("-x", "--exclude", help="Protocols to exclude from scanning (e.g. 'llmnr,nbns')")
parser.add_argument("-l", "--syslog-address", help="Syslog server address")
parser.add_argument("--test-webhooks", action="store_true", help="Test configured webhooks")
args = parser.parse_args(remaining_argv)
if int(args.verbosity) > 4:
print(f"Final config: {args}\n")
Expand Down Expand Up @@ -368,6 +381,7 @@ def parse_options():
slack_webhook=options.slack_webhook,
teams_webhook=options.teams_webhook,
syslog_address=options.syslog_address,
test_webhooks=options.test_webhooks
)

respotter.daemon()
6 changes: 3 additions & 3 deletions utils/discord.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from discord_webhook import DiscordWebhook, DiscordEmbed
from utils.errors import WebhookException

def send_discord_message(webhook_url, title, details):
webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True)
embed = DiscordEmbed(title=title, description=details, color=242424)
embed.set_author(name='Respotter')
embed.set_thumbnail(url='https://raw.githubusercontent.com/lawndoc/Respotter/main/assets/respotter_logo.png')
webhook.add_embed(embed)
response = webhook.execute()
if response.status_code == 200:
print("Message sent successfully")
pass
else:
print("Failed to send message.")
raise WebhookException(f"Failed to send message to Discord. Status code: {response.status_code}")
5 changes: 5 additions & 0 deletions utils/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

class WebhookException(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
10 changes: 6 additions & 4 deletions utils/slack.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from utils.errors import WebhookException
from slack_sdk import WebhookClient
from slack_sdk.errors import SlackApiError
import time
Expand All @@ -23,15 +24,16 @@ def send_slack_message(webhook_url, title, details):
]
)
if response.status_code == 200:
print("Message sent successfully")
pass
else:
raise WebhookException(f"Failed to send message to Slack. Status code: {response.status_code}")
except SlackApiError as e:
if e.response.status_code == 429:
# Slack rate limits to one message per channel per second, with short bursts of >1 allowed
retry_after = int(e.response.headers['Retry-After'])
print(f"Rate limited. Retrying in {retry_after} seconds")
time.sleep(retry_after)
response = client.send(
text=f"{title}\n{details}"
)
else :
print(f"Failed to send message: {e.response.status_code}")
else:
raise WebhookException(f"Failed to send message to Slack. Status code: {e.response.status_code}")
9 changes: 8 additions & 1 deletion utils/teams.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from utils.errors import WebhookException
import requests


Expand All @@ -20,6 +21,12 @@ def send_teams_message(webhook_url, title, details):
"url": "https://raw.githubusercontent.com/lawndoc/Respotter/main/assets/respotter_logo.png",
"altText": "Respotter Alert",
},
{
"type": "TextBlock",
"size": "Large",
"weight": "Bolder",
"text": title
},
{
"type": "TextBlock",
"wrap": True,
Expand All @@ -32,4 +39,4 @@ def send_teams_message(webhook_url, title, details):
}
response = requests.post(webhook_url, json=json_data, headers=headers)
if response.status_code != 202:
print(f"[!] ERROR: failed to send teams webhook - {response.status_code} {response.reason}")
raise WebhookException(f"Failed to send message to Teams. Status code: {response.status_code}")

0 comments on commit 84b07ef

Please sign in to comment.