13
13
from scapy .layers .llmnr import LLMNRQuery , LLMNRResponse
14
14
from scapy .layers .netbios import NBNSQueryRequest , NBNSQueryResponse , NBNSHeader
15
15
from time import sleep
16
- from utils .teams import send_teams_message
17
16
from utils .discord import send_discord_message
17
+ from utils .errors import WebhookException
18
18
from utils .slack import send_slack_message
19
+ from utils .teams import send_teams_message
19
20
import logging
20
21
import logging .config
21
22
import logging .handlers
@@ -41,6 +42,7 @@ def __init__(self,
41
42
slack_webhook = "" ,
42
43
teams_webhook = "" ,
43
44
syslog_address = "" ,
45
+ test_webhooks = False
44
46
):
45
47
# initialize logger
46
48
self .log = logging .getLogger ('respotter' )
@@ -100,24 +102,35 @@ def __init__(self,
100
102
self .webhooks [service ] = webhook
101
103
else :
102
104
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 } " )
103
118
104
119
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 ()
120
120
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 ()
121
134
with open ("state/state.json" , "r+" ) as state_file :
122
135
state = json .load (state_file )
123
136
new_state = deepcopy (self .responder_alerts )
@@ -128,26 +141,24 @@ def webhook_responder_alert(self, responder_ip):
128
141
json .dump (state , state_file )
129
142
130
143
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 ()}
150
144
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 ()}
151
162
with open ("state/state.json" , "r+" ) as state_file :
152
163
state = json .load (state_file )
153
164
new_state = deepcopy (self .vulnerable_alerts )
@@ -319,6 +330,7 @@ def parse_options():
319
330
"subnet" : "" ,
320
331
"syslog_address" : "" ,
321
332
"teams_webhook" : "" ,
333
+ "test_webhooks" : False ,
322
334
"timeout" : 1 ,
323
335
"verbosity" : 2 ,
324
336
}
@@ -339,6 +351,7 @@ def parse_options():
339
351
parser .add_argument ("-n" , "--hostname" , help = "Hostname to scan for" )
340
352
parser .add_argument ("-x" , "--exclude" , help = "Protocols to exclude from scanning (e.g. 'llmnr,nbns')" )
341
353
parser .add_argument ("-l" , "--syslog-address" , help = "Syslog server address" )
354
+ parser .add_argument ("--test-webhooks" , action = "store_true" , help = "Test configured webhooks" )
342
355
args = parser .parse_args (remaining_argv )
343
356
if int (args .verbosity ) > 4 :
344
357
print (f"Final config: { args } \n " )
@@ -368,6 +381,7 @@ def parse_options():
368
381
slack_webhook = options .slack_webhook ,
369
382
teams_webhook = options .teams_webhook ,
370
383
syslog_address = options .syslog_address ,
384
+ test_webhooks = options .test_webhooks
371
385
)
372
386
373
387
respotter .daemon ()
0 commit comments