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