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 default wordlists used to bruteforce the SNMP community strings are nselib/data/snmpcommunities.lst and nselib/data/passwords.lst. If the passdb or snmplist argument is specified, that one is used as the wordlist. The passdb argument has precedence over snmplist. No output is reported if no valid account is found. ]] -- 2008-07-03 Philip Pickering, basic verstion -- 2011-07-17 Gorjan Petrovski, Patrik Karlsson, optimization and creds -- accounts, rejected use of the brute library because of -- implementation using unconnected sockets. --- -- @usage -- nmap -sU --script snmp-brute [--script-args [ passdb= | snmplist= ]] -- -- @args snmpcommunity The SNMP community string to use. If it's supplied, this -- script will not run. -- @args snmplist 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" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"intrusive", "auth"} require "shortport" require "snmp" require "creds" require "unpwdb" require "nmap" require "packet" portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) local filltable = function(filename, table) 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 communities_iterator = function() local function next_community() local snmplist = stdnse.get_script_args("snmplist") local passdb = stdnse.get_script_args("passdb") if passdb then local communities = {} local filename = nmap.fetchfile(passdb) if not filltable(filename, communities) then stdnse.print_debug("Cannot open snmplist file") return end for _, c in ipairs(communities) do coroutine.yield(c) end elseif snmplist then local communities = {} local filename = nmap.fetchfile(snmplist) if not filltable(filename, communities) then stdnse.print_debug("Cannot open snmplist file") return end for _, c in ipairs(communities) do coroutine.yield(c) end else local communities = {} local filename = nmap.fetchfile("nselib/data/snmpcommunities.lst") if not filltable(filename, communities) then stdnse.print_debug("Cannot open snmp communities file.") return end for _, c in ipairs(communities) do coroutine.yield(c) end local try = nmap.new_try() passwords = try(unpwdb.passwords()) for p in passwords do coroutine.yield(p) end end while(true) do coroutine.yield(nil, nil) end end return coroutine.wrap(next_community) end local communities = function() 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 unpwdb.limited_iterator(communities_iterator, time_limit, count_limit) end local send_snmp_queries = function(host, port, result) local condvar = nmap.condvar(result) local socket = nmap.new_socket("udp") --socket:set_timeout(host.times.timeout*1000) local request = snmp.buildGetRequest({}, "1.3.6.1.2.1.1.3.0") local payload, status, response local comm_iter = communities() for community_string in comm_iter() 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, 0, community_string)) status, err = socket:sendto(host, port, payload) if not status then result.status = false result.msg = "Could not send SNMP probe" condvar "signal" return end end socket:close() result.sent = true condvar("signal") end local sniff_snmp_responses = function(host, port, result) local condvar = nmap.condvar(result) local pcap = nmap.new_socket() pcap:set_timeout(host.times.timeout * 1000 * 3) local ip = host.bin_ip_src ip = string.format("%d.%d.%d.%d",ip:byte(1),ip:byte(2),ip:byte(3),ip:byte(4)) pcap:pcap_open(host.interface, 104, false,"dst host " .. ip .. " and udp and port 161") -- last_run indicated whether there will be only one more receive local last_run = false -- receive even when status=false untill all the probes are sent while true do 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(28, #p.buf) local res _, res = snmp.decode(response) if type(res) == "table" then result.communities[ #(result.communities) + 1 ] = res[2] else result.status = false result.msg = "Wrong type of SNMP response received" condvar "signal" return end else if last_run then condvar "signal" return else if result.sent then last_run = true end end end end pcap:close() condvar "signal" return end action = function(host, port) if nmap.registry.snmpcommunity or nmap.registry.args.snmpcommunity then return end local result = {} local threads = {} local condvar = nmap.condvar(result) result.sent = false --whether the probes are sent result.communities = {} -- list of valid community strings result.msg = "" -- Error/Status msg result.status = true -- Status (is everything ok) local recv_co = stdnse.new_thread(sniff_snmp_responses, host, port, result) local send_co = stdnse.new_thread(send_snmp_queries, host, port, result) 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 recv_dead then break end end if result.status then -- add the community strings to the creds database local c = creds.Credentials:new(SCRIPT_NAME, host, port) for _, community_string in ipairs(result.communities) do c:add("",community_string, creds.State.VALID) end -- insert the first community string as a snmpcommunity registry field local creds_iter = c:getCredentials() if creds_iter then local account = creds_iter() if account then nmap.registry.snmpcommunity = account.pass end end -- return output return tostring(c) else stdnse.print_debug("An error occured: "..result.msg) end end