1
0
mirror of https://github.com/nmap/nmap.git synced 2026-02-07 22:16:33 +00:00

o [NSE] Added a rsync library and two new script:

+ rsync-list-modules - list available rsync modules
  + rsync-brute - attempts to brute force passwords against a rsync module
  [Patrik]
This commit is contained in:
patrik
2012-02-05 10:10:59 +00:00
parent 37d099c4f0
commit 908ac61fb0
5 changed files with 322 additions and 0 deletions

View File

@@ -1,5 +1,10 @@
# Nmap Changelog ($Id$); -*-text-*-
o [NSE] Added a rsync library and two new script:
+ rsync-list-modules - list available rsync modules
+ rsync-brute - attempts to brute force passwords against a rsync module
[Patrik]
o Added --with-apr and --with-subversion configuration options to
support systems where those libraries aren't in the usual places.
[David]

168
nselib/rsync.lua Normal file
View File

@@ -0,0 +1,168 @@
---
-- A minimalist RSYNC library
--
module(... or "rsync",package.seeall)
require 'base64'
require 'match'
stdnse.silent_require 'openssl'
-- The Helper class serves as the main interface for script writers
Helper = {
-- Creates a new instance of the Helper class
-- @param host table as received by the action function
-- @param port table as received by the action function
-- @param options table containing any additional options
-- @return o instance of Helper
new = function(self, host, port, options)
local o = { host = host, port = port, options = options or {} }
assert(o.options.module, "No rsync module was specified, aborting ...")
setmetatable(o, self)
self.__index = self
return o
end,
-- Handles send and receive of control messages
-- @param data string containing the command to send
-- @return status true on succes, false on failure
-- @return data containing the response from the server
-- err string, if status is false
ctrl_exch = function(self, data)
local status, err = self.socket:send(data.."\n")
if ( not(status) ) then
return false, err
end
local status, data = self.socket:receive_buf("\n")
if( not(status) ) then
return false, err
end
return true, data
end,
-- Connects to the rsync server
-- @return status, true on success, false on failure
-- @return err string containing an error message if status is false
connect = function(self)
self.socket = nmap.new_socket()
self.socket:set_timeout(self.options.timeout or 5000)
local status, err = self.socket:connect(self.host, self.port)
if ( not(status) ) then
return false, err
end
local data
status, data = self:ctrl_exch("@RSYNCD: 29")
if ( not(status) ) then
return false, data
end
if ( not(data:match("^@RSYNCD: [%.%d]+$")) ) then
return false, "Protocol error"
end
return true
end,
-- Authenticates against the rsync module. If no username is given, assume
-- no authentication is required.
-- @param username [optional] string containing the username
-- @param password [optional] string containing the password
login = function(self, username, password)
password = password or ""
local status, data = self:ctrl_exch(self.options.module)
if (not(status)) then
return false, data
end
local chall
if ( data:match("@RSYNCD: OK") ) then
return true, "No authentication was required"
else
chall = data:match("^@RSYNCD: AUTHREQD (.*)$")
if ( not(chall) and data:match("^@ERROR: Unknown module") ) then
return false, data:match("^@ERROR: (.*)$")
elseif ( not(chall) ) then
return false, "Failed to retrieve challenge"
end
end
if ( chall and not(username) ) then
return false, "Authentication required"
end
local md4 = openssl.md4("\0\0\0\0" .. password .. chall)
local resp = base64.enc(md4):sub(1,-3)
status, data = self:ctrl_exch(username .. " " .. resp)
if (not(status)) then
return false, data
end
if ( data == "@RSYNCD: OK" ) then
return true, "Authentication successfull"
end
return false, "Authentication failed"
end,
-- Lists accessible modules from the rsync server
-- @return status true on success, false on failure
-- @return modules table containing a list of modules
listModules = function(self)
local status, data = self.socket:send("\n")
if (not(status)) then
return false, data
end
local modules = {}
while(true) do
status, data = self.socket:receive_buf("\n")
if (not(status)) then
return false, data
end
if ( data == "@RSYNCD: EXIT" ) then
break
else
table.insert(modules, data)
end
end
return true, modules
end,
-- Lists the files available for the directory/module
-- TODO: Add support for parsing results, seemed straight forward at
-- first, but wasn't.
listFiles = function(self)
-- list recursively and enable MD4 checksums
local data = ("--server\n--sender\n-rc\n.\n%s\n\n"):format(self.options.module)
local status, data = self.socket:send(data)
if ( not(status) ) then
return false, data
end
status, data = self.socket:receive_bytes(4)
if ( not(status) ) then
return false, data
end
status, data = self.socket:send("\0\0\0\0")
if ( not(status) ) then
return false, data
end
status, data = self.socket:receive_buf(match.numbytes(4))
if ( not(status) ) then
return false, data
end
local pos, len = bin.unpack("<S", data)
status, data = self.socket:receive_buf(match.numbytes(len))
if ( not(status) ) then
return false, data
end
-- Parsing goes here
end,
-- Disconnects from the rsync server
-- @return status true on success, false on failure
disconnect = function(self) return self.socket:close() end,
}

103
scripts/rsync-brute.nse Normal file
View File

@@ -0,0 +1,103 @@
description = [[
Performs brute force password auditing against rsync.
]]
---
-- @usage
-- nmap -p 873 --script rsync-brute --script-args 'rsync-brute.module=www' <ip>
--
-- @output
-- PORT STATE SERVICE REASON
-- 873/tcp open rsync syn-ack
-- | rsync-brute:
-- | Accounts
-- | user1:laptop - Valid credentials
-- | user2:password - Valid credentials
-- | Statistics
-- |_ Performed 1954 guesses in 20 seconds, average tps: 97
--
-- @args rsync-brute.module - the module against which brute forcing should be performed
require 'shortport'
require 'brute'
require 'rsync'
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"brute", "intrusive"}
portrule = shortport.port_or_service(873, "rsync", "tcp")
Driver = {
new = function(self, host, port, options)
local o = { host = host, port = port, options = options }
setmetatable(o, self)
self.__index = self
return o
end,
connect = function(self)
self.helper = rsync.Helper:new(self.host, self.port, self.options)
return self.helper:connect()
end,
login = function(self, username, password)
local status, data = self.helper:login(username, password)
-- retry unless we have an authentication failed error
if( not(status) and data ~= "Authentication failed" ) then
local err = brute.Error:new( data )
err:setRetry( true )
return false, err
elseif ( not(status) ) then
return false, brute.Error:new( "Login failed" )
else
return true, brute.Account:new(username, password, creds.State.VALID)
end
end,
disconnect = function( self )
return self.helper:disconnect()
end
}
local function isModuleValid(host, port, module)
local helper = rsync.Helper:new(host, port, { module = module })
if ( not(helper) ) then
return false, "Failed to create helper"
end
local status, data = helper:connect()
if ( not(status) ) then
return false, "Failed to connect to server"
end
status, data = helper:login()
if ( status and data == "No authentication was required" ) then
return false, data
elseif ( not(status) and data == "Authentication required" ) then
return true
elseif ( not(status) and data == ("Unknown module '%s'"):format(module) ) then
return false, data
end
return false, ("Brute pre-check failed for unknown reason: (%s)"):format(data)
end
action = function(host, port)
local mod = stdnse.get_script_args(SCRIPT_NAME .. ".module")
if ( not(mod) ) then
return "\n ERROR: rsync-brute.module was not supplied"
end
local status, err = isModuleValid(host, port, mod)
if ( not(status) ) then
return ("\n ERROR: %s"):format(err)
end
local engine = brute.Engine:new(Driver, host, port, { module = mod })
engine.options.script_name = SCRIPT_NAME
status, result = engine:start()
return result
end

View File

@@ -0,0 +1,44 @@
description = [[
List modules available for rsync synchronization
]]
---
-- @usage
-- nmap -p 873 --script rsync-list-modules <ip>
--
-- @output
-- PORT STATE SERVICE
-- 873/tcp open rsync
-- | rsync-list-modules:
-- | www www directory
-- | log log directory
-- |_ etc etc directory
--
require 'rsync'
require 'shortport'
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
portrule = shortport.port_or_service(873, "rsync", "tcp")
action = function(host, port)
local helper = rsync.Helper:new(host, port, { module = "" })
if ( not(helper) ) then
return "\n ERROR: Failed to create rsync.Helper"
end
local status, err = helper:connect()
if ( not(status) ) then
return "\n ERROR: Failed to connect to rsync server"
end
local modules = {}
status, modules = helper:listModules()
if ( not(status) ) then
return "\n ERROR: Failed to retrieve a list of modules"
end
return stdnse.format_output(true, modules)
end

View File

@@ -244,6 +244,8 @@ Entry { filename = "riak-http-info.nse", categories = { "discovery", "safe", } }
Entry { filename = "rlogin-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "rmi-dumpregistry.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "rpcinfo.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "rsync-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "rsync-list-modules.nse", categories = { "discovery", "safe", } }
Entry { filename = "rtsp-methods.nse", categories = { "default", "safe", } }
Entry { filename = "rtsp-url-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "servicetags.nse", categories = { "default", "discovery", "safe", } }