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:
@@ -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
168
nselib/rsync.lua
Normal 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
103
scripts/rsync-brute.nse
Normal 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
|
||||
44
scripts/rsync-list-modules.nse
Normal file
44
scripts/rsync-list-modules.nse
Normal 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
|
||||
@@ -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", } }
|
||||
|
||||
Reference in New Issue
Block a user