diff --git a/nselib/data/snmpcommunities.lst b/nselib/data/snmpcommunities.lst new file mode 100644 index 000000000..ff068b049 --- /dev/null +++ b/nselib/data/snmpcommunities.lst @@ -0,0 +1,91 @@ +#!comment: ***********************IMPORTANT NMAP LICENSE TERMS************************ +#!comment: * * +#!comment: * The Nmap Security Scanner is (C) 1996-2010 Insecure.Com LLC. Nmap is * +#!comment: * also a registered trademark of Insecure.Com LLC. This program is free * +#!comment: * software; you may redistribute and/or modify it under the terms of the * +#!comment: * GNU General Public License as published by the Free Software * +#!comment: * Foundation; Version 2 with the clarifications and exceptions described * +#!comment: * below. This guarantees your right to use, modify, and redistribute * +#!comment: * this software under certain conditions. If you wish to embed Nmap * +#!comment: * technology into proprietary software, we sell alternative licenses * +#!comment: * (contact sales@insecure.com). Dozens of software vendors already * +#!comment: * license Nmap technology such as host discovery, port scanning, OS * +#!comment: * detection, and version detection. * +#!comment: * * +#!comment: * Note that the GPL places important restrictions on "derived works", yet * +#!comment: * it does not provide a detailed definition of that term. To avoid * +#!comment: * misunderstandings, we consider an application to constitute a * +#!comment: * "derivative work" for the purpose of this license if it does any of the * +#!comment: * following: * +#!comment: * o Integrates source code from Nmap * +#!comment: * o Reads or includes Nmap copyrighted data files, such as * +#!comment: * nmap-os-db or nmap-service-probes. * +#!comment: * o Executes Nmap and parses the results (as opposed to typical shell or * +#!comment: * execution-menu apps, which simply display raw Nmap output and so are * +#!comment: * not derivative works.) * +#!comment: * o Integrates/includes/aggregates Nmap into a proprietary executable * +#!comment: * installer, such as those produced by InstallShield. * +#!comment: * o Links to a library or executes a program that does any of the above * +#!comment: * * +#!comment: * The term "Nmap" should be taken to also include any portions or derived * +#!comment: * works of Nmap. This list is not exclusive, but is meant to clarify our * +#!comment: * interpretation of derived works with some common examples. Our * +#!comment: * interpretation applies only to Nmap--we don't speak for other people's * +#!comment: * GPL works. * +#!comment: * * +#!comment: * If you have any questions about the GPL licensing restrictions on using * +#!comment: * Nmap in non-GPL works, we would be happy to help. As mentioned above, * +#!comment: * we also offer alternative license to integrate Nmap into proprietary * +#!comment: * applications and appliances. These contracts have been sold to dozens * +#!comment: * of software vendors, and generally include a perpetual license as well * +#!comment: * as providing for priority support and updates as well as helping to * +#!comment: * fund the continued development of Nmap technology. Please email * +#!comment: * sales@insecure.com for further information. * +#!comment: * * +#!comment: * As a special exception to the GPL terms, Insecure.Com LLC grants * +#!comment: * permission to link the code of this program with any version of the * +#!comment: * OpenSSL library which is distributed under a license identical to that * +#!comment: * listed in the included docs/licenses/OpenSSL.txt file, and distribute * +#!comment: * linked combinations including the two. You must obey the GNU GPL in all * +#!comment: * respects for all of the code used other than OpenSSL. If you modify * +#!comment: * this file, you may extend this exception to your version of the file, * +#!comment: * but you are not obligated to do so. * +#!comment: * * +#!comment: * If you received these files with a written license agreement or * +#!comment: * contract stating terms other than the terms above, then that * +#!comment: * alternative license agreement takes precedence over these comments. * +#!comment: * * +#!comment: * Source is provided to this software because we believe users have a * +#!comment: * right to know exactly what a program is going to do before they run it. * +#!comment: * This also allows you to audit the software for security holes (none * +#!comment: * have been found so far). * +#!comment: * * +#!comment: * Source code also allows you to port Nmap to new platforms, fix bugs, * +#!comment: * and add new features. You are highly encouraged to send your changes * +#!comment: * to nmap-dev@insecure.org for possible incorporation into the main * +#!comment: * distribution. By sending these changes to Fyodor or one of the * +#!comment: * Insecure.Org development mailing lists, it is assumed that you are * +#!comment: * offering the Nmap Project (Insecure.Com LLC) the unlimited, * +#!comment: * non-exclusive right to reuse, modify, and relicense the code. Nmap * +#!comment: * will always be available Open Source, but this is important because the * +#!comment: * inability to relicense code has caused devastating problems for other * +#!comment: * Free Software projects (such as KDE and NASM). We also occasionally * +#!comment: * relicense the code to third parties as discussed above. If you wish to * +#!comment: * specify special license conditions of your contributions, just say so * +#!comment: * when you send them. * +#!comment: * * +#!comment: * This program is distributed in the hope that it will be useful, but * +#!comment: * WITHOUT ANY WARRANTY; without even the implied warranty of * +#!comment: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +#!comment: * General Public License v2.0 for more details at * +#!comment: * http://www.gnu.org/licenses/gpl-2.0.html , or in the COPYING file * +#!comment: * included with Nmap. * +#!comment: * * +#!comment: ***************************************************************************/ + +public +private +snmpd +mngt +cisco +admin diff --git a/nselib/unpwdb.lua b/nselib/unpwdb.lua index 1446950d3..60ad7dc1b 100644 --- a/nselib/unpwdb.lua +++ b/nselib/unpwdb.lua @@ -212,7 +212,7 @@ end -- @param count_limit Count limit in seconds. Use 0 or nil for no limit. -- @return boolean Status. -- @return function The wrapped iterator. -local limited_iterator = function(iterator, time_limit, count_limit) +limited_iterator = function(iterator, time_limit, count_limit) local start, count, elem time_limit = (time_limit and time_limit > 0) and time_limit diff --git a/scripts/snmp-brute.nse b/scripts/snmp-brute.nse index cfa746d13..a61a6d56d 100644 --- a/scripts/snmp-brute.nse +++ b/scripts/snmp-brute.nse @@ -1,9 +1,29 @@ 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 +-- 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. @@ -11,9 +31,11 @@ Attempts to find an SNMP community string by brute force guessing. -- @output -- PORT STATE SERVICE -- 161/udp open snmp --- |_snmp-brute: public +-- | snmp-brute: +-- | dragon - Account is valid +-- |_ jordan - Account is valid -author = "Philip Pickering" +author = "Philip Pickering, Gorjan Petrovski, Patrik Karlsson" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" @@ -22,91 +44,223 @@ categories = {"intrusive", "auth"} require "shortport" require "snmp" require "creds" +require "unpwdb" +require "nmap" +require "packet" portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) -action = function(host, port) +local filltable = function(filename, table) + local file = io.open(filename, "r") - if nmap.registry.snmpcommunity or nmap.registry.args.snmpcommunity then return end - - -- create the socket used for our connection - local socket = nmap.new_socket() - - -- set a reasonable timeout value - socket:set_timeout(5000) - - -- do some exception handling / cleanup - local catch = function() - socket:close() - end - - local try = nmap.new_try(catch) - - -- connect to the potential SNMP system - try(socket:connect(host, port)) - - - local request = snmp.buildGetRequest({}, "1.3.6.1.2.1.1.3.0") - - local commFile = nmap.registry.args.snmplist and nmap.fetchfile(nmap.registry.args.snmplist) - local commTable - - -- fetch wordlist from file (from unpwdb-lib) - if commFile then - local file = io.open(commFile) - - if file then - commTable = {} - while true do - local l = file:read() - - if not l then - break - end - - -- Comments takes up a whole line - if not l:match("#!comment:") then - table.insert(commTable, l) - end + 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() - end - end - - -- default wordlist - if (not commTable) then commTable = {'public', 'private', 'snmpd', 'snmp', 'mngt', 'cisco', 'admin'} end - - -- send all possible words out before waiting for an answer - for _, commStr in ipairs(commTable) do - local payload = snmp.encode(snmp.buildPacket(request, 0, commStr)) - try(socket:send(payload)) - end - - -- finally wait for a response - local status - local response - - status, response = socket:receive_bytes(1) - - if (not status) then - return - end - - if (response == "TIMEOUT") then - return - end - nmap.set_port_state(host, port, "open") - - local result - _, result = snmp.decode(response) - - -- response contains valid community string - if type(result) == "table" then - nmap.registry.snmpcommunity = result[2] - return result[2] - end - - return + + 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