forked from VicariusInc/vicarius-nmap
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvicarius-vulnerability-scan.nse
410 lines (327 loc) · 11.1 KB
/
vicarius-vulnerability-scan.nse
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
description = [[
INTRODUCTION
Vicarius Vulnerability scan is a nmap NSE to detect vulnerabilities.
It is based on vulscan - https://github.com/scipag/vulscan
The nmap option -sV enables version detection per service which is used to
determine potential flaws according to the identified product.
The data is looked up in an offline CVE DB that is up-to-date with MITRE-CVE DB (https://cve.mitre.org)
as to March 2022.
INSTALLATION
Please copy the vicarius-vulnerability-scan.nse file into the nmap scripts folder:
<Nmap Folder>\scripts\vicarius-vulnerability-scan.nse
And the cve.csv file into a sub-folder named vicarius-vulnerability-scan:
<Nmap Folder>\scripts\vicarius-vulnerability-scan\cve.csv
USAGE
You have to run the following minimal command to initiate a simple
vulnerability scan:
nmap -sV --script=vicarius-vulnerability-scan.nse www.example.com
DISCLAIMER
Keep in mind that this kind of derivative vulnerability scanning
heavily relies on the confidence of the version detection of nmap, the
amount of documented vulnerebilities and the accuracy of pattern
matching. The existence of potential flaws is not verified with
additional scanning nor exploiting techniques.
]]
author = "Vicarius LTD."
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "safe", "vuln"}
local stdnse = require("stdnse")
local have_stringaux, stringaux = pcall(require, "stringaux")
local strsplit = (have_stringaux and stringaux or stdnse).strsplit
portrule = function(host, port)
if port.version.product ~= nil and port.version.product ~= "" then
return true
else
stdnse.print_debug(1, "vicarius-vulnerability-scan: No version detection data available. Analysis not possible.")
end
end
action = function(host, port)
local prod = port.version.product -- product name
local ver = port.version.version -- product version
local struct = "[{id}] {title}\n" -- default report structure
local db = {} -- vulnerability database
local db_link = "" -- custom link for vulnerability databases
local vul = {} -- details for the vulnerability
local v_count = 0 -- counter for the vulnerabilities
local s = "" -- the output string
stdnse.print_debug(1, "vicarius-vulnerability-scan: Found service " .. prod)
-- vulnerability DB to compare with
db[1] = {name="MITRE CVE", file="cve.csv", url="https://cve.mitre.org", link="https://cve.mitre.org/cgi-bin/cvename.cgi?name={id}"}
stdnse.print_debug(1, "vicarius-vulnerability-scan: Using multi db mode (" .. #db .. " databases) ...")
for i,v in ipairs(db) do
vul = find_vulnerabilities(prod, ver, v.file)
s = s .. v.name .. " - " .. v.url .. ":\n"
if #vul > 0 then
v_count = v_count + #vul
s = s .. prepare_result(vul, struct, v.link) .. "\n"
else
s = s .. "No findings\n\n"
end
stdnse.print_debug(1, "vicarius-vulnerability-scan: " .. #vul .. " matches in " .. v.file)
end
stdnse.print_debug(1, "vicarius-vulnerability-scan: " .. v_count .. " matches in total")
if s then
return s
end
end
function does_version_match_function(v_title_lower, ver)
local exact_version
local range_start
local range_end
local left1
local left2
local left3
local right1
local right2
local right3
local beforeEqual_start
local beforeEqual_end
local equals1
local equals2
local beforeEqual1
local beforeEqual2
local beforeEqual3
local before_start
local before_end
local before1
local before2
local before3
local ver_start
local ver_end
local ver1
local ver2
local ver3
exact_version = string.find(v_title_lower, ver, 1)
if exact_version ~= nil then
return true
end
range_start,range_end,left1,left2,left3,right1,right2,right3 = string.find(v_title_lower, "(%d+)%.(%d+)%.(%d+)%s*%-%s*(%d+)%.(%d+)%.(%d+)", 1)
beforeEqual_start,beforeEqual_end,equals1,equals2,beforeEqual1,beforeEqual2,beforeEqual3 = string.find(v_title_lower, "(%d+)%.(%d+).x%s*before%s*(%d+)%.(%d+)%.(%d+)", 1)
before_start,before_end,before1,before2,before3 = string.find(v_title_lower, "before%s*(%d+)%.(%d+)%.(%d+)", 1)
if range_start == nil and before_start == nil and beforeEqual_start == nil then
return true
end
ver_start,ver_end,ver1,ver2,ver3 = string.find(ver, "(%d+)%.(%d+)%.?(%d+)?", 1)
if range_start ~= nil and range_end ~= nil then
-- check if version in range
if is_smaller(left1,left2,left3,ver1,ver2,ver3) ~= nil and is_smaller(ver1,ver2,ver3,right1,right2,right3) ~= nil then
return true
else
return nil
end
end
if beforeEqual_start ~= nil and beforeEqual_end ~= nil then
-- check if version in range
if is_equal(equals1,equals2,ver1,ver2) ~= nil and is_smaller(ver1,ver2,ver3,beforeEqual1,beforeEqual2,beforeEqual3) ~= nil then
return true
else
return nil
end
end
if before_start ~= nil and before_end ~= nil then
-- check if version in range
if is_smaller(ver1,ver2,ver3,before1,before2,before3) ~= nil then
return true
else
return nil
end
end
end
function is_equal(left1,left2,right1,right2)
if type(left1) ~= "number" or type(right1) ~= "number" or left1 ~= right1 then
return nil
end
if type(left2) ~= "number" or type(right2) ~= "number" or left2 ~= right2 then
return nil
end
return true
end
function is_smaller(left1,left2,left3,right1,right2,right3)
if type(left1) == "number" and type(right1) == "number" and left1 < right1 then
return true
end
if type(left2) == "number" and type(right2) == "number" and left2 < right2 then
return true
end
if type(left3) == "number" and type(right3) == "number" and left3 < right3 then
return true
end
return nil
end
-- Find the product matches in the vulnerability databases
function find_vulnerabilities(prod, ver, db)
local v = {} -- matching vulnerabilities
local v_id -- id of vulnerability
local v_title -- title of vulnerability
local v_title_lower -- title of vulnerability in lowercase for speedup
local v_found -- if a match could be found
local matches_found
-- Load database
local v_entries = read_from_file("scripts/vicarius-vulnerability-scan/" .. db)
stdnse.print_debug(1, "vicarius-vulnerability-scan: Starting search of " .. prod ..
" in " .. db ..
" (" .. #v_entries .. " entries) ...")
-- Iterate through the vulnerabilities in the database
for i=1, #v_entries, 1 do
v_id = extract_from_table(v_entries[i], 1, ";")
v_title = extract_from_table(v_entries[i], 2, ";")
matches_found,version_match = does_cve_apply(v_title, prod, ver)
if matches_found ~= nil and type(matches_found) == "number" then
if #v == 0 then
-- Initiate table
v[1] = {
id = v_id,
title = v_title,
product = prod,
version = ver,
matches = matches_found
}
else
v[#v+1] = {
id = v_id,
title = v_title,
product = prod,
version = ver,
matches = matches_found
}
end
end
end
return v
end
-- Prepare the resulting matches
function prepare_result(v, struct, link)
local grace = 0 -- grace trigger
local match_max = 0 -- counter for maximum matches
local match_max_title = "" -- title of the maximum match
local s = "" -- the output string
-- Search the entries with the best matches
if #v > 0 then
-- Find maximum matches
for i=1, #v, 1 do
if v[i].matches > match_max then
match_max = v[i].matches
match_max_title = v[i].title
end
end
stdnse.print_debug(2, "vicarius-vulnerability-scan: Maximum matches of a finding are " ..
match_max .. " (" .. match_max_title .. ")")
if match_max > 0 then
--for matchpoints=match_max, 1, -1 do
matchpoints=match_max
if matchpoints > 2 then
for i=1, #v, 1 do
if v[i].matches == matchpoints then
stdnse.print_debug(2, "vicarius-vulnerability-scan: Setting up result id " .. i)
s = s .. report_parsing(v[i], struct, link)
end
end
end
end
end
return s
end
-- Parse the report output structure
function report_parsing(v, struct, link)
local s = struct
--database data (needs to be first)
--s = string.gsub(s, "{link}", escape(link))
--layout elements (needs to be second)
s = string.gsub(s, "\\n", "\n")
s = string.gsub(s, "\\t", "\t")
--vulnerability data (needs to be third)
s = string.gsub(s, "{id}", escape(v.id))
s = string.gsub(s, "{title}", escape(v.title))
s = string.gsub(s, "{matches}", escape(v.matches))
s = string.gsub(s, "{product}", escape(v.product))
if v.version ~= nil then
s = string.gsub(s, "{version}", escape(v.version))
end
return s
end
-- find if the CVE applies to the product
function does_cve_apply(v_title, prod, ver)
local v_found -- if a match could be found
local matched_words = {}
local prod_words = strsplit(" ", prod)
local non_unique_matches
local unique_search
local unique_match_found
local does_version_match
if type(v_title) == "string" then
v_title_lower = string.lower(v_title)
-- Find the matches for the database entry
for j=1, #prod_words, 1 do
v_found = string.find(v_title_lower, escape(string.lower(prod_words[j])), 1)
if type(v_found) == "number" and v_found>0 then
matched_words[#matched_words+1] = prod_words[j]
--if #matched_words == 0 then
-- Initiate table
--matched_words[1] = prod_words[j]
--else
-- Create new entry
--matched_words[#matched_words+1] = prod_words[j]
--end
end
end
-- Search for unique matches
non_unique_matches = "microsoft windows linux mac"
unique_match_found = nil
for j=1, #matched_words, 1 do
unique_search = string.find(non_unique_matches, escape(string.lower(matched_words[j])), 1)
if unique_search == nil then
unique_match_found = true
break
end
end
if unique_match_found == nil then
return unique_match_found
end
-- Additional version matching
if ver ~= nil and ver ~= "" then
does_version_match = does_version_match_function(v_title_lower, ver)
if does_version_match == nil then
return nil
else
return #matched_words, true
end
end
return #matched_words, nil
end
return nil
end
-- Get the row of a CSV file
function extract_from_table(line, col, del)
local val = strsplit(del, line)
if type(val[col]) == "string" then
return val[col]
end
end
-- Read a file
function read_from_file(file)
local filepath = nmap.fetchfile(file)
if filepath then
local f, err, _ = io.open(filepath, "r")
if not f then
stdnse.print_debug(1, "vicarius-vulnerability-scan: Failed to open file" .. file)
end
local line, ret = nil, {}
while true do
line = f:read()
if not line then break end
ret[#ret+1] = line
end
f:close()
return ret
else
stdnse.print_debug(1, "vicarius-vulnerability-scan: File " .. file .. " not found")
return ""
end
end
-- We don't like unescaped things
function escape(s)
if s ~= nil then
s = string.gsub(s, "%%", "%%%%")
end
return s
end