1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 04:31:29 +00:00

Add cassandra scripts from Vlatko Kosturjak.

This commit is contained in:
david
2012-09-20 06:30:48 +00:00
parent 00cc96ba95
commit 8f39b485a0
5 changed files with 439 additions and 0 deletions

View File

@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*-
o [NSE] Added cassandra-brute and cassandra-info by Vlatko Kosturjak,
scripts for the Apache Cassandra database.
o [NSE] Added ipv6-ra-flood script by Adam Števko. This script sends a
flood of router advertisements, which can DoS certain operating
systems including Windows.

210
nselib/cassandra.lua Normal file
View File

@@ -0,0 +1,210 @@
---
-- Library methods for handling Cassandra Thrift communication as client
--
-- @author Vlatko Kosturjak
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
--
-- Version 0.1
--
local bin = require "bin"
local nmap = require "nmap"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
_ENV = stdnse.module("cassandra", stdnse.seeall)
--[[
Cassandra Thrift protocol implementation.
For more information about Cassandra, see:
http://cassandra.apache.org/
]]--
-- Protocol magic strings
CASSANDRAREQ = string.char(0x80,0x01,0x00,0x01)
CASSANDRARESP = string.char(0x80,0x01,0x00,0x02)
CASSLOGINMAGIC = string.char(0x00, 0x00,0x00,0x01,0x0c,0x00,0x01,0x0d,0x00,0x01,0x0b,0x0b,0x00,0x00,0x00,0x02)
LOGINSUCC = string.char(0x00,0x00,0x00,0x01,0x00)
LOGINFAIL = string.char(0x00,0x00,0x00,0x01,0x0b)
LOGINACC = string.char(0x00,0x00,0x00,0x01,0x0c)
--Returns string in format length+string itself
--@param str to format
--@return str : string in format length+string itself
function pack4str (str)
return (bin.pack(">I",string.len(str)) .. str)
end
--Returns string in cassandra format for login
--@param username to put in format
--@param password to put in format
--@return str : string in cassandra format for login
function loginstr (username, password)
local str = CASSANDRAREQ .. pack4str ("login")
str = str .. CASSLOGINMAGIC
str = str .. pack4str("username")
str = str .. pack4str(username)
str = str .. pack4str("password")
str = str .. pack4str(password)
str = str .. string.char (0x00, 0x00) -- add two null on the end
return str
end
--Invokes command over socket and returns the response
--@param socket to connect to
--@param command to invoke
--@param cnt is protocol count
--@return status : true if ok; false if bad
--@return result : value if status ok, error msg if bad
function cmdstr (command,cnt)
local str = CASSANDRAREQ .. pack4str (command)
str = str .. bin.pack(">I",cnt)
str = str .. string.char (0x00) -- add null on the end
return str
end
--Invokes command over socket and returns the response
--@param socket to connect to
--@param command to invoke
--@param cnt is protocol count
--@return status : true if ok; false if bad
--@return result : value if status ok, error msg if bad
function sendcmd (socket, command, cnt)
local cmdstr = cmdstr (command,cnt)
local response
local status, err = socket:send(bin.pack(">I",string.len(cmdstr)))
if ( not(status) ) then
return false, "error sending packet length"
end
status, err = socket:send(cmdstr)
if ( not(status) ) then
return false, "error sending packet payload"
end
status, response = socket:receive_bytes(4)
if ( not(status) ) then
return false, "error receiving length"
end
_,size = bin.unpack(">I",response,1)
if (string.len(response) < size+4 ) then
status, resp2 = socket:receive_bytes(size+4 - string.len(response))
if ( not(status) ) then
return false, "error receiving payload"
end
response = response .. resp2
end
-- magic response starts at 5th byte for 4 bytes, 4 byte for length + length of string commmand
if (string.sub(response,5,8+4+string.len(command)) ~= CASSANDRARESP..pack4str(command)) then
return false, "protocol response error"
end
return true, response
end
--Return Cluster Name
--@param socket to connect to
--@param cnt is protocol count
--@return status : true if ok; false if bad
--@return result : value if status ok, error msg if bad
function describe_cluster_name (socket,cnt)
local cname = "describe_cluster_name"
local size
local status,resp = sendcmd(socket,cname,cnt)
if (not(status)) then
stdnse.print_debug(1, "sendcmd"..resp)
return false, "error in communication"
end
-- grab the size
-- pktlen(4) + CASSANDRARESP(4) + lencmd(4) + lencmd(v) + params(7) + next byte position
position = 12+string.len(cname)+7+1
_,size = bin.unpack(">I",resp,position)
-- read the string after the size
local value = string.sub(resp,position+4,position+4+size-1)
return true, value
end
--Return API version
--@param socket to connect to
--@param cnt is protocol count
--@return status : true if ok; false if bad
--@return result : value if status ok, error msg if bad
function describe_version (socket,cnt)
local cname = "describe_version"
local size
local status,resp = sendcmd(socket,cname,cnt)
if (not(status)) then
stdnse.print_debug(1, "sendcmd"..resp)
return false, "error in communication"
end
-- grab the size
-- pktlen(4) + CASSANDRARESP(4) + lencmd(4) + lencmd(v) + params(7) + next byte position
position = 12+string.len(cname)+7+1
_,size = bin.unpack(">I",resp,position)
-- read the string after the size
local value = string.sub(resp,position+4,position+4+size-1)
return true, value
end
--Login to Cassandra
--@param socket to connect to
--@param username to connect to
--@param password to connect to
--@return status : true if ok; false if bad
--@return result : table of status ok, error msg if bad
--@return if status ok : remaining data read from socket but not used
function login (socket,username,password)
local loginstr = loginstr (username, password)
local combo = username..":"..password
local status, err = socket:send(bin.pack(">I",string.len(loginstr)))
if ( not(status) ) then
stdnse.print_debug(3, "cannot send len "..combo)
return false, "Failed to connect to server"
end
status, err = socket:send(loginstr)
if ( not(status) ) then
stdnse.print_debug(3, "Sent packet for "..combo)
return false, err
end
status, response = socket:receive_bytes(22)
if ( not(status) ) then
stdnse.print_debug(3, "Receive packet for "..combo)
return false, err
end
_, size = bin.unpack(">I", response, 1)
loginresp = string.sub(response,5,17)
if (loginresp ~= CASSANDRARESP..pack4str("login")) then
return false, "protocol error"
end
magic = string.sub(response,18,22)
stdnse.print_debug(3, "packet for "..combo)
stdnse.print_debug(3, "packet hex: %s", stdnse.tohex(response) )
stdnse.print_debug(3, "size packet hex: %s", stdnse.tohex(size) )
stdnse.print_debug(3, "magic packet hex: %s", stdnse.tohex(magic) )
if (magic == LOGINSUCC) then
return true
else
return false, "Login failed."
end
end
return _ENV;

131
scripts/cassandra-brute.nse Normal file
View File

@@ -0,0 +1,131 @@
local brute = require "brute"
local creds = require "creds"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local cassandra = require "cassandra"
description = [[
Performs brute force password auditing against the Cassandra database.
For more information about Cassandra, see:
http://cassandra.apache.org/
]]
---
-- @usage
-- nmap -p 9160 <ip> --script=cassandra-brute
--
-- @output
-- PORT STATE SERVICE VERSION
-- 9160/tcp open apani1?
-- | cassandra-brute:
-- | Accounts
-- | admin:lover - Valid credentials
-- | Statistics
-- |_ Performed 4581 guesses in 1 seconds, average tps: 4581
--
author = "Vlatko Kosturjak"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive", "brute"}
portrule = shortport.port_or_service({9160}, {"cassandra"})
Driver = {
new = function(self, host, port, options)
local o = { host = host, port = port, socket = nmap.new_socket() }
setmetatable(o, self)
self.__index = self
return o
end,
connect = function(self)
return self.socket:connect(self.host, self.port)
end,
-- bit faster login function than in cassandra library (no protocol error checks)
login = function(self, username, password)
local response, magic, size
local loginstr = cassandra.loginstr (username, password)
local status, err = self.socket:send(bin.pack(">I",string.len(loginstr)))
local combo = username..":"..password
if ( not(status) ) then
local err = brute.Error:new( "couldn't send length:"..combo )
err:setAbort( true )
return false, err
end
status, err = self.socket:send(loginstr)
if ( not(status) ) then
local err = brute.Error:new( "couldn't send login packet: "..combo )
err:setAbort( true )
return false, err
end
status, response = self.socket:receive_bytes(22)
if ( not(status) ) then
local err = brute.Error:new( "couldn't receive login reply size: "..combo )
err:setAbort( true )
return false, err
end
_, size = bin.unpack(">I", response, 1)
magic = string.sub(response,18,22)
if (magic == cassandra.LOGINSUCC) then
stdnse.print_debug(3, "Account SUCCESS: "..combo)
return true, brute.Account:new(username, password, creds.State.VALID)
elseif (magic == cassandra.LOGINFAIL) then
stdnse.print_debug(3,"Account FAIL: "..combo)
return false, brute.Error:new( "Incorrect password" )
elseif (magic == cassandra.LOGINACC) then
stdnse.print_debug(3, "Account VALID, but wrong password: "..combo)
return false, brute.Error:new( "Good user, bad password" )
else
stdnse.print_debug(3, "Unrecognized packet for "..combo)
stdnse.print_debug(3, "packet hex: %s", stdnse.tohex(response) )
stdnse.print_debug(3, "size packet hex: %s", stdnse.tohex(size) )
stdnse.print_debug(3, "magic packet hex: %s", stdnse.tohex(magic) )
local err = brute.Error:new( response )
err:setRetry( true )
return false, err
end
end,
disconnect = function(self)
return self.socket:close()
end,
}
local function noAuth(host, port)
local socket = nmap.new_socket()
local status, result = socket:connect(host, port)
local stat,err = cassandra.login (socket,"default","")
socket:close()
if (stat) then
return true
else
return false
end
end
action = function(host, port)
if ( noAuth(host, port) ) then
return "Any username and password would do, 'default' was used to test."
end
local engine = brute.Engine:new(Driver, host, port )
engine.options.script_name = SCRIPT_NAME
engine.options.firstonly = true
local status, result = engine:start()
return result
end

View File

@@ -0,0 +1,93 @@
local creds = require "creds"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local bin = require "bin"
local string = require "string"
local cassandra = stdnse.silent_require "cassandra"
description = [[
Attempts to get basic info and server status from a Cassandra database.
For more information about Cassandra, see:
http://cassandra.apache.org/
]]
---
-- @usage
-- nmap -p 9160 <ip> --script=cassandra-info
--
-- @output
-- PORT STATE SERVICE REASON
-- 9160/tcp open cassandra syn-ack
-- | cassandra-info:
-- | Cluster name: Test Cluster
-- |_ Version: 19.10.0
--
-- version 0.1
-- Created 14/09/2012 - v0.1 - created by Vlatko Kosturjak <kost@linux.hr>
author = "Vlatko Kosturjak"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe"}
dependencies = {"cassandra-brute"}
portrule = shortport.port_or_service({9160}, {"cassandra"})
function action(host,port)
local socket = nmap.new_socket()
local cassinc = 2 -- cmd/resp starts at 2
-- 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, port) )
local results = {}
-- uglyness to allow creds.cassandra to work, as the port is not recognized
-- as cassandra even when service scan was run, taken from mongodb
local ps = port.service
port.service = 'cassandra'
local c = creds.Credentials:new(creds.ALL_DATA, host, port)
for cred in c:getCredentials(creds.State.VALID + creds.State.PARAM) do
local status, err = cassandra.login(socket, cred.user, cred.pass)
table.insert(results, ("Using credentials: %s"):format(cred.user.."/"..cred.pass))
if ( not(status) ) then
return err
end
end
port.service = ps
local status, val = cassandra.describe_cluster_name(socket,cassinc)
if (not(status)) then
return "Error getting cluster name: " .. val
end
cassinc = cassinc + 1
port.version.name ='cassandra'
port.version.product='Cassandra'
port.version.name_confidence = 100
nmap.set_port_version(host,port)
table.insert(results, ("Cluster name: %s"):format(val))
local status, val = cassandra.describe_version(socket,cassinc)
if (not(status)) then
return "Error getting version: " .. val
end
cassinc = cassinc + 1
port.version.product='Cassandra ('..val..')'
nmap.set_port_version(host,port)
table.insert(results, ("Version: %s"):format(val))
return stdnse.format_output(true, results)
end

View File

@@ -52,6 +52,8 @@ Entry { filename = "broadcast-wake-on-lan.nse", categories = { "broadcast", "saf
Entry { filename = "broadcast-wpad-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-wsdd-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-xdmcp-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "cassandra-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "cassandra-info.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "cccam-version.nse", categories = { "version", } }
Entry { filename = "citrix-brute-xml.nse", categories = { "auth", "intrusive", } }
Entry { filename = "citrix-enum-apps-xml.nse", categories = { "discovery", "safe", } }