summaryrefslogtreecommitdiffstats
path: root/scripts/nping-brute.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/nping-brute.nse')
-rw-r--r--scripts/nping-brute.nse151
1 files changed, 151 insertions, 0 deletions
diff --git a/scripts/nping-brute.nse b/scripts/nping-brute.nse
new file mode 100644
index 0000000..89577f9
--- /dev/null
+++ b/scripts/nping-brute.nse
@@ -0,0 +1,151 @@
+local brute = require "brute"
+local creds = require "creds"
+local nmap = require "nmap"
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local string = require "string"
+
+local openssl = stdnse.silent_require "openssl"
+
+description = [[
+Performs brute force password auditing against an Nping Echo service.
+
+See https://nmap.org/book/nping-man-echo-mode.html for Echo Mode
+documentation.
+]]
+
+---
+-- @usage
+-- nmap -p 9929 --script nping-brute <target>
+--
+-- @output
+-- 9929/tcp open nping-echo
+-- | nping-brute:
+-- | Accounts
+-- | 123abc => Valid credentials
+-- | Statistics
+-- |_ Perfomed 204 guesses in 204 seconds, average tps: 1
+
+author = "Toni Ruottu"
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+categories = {"brute", "intrusive"}
+
+
+portrule = shortport.port_or_service(9929, "nping-echo")
+
+local function readmessage(socket, length)
+ local msg = ""
+ while #msg < length do
+ local status, tmp = socket:receive_bytes(1)
+ if not status then
+ return nil
+ end
+ msg = msg .. tmp
+ end
+ return msg
+end
+
+Driver =
+{
+ NEP_VERSION = 0x01,
+ AES_128_CBC = "aes-128-cbc",
+ SHA256 = "sha256",
+
+ new = function(self, host, port)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.host = host
+ o.port = port
+ return o
+ end,
+
+ nepkey = function(self, password, nonce, typeid)
+ local seed = password .. nonce .. typeid
+ local h = openssl.digest(self.SHA256, seed)
+ for i = 1, 1000 do
+ h = openssl.digest(self.SHA256, h)
+ end
+ return string.unpack("c16", h)
+ end,
+
+ getservernonce = function(self, serverhs)
+ local offset = 63 -- 63 bytes of header before the nonce
+ return serverhs:sub(offset+1, offset+4)
+ end,
+
+ chsbody = function(self)
+ local IP4 = "\x04"
+ local IP6 = "\x06"
+ local family = IP6
+ local target = self.host.bin_ip
+ if #target == 4 then
+ target = target .. ("\0"):rep(12)
+ family = IP4
+ end
+ return target .. family .. ("\0"):rep(15)
+ end,
+
+ clienths = function(self, snonce, password)
+ local NEP_HANDSHAKE_CLIENT = 0x02
+ local NEP_HANDSHAKE_CLIENT_LEN = 36
+ local NEP_CLIENT_CIPHER_ID = "NEPkeyforCiphertextClient2Server"
+ local NEP_CLIENT_MAC_ID = "NEPkeyforMACClient2Server"
+
+ local now = nmap.clock()
+ local seqb = openssl.rand_bytes(4)
+ local cnonce = openssl.rand_bytes(32)
+ local nonce = snonce .. cnonce
+ local enckey = self:nepkey(password, nonce, NEP_CLIENT_CIPHER_ID)
+ local mackey = self:nepkey(password, nonce, NEP_CLIENT_MAC_ID)
+ local iv = string.unpack("c16", cnonce)
+ local plain = self:chsbody()
+ local crypted = openssl.encrypt(self.AES_128_CBC, enckey, iv, plain)
+ local head = string.pack(">BB I2 c4 I4 x4", self.NEP_VERSION, NEP_HANDSHAKE_CLIENT, NEP_HANDSHAKE_CLIENT_LEN, seqb, now) .. nonce
+ local mac = openssl.hmac(self.SHA256, mackey, head .. plain)
+
+ return head .. crypted .. mac
+ end,
+
+ testpass = function(self, password)
+ local SERVERHS_LEN = 96
+ local FINALHS_LEN = 112
+ local serverhs = readmessage(self.socket, SERVERHS_LEN)
+ if serverhs == nil then
+ return false
+ end
+ local snonce = self:getservernonce(serverhs)
+ local response = self:clienths(snonce, password)
+ self.socket:send(response)
+ local finalhs = readmessage(self.socket, FINALHS_LEN)
+ if finalhs == nil then
+ return false
+ end
+ return true
+ end,
+
+ connect = function(self)
+ self.socket = brute.new_socket()
+ return self.socket:connect(self.host, self.port)
+ end,
+
+ login = function(self, _, password)
+ if self:testpass(password) then
+ return true, creds.Account:new("", password, creds.State.VALID)
+ end
+ return false, brute.Error:new("Incorrect password")
+ end,
+
+ disconnect = function(self)
+ return self.socket:close()
+ end,
+}
+
+action = function(host, port)
+ local engine = brute.Engine:new(Driver, host, port)
+ engine.options.firstonly = true
+ engine.options:setOption("passonly", true)
+ engine.options.script_name = SCRIPT_NAME
+ local status, result = engine:start()
+ return result
+end