├── README.md └── winVulnDetection.nse /README.md: -------------------------------------------------------------------------------- 1 | # nse_winVulnDetection_csv 2 | Checks if a windows machine with the smb service actve is vulnerable to the CVEs of a CSV file passed as argument to the script. 3 | -------------------------------------------------------------------------------- /winVulnDetection.nse: -------------------------------------------------------------------------------- 1 | local stdnse = require "stdnse" 2 | local smb = require "smb" 3 | local smb2 = require "smb2" 4 | 5 | description = [[Check for recently patched vulnerabilities]] 6 | 7 | --- 8 | -- @usage nmap -p445 --script PATH_TO_NSE_SCRIPT --script-args winVulnDetection.csv=PATH_TO_CSV_FILE 9 | -- 10 | -- @outout 11 | -- Host script results: 12 | -- | winVulnDetection: 13 | -- | MS17-006: VULNERABLE 14 | -- | MS17-006 severity: Critical 15 | -- | CVE_line_2: CVE format incorrect 16 | -- |_ MS17-008: No restart is needed 17 | -- 18 | -- @args 19 | -- winVulnDetection.csv=PATH_TO_CSV_FILE 20 | --- 21 | 22 | categories = {"safe", "vuln", "external"} 23 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 24 | author = "Lucie Gratuze and Carlos Polop Martin" 25 | 26 | local function split_cve(str_cve) --This function gets the needed information from each cve. 27 | local id, severity, restart, link, pubDate, titleSum 28 | id, severity, restart, link, pubDate, titleSum = str_cve:match("%s*'([^']*)'%s*,%s*'([^']*)'%s*,%s*'([^']*)'%s*,%s*'([^']*)'%s*,%s*'([^']*)'%s*,%s*'([^']*)'%s*") 29 | if id == nil then return nil end 30 | cve_array = {} 31 | cve_array["id"] = id:gsub("'", "") 32 | cve_array["severity"] = severity:gsub("'", "") 33 | cve_array["restart"] = restart:gsub("'", "") 34 | cve_array["link"] = link:gsub("'", "") 35 | cve_array["pubDate"] = pubDate:gsub("'", "") 36 | cve_array["titleSum"] = titleSum:gsub("'", "") 37 | return cve_array 38 | end 39 | 40 | local function get_pubDate(input_url) -- This function connects to the url found in the .csv, retrieves the publication or last update date, and changes its format to a usable one. 41 | local http = require "http" 42 | local response = http.get_url(input_url) 43 | --stdnse.debug1("DEBUG HTTP response Body: %s",response.body) 44 | local html = response.body 45 | local pub_date = html:match("Published: ([^<]*)") 46 | --stdnse.debug1("DEBUG HTTP published: %s",pub_date) 47 | 48 | local day = pub_date:match("%d%d") 49 | local month = pub_date:match("^%a+") 50 | local year = pub_date:match("%d%d%d%d$") 51 | --stdnse.debug1("DEBUG HTTP day: %s",day) 52 | --stdnse.debug1("DEBUG HTTP month: %s",month) 53 | --stdnse.debug1("DEBUG HTTP year: %s",year) 54 | 55 | local month2 56 | if (month:find("Jan")) then month2 = '01' 57 | elseif (month:find("Feb")) then month2 = '02' 58 | elseif (month:find("Mar")) then month2 = '03' 59 | elseif (month:find("Apr")) then month2 = '04' 60 | elseif (month:find("May")) then month2 = '05' 61 | elseif (month:find("Jun")) then month2 = '06' 62 | elseif (month:find("Jul")) then month2 = '07' 63 | elseif (month:find("Aug")) then month2 = '08' 64 | elseif (month:find("Sep")) then month2 = '09' 65 | elseif (month:find("Oct")) then month2 = '10' 66 | elseif (month:find("Nov")) then month2 = '11' 67 | elseif (month:find("Dec")) then month2 = '12' 68 | end 69 | 70 | local globDate = year .. "-" .. month2 .. "-" .. day 71 | --stdnse.debug1("DEBUG HTTP pub_Date: %s",globDate) 72 | return globDate 73 | end 74 | 75 | local function file_exists(file) 76 | local f = io.open(file, "rb") 77 | if f then f:close() end 78 | return f ~= nil 79 | end 80 | 81 | 82 | local function check_readFile(path) --Checks that the file exists, and, if so, returns its content. 83 | if not file_exists(path) then return nil end 84 | lines = {} 85 | for line in io.lines(path) do 86 | lines[#lines + 1] = line 87 | end 88 | return lines 89 | end 90 | 91 | local function get_startDate_sbm(host) --Connects to the smb port of a machine, and retrieves the date when the machine was started. 92 | local smbstate, status, overrides 93 | overrides = {} 94 | status, smbstate = smb.start(host) 95 | status = smb2.negotiate_v2(smbstate, overrides) 96 | if status then 97 | stdnse.debug1("SMB2: Date: %s (%s) Start date:%s (%s)", smbstate['date'], smbstate['time'], smbstate['start_date'], smbstate['start_time']) 98 | return smbstate['start_date'] 99 | else 100 | stdnse.debug1("Negotiation failed") 101 | return nil 102 | end 103 | end 104 | 105 | local function is_vuln(start_date, pub_date, output) --Compares the publication date of the vulnerability and the start date of the machine. 106 | local year1, month1, day1 = start_date:match("(%d%d%d%d)-(%d%d)-(%d%d)") 107 | local year2, month2, day2 = pub_date:match("(%d%d%d%d)-(%d%d)-(%d%d)") 108 | 109 | if year1year2 then return false 111 | elseif month1month2 then return false 113 | elseif day1day2 then return false 115 | 116 | else return false 117 | end 118 | end 119 | 120 | hostrule = function(host) --Hostrule of our function: the smb port must be opened. 121 | return smb.get_port(host) ~= nil 122 | end 123 | 124 | action = function(host, url) --Main function: uses the previous ones to determine whether the machine is vulnerable or not. 125 | local output = stdnse.output_table() 126 | 127 | -- Check param 128 | local csv_path = stdnse.get_script_args(SCRIPT_NAME..".csv") or nil 129 | if (csv_path == nil) then 130 | output.error = "No csv file in args" 131 | --stdnse.debug1("DEBUG NOT ARG %s.csv",SCRIPT_NAME) 132 | return output 133 | end 134 | -- Check file exists 135 | local csv_content = check_readFile(csv_path) 136 | if (csv_content == nil) then 137 | output.error = "CSV file does not exist" 138 | --stdnse.debug1("DEBUG No exists csv %s",csv_path) 139 | return output 140 | end 141 | 142 | -- Get the start date of the machine 143 | local start_date = get_startDate_sbm(host) 144 | 145 | if (start_date == nil) then 146 | output.error = "SMB negociation failed" 147 | return output 148 | end 149 | 150 | for i, cve_content in ipairs(csv_content) do 151 | -- Check correct cve format 152 | local cve_array = split_cve(cve_content) 153 | if cve_array == nil then 154 | output["CVE_line_"..i] = "CVE format incorrect" 155 | else 156 | -- Check if restart is needed 157 | if cve_array["restart"]:lower() ~= "yes" then 158 | output[cve_array["id"]] = "No restart is needed" 159 | 160 | else 161 | stdnse.debug1("DEBUG id: %s",cve_array["id"]) 162 | stdnse.debug1("DEBUG severity: %s",cve_array["severity"]) 163 | stdnse.debug1("DEBUG restart: %s",cve_array["restart"]) 164 | stdnse.debug1("DEBUG link: %s",cve_array["link"]) 165 | stdnse.debug1("DEBUG pubDate: %s",cve_array["pubDate"]) 166 | --stdnse.debug1("DEBUG titleSum: %s",cve_array["titleSum"]) 167 | 168 | -- Get the publication date in the web 169 | local pub_date = get_pubDate(cve_array["link"]) 170 | 171 | local year2, month2, day2 = pub_date:match("(%d%d%d%d)-(%d%d)-(%d%d)") 172 | stdnse.debug1("DEBUG year2 = %s, month2 = %s, day2 = %s", year2, month2, day2) 173 | if (year2 == nil) then 174 | output[cve_array["id"]] = "Incorrect date format in web page" 175 | else 176 | -- Finally, check if vulnerable 177 | if (is_vuln(start_date, pub_date, output)) then 178 | output[cve_array["id"]] = "VULNERABLE" 179 | output[cve_array["id"].." severity"] = cve_array["severity"] 180 | else 181 | output[cve_array["id"]] = "Not Vulnerable" 182 | end 183 | end 184 | end 185 | end 186 | end 187 | return output 188 | end 189 | --------------------------------------------------------------------------------