diff --git a/CHANGELOG b/CHANGELOG index b20ac00ea..c5b4401a9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,11 @@ [NOT YET RELEASED] +o [NSE] Added two new scripts for the MongoDB database from Martin + Holst Swende. mongodb-info gets information like the version number, + memory use, and operating system. mongodb-databases lists the + databases and their size on disk. + o [NSE] Added the new lexmark-config script that lists product information and configuration for Lexmark printers. [Patrik Karlsson] diff --git a/nselib/mongodb.lua b/nselib/mongodb.lua new file mode 100644 index 000000000..ae1fa57fc --- /dev/null +++ b/nselib/mongodb.lua @@ -0,0 +1,632 @@ +--- Library methods for handling MongoDB, creating and parsing packets +-- +-- @author Martin Holst Swende +-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html +-- +-- Version 0.1 + +-- Created 01/13/2010 - v0.1 - created by Martin Holst Swende +module("mongodb", package.seeall) +require("bin") +--require("bson") + +-- Some lazy shortcuts + +local function dbg(str,...) + stdnse.print_debug("MngoDb:"..str, unpack(arg)) +end +--local dbg =stdnse.print_debug + +local err =stdnse.log_error + +---------------------------------------------------------------------- +-- First of all comes a Bson parsing library. This can easily be moved out into a separate library should other +-- services start to use Bson +---------------------------------------------------------------------- +-- Library methods for handling the BSON format +-- +-- For more documentation about the BSON format, +---and more details about it's implementations, check out the +-- python BSON implementation which is available at +-- http://github.com/mongodb/mongo-python-driver/blob/master/pymongo/bson.py +-- and licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0) +-- +-- @author Martin Holst Swende +-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html +-- +-- Version 0.1 + +-- Created 01/13/2010 - v0.1 - created by Martin Holst Swende +--module("bson", package.seeall) +--require("bin") +local function dbg_err(str,...) + stdnse.print_debug("Bson-ERR:"..str, unpack(arg)) +end +--local err =stdnse.log_error + +-- Packs data into nullterminated string +--@param input the string to pack +--@return the packed nullterminated string +local function make_nullterminated_string(input) + return bin.pack("z",input) +end + +--Converts an element (key, value) into bson binary data +--@param key the key name, must *NOT* contain . (period) or start with $ +--@param value, the element value +--@return status : true if ok, false if error +--@return result : the packed binary data OR error message +local function _element_to_bson(key, value) + + --Some constraints-checking + if type(key) ~= 'string' then + return false, "Documents must have only string keys, key was " .. type(key) + end + if key:sub(1,1) == "$" then + return false, "key must not start with $: ".. key + end + if key:find("%.") then + return false, ("key %r must not contain '.'"):format(tostring(key)) + end + + local name =bin.pack("z",key) -- null-terminated string + if type(value) == 'string' then + local cstring = bin.pack("z",value) -- null-terminated string + local length = bin.pack(" 4 * 1024 * 1024 then + return false, "document too large - BSON documents are limited to 4 MB" + end + dbg("Packet length is %d",length) + --Final pack + return true, bin.pack("I", length) .. elements .. bin.pack('H',"00") +end + +-- Reads a null-terminated string. If length is supplied, it is just cut +-- out from the data, otherwise the data is scanned for at null-char. +--@param data the data which starts with a c-string +--@param length optional length of the string +--@return the string +--@return the remaining data (*without* null-char) +local function get_c_string(data,length) + if not length then + local index = data:find(string.char(0)) + if index == nil then + error({code="C-string did not contain NULL char"}) + end + length = index + end + local value = data:sub(1,length-1) + + --dbg("Found char at pos %d, data is %s c-string is %s",length, data, value) + + return value, data:sub(length+1) +end + +-- Element parser. Parse data elements +-- @param data String containing binary data +-- @return Position in the data string where parsing stopped +-- @return Unpacked value +-- @return error string if error occurred +local function parse(code,data) + if 1 == code then -- double + return bin.unpack(" + local value = get_c_string(data:sub(5), len) + -- Count position as header (=4) + length of string (=len)+ null char (=1) + return 4+len+1,value + elseif 3 == code or 4 == code then -- table or array + local object, err + + -- Need to know the length, to return later + local _,obj_size = bin.unpack(" 1 do + key, value, data = _element_to_dict(data) + dbg("Parsed (%s='%s'), data left : %d", tostring(key),tostring(value), data:len()) + if type(value) ~= 'table' then value=tostring(value) end + result[key] = value + end + return result +end + +--Checks if enough data to parse the result is captured +--@data binary bson data read from socket +--@return true if the full bson table is contained in the data, false if data is incomplete +--@return required size of packet, if known, otherwise nil +function isPacketComplete(data) + -- First, we check that the header is complete + if data:len() < 4 then + local err_msg = "Not enough data in buffer, at least 4 bytes header info expected" + return false + end + + local _,obj_size = bin.unpack("stdnse.format_output +function queryResultToTable( resultTable ) + + local result = {} + for k,v in pairs( resultTable ) do + if type(v) == 'table' then + table.insert(result,k) + table.insert(result,queryResultToTable(v)) + else + table.insert(result,(("%s = %s"):format(tostring(k), tostring(v)))) + end + end + return result + +end +---------------------------------------------------------------------------------- +-- Test-code for debugging purposes below +---------------------------------------------------------------------------------- + + +--- Prints data (string) as Hex values, e.g so it more easily can +-- be compared with a packet dump +-- @param strData the data in a string format + +local function printBuffer(strData) + local out = '' + local ch + for i = 1,strData:len() do + out = out .." " + ch =strData:byte(i) + if(ch < 16) then + ch = string.format("0%x",ch) + else ch = string.format("%x",ch) + end + --if ch > 64 and ch < 123 then + -- out = out .. string.char(ch) + --else + out = out .. ch + --end + end + print(out) +end + +function test() + local res + res = versionQuery() + print(type(res),res:len(),res) + local out= bin.unpack('C'..string.len(res),res) + printBuffer(res) +end +--test() + diff --git a/scripts/mongodb-databases.nse b/scripts/mongodb-databases.nse new file mode 100644 index 000000000..4a46b16b2 --- /dev/null +++ b/scripts/mongodb-databases.nse @@ -0,0 +1,74 @@ +description = [[ +Attempts to get tables from a MongoDB +]] + +--- +-- @usage +-- nmap -p 27017 --script mongodb-get-tables-query +-- @output +-- PORT STATE SERVICE REASON +-- 27017/tcp open unknown syn-ack +-- | mongodb-get-tables-query: +-- | ok = 1 +-- | databases +-- | 1 +-- | empty = false +-- | sizeOnDisk = 83886080 +-- | name = test +-- | 0 +-- | empty = false +-- | sizeOnDisk = 83886080 +-- | name = httpstorage +-- | 3 +-- | empty = true +-- | sizeOnDisk = 1 +-- | name = local +-- | 2 +-- | empty = true +-- | sizeOnDisk = 1 +-- | name = admin +-- |_ totalSize = 167772160 +--@version 0.1 +-- Created 01/12/2010 - v0.1 - created by Martin Holst Swende + + +author = "Martin Holst Swende" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} + +require "mongodb" +require "shortport" + +portrule = shortport.port_or_service({27017}, {"mongodb"}) +function action(host,port) + + local socket = nmap.new_socket() + + -- set a reasonable timeout value + socket:set_timeout(10000) + -- do some exception / cleanup + local catch = function() + socket:close() + end + + local try = nmap.new_try(catch) + + try( socket:connect(host.ip, port.number, "tcp") ) + + local req, result, packet, err, status + --Build packet + status, packet = mongodb.listDbQuery() + if not status then return result end-- Error message + + --- Send packet + status, result = mongodb.query(socket, packet) + if not status then return result end-- Error message + + local output = mongodb.queryResultToTable(result) + if err ~= nil then + stdnse.log_error(err) + end + if result ~= nil then + return stdnse.format_output(true, output ) + end +end \ No newline at end of file diff --git a/scripts/mongodb-info.nse b/scripts/mongodb-info.nse new file mode 100644 index 000000000..47cf81897 --- /dev/null +++ b/scripts/mongodb-info.nse @@ -0,0 +1,94 @@ +description = [[ +Attempts to get build info and server status from a MongoDB +]] + +--- +-- @usage +-- nmap -p 27017 --script mongodb-info +-- @output +-- PORT STATE SERVICE REASON +-- 27017/tcp open unknown syn-ack +-- | mongodb-info: +-- | MongoDB Build info +-- | ok = 1 +-- | bits = 64 +-- | version = 1.3.1- +-- | gitVersion = d1f0ffe23bcd667f4ed18a27b5fd31a0beab5535 +-- | sysInfo = Linux domU-12-31-39-06-79-A1 2.6.21.7-2.ec2.v1.2.fc8xen #1 SMP Fri Nov 20 17:48:28 EST 2009 x86_64 BOOST_LIB_VERSION=1_41 +-- | Server status +-- | opcounters +-- | delete = 0 +-- | insert = 3 +-- | getmore = 0 +-- | update = 0 +-- | query = 10 +-- | connections +-- | available = 19999 +-- | current = 1 +-- | uptime = 747 +-- | mem +-- | resident = 9 +-- | virtual = 210 +-- | supported = true +-- | mapped = 80 +-- | ok = 1 +-- | globalLock +-- | ratio = 0.010762343463949 +-- | lockTime = 8037112 +-- | totalTime = 746780850 +-- | extra_info +-- | heap_usage_bytes = 117120 +-- | note = fields vary by platform +-- |_ page_faults = 0 +--@version 0.2 +-- Created 01/12/2010 - v0.1 - created by Martin Holst Swende + + +author = "Martin Holst Swende" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} + +require "mongodb" +require "shortport" + +portrule = shortport.port_or_service({27017}, {"mongodb"}) +function action(host,port) + + local socket = nmap.new_socket() + + -- set a reasonable timeout value + socket:set_timeout(10000) + -- do some exception / cleanup + local catch = function() + socket:close() + end + + local try = nmap.new_try(catch) + + try( socket:connect(host.ip, port.number, "tcp") ) + + local req, status, statusresponse, buildinfo, packet, err + + status, packet = mongodb.serverStatusQuery() + if not status then return packet end + + status,statQResult = mongodb.query(socket, packet) + + if not status then return statResult end + + status, packet = mongodb.buildInfoQuery() + if not status then return packet end + + status, buildQResult = mongodb.query(socket,packet ) + + if not status then + stdnse.log_error(buildQResult) + return buildQResult + end + + local stat_out = mongodb.queryResultToTable(statQResult) + local build_out = mongodb.queryResultToTable(buildQResult) + local output = {"MongoDB Build info",build_out,"Server status",stat_out} + + return stdnse.format_output(true, output ) +end \ No newline at end of file