Skip to content

Commit 84b07ef

Browse files
authored
Merge pull request #53 from lawndoc/test-webhooks #minor
Test webhooks
2 parents 6694938 + 75e94a5 commit 84b07ef

File tree

6 files changed

+73
-44
lines changed

6 files changed

+73
-44
lines changed

Diff for: config.json.template

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
"hostname": "Loremipsumdolorsitamet",
66
"slack_webhook": "",
77
"subnet": "",
8+
"syslog_address": "",
89
"teams_webhook": "",
10+
"test_webhooks": false,
911
"timeout": 1,
1012
"verbosity": 2,
11-
"syslog_address": "",
1213
}

Diff for: respotter.py

+49-35
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
from scapy.layers.llmnr import LLMNRQuery, LLMNRResponse
1414
from scapy.layers.netbios import NBNSQueryRequest, NBNSQueryResponse, NBNSHeader
1515
from time import sleep
16-
from utils.teams import send_teams_message
1716
from utils.discord import send_discord_message
17+
from utils.errors import WebhookException
1818
from utils.slack import send_slack_message
19+
from utils.teams import send_teams_message
1920
import logging
2021
import logging.config
2122
import logging.handlers
@@ -41,6 +42,7 @@ def __init__(self,
4142
slack_webhook="",
4243
teams_webhook="",
4344
syslog_address="",
45+
test_webhooks=False
4446
):
4547
# initialize logger
4648
self.log = logging.getLogger('respotter')
@@ -100,24 +102,35 @@ def __init__(self,
100102
self.webhooks[service] = webhook
101103
else:
102104
self.log.warning(f"[-] WARNING: {service} webhook URL not set")
105+
if test_webhooks:
106+
self.webhook_test()
107+
108+
def webhook_test(self):
109+
title = "Test message"
110+
details = "Respotter is starting up... This is a test message."
111+
for service in ["teams", "discord", "slack"]:
112+
if service in self.webhooks:
113+
try:
114+
eval(f"send_{service}_message")(self.webhooks[service], title=title, details=details)
115+
self.log.info(f"[+] {service.capitalize()} webhook test successful")
116+
except WebhookException as e:
117+
self.log.error(f"[!] {service.capitalize()} webhook test failed: {e}")
103118

104119
def webhook_responder_alert(self, responder_ip):
105-
if responder_ip in self.responder_alerts:
106-
if self.responder_alerts[responder_ip] > datetime.now() - timedelta(hours=1):
107-
return
108-
title = "Responder instance found"
109-
details = f"Responder instance found at {responder_ip}"
110-
if "teams" in self.webhooks:
111-
send_teams_message(self.webhooks["teams"], title=title, details=details)
112-
self.log.info(f"[+] Alert sent to Teams for {responder_ip}")
113-
if "discord" in self.webhooks:
114-
send_discord_message(self.webhooks["discord"], title=title, details=details)
115-
self.log.info(f"[+] Alert sent to Discord for {responder_ip}")
116-
if "slack" in self.webhooks:
117-
send_slack_message(self.webhooks["slack"], title=title, details=details)
118-
self.log.info(f"[+] Alert sent to Slack for {responder_ip}")
119-
self.responder_alerts[responder_ip] = datetime.now()
120120
with self.state_lock:
121+
if responder_ip in self.responder_alerts:
122+
if self.responder_alerts[responder_ip] > datetime.now() - timedelta(hours=1):
123+
return
124+
title = "Responder detected!"
125+
details = f"Responder instance found at {responder_ip}"
126+
for service in ["teams", "discord", "slack"]:
127+
if service in self.webhooks:
128+
try:
129+
eval(f"send_{service}_message")(self.webhooks[service], title=title, details=details)
130+
self.log.info(f"[+] Alert sent to {service.capitalize()} for {responder_ip}")
131+
except WebhookException as e:
132+
self.log.error(f"[!] {service.capitalize()} webhook failed: {e}")
133+
self.responder_alerts[responder_ip] = datetime.now()
121134
with open("state/state.json", "r+") as state_file:
122135
state = json.load(state_file)
123136
new_state = deepcopy(self.responder_alerts)
@@ -128,26 +141,24 @@ def webhook_responder_alert(self, responder_ip):
128141
json.dump(state, state_file)
129142

130143
def webhook_sniffer_alert(self, protocol, requester_ip, requested_hostname):
131-
if requester_ip in self.vulnerable_alerts:
132-
if protocol in self.vulnerable_alerts[requester_ip]:
133-
if self.vulnerable_alerts[requester_ip][protocol] > datetime.now() - timedelta(days=1):
134-
return
135-
title = f"{protocol.upper()} query detected"
136-
details = f"{protocol.upper()} query for '{requested_hostname}' from {requester_ip} - potentially vulnerable to Responder"
137-
if "teams" in self.webhooks:
138-
send_teams_message(self.webhooks["teams"], title=title, details=details)
139-
self.log.info(f"[+] Alert sent to Teams for {requester_ip}")
140-
if "discord" in self.webhooks:
141-
send_discord_message(self.webhooks["discord"], title=title, details=details)
142-
self.log.info(f"[+] Alert sent to Discord for {requester_ip}")
143-
if "slack" in self.webhooks:
144-
send_slack_message(self.webhooks["slack"], title=title, details=details)
145-
self.log.info(f"[+] Alert sent to Slack for {requester_ip}")
146-
if requester_ip in self.vulnerable_alerts:
147-
self.vulnerable_alerts[requester_ip][protocol] = datetime.now()
148-
else:
149-
self.vulnerable_alerts[requester_ip] = {protocol: datetime.now()}
150144
with self.state_lock:
145+
if requester_ip in self.vulnerable_alerts:
146+
if protocol in self.vulnerable_alerts[requester_ip]:
147+
if self.vulnerable_alerts[requester_ip][protocol] > datetime.now() - timedelta(days=1):
148+
return
149+
title = f"Vulnerable host detected!"
150+
details = f"{protocol.upper()} query for '{requested_hostname}' from {requester_ip} - potentially vulnerable to Responder"
151+
for service in ["teams", "discord", "slack"]:
152+
if service in self.webhooks:
153+
try:
154+
eval(f"send_{service}_message")(self.webhooks[service], title=title, details=details)
155+
self.log.info(f"[+] Alert sent to {service.capitalize()} for {requester_ip}")
156+
except WebhookException as e:
157+
self.log.error(f"[!] {service.capitalize()} webhook failed: {e}")
158+
if requester_ip in self.vulnerable_alerts:
159+
self.vulnerable_alerts[requester_ip][protocol] = datetime.now()
160+
else:
161+
self.vulnerable_alerts[requester_ip] = {protocol: datetime.now()}
151162
with open("state/state.json", "r+") as state_file:
152163
state = json.load(state_file)
153164
new_state = deepcopy(self.vulnerable_alerts)
@@ -319,6 +330,7 @@ def parse_options():
319330
"subnet": "",
320331
"syslog_address": "",
321332
"teams_webhook": "",
333+
"test_webhooks": False,
322334
"timeout": 1,
323335
"verbosity": 2,
324336
}
@@ -339,6 +351,7 @@ def parse_options():
339351
parser.add_argument("-n", "--hostname", help="Hostname to scan for")
340352
parser.add_argument("-x", "--exclude", help="Protocols to exclude from scanning (e.g. 'llmnr,nbns')")
341353
parser.add_argument("-l", "--syslog-address", help="Syslog server address")
354+
parser.add_argument("--test-webhooks", action="store_true", help="Test configured webhooks")
342355
args = parser.parse_args(remaining_argv)
343356
if int(args.verbosity) > 4:
344357
print(f"Final config: {args}\n")
@@ -368,6 +381,7 @@ def parse_options():
368381
slack_webhook=options.slack_webhook,
369382
teams_webhook=options.teams_webhook,
370383
syslog_address=options.syslog_address,
384+
test_webhooks=options.test_webhooks
371385
)
372386

373387
respotter.daemon()

Diff for: utils/discord.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from discord_webhook import DiscordWebhook, DiscordEmbed
2+
from utils.errors import WebhookException
23

34
def send_discord_message(webhook_url, title, details):
45
webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True)
56
embed = DiscordEmbed(title=title, description=details, color=242424)
6-
embed.set_author(name='Respotter')
77
embed.set_thumbnail(url='https://raw.githubusercontent.com/lawndoc/Respotter/main/assets/respotter_logo.png')
88
webhook.add_embed(embed)
99
response = webhook.execute()
1010
if response.status_code == 200:
11-
print("Message sent successfully")
11+
pass
1212
else:
13-
print("Failed to send message.")
13+
raise WebhookException(f"Failed to send message to Discord. Status code: {response.status_code}")

Diff for: utils/errors.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
class WebhookException(Exception):
3+
def __init__(self, message):
4+
self.message = message
5+
super().__init__(self.message)

Diff for: utils/slack.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from utils.errors import WebhookException
12
from slack_sdk import WebhookClient
23
from slack_sdk.errors import SlackApiError
34
import time
@@ -23,15 +24,16 @@ def send_slack_message(webhook_url, title, details):
2324
]
2425
)
2526
if response.status_code == 200:
26-
print("Message sent successfully")
27+
pass
28+
else:
29+
raise WebhookException(f"Failed to send message to Slack. Status code: {response.status_code}")
2730
except SlackApiError as e:
2831
if e.response.status_code == 429:
2932
# Slack rate limits to one message per channel per second, with short bursts of >1 allowed
3033
retry_after = int(e.response.headers['Retry-After'])
31-
print(f"Rate limited. Retrying in {retry_after} seconds")
3234
time.sleep(retry_after)
3335
response = client.send(
3436
text=f"{title}\n{details}"
3537
)
36-
else :
37-
print(f"Failed to send message: {e.response.status_code}")
38+
else:
39+
raise WebhookException(f"Failed to send message to Slack. Status code: {e.response.status_code}")

Diff for: utils/teams.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from utils.errors import WebhookException
12
import requests
23

34

@@ -20,6 +21,12 @@ def send_teams_message(webhook_url, title, details):
2021
"url": "https://raw.githubusercontent.com/lawndoc/Respotter/main/assets/respotter_logo.png",
2122
"altText": "Respotter Alert",
2223
},
24+
{
25+
"type": "TextBlock",
26+
"size": "Large",
27+
"weight": "Bolder",
28+
"text": title
29+
},
2330
{
2431
"type": "TextBlock",
2532
"wrap": True,
@@ -32,4 +39,4 @@ def send_teams_message(webhook_url, title, details):
3239
}
3340
response = requests.post(webhook_url, json=json_data, headers=headers)
3441
if response.status_code != 202:
35-
print(f"[!] ERROR: failed to send teams webhook - {response.status_code} {response.reason}")
42+
raise WebhookException(f"Failed to send message to Teams. Status code: {response.status_code}")

0 commit comments

Comments
 (0)