mirror of
https://github.com/nmap/nmap.git
synced 2026-08-03 22:29:06 +00:00
* Add a new script snmp-ios-config that pulls the config from a Cisco devices
using SNMP and tftp. The script was created by Vikas Singhal. * Add tftp library, used by the snmp-ios-config script, that acts as a server and receives the config file from the device. [Patrik]
This commit is contained in:
parent
60cd2ef9cf
commit
e50d65755b
3 changed files with 545 additions and 0 deletions
339
nselib/tftp.lua
Normal file
339
nselib/tftp.lua
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
--- Library implementing a minimal TFTP server
|
||||
--
|
||||
-- Currently only write-operations are supported so that script can trigger
|
||||
-- TFTP transfers and receive the files and return them as result.
|
||||
--
|
||||
-- The library contains the following classes
|
||||
-- * <code>Packet</code>
|
||||
-- ** The <code>Packet</code> classes contain one class for each TFTP operation.
|
||||
-- * <code>File</code>
|
||||
-- ** The <code>File</code> class holds a recieved file including the name and contents
|
||||
-- * <code>ConnHandler</code>
|
||||
-- ** The <code>ConnHandler</code> class handles and processes incoming connections.
|
||||
--
|
||||
-- The following code snipplet starts the TFTP server and waits for the file incoming.txt
|
||||
-- to be uploaded for 10 seconds:
|
||||
-- <code>
|
||||
-- tftp.start()
|
||||
-- local status, f = tftp.waitFile("incoming.txt", 10)
|
||||
-- if ( status ) then return f:getContent() end
|
||||
-- </code>
|
||||
--
|
||||
-- @author Patrik Karlsson <patrik@cqure.net>
|
||||
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
|
||||
--
|
||||
|
||||
-- version 0.2
|
||||
--
|
||||
-- 2011-01-22 - re-wrote library to use coroutines instead of new_thread code.
|
||||
|
||||
module(... or "tftp", package.seeall)
|
||||
|
||||
threads, infiles, running = {}, {}, {}
|
||||
state = "STOPPED"
|
||||
srvthread = {}
|
||||
|
||||
-- All opcodes supported by TFTP
|
||||
OpCode = {
|
||||
RRQ = 1,
|
||||
WRQ = 2,
|
||||
DATA = 3,
|
||||
ACK = 4,
|
||||
ERROR = 5,
|
||||
}
|
||||
|
||||
|
||||
--- A minimal packet implementation
|
||||
--
|
||||
-- The current code only implements the ACK and ERROR packets
|
||||
-- As the server is write-only the other packet types are not needed
|
||||
Packet = {
|
||||
|
||||
-- Implements the ACK packet
|
||||
ACK = {
|
||||
|
||||
new = function( self, block )
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.block = block
|
||||
return o
|
||||
end,
|
||||
|
||||
__tostring = function( self )
|
||||
return bin.pack(">SS", OpCode.ACK, self.block)
|
||||
end,
|
||||
|
||||
},
|
||||
|
||||
-- Implements the error packet
|
||||
ERROR = {
|
||||
|
||||
new = function( self, code, msg )
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.msg = msg
|
||||
o.code = code
|
||||
return o
|
||||
end,
|
||||
|
||||
__tostring = function( self )
|
||||
return bin.pack(">SSz", OpCode.ERROR, self.code, self.msg)
|
||||
end,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
--- The File class holds files received by the TFTP server
|
||||
File = {
|
||||
|
||||
--- Creates a new file object
|
||||
--
|
||||
-- @param filename string containing the filename
|
||||
-- @param content string containing the file content
|
||||
-- @return o new class instance
|
||||
new = function(self, filename, content, sender)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.name = filename
|
||||
o.content = content
|
||||
o.sender = sender
|
||||
return o
|
||||
end,
|
||||
|
||||
getContent = function(self) return self.content end,
|
||||
setContent = function(self, content) self.content = content end,
|
||||
|
||||
getName = function(self) return self.name end,
|
||||
setName = function(self, name) self.name = name end,
|
||||
|
||||
setSender = function(self, sender) self.sender = sender end,
|
||||
getSender = function(self) return self.sender end,
|
||||
}
|
||||
|
||||
|
||||
-- The thread dispatcher is called by the start function once
|
||||
local function dispatcher()
|
||||
|
||||
local last = os.time()
|
||||
local f_condvar = nmap.condvar(infiles)
|
||||
local s_condvar = nmap.condvar(state)
|
||||
|
||||
while(true) do
|
||||
|
||||
-- check if other scripts are active
|
||||
local counter = 0
|
||||
for t in pairs(running) do
|
||||
counter = counter + 1
|
||||
end
|
||||
if ( counter == 0 ) then
|
||||
state = "STOPPING"
|
||||
s_condvar "broadcast"
|
||||
end
|
||||
|
||||
local n = table.getn(threads)
|
||||
if ( n == 0 ) then break end
|
||||
for i=1,n do
|
||||
local status, res = coroutine.resume(threads[i])
|
||||
if ( not(res) ) then -- thread finished its task?
|
||||
table.remove(threads, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Make sure to process waitFile atleast every 2 seconds
|
||||
-- in case no files have arrived
|
||||
if ( os.time() - last >= 2 ) then
|
||||
last = os.time()
|
||||
f_condvar "broadcast"
|
||||
end
|
||||
|
||||
end
|
||||
state = "STOPPED"
|
||||
s_condvar "broadcast"
|
||||
stdnse.print_debug("Exiting _dispatcher")
|
||||
end
|
||||
|
||||
-- Processes a new incoming file transfer
|
||||
-- Currently only uploads are supported
|
||||
--
|
||||
-- @param host containing the hostname or ip of the initiating host
|
||||
-- @param port containing the port of the initiating host
|
||||
-- @param data string containing the initial data passed to the server
|
||||
local function processConnection( host, port, data )
|
||||
local pos, op = bin.unpack(">S", data)
|
||||
local socket = nmap.new_socket("udp")
|
||||
|
||||
socket:set_timeout(1000)
|
||||
local status, err = socket:connect(host, port)
|
||||
if ( not(status) ) then return status, err end
|
||||
|
||||
socket:set_timeout(10)
|
||||
|
||||
-- If we get anything else than a write request, abort the connection
|
||||
if ( OpCode.WRQ ~= op ) then
|
||||
stdnse.print_debug("Unsupported opcode")
|
||||
socket:send( tostring(Packet.ERROR:new(0, "TFTP server has write-only support")))
|
||||
end
|
||||
|
||||
local pos, filename, enctype = bin.unpack("zz", data, pos)
|
||||
status, err = socket:send( tostring( Packet.ACK:new(0) ) )
|
||||
|
||||
local blocks = {}
|
||||
local lastread = os.time()
|
||||
|
||||
while( true ) do
|
||||
local status, pdata = socket:receive()
|
||||
if ( not(status) ) then
|
||||
-- if we're here and havent succesfully read a packet for 5 seconds, abort
|
||||
if ( os.time() - lastread > 5 ) then
|
||||
coroutine.yield(false)
|
||||
else
|
||||
coroutine.yield(true)
|
||||
end
|
||||
else
|
||||
-- record last time we had a succesful read
|
||||
lastread = os.time()
|
||||
pos, op = bin.unpack(">S", pdata)
|
||||
if ( OpCode.DATA ~= op ) then
|
||||
stdnse.print_debug("Expected a data packet, terminating TFTP transfer")
|
||||
end
|
||||
|
||||
local block, data
|
||||
pos, block, data = bin.unpack(">SA" .. #pdata - 4, pdata, pos )
|
||||
|
||||
blocks[block] = data
|
||||
|
||||
-- First block was not 1
|
||||
if ( #blocks == 0 ) then
|
||||
socket:send( tostring(Packet.ERROR:new(0, "Did not receive block 1")))
|
||||
break
|
||||
end
|
||||
|
||||
-- for every fith block check that we've received the preceeding four
|
||||
if ( ( #blocks % 5 ) == 0 ) then
|
||||
for b = #blocks - 4, #blocks do
|
||||
if ( not(blocks[b]) ) then
|
||||
socket:send( tostring(Packet.ERROR:new(0, "Did not receive block " .. b)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Ack the data block
|
||||
status, err = socket:send( tostring(Packet.ACK:new(block)) )
|
||||
|
||||
if ( ( #blocks % 20 ) == 0 ) then
|
||||
-- yield every 5th iteration so other threads may work
|
||||
coroutine.yield(true)
|
||||
end
|
||||
|
||||
-- If the data length was less than 512, this was our last block
|
||||
if ( #data < 512 ) then
|
||||
socket:close()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local filecontent = ""
|
||||
|
||||
-- Make sure we received all the blocks needed to proceed
|
||||
for i=1, #blocks do
|
||||
if ( not(blocks[i]) ) then
|
||||
return false, ("Block #%d was missing in transfer")
|
||||
end
|
||||
filecontent = filecontent .. blocks[i]
|
||||
end
|
||||
stdnse.print_debug("Finnished receiving file \"%s\"", filename)
|
||||
|
||||
-- Add anew file to the global infiles table
|
||||
table.insert( infiles, File:new(filename, filecontent, host) )
|
||||
|
||||
local condvar = nmap.condvar(infiles)
|
||||
condvar "broadcast"
|
||||
end
|
||||
|
||||
-- Waits for a connection from a client
|
||||
local function waitForConnection()
|
||||
|
||||
local srvsock = nmap.new_socket("udp")
|
||||
local status = srvsock:bind(nil, 69)
|
||||
assert(status, "Failed to bind to TFTP server port")
|
||||
|
||||
srvsock:set_timeout(0)
|
||||
|
||||
while( state == "RUNNING" ) do
|
||||
local status, data = srvsock:receive()
|
||||
if ( not(status) ) then
|
||||
coroutine.yield(true)
|
||||
else
|
||||
local status, _, _, rhost, rport = srvsock:get_info()
|
||||
local x = coroutine.create( function() processConnection(rhost, rport, data) end )
|
||||
table.insert( threads, x )
|
||||
coroutine.yield(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Starts the TFTP server and creates a new thread handing over to the dispatcher
|
||||
function start()
|
||||
local disp = nil
|
||||
local mutex = nmap.mutex("srvsocket")
|
||||
|
||||
-- register a running script
|
||||
running[coroutine.running()] = true
|
||||
|
||||
mutex "lock"
|
||||
if ( state == "STOPPED" ) then
|
||||
srvthread = coroutine.running()
|
||||
table.insert( threads, coroutine.create( waitForConnection ) )
|
||||
stdnse.new_thread( dispatcher )
|
||||
state = "RUNNING"
|
||||
end
|
||||
mutex "done"
|
||||
|
||||
end
|
||||
|
||||
local function waitLast()
|
||||
-- The thread that started the server needs to wait here until the rest
|
||||
-- of the scripts finnish running. We know we are done once the state
|
||||
-- shifts to STOPPED and we get a singla from the condvar in the
|
||||
-- dispatcher
|
||||
local s_condvar = nmap.condvar(state)
|
||||
while( srvthread == coroutine.running() and state ~= "STOPPED" ) do
|
||||
s_condvar "wait"
|
||||
end
|
||||
end
|
||||
|
||||
--- Waits for a file with a specific filename for at least the number of
|
||||
-- seconds specified by the timeout parameter. If this function is called
|
||||
-- from the thread that's running the server it will wait until all the
|
||||
-- other threads have finnished executing before returning.
|
||||
--
|
||||
-- @param filename string containing the name of the file to receive
|
||||
-- @param timeout number containing the minimum number of seconds to wait
|
||||
-- for the file to be received
|
||||
-- @return status true on success false on failure
|
||||
-- @return File instance on success, nil on failure
|
||||
function waitFile( filename, timeout )
|
||||
local condvar = nmap.condvar(infiles)
|
||||
local t = os.time()
|
||||
while(os.time() - t < timeout) do
|
||||
for _, f in ipairs(infiles) do
|
||||
if (f:getName() == filename) then
|
||||
running[coroutine.running()] = nil
|
||||
waitLast()
|
||||
return true, f
|
||||
end
|
||||
end
|
||||
condvar "wait"
|
||||
end
|
||||
-- de-register a running script
|
||||
running[coroutine.running()] = nil
|
||||
waitLast()
|
||||
|
||||
return false
|
||||
end
|
||||
|
|
@ -126,6 +126,7 @@ Entry { filename = "realvnc-auth-bypass.nse", categories = { "auth", "default",
|
|||
Entry { filename = "resolveall.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "rmi-dumpregistry.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "rpcinfo.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "servicetags.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "skypev2-version.nse", categories = { "version", } }
|
||||
Entry { filename = "smb-brute.nse", categories = { "auth", "intrusive", } }
|
||||
Entry { filename = "smb-check-vulns.nse", categories = { "dos", "exploit", "intrusive", "vuln", } }
|
||||
|
|
@ -149,6 +150,7 @@ Entry { filename = "smtp-strangeport.nse", categories = { "malware", "safe", } }
|
|||
Entry { filename = "sniffer-detect.nse", categories = { "discovery", "intrusive", } }
|
||||
Entry { filename = "snmp-brute.nse", categories = { "auth", "intrusive", } }
|
||||
Entry { filename = "snmp-interfaces.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "snmp-ios-config.nse", categories = { "intrusive", } }
|
||||
Entry { filename = "snmp-netstat.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "snmp-processes.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "snmp-sysdescr.nse", categories = { "default", "discovery", "safe", } }
|
||||
|
|
|
|||
204
scripts/snmp-ios-config.nse
Normal file
204
scripts/snmp-ios-config.nse
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
description = [[
|
||||
Download IOS configuration using SNMP RW (v1) and displays the result or saves it to a file.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap -sU -p 161 --script snmp-ios-config --script-args snmpcommunity=<community> <target>
|
||||
--
|
||||
-- @output
|
||||
-- | snmp-ios-config:
|
||||
-- | !
|
||||
-- | version 12.3
|
||||
-- | service timestamps debug datetime msec
|
||||
-- | service timestamps log datetime msec
|
||||
-- | no service password-encryption
|
||||
-- | !
|
||||
-- | hostname Router
|
||||
-- | !
|
||||
-- | boot-start-marker
|
||||
-- | boot-end-marker
|
||||
-- <snip>
|
||||
--
|
||||
-- @args snmp-ios-config.tftproot If set, specifies to what directory the downloaded config should be saved
|
||||
--
|
||||
-- Version 0.2
|
||||
-- Created 01/03/2011 - v0.1 - created by Vikas Singhal
|
||||
-- Revised 02/22/2011 - v0.2 - cleaned up and added support for built-in tftp, Patrik Karlsson <patrik@cqure.net>
|
||||
|
||||
author = "Vikas Singhal, Patrik Karlsson"
|
||||
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
|
||||
categories = {"intrusive"}
|
||||
|
||||
dependencies = {"snmp-brute"}
|
||||
|
||||
require "shortport"
|
||||
require "snmp"
|
||||
require "tftp"
|
||||
|
||||
portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"})
|
||||
|
||||
local function sendrequest(socket, oid, setparam)
|
||||
local payload
|
||||
local options = {}
|
||||
options.reqId = 28428 -- unnecessary?
|
||||
payload = snmp.encode(snmp.buildPacket(snmp.buildSetRequest(options, oid,setparam)))
|
||||
|
||||
try(socket:send(payload))
|
||||
|
||||
-- read in any response we might get
|
||||
local status, response = socket:receive()
|
||||
if ( not(status) ) then return status, response end
|
||||
|
||||
local result = snmp.fetchFirst(response)
|
||||
return true
|
||||
end
|
||||
|
||||
---
|
||||
-- Sends SNMP packets to host and reads responses
|
||||
action = function(host, port)
|
||||
|
||||
local tftproot = stdnse.get_script_args("snmp-ios-config.tftproot")
|
||||
|
||||
if ( tftproot and not( tftproot:match("[\\/]+$") ) ) then
|
||||
return "ERROR: tftproot needs to end with slash"
|
||||
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
|
||||
catch = function() socket:close() end
|
||||
|
||||
try = nmap.new_try(catch)
|
||||
|
||||
-- connect to the potential SNMP system
|
||||
try(socket:connect(host.ip, port.number, "udp"))
|
||||
|
||||
local status, tftpserver, _, _, _ = socket:get_info()
|
||||
if( not(status) ) then
|
||||
return "ERROR: Failed to determin local ip"
|
||||
end
|
||||
|
||||
-- build a SNMP v1 packet
|
||||
-- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.2.9999 (ConfigCopyProtocol is set to TFTP [1] )
|
||||
|
||||
request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.2.9999",1)
|
||||
|
||||
-- Fail silently if the first request doesn't get a proper response
|
||||
if ( not(request) ) then return end
|
||||
|
||||
-- since we got something back, the port is definitely open
|
||||
nmap.set_port_state(host, port, "open")
|
||||
|
||||
-------------------------------------------------
|
||||
-- build a SNMP v1 packet
|
||||
-- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.3 (SourceFileType is set to running-config [4] )
|
||||
|
||||
request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.3.9999",4)
|
||||
|
||||
-------------------------------------------------
|
||||
-- build a SNMP v1 packet
|
||||
-- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.4 (DestinationFileType is set to networkfile [1] )
|
||||
|
||||
request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.4.9999",1)
|
||||
|
||||
-------------------------------------------------
|
||||
-- build a SNMP v1 packet
|
||||
-- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.15 (ServerAddress is set to the IP address of the TFTP server )
|
||||
|
||||
local tbl = {}
|
||||
tbl._snmp = '40'
|
||||
for octet in tftpserver:gmatch("%d+") do
|
||||
table.insert(tbl, octet)
|
||||
end
|
||||
|
||||
request = sendrequest(socket, nil, { { snmp.str2oid(".1.3.6.1.4.1.9.9.96.1.1.1.1.5.9999"), tbl } } )
|
||||
-- request = sendrequest(".1.3.6.1.4.1.9.9.96.1.1.1.1.5.9999",tftpserver)
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
-- build a SNMP v1 packet
|
||||
-- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.15 (ServerAddressType is set 1 for ipv4 )
|
||||
-- more options - 1:ipv4, 2:ipv6, 3:ipv4z, 4:ipv6z, 16:dns
|
||||
|
||||
request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.15.9999",1)
|
||||
|
||||
-------------------------------------------------
|
||||
-- build a SNMP v1 packet
|
||||
-- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.16 (ServerAddress is set to the IP address of the TFTP server )
|
||||
|
||||
request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.16.9999",tftpserver)
|
||||
|
||||
-------------------------------------------------
|
||||
-- build a SNMP v1 packet
|
||||
-- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.6 (CopyFilename is set to IP-config)
|
||||
|
||||
request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.6.9999",host.ip .. "-config")
|
||||
|
||||
-------------------------------------------------
|
||||
-- build a SNMP v1 packet
|
||||
-- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.14 (Start copying by setting CopyStatus to active [1])
|
||||
-- more options: 1:active, 2:notInService, 3:notReady, 4:createAndGo, 5:createAndWait, 6:destroy
|
||||
|
||||
request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.14.9999",1)
|
||||
|
||||
-- wait for sometime and print the status of filetransfer
|
||||
tftp.start()
|
||||
local status, infile = tftp.waitFile(host.ip .. "-config", 10)
|
||||
|
||||
-- build a SNMP v1 packet
|
||||
-- get value: .1.3.6.1.4.1.9.9.96.1.1.1.1.10 (Check the status of filetransfer) 1:waiting, 2:running, 3:successful, 4:failed
|
||||
|
||||
local options = {}
|
||||
options.reqId = 28428
|
||||
local payload = snmp.encode(snmp.buildPacket(snmp.buildGetRequest(options, ".1.3.6.1.4.1.9.9.96.1.1.1.1.10.9999")))
|
||||
|
||||
try(socket:send(payload))
|
||||
|
||||
local status
|
||||
local response
|
||||
-- read in any response we might get
|
||||
status, response = socket:receive()
|
||||
|
||||
if (not status) or (response == "TIMEOUT") then
|
||||
return "\n ERROR: Failed to receive cisco configuration file"
|
||||
end
|
||||
|
||||
local result
|
||||
result = snmp.fetchFirst(response)
|
||||
|
||||
if result == 3 then
|
||||
result = ( infile and infile:getContent() )
|
||||
|
||||
if ( tftproot ) then
|
||||
local fname = tftproot .. host.ip .. "-config"
|
||||
local file, err = io.open(fname, "w")
|
||||
if ( file ) then
|
||||
file:write(result)
|
||||
file:close()
|
||||
else
|
||||
return "\n ERROR: " .. file
|
||||
end
|
||||
result = ("\n Configuration saved to (%s)"):format(fname)
|
||||
end
|
||||
else
|
||||
result = "Not successful! error code: " .. result .. " (1:waiting, 2:running, 3:successful, 4:failed)"
|
||||
end
|
||||
|
||||
-------------------------------------------------
|
||||
-- build a SNMP v1 packet
|
||||
-- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.14 (Destroy settings by setting CopyStatus to destroy [6])
|
||||
|
||||
request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.14.9999",6)
|
||||
|
||||
try(socket:close())
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue