diff options
Diffstat (limited to '')
-rw-r--r-- | scripts/snmp-brute.nse | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/scripts/snmp-brute.nse b/scripts/snmp-brute.nse new file mode 100644 index 0000000..a284dbc --- /dev/null +++ b/scripts/snmp-brute.nse @@ -0,0 +1,278 @@ +local coroutine = require "coroutine" +local creds = require "creds" +local io = require "io" +local nmap = require "nmap" +local packet = require "packet" +local shortport = require "shortport" +local snmp = require "snmp" +local stdnse = require "stdnse" +local unpwdb = require "unpwdb" + +description = [[ +Attempts to find an SNMP community string by brute force guessing. + +This script opens a sending socket and a sniffing pcap socket in parallel +threads. The sending socket sends the SNMP probes with the community strings, +while the pcap socket sniffs the network for an answer to the probes. If +valid community strings are found, they are added to the creds database and +reported in the output. + +The script takes the <code>snmp-brute.communitiesdb</code> argument that +allows the user to define the file that contains the community strings to +be used. If not defined, the default wordlist used to bruteforce the SNMP +community strings is <code>nselib/data/snmpcommunities.lst</code>. In case +this wordlist does not exist, the script falls back to +<code>nselib/data/passwords.lst</code> + +No output is reported if no valid account is found. +]] +-- 2008-07-03 Philip Pickering, basic version +-- 2011-07-17 Gorjan Petrovski, Patrik Karlsson, optimization and creds +-- accounts, rejected use of the brute library because of +-- implementation using unconnected sockets. +-- 2011-12-29 Patrik Karlsson - Added lport to sniff_snmp_responses to fix +-- bug preventing multiple scripts from working +-- properly. +-- 2015-05-31 Gioacchino Mazzurco - Add IPv6 support by making the script IP +-- version agnostic + +--- +-- @usage +-- nmap -sU --script snmp-brute <target> [--script-args snmp-brute.communitiesdb=<wordlist> ] +-- +-- @args snmp-brute.communitiesdb The filename of a list of community strings to try. +-- +-- @output +-- PORT STATE SERVICE +-- 161/udp open snmp +-- | snmp-brute: +-- | dragon - Valid credentials +-- |_ jordan - Valid credentials + +author = {"Philip Pickering", "Gorjan Petrovski", "Patrik Karlsson", "Gioacchino Mazzurco"} + +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" + +categories = {"intrusive", "brute"} + + +portrule = shortport.port_or_service(161, "snmp", "udp", {"open", "open|filtered"}) + +local communitiestable = {} + +local filltable = function(filename, table) + if #table ~= 0 then + return true + end + + local file = io.open(filename, "r") + + if not file then + return false + end + + for l in file:lines() do + -- Comments takes up a whole line + if not l:match("#!comment:") then + table[#table + 1] = l + end + end + + file:close() + + return true +end + +local closure = function(table) + local i = 1 + + return function(cmd) + if cmd == "reset" then + i = 1 + return + end + local elem = table[i] + if elem then i = i + 1 end + return elem + end +end + +local communities_raw = function(path) + if not path then + return false, "Cannot find communities list" + end + + if not filltable(path, communitiestable) then + return false, "Error parsing communities list" + end + + return true, closure(communitiestable) +end + +local communities = function() + local communities_file = stdnse.get_script_args('snmp-brute.communitiesdb') or + nmap.fetchfile("nselib/data/snmpcommunities.lst") + + if communities_file then + stdnse.debug1("Using the %s as the communities file", communities_file) + + local status, iterator = communities_raw(communities_file) + + if not status then + return false, iterator + end + + local time_limit = unpwdb.timelimit() + local count_limit = 0 + + if stdnse.get_script_args("unpwdb.passlimit") then + count_limit = tonumber(stdnse.get_script_args("unpwdb.passlimit")) + end + + return true, unpwdb.limited_iterator(iterator, time_limit, count_limit, "communities") + else + stdnse.debug1("Cannot read the communities file, using the nmap username/password database instead") + + return unpwdb.passwords() + end +end + +local send_snmp_queries = function(socket, result, nextcommunity) + local condvar = nmap.condvar(result) + + local request = snmp.buildGetRequest({}, "1.3.6.1.2.1.1.3.0") + + local payload, status, response, err + local community = nextcommunity() + + while community do + if result.status == false then + --in case the sniff_snmp_responses thread was shut down + condvar("signal") + return + end + payload = snmp.encode(snmp.buildPacket(request, nil, community)) + status, err = socket:send(payload) + if not status then + result.status = false + result.msg = "Could not send SNMP probe" + condvar "signal" + return + end + + community = nextcommunity() + end + + result.sent = true + condvar("signal") +end + +local sniff_snmp_responses = function(host, port, lport, result) + local condvar = nmap.condvar(result) + local pcap = nmap.new_socket() + pcap:set_timeout(host.times.timeout * 1000 * 3) + pcap:pcap_open(host.interface, 300, false, + ("src host %s and udp and src port %d and dst port %d"):format(host.ip, port.number, lport)) + + local communities = creds.Credentials:new(SCRIPT_NAME, host, port) + + -- last_run indicated whether there will be only one more receive + local last_run = false + + -- receive even when status=false until all the probes are sent + while true do + if coroutine.status(result.main_thread) == "dead" then + -- Oops, main thread quit. Time to bail. + return + end + + local status, plen, l2, l3, _ = pcap:pcap_receive() + + if status then + local p = packet.Packet:new(l3,#l3) + if not p:udp_parse() then + --shouldn't happen + result.status = false + result.msg = "Wrong type of packet received" + condvar "signal" + return + end + + local response = p:raw(p.udp_offset + 8, #p.buf) + local res = snmp.decode(response) + + if type(res) == "table" then + communities:add(nil, res[2], creds.State.VALID) + else + result.status = false + result.msg = "Wrong type of SNMP response received" + condvar "signal" + return + end + else + if last_run or not result.status then + condvar "signal" + return + else + if result.sent then + last_run = true + end + end + end + end + pcap:close() + condvar "signal" + return +end + +local function fail (err) return stdnse.format_output(false, err) end + +action = function(host, port) + local status, nextcommunity = communities() + + if not status then + return fail("Failed to read the communities database") + end + + local result = {} + local threads = {} + + local condvar = nmap.condvar(result) + + result.sent = false --whether the probes are sent + result.msg = "" -- Error/Status msg + result.status = true -- Status (is everything ok) + result.main_thread = coroutine.running() -- to check if the main thread is dead. + + local socket = nmap.new_socket("udp") + status = socket:connect(host, port) + + if ( not(status) ) then + return fail("Failed to connect to server") + end + + local status, _, lport = socket:get_info() + if( not(status) ) then + return fail("Failed to retrieve local port") + end + + local recv_co = stdnse.new_thread(sniff_snmp_responses, host, port, lport, result) + local send_co = stdnse.new_thread(send_snmp_queries, socket, result, nextcommunity) + + local recv_dead, send_dead + while true do + condvar "wait" + recv_dead = (coroutine.status(recv_co) == "dead") + send_dead = (coroutine.status(send_co) == "dead") + if send_dead then result.sent = true end + if recv_dead then break end + end + + socket:close() + + if result.status then + return creds.Credentials:new(SCRIPT_NAME, host, port):getTable() + else + stdnse.debug1("An error occurred: "..result.msg) + end +end |