summaryrefslogtreecommitdiffstats
path: root/scripts/svn-brute.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/svn-brute.nse')
-rw-r--r--scripts/svn-brute.nse273
1 files changed, 273 insertions, 0 deletions
diff --git a/scripts/svn-brute.nse b/scripts/svn-brute.nse
new file mode 100644
index 0000000..4c0aaf8
--- /dev/null
+++ b/scripts/svn-brute.nse
@@ -0,0 +1,273 @@
+local brute = require "brute"
+local creds = require "creds"
+local nmap = require "nmap"
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local stringaux = require "stringaux"
+local openssl = stdnse.silent_require "openssl"
+
+description = [[
+Performs brute force password auditing against Subversion source code control servers.
+]]
+
+---
+-- @usage
+-- nmap --script svn-brute --script-args svn-brute.repo=/svn/ -p 3690 <host>
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 3690/tcp open svn syn-ack
+-- | svn-brute:
+-- | Accounts
+-- |_ patrik:secret => Login correct
+--
+-- Summary
+-- -------
+-- x The svn class contains the code needed to perform CRAM-MD5
+-- authentication
+-- x The Driver class contains the driver implementation used by the brute
+-- library
+--
+-- @args svn-brute.repo the Subversion repository against which to perform
+-- password guessing
+-- @args svn-brute.force force password guessing when service is accessible
+-- both anonymously and through authentication
+
+--
+-- Version 0.1
+-- Created 07/12/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
+--
+
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+categories = {"intrusive", "brute"}
+
+portrule = shortport.port_or_service(3690, "svnserve", "tcp", "open")
+
+svn =
+{
+ svn_client = "nmap-brute v0.1",
+
+ new = function(self, host, port, repo)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.host = host
+ o.port = port
+ o.repo = repo
+ o.invalid_users = {}
+ return o
+ end,
+
+ --- Connects to the SVN - repository
+ --
+ -- @return status true on success, false on failure
+ -- @return err string containing an error message on failure
+ connect = function(self)
+ local repo_url = ( "svn://%s/%s" ):format(self.host.ip, self.repo)
+ local status, msg
+
+ self.socket = brute.new_socket()
+
+ local result
+ status, result = self.socket:connect(self.host, self.port)
+ if( not(status) ) then
+ return false, result
+ end
+
+ status, msg = self.socket:receive_bytes(1)
+ if ( not(status) or not( msg:match("^%( success") ) ) then
+ return false, "Banner reports failure"
+ end
+
+ msg = ("( 2 ( edit-pipeline svndiff1 absent-entries depth mergeinfo log-revprops ) %d:%s %d:%s ( ) ) "):format( #repo_url, repo_url, #self.svn_client, self.svn_client )
+ status = self.socket:send( msg )
+ if ( not(status) ) then
+ return false, "Send failed"
+ end
+
+ status, msg = self.socket:receive_bytes(1)
+ if ( not(status) ) then
+ return false, "Receive failed"
+ end
+
+ if ( msg:match("%( success") ) then
+ local tmp = msg:match("%( success %( %( ([%S+%s*]-) %)")
+ if ( not(tmp) ) then return false, "Failed to detect authentication" end
+ tmp = stringaux.strsplit(" ", tmp)
+ self.auth_mech = {}
+ for _, v in pairs(tmp) do self.auth_mech[v] = true end
+ elseif ( msg:match("%( failure") ) then
+ return false
+ end
+
+ return true
+ end,
+
+ --- Attempts to login to the SVN server
+ --
+ -- @param username string containing the login username
+ -- @param password string containing the login password
+ -- @return status, true on success, false on failure
+ -- @return err string containing error message on failure
+ login = function( self, username, password )
+ local status, msg
+ local challenge, digest
+
+ if ( self.auth_mech["CRAM-MD5"] ) then
+ msg = "( CRAM-MD5 ( ) ) "
+ status = self.socket:send( msg )
+
+ status, msg = self.socket:receive_bytes(1)
+ if ( not(status) ) then
+ return false, "error"
+ end
+
+ challenge = msg:match("<.+>")
+
+ if ( not(challenge) ) then
+ return false, "Failed to read challenge"
+ end
+
+ digest = stdnse.tohex(openssl.hmac('md5', password, challenge))
+ msg = ("%d:%s %s "):format(#username + 1 + #digest, username, digest)
+ self.socket:send( msg )
+
+ status, msg = self.socket:receive_bytes(1)
+ if ( not(status) ) then
+ return false, "error"
+ end
+
+ if ( msg:match("Username not found") ) then
+ return false, "Username not found"
+ elseif ( msg:match("success") ) then
+ return true, "Authentication success"
+ else
+ return false, "Authentication failed"
+ end
+ else
+ return false, "Unsupported auth-mechanism"
+ end
+
+ end,
+
+ --- Close the SVN connection
+ --
+ -- @return status true on success, false on failure
+ close = function(self)
+ return self.socket:close()
+ end,
+
+}
+
+
+Driver =
+{
+ new = function(self, host, port, invalid_users )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.host = host
+ o.port = port
+ o.repo = stdnse.get_script_args('svn-brute.repo')
+ o.invalid_users = invalid_users
+ return o
+ end,
+
+ connect = function( self )
+ local status, msg
+
+ self.svn = svn:new( self.host, self.port, self.repo )
+ status, msg = self.svn:connect()
+ if ( not(status) ) then
+ local err = brute.Error:new( "Failed to connect to SVN server" )
+ -- This might be temporary, set the retry flag
+ err:setRetry( true )
+ return false, err
+ end
+
+ return true
+ end,
+
+ disconnect = function( self )
+ self.svn:close()
+ end,
+
+ --- Attempts to login to the SVN server
+ --
+ -- @param username string containing the login username
+ -- @param password string containing the login password
+ -- @return status, true on success, false on failure
+ -- @return brute.Error object on failure
+ -- creds.Account object on success
+ login = function( self, username, password )
+ local status, msg
+
+ if ( self.invalid_users[username] ) then
+ return false, brute.Error:new( "User is invalid" )
+ end
+
+ status, msg = self.svn:login( username, password )
+
+ if ( not(status) and msg:match("Username not found") ) then
+ self.invalid_users[username] = true
+ return false, brute.Error:new("Username not found")
+ elseif ( status and msg:match("success") ) then
+ return true, creds.Account:new(username, password, creds.State.VALID)
+ else
+ return false, brute.Error:new( "Incorrect password" )
+ end
+ end,
+
+ --- Verifies whether the repository is valid
+ --
+ -- @return status, true on success, false on failure
+ -- @return err string containing an error message on failure
+ check = function( self )
+ local svn = svn:new( self.host, self.port, self.repo )
+ local status = svn:connect()
+
+ svn:close()
+
+ if ( status ) then
+ return true
+ else
+ return false, ("Failed to connect to SVN repository (%s)"):format(self.repo)
+ end
+ end,
+}
+
+
+
+action = function(host, port)
+ local status, accounts
+
+ local repo = stdnse.get_script_args('svn-brute.repo')
+ local force = stdnse.get_script_args('svn-brute.force')
+
+ if ( not(repo) ) then
+ return "No repository specified (see svn-brute.repo)"
+ end
+
+ local svn = svn:new( host, port, repo )
+ local status = svn:connect()
+
+ if ( status and svn.auth_mech["ANONYMOUS"] and not(force) ) then
+ return " \n Anonymous SVN detected, no authentication needed"
+ end
+
+ if ( not(svn.auth_mech) or not( svn.auth_mech["CRAM-MD5"] ) ) then
+ return " \n No supported authentication mechanisms detected"
+ end
+
+ local invalid_users = {}
+ local engine = brute.Engine:new(Driver, host, port, invalid_users)
+ engine.options.script_name = SCRIPT_NAME
+ status, accounts = engine:start()
+ if( not(status) ) then
+ return accounts
+ end
+
+ return accounts
+end