description = [[ Performs brute force password auditing against Mikrotik RouterOS devices with the API RouterOS interface enabled. Additional information: * http://wiki.mikrotik.com/wiki/API ]] --- -- @usage -- nmap -p8728 --script mikrotik-routeros-brute -- -- @output -- PORT STATE SERVICE REASON -- 8728/tcp open unknown syn-ack -- | mikrotik-routeros-brute: -- | Accounts -- | admin:dOsmyvsvJGA967eanX - Valid credentials -- | Statistics -- |_ Performed 60 guesses in 602 seconds, average tps: 0 -- -- @args mikrotik-routerous-brute.threads sets the number of threads. Default: 1 -- --- author = "Paulino Calderon " license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"intrusive", "brute"} local shortport = require "shortport" local bin = require "bin" local brute = require "brute" local creds = require "creds" local nmap = require "nmap" local stdnse = require "stdnse" local string = require "string" local openssl = stdnse.silent_require "openssl" portrule = shortport.portnumber(8728, "tcp") Driver = { new = function(self, host, port, options ) local o = { host = host, port = port, options = options } setmetatable(o, self) self.__index = self o.emptypass = true return o end, connect = function( self ) self.s = nmap.new_socket("tcp") self.s:set_timeout(self.options['timeout']) return self.s:connect(self.host, self.port, "tcp") end, login = function( self, username, password ) local status, data, try data = bin.pack("cAx", 0x6,"/login") --Connect to service and obtain the challenge response try = nmap.new_try(function() return false end) try(self.s:send(data)) data = try(self.s:receive_bytes(50)) stdnse.debug1("Response #1:%s", data) local _, _, ret = string.find(data, '!done%%=ret=(.+)') --If we find the challenge value we continue the connection process if ret then stdnse.debug1("Challenge value found:%s", ret) local md5str = bin.pack("xAA", password, bin.pack("H", ret)) --appends pwd and challenge local chksum = stdnse.tohex(openssl.md5(md5str)) local user_l = username:len()+6 --we add six because of the string "=name=" local login_pkt = bin.pack("cAcAcAx", 0x6, "/login", user_l, "=name="..username, 0x2c, "=response=00"..chksum) try(self.s:send(login_pkt)) data = try(self.s:receive_bytes(50)) stdnse.debug1("Response #2:%s", data) if data then if string.find(data, "message=cannot") == nil then local c = creds.Credentials:new(SCRIPT_NAME, self.host, self.port ) c:add(username, password, creds.State.VALID ) end end end return false, brute.Error:new( "Incorrect password" ) end, disconnect = function( self ) return self.s:close() end } action = function(host, port) local thread_num = stdnse.get_script_args(SCRIPT_NAME..".threads") or 1 local options = {timeout = 5000} local bengine = brute.Engine:new(Driver, host, port, options) bengine:setMaxThreads(thread_num) bengine.options.script_name = SCRIPT_NAME local _, result = bengine:start() return result end