mirror of
https://github.com/nmap/nmap.git
synced 2025-12-21 06:59:01 +00:00
Add amqp-info by Sebastian Dragomir.
This commit is contained in:
398
nselib/amqp.lua
Normal file
398
nselib/amqp.lua
Normal file
@@ -0,0 +1,398 @@
|
||||
---
|
||||
-- The AMQP library provides some basic functionality for retrieving information
|
||||
-- about an AMQP server's properties.
|
||||
--
|
||||
-- Summary
|
||||
-- -------
|
||||
-- The library currently supports the AMQP 0-9 and 0-8 protocol specifications.
|
||||
--
|
||||
-- Overview
|
||||
-- --------
|
||||
-- The library contains the following classes:
|
||||
--
|
||||
-- o AMQP
|
||||
-- - This class contains the core functions needed to communicate with AMQP
|
||||
--
|
||||
-- o AMQPSocket
|
||||
-- - This is a copy of the VNCSocket class.
|
||||
|
||||
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
|
||||
-- @author "Sebastian Dragomir <velorien@gmail.com>"
|
||||
|
||||
-- Version 0.1
|
||||
|
||||
-- Created 05/04/2011 - v0.1 - created by Sebastian Dragomir <velorien@gmail.com>
|
||||
|
||||
module(... or "amqp", package.seeall)
|
||||
|
||||
require "bin"
|
||||
require "stdnse"
|
||||
|
||||
AMQP = {
|
||||
|
||||
-- protocol versions sent by the server
|
||||
versions = {
|
||||
[0x0800] = "0-8",
|
||||
[0x0009] = "0-9"
|
||||
},
|
||||
|
||||
-- version strings the client supports
|
||||
client_version_strings = {
|
||||
["0-8"] = string.char(0x01) .. string.char(0x01) .. string.char(0x08) .. string.char(0x00),
|
||||
["0-9"] = string.char(0x00) .. string.char(0x00) .. string.char(0x09) .. string.char(0x00),
|
||||
["0-9-1"] = string.char(0x00) .. string.char(0x00) .. string.char(0x09) .. string.char(0x01)
|
||||
},
|
||||
|
||||
new = function(self, host, port)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.host = host
|
||||
o.port = port
|
||||
o.amqpsocket = AMQPSocket:new()
|
||||
o.cli_version = self.client_version_strings[nmap.registry.args['amqp.version']] or self.client_version_strings["0-9-1"]
|
||||
o.protover = nil
|
||||
o.server_version = nil
|
||||
o.server_product = nil
|
||||
o.serer_properties = nil
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Connects the AMQP socket
|
||||
connect = function(self)
|
||||
local data, status, msg
|
||||
|
||||
status, msg = self.amqpsocket:connect(self.host, self.port, "tcp")
|
||||
return status, msg
|
||||
end,
|
||||
|
||||
--- Disconnects the AMQP socket
|
||||
disconnect = function(self)
|
||||
self.amqpsocket:close()
|
||||
end,
|
||||
|
||||
--- Decodes a table value in the server properties field.
|
||||
--
|
||||
-- @param tbl the decoded table
|
||||
-- @param tsize number, the table size in bytes
|
||||
-- @return status, true on success, false on failure
|
||||
-- @return error string containing error message if status is false
|
||||
-- @return decoded value
|
||||
decodeTable = function(self, tbl, tsize)
|
||||
local status, err, tmp, read, value
|
||||
read = 0
|
||||
|
||||
while read < tsize do
|
||||
local key, value
|
||||
|
||||
status, tmp = self.amqpsocket:recv( 1 )
|
||||
if ( not(status) ) then
|
||||
return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading key length", nil
|
||||
end
|
||||
read = read + 1
|
||||
|
||||
tmp = select( 2, bin.unpack("C", tmp) )
|
||||
status, key = self.amqpsocket:recv( tmp )
|
||||
if ( not(status) ) then
|
||||
return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading key", nil
|
||||
end
|
||||
read = read + tmp
|
||||
|
||||
status, tmp = self.amqpsocket:recv( 1 )
|
||||
if ( not(status) ) then
|
||||
return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading value type for " .. key, nil
|
||||
end
|
||||
read = read + 1
|
||||
|
||||
if ( tmp == 'F' ) then -- table type
|
||||
status, tmp = self.amqpsocket:recv( 4 )
|
||||
if ( not(status) ) then
|
||||
return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading table size", nil
|
||||
end
|
||||
|
||||
read = read + 4
|
||||
value = {}
|
||||
tmp = select( 2, bin.unpack(">I", tmp) )
|
||||
status, err, value = self:decodeTable(value, tmp)
|
||||
read = read + tmp
|
||||
table.insert(tbl, key .. ": ")
|
||||
table.insert(tbl, value)
|
||||
elseif ( tmp == 'S' ) then -- string type
|
||||
status, err, value, read = self:decodeString(key, read)
|
||||
if ( key == "product" ) then
|
||||
self.server_product = value
|
||||
elseif ( key == "version" ) then
|
||||
self.server_version = value
|
||||
end
|
||||
table.insert(tbl, key .. ": " .. value)
|
||||
elseif ( tmp == 't' ) then -- boolean type
|
||||
status, err, value, read = self:decodeBoolean(key, read)
|
||||
table.insert(tbl, key .. ": " .. value)
|
||||
end
|
||||
|
||||
if ( not(status) ) then
|
||||
return status, err, nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return true, nil, tbl
|
||||
end,
|
||||
|
||||
--- Decodes a string value in the server properties field.
|
||||
--
|
||||
-- @param key string, the key being read
|
||||
-- @param read number, number of bytes already read
|
||||
-- @return status, true on success, false on failure
|
||||
-- @return error string containing error message if status is false
|
||||
-- @return decoded value
|
||||
-- @return number of bytes read after decoding this value
|
||||
decodeString = function(self, key, read)
|
||||
local value, status, tmp
|
||||
status, tmp = self.amqpsocket:recv( 4 )
|
||||
if ( not(status) ) then
|
||||
return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading value size for " .. key, nil, 0
|
||||
end
|
||||
|
||||
read = read + 4
|
||||
tmp = select( 2, bin.unpack(">I", tmp) )
|
||||
status, value = self.amqpsocket:recv( tmp )
|
||||
|
||||
if ( not(status) ) then
|
||||
return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading value for " .. key, nil, 0
|
||||
end
|
||||
read = read + tmp
|
||||
|
||||
return true, nil, value, read
|
||||
end,
|
||||
|
||||
--- Decodes a boolean value in the server properties field.
|
||||
--
|
||||
-- @param key string, the key being read
|
||||
-- @param read number, number of bytes already read
|
||||
-- @return status, true on success, false on failure
|
||||
-- @return error string containing error message if status is false
|
||||
-- @return decoded value
|
||||
-- @return number of bytes read after decoding this value
|
||||
decodeBoolean = function(self, key, read)
|
||||
local status, value
|
||||
status, value = self.amqpsocket:recv( 1 )
|
||||
if ( not(status) ) then
|
||||
return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading value for " .. key, nil, 0
|
||||
end
|
||||
|
||||
value = select( 2, bin.unpack("C", value) )
|
||||
read = read + 1
|
||||
|
||||
return true, nil, value == 0x01 and "YES" or "NO", read
|
||||
end,
|
||||
|
||||
--- Performs the AMQP handshake and determines
|
||||
-- o The AMQP protocol version
|
||||
-- o The server properties/capabilities
|
||||
--
|
||||
-- @return status, true on success, false on failure
|
||||
-- @return error string containing error message if status is false
|
||||
handshake = function(self)
|
||||
local _, status, err, version, tmp, value, properties
|
||||
|
||||
status = self.amqpsocket:send( "AMQP" .. self.cli_version )
|
||||
if ( not(status) ) then
|
||||
return false, "ERROR: AMQP:handshake failed while sending client version"
|
||||
end
|
||||
|
||||
status, tmp = self.amqpsocket:recv( 11 )
|
||||
if ( not(status) ) then
|
||||
return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading frame header"
|
||||
end
|
||||
|
||||
-- check if the server rejected our proposed version
|
||||
if ( #tmp ~= 11 ) then
|
||||
if ( #tmp == 8 and select( 2, bin.unpack(">I", tmp) ) == 0x414D5150 ) then
|
||||
local vi, vii, v1, v2, v3, v4, found
|
||||
_, vi = bin.unpack(">I", tmp, 5)
|
||||
found = false
|
||||
|
||||
-- check if we support the server's version
|
||||
for _, v in pairs( self.client_version_strings ) do
|
||||
_, vii = bin.unpack(">I", v)
|
||||
if ( vii == vi ) then
|
||||
version = v
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- try again with new version string
|
||||
if ( found and version ~= self.cli_version ) then
|
||||
self.cli_version = version
|
||||
self:disconnect()
|
||||
status, err = self:connect()
|
||||
|
||||
if ( not(status) ) then
|
||||
return status, err
|
||||
end
|
||||
|
||||
return self:handshake()
|
||||
end
|
||||
|
||||
-- version unsupported
|
||||
_, v1, v2, v3, v4 = bin.unpack(">CCCC", tmp, 5)
|
||||
return false, ("ERROR: AMQP:handshake unsupported version (%d.%d.%d.%d)"):format( v1, v2, v3, v4 )
|
||||
else
|
||||
return false, ("ERROR: AMQP:handshake server might not be AMQP, received: %s"):format( tmp )
|
||||
end
|
||||
end
|
||||
|
||||
-- parse frame header
|
||||
local frametype, chnumber, framesize, method
|
||||
_, frametype, chnumber, framesize, method = bin.unpack(">CSII", tmp)
|
||||
stdnse.print_debug("frametype: %d, chnumber: %d, framesize: %d, method: %d", frametype, chnumber, framesize, method)
|
||||
|
||||
if (frametype ~= 1) then
|
||||
return false, ("ERROR: AQMP:handshake expected header (1) frame, but was %d"):format(frametype)
|
||||
end
|
||||
|
||||
if (method ~= 0x000A000A) then
|
||||
return false, ("ERROR: AQMP:handshake expected connection.start (0x000A000A) method, but was %x"):format(method)
|
||||
end
|
||||
|
||||
-- parse protocol version
|
||||
status, tmp = self.amqpsocket:recv( 2 )
|
||||
if ( not(status) ) then
|
||||
return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading version"
|
||||
end
|
||||
version = select( 2, bin.unpack(">S", tmp) )
|
||||
self.protover = AMQP.versions[version]
|
||||
|
||||
if ( not(self.protover) ) then
|
||||
return false, ("ERROR: AMQP:handshake unsupported version (%x)"):format(version)
|
||||
end
|
||||
|
||||
-- parse server properties
|
||||
status, tmp = self.amqpsocket:recv( 4 )
|
||||
if ( not(status) ) then
|
||||
return status, "ERROR: AMQP:handshake connection closed unexpectedly while reading server properties size"
|
||||
end
|
||||
|
||||
local tablesize = select( 2, bin.unpack(">I", tmp) )
|
||||
properties = {}
|
||||
status, err, properties = self:decodeTable(properties, tablesize)
|
||||
|
||||
if ( not(status) ) then
|
||||
return status, err
|
||||
end
|
||||
|
||||
status, err, value, tmp = self:decodeString("mechanisms", 0)
|
||||
if ( not(status) ) then
|
||||
return status, err
|
||||
end
|
||||
table.insert(properties, "mechanisms: " .. value)
|
||||
|
||||
status, err, value, tmp = self:decodeString("locales", 0)
|
||||
if ( not(status) ) then
|
||||
return status, err
|
||||
end
|
||||
table.insert(properties, "locales: " .. value)
|
||||
|
||||
self.server_properties = properties
|
||||
|
||||
return true
|
||||
end,
|
||||
|
||||
--- Returns the protocol version reported by the server
|
||||
--
|
||||
-- @return string containing the version number
|
||||
getProtocolVersion = function( self )
|
||||
return self.protover
|
||||
end,
|
||||
|
||||
--- Returns the product version reported by the server
|
||||
--
|
||||
-- @return string containing the version number
|
||||
getServerVersion = function( self )
|
||||
return self.server_version
|
||||
end,
|
||||
|
||||
--- Returns the product name reported by the server
|
||||
--
|
||||
-- @return string containing the product name
|
||||
getServerProduct = function( self )
|
||||
return self.server_product
|
||||
end,
|
||||
|
||||
--- Returns the properties reported by the server
|
||||
--
|
||||
-- @return table containing server properties
|
||||
getServerProperties = function( self )
|
||||
return self.server_properties
|
||||
end,
|
||||
}
|
||||
|
||||
AMQPSocket =
|
||||
{
|
||||
retries = 3,
|
||||
|
||||
new = function(self)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.Socket = nmap.new_socket()
|
||||
o.Buffer = nil
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Establishes a connection.
|
||||
--
|
||||
-- @param hostid Hostname or IP address.
|
||||
-- @param port Port number.
|
||||
-- @param protocol <code>"tcp"</code>, <code>"udp"</code>, or
|
||||
-- @return Status (true or false).
|
||||
-- @return Error code (if status is false).
|
||||
connect = function( self, hostid, port, protocol )
|
||||
return self.Socket:connect( hostid, port, protocol )
|
||||
end,
|
||||
|
||||
--- Closes an open connection.
|
||||
--
|
||||
-- @return Status (true or false).
|
||||
-- @return Error code (if status is false).
|
||||
close = function( self )
|
||||
self.Buffer = nil
|
||||
return self.Socket:close()
|
||||
end,
|
||||
|
||||
--- Opposed to the <code>socket:receive_bytes</code> function, that returns
|
||||
-- at least x bytes, this function returns the amount of bytes requested.
|
||||
--
|
||||
-- @param count of bytes to read
|
||||
-- @return true on success, false on failure
|
||||
-- @return data containing bytes read from the socket
|
||||
-- err containing error message if status is false
|
||||
recv = function( self, count )
|
||||
local status, data
|
||||
|
||||
self.Buffer = self.Buffer or ""
|
||||
|
||||
if ( #self.Buffer < count ) then
|
||||
status, data = self.Socket:receive_bytes( count - #self.Buffer )
|
||||
if ( not(status) ) then
|
||||
return false, data
|
||||
end
|
||||
self.Buffer = self.Buffer .. data
|
||||
end
|
||||
|
||||
data = self.Buffer:sub( 1, count )
|
||||
self.Buffer = self.Buffer:sub( count + 1)
|
||||
|
||||
return true, data
|
||||
end,
|
||||
|
||||
--- Sends data over the socket
|
||||
--
|
||||
-- @return Status (true or false).
|
||||
-- @return Error code (if status is false).
|
||||
send = function( self, data )
|
||||
return self.Socket:send( data )
|
||||
end,
|
||||
}
|
||||
61
scripts/amqp-info.nse
Normal file
61
scripts/amqp-info.nse
Normal file
@@ -0,0 +1,61 @@
|
||||
description = [[
|
||||
Gathers information from an AMQP server.
|
||||
It lists all of its server properties.
|
||||
|
||||
See http://www.rabbitmq.com/extensions.html for details on the
|
||||
<code>server-properties</code> field.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap --script amqp-info -p5672 <target>
|
||||
---
|
||||
-- @args amqp.version Can be used to specify the client version to use (currently, 0-8, 0-9 or 0-9-1)
|
||||
--
|
||||
-- @output
|
||||
-- 5672/tcp open amqp
|
||||
-- | amqp-info:
|
||||
-- | capabilities:
|
||||
-- | publisher_confirms: YES
|
||||
-- | exchange_exchange_bindings: YES
|
||||
-- | basic.nack: YES
|
||||
-- | consumer_cancel_notify: YES
|
||||
-- | copyright: Copyright (C) 2007-2011 VMware, Inc.
|
||||
-- | information: Licensed under the MPL. See http://www.rabbitmq.com/
|
||||
-- | platform: Erlang/OTP
|
||||
-- | product: RabbitMQ
|
||||
-- | version: 2.4.0
|
||||
-- | mechanisms: PLAIN AMQPLAIN
|
||||
-- |_ locales: en_US
|
||||
|
||||
author = "Sebastian Dragomir"
|
||||
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
|
||||
categories = {"default", "discovery", "safe"}
|
||||
|
||||
require("stdnse")
|
||||
require("shortport")
|
||||
require("amqp")
|
||||
|
||||
portrule = shortport.port_or_service(5672, "amqp", "tcp", "open")
|
||||
|
||||
action = function(host, port)
|
||||
local cli = amqp.AMQP:new( host.ip, port.number )
|
||||
|
||||
local status, data = cli:connect()
|
||||
if not status then return "Unable to open connection: " .. data end
|
||||
|
||||
status, data = cli:handshake()
|
||||
if not status then return data end
|
||||
|
||||
cli:disconnect()
|
||||
|
||||
port.version.name = "amqp"
|
||||
port.version.product = cli:getServerProduct()
|
||||
port.version.extrainfo = cli:getProtocolVersion()
|
||||
port.version.version = cli:getServerVersion()
|
||||
nmap.set_port_version(host, port, "hardmatched")
|
||||
|
||||
return stdnse.format_output(status, cli:getServerProperties())
|
||||
end
|
||||
@@ -4,6 +4,7 @@ Entry { filename = "afp-ls.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "afp-path-vuln.nse", categories = { "exploit", "intrusive", "vuln", } }
|
||||
Entry { filename = "afp-serverinfo.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "afp-showmount.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "amqp-info.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "asn-query.nse", categories = { "discovery", "external", "safe", } }
|
||||
Entry { filename = "auth-owners.nse", categories = { "default", "safe", } }
|
||||
Entry { filename = "auth-spoof.nse", categories = { "malware", "safe", } }
|
||||
|
||||
Reference in New Issue
Block a user