-
Notifications
You must be signed in to change notification settings - Fork 30
/
ts_block.vbs
358 lines (273 loc) · 13.8 KB
/
ts_block.vbs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
Option Explicit
' ts_block.vbs - Blocks IP addresses generating invalid Terminal Services logons.
' Copyright 2011 Wellbury LLC - See LICENSE for license information
'
' Release 20110831 - Adapted from sshd_block release 20100120
' Release 20120530 - No change from 20110831 code for ts_block script
' External executables required to be accessible from PATH:
'
' ROUTE.EXE For black-hole routing blocked IP addresses in Windows 2003
' NETSH.EXE For black-hole firewall rule creation on Windows Vista / 2008 / 7 / 2008 R2
' EVENTCREATE.EXE For writing to the event log (if enabled)
'
' For support, please contact Evan Anderson at Wellbury LLC:
' [email protected], (866) 569-9799, ext 801
' Main
Dim objShell, objWMIService, objEventSink, blackHoleIPAddress, regexpSanitizeEventLog, regexpSanitizeIP
Dim dictIPLastSeenTime, dictIPBadLogons, dictUnblockTime, dictBlockImmediatelyUsers
Dim colOperatingSystem, intOSBuild, intBlackholeStyle
Dim intBlockDuration, intBlockAttempts, intBlockTimeout
' =====================( Configuration )=====================
' Set to 0 to disable debugging output
Const DEBUGGING = 0
' Set to 0 to disable event log reporting of blocks / unblocks
Const USE_EVENTLOG = 1
Const EVENTLOG_SOURCE = "ts_block"
Const EVENTLOG_TYPE_INFORMATION = "INFORMATION"
Const EVENTLOG_TYPE_ERROR = "ERROR"
Const EVENTLOG_ID_STARTED = 1
Const EVENTLOG_ID_ERROR_NO_BLACKHOLE_IP = 2
Const EVENTLOG_ID_ERROR_WIN_XP = 3
Const EVENTLOG_ID_BLOCK = 256
Const EVENTLOG_ID_UNBLOCK = 257
' Registry path for configuration
Const REG_CONFIG_PATH = "HKLM\Software\Policies\Wellbury LLC\ts_block\"
' Number of failed logons in time window before IP will be blocked
Const DEFAULT_BLOCK_ATTEMPTS = 5 ' Attempts
Const REG_BLOCK_ATTEMPTS = "BlockAttempts"
' Expiration (in seconds) for IPs to be blocked
Const DEFAULT_BLOCK_DURATION = 300
Const REG_BLOCK_DURATION = "BlockDuration"
' Timeout for attempts before a new attempt is considered attempt #1
Const DEFAULT_BLOCK_TIMEOUT = 120 ' in X seconds
Const REG_BLOCK_TIMEOUT = "BlockTimeout"
' Black hole IP address (if hard-specified)
Const REG_BLACKHOLE_IP = "BlackholeIP"
' Usernames that attempted logons for result in immediate blocking
Set dictBlockImmediatelyUsers = CreateObject("Scripting.Dictionary")
dictBlockImmediatelyUsers.Add "administrator", 1
dictBlockImmediatelyUsers.Add "root", 1
dictBlockImmediatelyUsers.Add "guest", 1
' ===================( End Configuration )===================
Const TS_BLOCK_VERSION = "20110831"
Const BLACKHOLE_ROUTE = 1 ' Blackhole packets via routing table
Const BLACKHOLE_FIREWALL = 2 ' Blackhole packets via firewall
' =====================( Stress Testing )====================
' Set to 1 to perform stress testing
Const TESTING = 0
' Number of "bogus" blocks to load
Const TESTING_IP_ADDRESSES = 10000
' Minimum and maximum milliseconds between adding "bogus" IPs to the block list during testing
Const TESTING_IP_MIN_LATENCY = 10
Const TESTING_IP_MAX_LATENCY = 50
If TESTING Then
Dim testLatency, cumulativeLatency, testLoop, maxBlocked, blockedAddresses
Randomize
End If
' ===================( End Stress Testing )==================
Set dictIPLastSeenTime = CreateObject("Scripting.Dictionary")
Set dictIPBadLogons = CreateObject("Scripting.Dictionary")
Set dictUnblockTime = CreateObject("Scripting.Dictionary")
Set objShell = CreateObject("WScript.Shell")
Set regexpSanitizeEventLog = new Regexp
regexpSanitizeEventLog.Global = True
regexpSanitizeEventLog.Pattern = "[^0-9a-zA-Z._ /:\-]"
Set regexpSanitizeIP = new Regexp
regexpSanitizeIP.Global = True
regexpSanitizeIP.Pattern = "[^0-9.]"
' Get OS build number
Set objWMIService = GetObject("winmgmts:{(security)}!root/cimv2")
Set colOperatingSystem = objWMIService.ExecQuery("SELECT BuildNumber FROM Win32_OperatingSystem")
For Each intOSBuild in colOperatingSystem
' Windows OS versions with the "Advanced Firewall" functionality have build numbers greater than 4000
If intOSBuild.BuildNumber < 4000 Then intBlackholeStyle = BLACKHOLE_ROUTE Else intBlackholeStyle = BLACKHOLE_FIREWALL
If intOSBuild.BuildNumber = 2600 Then
LogEvent EVENTLOG_ID_ERROR_WIN_XP, EVENTLOG_TYPE_ERROR, "Fatal Error - Windows XP does not provide an IP address to black-hole in failure audit event log entries."
WScript.Quit EVENTLOG_ID_ERROR_WIN_XP
End If
If DEBUGGING Then WScript.Echo "intBlackHoleStyle = " & intBlackHoleStyle
Next ' intOSBuild
' Read configuration from the registry, if present, in a really simplsitic way
On Error Resume Next ' Noooo!!!
intBlockDuration = DEFAULT_BLOCK_DURATION
If CInt(objShell.RegRead(REG_CONFIG_PATH & REG_BLOCK_DURATION)) > 0 Then intBlockDuration = CInt(objShell.RegRead(REG_CONFIG_PATH & REG_BLOCK_DURATION))
intBlockAttempts = DEFAULT_BLOCK_ATTEMPTS
If CInt(objShell.RegRead(REG_CONFIG_PATH & REG_BLOCK_ATTEMPTS)) > 0 Then intBlockAttempts = CInt(objShell.RegRead(REG_CONFIG_PATH & REG_BLOCK_ATTEMPTS))
intBlockTimeout = DEFAULT_BLOCK_TIMEOUT
If CInt(objShell.RegRead(REG_CONFIG_PATH & REG_BLOCK_TIMEOUT)) > 0 Then intBlockTimeout = CInt(objShell.RegRead(REG_CONFIG_PATH & REG_BLOCK_TIMEOUT))
If objShell.RegRead(REG_CONFIG_PATH & REG_BLACKHOLE_IP) <> "" Then
blackHoleIPAddress = regexpSanitizeIP.Replace(objShell.RegRead(REG_CONFIG_PATH & REG_BLACKHOLE_IP), "")
Else
blackHoleIPAddress = ""
End If
On Error Goto 0
' Only obtain a blackhole adapter address on versions of Windows where it is required
If (intBlackholeStyle = BLACKHOLE_ROUTE) and (blackHoleIPAddress = "") Then
blackHoleIPAddress = GetBlackholeIP()
If IsNull(blackHoleIPAddress) Then
LogEvent EVENTLOG_ID_ERROR_NO_BLACKHOLE_IP, EVENTLOG_TYPE_ERROR, "Fatal Error - Could not obtain an IP address for an interface with no default gateway specified."
WScript.Quit EVENTLOG_ID_ERROR_NO_BLACKHOLE_IP
End If
End If
If DEBUGGING Then LogEvent EVENTLOG_ID_STARTED, EVENTLOG_TYPE_INFORMATION, "Block Duration: " & intBlockDuration
If DEBUGGING Then LogEvent EVENTLOG_ID_STARTED, EVENTLOG_TYPE_INFORMATION, "Block Attempts: " & intBlockAttempts
If DEBUGGING Then LogEvent EVENTLOG_ID_STARTED, EVENTLOG_TYPE_INFORMATION, "Block Timeout: " & intBlockTimeout
If DEBUGGING Then LogEvent EVENTLOG_ID_STARTED, EVENTLOG_TYPE_INFORMATION, "Blackhole IP: " & blackHoleIPAddress
' Create event sink to catch security events
Set objEventSink = WScript.CreateObject("WbemScripting.SWbemSink", "eventSink_")
objWMIService.ExecNotificationQueryAsync objEventSink, "SELECT * FROM __InstanceCreationEvent WHERE TargetInstance ISA 'Win32_NTLogEvent' AND TargetInstance.Logfile = 'Security' AND TargetInstance.EventType = 5 AND (TargetInstance.EventIdentifier = 529 OR TargetInstance.EventIdentifier = 4625) AND (TargetInstance.SourceName = 'Security' OR TargetInstance.SourceName = 'Microsoft-Windows-Security-Auditing')"
LogEvent EVENTLOG_ID_STARTED, EVENTLOG_TYPE_INFORMATION, EVENTLOG_SOURCE & " (version " & TS_BLOCK_VERSION & ") started."
If TESTING Then
If DEBUGGING Then WScript.Echo "Stress test loop"
For testLoop = 1 to TESTING_IP_ADDRESSES
testLatency = Int(Rnd() * (TESTING_IP_MAX_LATENCY - TESTING_IP_MIN_LATENCY)) + TESTING_IP_MIN_LATENCY
WScript.Sleep(testLatency)
Block(CStr(Int(Rnd * 256)) & "." & CStr(Int(Rnd * 256)) & "." & CStr(Int(Rnd * 256)) & "." & CStr(Int(Rnd * 256)))
blockedAddresses = blockedAddresses + 1
' Try to ExpireBlocks no more often than once every 1000ms
cumulativeLatency = cumulativeLatency + testLatency
If cumulativeLatency >= 250 Then
if blockedAddresses > maxBlocked Then maxBlocked = blockedAddresses
cumulativeLatency = 0
ExpireBlocks
End If
Next ' testLoop
' Drain the queue
While dictUnblockTime.Count > 0
WScript.Sleep(250)
ExpireBlocks
Wend
WScript.Echo "Stress test completed. " & TESTING_IP_ADDRESSES & " tested with a maximum of " & maxBlocked & " addresses blocked at once."
' Loop until killed
While (True)
WScript.Sleep(250)
Wend
Else
If DEBUGGING Then WScript.Echo "Entering normal operation busy-wait loop."
' Loop sleeping for 250ms, expiring blocks
While (True)
WScript.Sleep(250)
ExpireBlocks
Wend
End If
Sub Block(IP)
' Block an IP address and set the time for the block expiration
Dim strRunCommand
Dim intRemoveBlockTime
' Block an IP address (either by black-hole routing it or adding a firewall rule)
If (TESTING <> 1) Then
If intBlackholeStyle = BLACKHOLE_ROUTE Then strRunCommand = "route add " & IP & " mask 255.255.255.255 " & blackHoleIPAddress
If intBlackholeStyle = BLACKHOLE_FIREWALL Then strRunCommand = "netsh advfirewall firewall add rule name=""Blackhole " & IP & """ dir=in protocol=any action=block remoteip=" & IP
If DEBUGGING Then WScript.Echo "Executing " & strRunCommand
objShell.Run strRunCommand
End If
' Calculate time to remove block and add to dictUnblockTime
intRemoveBlockTime = (Date + Time) + (intBlockDuration / (24 * 60 * 60))
If NOT dictUnblockTime.Exists(intRemoveBlockTime) Then
Set dictUnblockTime.Item(intRemoveBlockTime) = CreateObject("Scripting.Dictionary")
End If
If NOT dictUnblockTime.Item(intRemoveBlockTime).Exists(IP) Then dictUnblockTime.Item(intRemoveBlockTime).Add IP, 1
LogEvent EVENTLOG_ID_BLOCK, EVENTLOG_TYPE_INFORMATION, "Blocked " & IP & " until " & intRemoveBlockTime
End Sub
Sub Unblock(IP)
' Unblock an IP address
Dim strRunCommand
If (TESTING <> 1) Then
If intBlackholeStyle = BLACKHOLE_ROUTE Then strRunCommand = "route delete " & IP & " mask 255.255.255.255 " & blackHoleIPAddress
If intBlackholeStyle = BLACKHOLE_FIREWALL Then strRunCommand = "netsh advfirewall firewall delete rule name=""Blackhole " & IP & """"
If DEBUGGING Then WScript.Echo "Executing " & strRunCommand
objShell.Run strRunCommand
End If
LogEvent EVENTLOG_ID_UNBLOCK, EVENTLOG_TYPE_INFORMATION, "Unblocked " & IP
End Sub
Sub LogFailedLogonAttempt(IP)
' Log failed logon attempts and, if necessary, block the IP address
' Have we already seen this IP address before?
If dictIPLastSeenTime.Exists(IP) Then
' Be sure that prior attempts, if they are older than intBlockTimeout, don't count it against the IP
If (dictIPLastSeenTime.Item(IP) + (intBlockTimeout / (24 * 60 * 60))) <= (Date + Time) Then
If dictIPBadLogons.Exists(IP) Then dictIPBadLogons.Remove(IP)
End If
dictIPLastSeenTime.Item(IP) = (Date + Time)
Else
dictIPLastSeenTime.Add IP, (Date + Time)
End If
' Does this IP address already have a history of bad logons?
If dictIPBadLogons.Exists(IP) Then
dictIPBadLogons.Item(IP) = dictIPBadLogons.Item(IP) + 1
Else
dictIPBadLogons.Add IP, 1
End If
If DEBUGGING Then WScript.Echo "Logging bad attempt from " & IP & ", attempt # " & dictIPBadLogons.Item(IP)
' Should we block this IP address?
If dictIPBadLogons.Item(IP) = intBlockAttempts Then Block(IP)
End Sub
Sub ExpireBlocks()
Dim unblockTime, ipAddress
For Each unblockTime in dictUnblockTime.Keys
If unblockTime <= (Date + Time) Then
For Each ipAddress in dictUnblockTime.Item(unblockTime)
Unblock(ipAddress)
If TESTING Then blockedAddresses = blockedAddresses - 1
Next ' ipAddress
dictUnblockTime.Remove unblockTime
End If
Next 'ipAddress
End Sub
' Should an invalid logon from specified user result in an immediate block?
Function BlockImmediate(user)
Dim userToBlock
For Each userToBlock in dictBlockImmediatelyUsers.Keys
If UCase(user) = UCase(userToBlock) Then
BlockImmediate = True
Exit Function
End If
Next 'userToBlock
BlockImmediate = False
End Function
' Fires each time new security events are generated
Sub eventSink_OnObjectReady(objEvent, objWbemAsyncContext)
Dim arrEventMessage, arrInvalidLogonText
Dim IP, user
' Differentiate W2K3 and W2K8+
If objEvent.TargetInstance.SourceName = "Microsoft-Windows-Security-Auditing" Then
user = objEvent.TargetInstance.InsertionStrings(5)
IP = objEvent.TargetInstance.InsertionStrings(19)
Else
' Assume W2K3
user = objEvent.TargetInstance.InsertionStrings(0)
IP = objEvent.TargetInstance.InsertionStrings(11)
End If
' Make sure only characters allowed in IP addresses are passed to external commands
IP = regexpSanitizeIP.Replace(IP, "")
' If the event didn't generate both a username and IP address then do nothing
If (IP <> "") AND (user <> "") Then
If BlockImmediate(user) Then Block(IP) Else LogFailedLogonAttempt(IP)
End If
End Sub
Function GetBlackholeIP()
' Sift through the NICs on the machine to locate a NIC's IP to use to blackhole offending hosts.
' Look for a NIC with no default gateway set and an IP address assigned. Return NULL if we can't
' find one.
Dim objNICs, objNICConfig
Set objNICs = GetObject("winmgmts:\\.\root\cimv2").ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = TRUE")
' Scan for a NIC with no default gateway set and IP not 0.0.0.0
For Each objNICConfig in objNICs
If IsNull(objNICConfig.DefaultIPGateway) and (objNICConfig.IPAddress(0) <> "0.0.0.0") Then
If DEBUGGING Then WScript.Echo "Decided on black-hole IP address " & objNICConfig.IPAddress(0) & ", interface " & objNICConfig.Description
GetBlackholeIP = objNICConfig.IPAddress(0)
Exit Function
End If
Next
' Couldn't find anything, return NULL to let caller know we failed
GetBlackHoleIP = NULL
End Function
Sub LogEvent(ID, EventType, Message)
' Log an event to the Windows event log
' Sanitize input string
Message = regexpSanitizeEventLog.Replace(Message, "")
If DEBUGGING Then WScript.Echo "Event Log - Event ID: " & ID & ", Type: " & EventType & " - " & Message
' Don't hit the event log during testing
If TESTING Then Exit Sub
If USE_EVENTLOG Then objShell.Exec "EVENTCREATE /L APPLICATION /SO " & EVENTLOG_SOURCE & " /ID " & ID & " /T " & EventType & " /D """ & Message & """"
End Sub