mirror of
https://github.com/nmap/nmap.git
synced 2025-12-09 22:21:29 +00:00
o [NSE] Added a new library for PostgreSQL and the script pgsql-brute that uses
it to guess credentials. [Patrik]
This commit is contained in:
@@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
[NOT YET RELEASED]
|
[NOT YET RELEASED]
|
||||||
|
|
||||||
o [NSE] Added the script ldap-search which queries a LDAP directory
|
o [NSE] Added a new library for PostgreSQL and the script pgsql-brute that uses
|
||||||
for either all, or a number of pre-defined object types. [Patrik]
|
it to guess credentials. [Patrik]
|
||||||
|
|
||||||
|
o [NSE] Added the script ldap-search which queries a LDAP directory for either
|
||||||
|
all, or a number of pre-defined object types. [Patrik]
|
||||||
|
|
||||||
o The redistributable Visual C++ runtime components installer
|
o The redistributable Visual C++ runtime components installer
|
||||||
(vcredist_x86.exe) has been upgraded to version 9.0.30729.4148. Axel
|
(vcredist_x86.exe) has been upgraded to version 9.0.30729.4148. Axel
|
||||||
|
|||||||
623
nselib/pgsql.lua
Normal file
623
nselib/pgsql.lua
Normal file
@@ -0,0 +1,623 @@
|
|||||||
|
---
|
||||||
|
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
|
||||||
|
-- @author = "Patrik Karlsson <patrik@cqure.net>"
|
||||||
|
--
|
||||||
|
-- PostgreSQL library supporting both version 2 and version 3 of the protocol
|
||||||
|
-- The library currently contains the bare minimum to perform authentication
|
||||||
|
-- Authentication is supported with or without SSL enabled and using the
|
||||||
|
-- plain-text or MD5 authentication mechanisms
|
||||||
|
--
|
||||||
|
-- The PGSQL protocol is explained in detail in the following reference
|
||||||
|
-- ref: http://developer.postgresql.org/pgdocs/postgres/protocol.html
|
||||||
|
-- ref: http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html
|
||||||
|
-- ref: http://developer.postgresql.org/pgdocs/postgres/protocol-message-formats.html
|
||||||
|
--
|
||||||
|
-- Version 0.3
|
||||||
|
-- Created 02/05/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
||||||
|
-- Revised 02/20/2010 - v0.2 - added detectVersion to automaticaly detect and return
|
||||||
|
-- the correct version class
|
||||||
|
-- Revised 03/04/2010 - v0.3 - added support for trust authentication method
|
||||||
|
|
||||||
|
module(... or "pgsql",package.seeall)
|
||||||
|
|
||||||
|
require("openssl")
|
||||||
|
require("bit")
|
||||||
|
|
||||||
|
--- Supported pgsql message types
|
||||||
|
MessageType = {
|
||||||
|
Error = 0x45,
|
||||||
|
BackendKeyData = 0x4b,
|
||||||
|
AuthRequest=0x52,
|
||||||
|
ParameterStatus = 0x53,
|
||||||
|
ReadyForQuery = 0x5a,
|
||||||
|
PasswordMessage = 0x70,
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Supported authentication types
|
||||||
|
AuthenticationType = {
|
||||||
|
Success = 0x00,
|
||||||
|
Plain = 0x03,
|
||||||
|
MD5 = 0x05
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Version 2 of the protocol
|
||||||
|
v2 =
|
||||||
|
{
|
||||||
|
|
||||||
|
--- Pad a string with zeroes
|
||||||
|
--
|
||||||
|
-- @param str string containing the string to be padded
|
||||||
|
-- @param len number containing the wanted length
|
||||||
|
-- @return string containing the padded string value
|
||||||
|
zeroPad = function(str, len)
|
||||||
|
local padding = len - str:len()
|
||||||
|
|
||||||
|
if ( padding < 0 ) then
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
for i=1,padding do
|
||||||
|
str = str .. string.char(0x00)
|
||||||
|
end
|
||||||
|
return str
|
||||||
|
end,
|
||||||
|
|
||||||
|
messageDecoder = {
|
||||||
|
|
||||||
|
--- Decodes an Auth Request packet
|
||||||
|
--
|
||||||
|
-- @param data string containing raw data recieved from socket
|
||||||
|
-- @param len number containing the length as retrieved from the header
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return pos number containing the offset after decoding, -1 on error
|
||||||
|
-- @return response table containing zero or more of the following <code>salt</code> and <code>success</code>
|
||||||
|
-- error string containing error message if pos is -1
|
||||||
|
[MessageType.AuthRequest] = function( data, len, pos )
|
||||||
|
local _, authtype
|
||||||
|
local response = {}
|
||||||
|
pos, authtype = bin.unpack(">I", data, pos)
|
||||||
|
|
||||||
|
if ( authtype == AuthenticationType.MD5 ) then
|
||||||
|
if ( len - pos + 1 ) < 3 then
|
||||||
|
return -1, "ERROR: Malformed AuthRequest received"
|
||||||
|
end
|
||||||
|
pos, response.salt = bin.unpack("A4", data, pos)
|
||||||
|
elseif ( authtype == AuthenticationType.Plain ) then
|
||||||
|
--do nothing
|
||||||
|
elseif ( authtype == 0 ) then
|
||||||
|
response.success = true
|
||||||
|
else
|
||||||
|
stdnse.print_debug( ("unknown auth type: %d"):format(authtype) )
|
||||||
|
end
|
||||||
|
|
||||||
|
response.authtype = authtype
|
||||||
|
return pos, response
|
||||||
|
end,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Decodes an Error packet
|
||||||
|
--
|
||||||
|
-- @param data string containing raw data recieved from socket
|
||||||
|
-- @param len number containing the length as retrieved from the header
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return pos number containing the offset after decoding
|
||||||
|
-- @return response table containing zero or more of the following <code>error.severity</code>,
|
||||||
|
-- <code>error.code</code>, <code>error.message</code>, <code>error.file</code>,
|
||||||
|
-- <code>error.line</code> and <code>error.routine</code>
|
||||||
|
[MessageType.Error] = function( data, len, pos )
|
||||||
|
local tmp = data:sub(pos, pos + len - 4)
|
||||||
|
local response = {}
|
||||||
|
local pos_end = pos + len
|
||||||
|
|
||||||
|
response.error = {}
|
||||||
|
pos, response.error.message = bin.unpack("z", data, pos)
|
||||||
|
return pos, response
|
||||||
|
end,
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
--- Process the server response
|
||||||
|
--
|
||||||
|
-- @param data string containing the server response
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
processResponse = function(data, pos)
|
||||||
|
local ptype, len, status, response
|
||||||
|
local pos = pos or 1
|
||||||
|
|
||||||
|
pos, ptype = bin.unpack("C", data, pos)
|
||||||
|
len = data:len() - 1
|
||||||
|
|
||||||
|
if v2.messageDecoder[ptype] then
|
||||||
|
pos, response = v2.messageDecoder[ptype](data, len, pos)
|
||||||
|
|
||||||
|
if pos ~= -1 then
|
||||||
|
response.type = ptype
|
||||||
|
return pos, response
|
||||||
|
end
|
||||||
|
else
|
||||||
|
stdnse.print_debug( ("Missing decoder for %d"):format(ptype) )
|
||||||
|
return -1, ("Missing decoder for %d"):format(ptype)
|
||||||
|
end
|
||||||
|
return -1, "Decoding failed"
|
||||||
|
end,
|
||||||
|
|
||||||
|
|
||||||
|
--- Reads a packet and handles additional socket reads to retrieve remaining data
|
||||||
|
--
|
||||||
|
-- @param socket socket already connected to the pgsql server
|
||||||
|
-- @param data string containing any data already retrieved from the socket
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return data string containing the initial and any additional data
|
||||||
|
readPacket=function(socket, data, pos)
|
||||||
|
|
||||||
|
local pos = pos or 1
|
||||||
|
local data = data or ""
|
||||||
|
local status = true
|
||||||
|
local tmp = ""
|
||||||
|
local ptype, len
|
||||||
|
|
||||||
|
local catch = function() socket:close() stdnse.print_debug("processResponse(): failed") end
|
||||||
|
local try = nmap.new_try(catch)
|
||||||
|
|
||||||
|
if ( data == nil or data:len() == 0 ) then
|
||||||
|
data = try(socket:receive())
|
||||||
|
end
|
||||||
|
return data
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Sends a startup message to the server containing the username and database to connect to
|
||||||
|
--
|
||||||
|
-- @param socket socket already connected to the pgsql server
|
||||||
|
-- @param user string containing the name of the user
|
||||||
|
-- @param database string containing the name of the database
|
||||||
|
-- @return status true on success, false on failure
|
||||||
|
-- @return table containing a processed response from <code>processResponse</code>
|
||||||
|
-- string containing error message if status is false
|
||||||
|
sendStartup=function(socket, user, database)
|
||||||
|
local data, response, status, pos
|
||||||
|
local proto_ver, ptype, _, tmp
|
||||||
|
|
||||||
|
local tty, unused, args = "", "", ""
|
||||||
|
proto_ver = 0x0020000
|
||||||
|
user = v2.zeroPad(user, 32)
|
||||||
|
database = v2.zeroPad(database, 64)
|
||||||
|
data = bin.pack(">I>IAAAAA", 296, proto_ver, database, user, v2.zeroPad(args, 64), v2.zeroPad(unused, 64), v2.zeroPad(tty,64) )
|
||||||
|
|
||||||
|
socket:send( data )
|
||||||
|
|
||||||
|
-- attempt to verify version
|
||||||
|
status, data = socket:receive_bytes( 1 )
|
||||||
|
|
||||||
|
if ( not(status) ) then
|
||||||
|
return false, "sendStartup failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
data = v2.readPacket(socket, data )
|
||||||
|
pos, response = v2.processResponse( data )
|
||||||
|
|
||||||
|
if ( pos < 0 or response.type == MessageType.Error) then
|
||||||
|
return false, response.error.message or "unknown error"
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, response
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Attempts to authenticate to the pgsql server
|
||||||
|
-- Supports plain-text and MD5 authentication
|
||||||
|
--
|
||||||
|
-- @param socket socket already connected to the pgsql server
|
||||||
|
-- @param params table containing any additional parameters <code>authtype</code>, <code>version</code>
|
||||||
|
-- @param username string containing the username to use for authentication
|
||||||
|
-- @param password string containing the password to use for authentication
|
||||||
|
-- @param salt string containing the crypthographic salt value
|
||||||
|
-- @return status true on success, false on failure
|
||||||
|
-- @return result table containing parameter status information,
|
||||||
|
-- result string containing an error message if login fails
|
||||||
|
loginRequest = function ( socket, params, username, password, salt )
|
||||||
|
|
||||||
|
local catch = function() socket:close() stdnse.print_debug("loginRequest(): failed") end
|
||||||
|
local try = nmap.new_try(catch)
|
||||||
|
local response = {}
|
||||||
|
local status, data, len, pos, tmp
|
||||||
|
|
||||||
|
if ( params.authtype == AuthenticationType.MD5 ) then
|
||||||
|
local hash = createMD5LoginHash(username,password,salt)
|
||||||
|
data = bin.pack( ">Iz", 40, hash)
|
||||||
|
try( socket:send( data ) )
|
||||||
|
elseif ( params.authtype == AuthenticationType.Plain ) then
|
||||||
|
local data
|
||||||
|
data = bin.pack(">Iz", password:len() + 4, password)
|
||||||
|
try( socket:send( data ) )
|
||||||
|
elseif ( params.authtype == AuthenticationType.Success ) then
|
||||||
|
return true, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
data, response.params = "", {}
|
||||||
|
|
||||||
|
data = v2.readPacket(socket, data, 1)
|
||||||
|
pos, tmp = v2.processResponse(data, 1)
|
||||||
|
|
||||||
|
-- this should contain the AuthRequest packet
|
||||||
|
if tmp.type ~= MessageType.AuthRequest then
|
||||||
|
return false, "Expected AuthRequest got something else"
|
||||||
|
end
|
||||||
|
|
||||||
|
if not tmp.success then
|
||||||
|
return false, "Login failure"
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, response
|
||||||
|
end,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Version 3 of the protocol
|
||||||
|
v3 =
|
||||||
|
{
|
||||||
|
messageDecoder = {
|
||||||
|
|
||||||
|
--- Decodes an Auth Request packet
|
||||||
|
--
|
||||||
|
-- @param data string containing raw data recieved from socket
|
||||||
|
-- @param len number containing the length as retrieved from the header
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return pos number containing the offset after decoding, -1 on error
|
||||||
|
-- @return response table containing zero or more of the following <code>salt</code> and <code>success</code>
|
||||||
|
-- error string containing error message if pos is -1
|
||||||
|
[MessageType.AuthRequest] = function( data, len, pos )
|
||||||
|
local _, authtype
|
||||||
|
local response = {}
|
||||||
|
|
||||||
|
pos, authtype = bin.unpack(">I", data, pos)
|
||||||
|
|
||||||
|
if ( authtype == AuthenticationType.MD5 ) then
|
||||||
|
if ( len - pos + 1 ) < 3 then
|
||||||
|
return -1, "ERROR: Malformed AuthRequest received"
|
||||||
|
end
|
||||||
|
pos, response.salt = bin.unpack("A4", data, pos)
|
||||||
|
elseif ( authtype == AuthenticationType.Plain ) then
|
||||||
|
--do nothing
|
||||||
|
elseif ( authtype == 0 ) then
|
||||||
|
response.success = true
|
||||||
|
else
|
||||||
|
stdnse.print_debug( "unknown auth type: %d", authtype )
|
||||||
|
end
|
||||||
|
|
||||||
|
response.authtype = authtype
|
||||||
|
return pos, response
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Decodes an ParameterStatus packet
|
||||||
|
--
|
||||||
|
-- @param data string containing raw data recieved from socket
|
||||||
|
-- @param len number containing the length as retrieved from the header
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return pos number containing the offset after decoding
|
||||||
|
-- @return response table containing zero or more of the following <code>key</code> and <code>value</code>
|
||||||
|
[MessageType.ParameterStatus] = function( data, len, pos )
|
||||||
|
local tmp, _
|
||||||
|
local response = {}
|
||||||
|
|
||||||
|
tmp = data:sub(pos, pos + len - 4)
|
||||||
|
_, response.key, response.value = bin.unpack("zz", tmp)
|
||||||
|
return pos + len - 4, response
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Decodes an Error packet
|
||||||
|
--
|
||||||
|
-- @param data string containing raw data recieved from socket
|
||||||
|
-- @param len number containing the length as retrieved from the header
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return pos number containing the offset after decoding
|
||||||
|
-- @return response table containing zero or more of the following <code>error.severity</code>,
|
||||||
|
-- <code>error.code</code>, <code>error.message</code>, <code>error.file</code>,
|
||||||
|
-- <code>error.line</code> and <code>error.routine</code>
|
||||||
|
[MessageType.Error] = function( data, len, pos )
|
||||||
|
local tmp = data:sub(pos, pos + len - 4)
|
||||||
|
local _, value, prefix
|
||||||
|
local response = {}
|
||||||
|
local pos_end = pos + len
|
||||||
|
|
||||||
|
response.error = {}
|
||||||
|
|
||||||
|
while ( pos < pos_end - 5 ) do
|
||||||
|
pos, prefix, value = bin.unpack("Az", data, pos)
|
||||||
|
|
||||||
|
if prefix == 'S' then
|
||||||
|
response.error.severity = value
|
||||||
|
elseif prefix == 'C' then
|
||||||
|
response.error.code = value
|
||||||
|
elseif prefix == 'M' then
|
||||||
|
response.error.message = value
|
||||||
|
elseif prefix == 'F' then
|
||||||
|
response.error.file = value
|
||||||
|
elseif prefix == 'L' then
|
||||||
|
response.error.line = value
|
||||||
|
elseif prefix == 'R' then
|
||||||
|
response.error.routine = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return pos, response
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Decodes the BackendKeyData packet
|
||||||
|
--
|
||||||
|
-- @param data string containing raw data recieved from socket
|
||||||
|
-- @param len number containing the length as retrieved from the header
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return pos number containing the offset after decoding, -1 on error
|
||||||
|
-- @return response table containing zero or more of the following <code>pid</code> and <code>key</code>
|
||||||
|
-- error string containing error message if pos is -1
|
||||||
|
[MessageType.BackendKeyData] = function( data, len, pos )
|
||||||
|
local response = {}
|
||||||
|
|
||||||
|
if len ~= 12 then
|
||||||
|
return -1, "ERROR: Invalid BackendKeyData packet"
|
||||||
|
end
|
||||||
|
|
||||||
|
pos, response.pid, response.key = bin.unpack(">I>I", data, pos)
|
||||||
|
return pos, response
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Decodes an ReadyForQuery packet
|
||||||
|
--
|
||||||
|
-- @param data string containing raw data recieved from socket
|
||||||
|
-- @param len number containing the length as retrieved from the header
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return pos number containing the offset after decoding, -1 on error
|
||||||
|
-- @return response table containing zero or more of the following <code>status</code>
|
||||||
|
-- error string containing error message if pos is -1
|
||||||
|
[MessageType.ReadyForQuery] = function( data, len, pos )
|
||||||
|
local response = {}
|
||||||
|
|
||||||
|
if len ~= 5 then
|
||||||
|
return -1, "ERROR: Invalid ReadyForQuery packet"
|
||||||
|
end
|
||||||
|
|
||||||
|
pos, response.status = bin.unpack("C", data, pos )
|
||||||
|
return pos, response
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
--- Reads a packet and handles additional socket reads to retrieve remaining data
|
||||||
|
--
|
||||||
|
-- @param socket socket already connected to the pgsql server
|
||||||
|
-- @param data string containing any data already retrieved from the socket
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return data string containing the initial and any additional data
|
||||||
|
readPacket = function(socket, data, pos)
|
||||||
|
|
||||||
|
local pos = pos or 1
|
||||||
|
local data = data or ""
|
||||||
|
local status = true
|
||||||
|
local tmp = ""
|
||||||
|
local ptype, len
|
||||||
|
local header
|
||||||
|
|
||||||
|
local catch = function() socket:close() stdnse.print_debug("processResponse(): failed") end
|
||||||
|
local try = nmap.new_try(catch)
|
||||||
|
|
||||||
|
if ( data:len() - pos < 5 ) then
|
||||||
|
status, tmp = socket:receive_bytes( 5 - ( data:len() - pos ) )
|
||||||
|
end
|
||||||
|
|
||||||
|
if not status then
|
||||||
|
return nil, "Failed to read packet"
|
||||||
|
end
|
||||||
|
|
||||||
|
if tmp:len() ~= 0 then
|
||||||
|
data = data .. tmp
|
||||||
|
end
|
||||||
|
|
||||||
|
pos, header = v3.decodeHeader(data,pos)
|
||||||
|
|
||||||
|
while data:len() < header.len do
|
||||||
|
data = data .. try(socket:receive_bytes( ( header.len + 1 ) - data:len() ))
|
||||||
|
end
|
||||||
|
return data
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Decodes the postgres header
|
||||||
|
--
|
||||||
|
-- @param data string containing the server response
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return pos number containing the offset after decoding
|
||||||
|
-- @return header table containing <code>type</code> and <code>len</code>
|
||||||
|
decodeHeader = function(data, pos)
|
||||||
|
local ptype, len
|
||||||
|
|
||||||
|
pos, ptype, len = bin.unpack("C>I", data, pos)
|
||||||
|
return pos, { ['type'] = ptype, ['len'] = len }
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Process the server response
|
||||||
|
--
|
||||||
|
-- @param data string containing the server response
|
||||||
|
-- @param pos number containing the offset into the data buffer
|
||||||
|
-- @return pos number containing offset after decoding
|
||||||
|
-- @return response string containing decoded data
|
||||||
|
-- error message if pos is -1
|
||||||
|
processResponse = function(data, pos)
|
||||||
|
local ptype, len, status, response
|
||||||
|
local pos = pos or 1
|
||||||
|
local header
|
||||||
|
|
||||||
|
pos, header = v3.decodeHeader( data, pos )
|
||||||
|
|
||||||
|
if v3.messageDecoder[header.type] then
|
||||||
|
pos, response = v3.messageDecoder[header.type](data, header.len, pos)
|
||||||
|
|
||||||
|
if pos ~= -1 then
|
||||||
|
response.type = header.type
|
||||||
|
return pos, response
|
||||||
|
end
|
||||||
|
else
|
||||||
|
stdnse.print_debug( "Missing decoder for %d", header.type )
|
||||||
|
return -1, ("Missing decoder for %d"):format(header.type)
|
||||||
|
end
|
||||||
|
return -1, "Decoding failed"
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Attempts to authenticate to the pgsql server
|
||||||
|
-- Supports plain-text and MD5 authentication
|
||||||
|
--
|
||||||
|
-- @param socket socket already connected to the pgsql server
|
||||||
|
-- @param params table containing any additional parameters <code>authtype</code>, <code>version</code>
|
||||||
|
-- @param username string containing the username to use for authentication
|
||||||
|
-- @param password string containing the password to use for authentication
|
||||||
|
-- @param salt string containing the crypthographic salt value
|
||||||
|
-- @return status true on success, false on failure
|
||||||
|
-- @return result table containing parameter status information,
|
||||||
|
-- result string containing an error message if login fails
|
||||||
|
loginRequest = function ( socket, params, username, password, salt )
|
||||||
|
|
||||||
|
local catch = function() socket:close() stdnse.print_debug("loginRequest(): failed") end
|
||||||
|
local try = nmap.new_try(catch)
|
||||||
|
local response, header = {}, {}
|
||||||
|
local status, data, len, tmp, _
|
||||||
|
local pos = 1
|
||||||
|
|
||||||
|
if ( params.authtype == AuthenticationType.MD5 ) then
|
||||||
|
local hash = pgsql.createMD5LoginHash(username, password, salt)
|
||||||
|
data = bin.pack( "C>Iz", MessageType.PasswordMessage, 40, hash )
|
||||||
|
try( socket:send( data ) )
|
||||||
|
elseif ( params.authtype == AuthenticationType.Plain ) then
|
||||||
|
local data
|
||||||
|
data = bin.pack("C>Iz", MessageType.PasswordMessage, password:len() + 4, password)
|
||||||
|
try( socket:send( data ) )
|
||||||
|
elseif ( params.authtype == AuthenticationType.Success ) then
|
||||||
|
return true, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
data, response.params = "", {}
|
||||||
|
|
||||||
|
data = v3.readPacket(socket, data, 1)
|
||||||
|
pos, tmp = v3.processResponse(data, 1)
|
||||||
|
|
||||||
|
-- this should contain the AuthRequest packet
|
||||||
|
if tmp.type ~= MessageType.AuthRequest then
|
||||||
|
return false, "Expected AuthRequest got something else"
|
||||||
|
end
|
||||||
|
|
||||||
|
if not tmp.success then
|
||||||
|
return false, "Login failure"
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
data = v3.readPacket(socket, data, pos)
|
||||||
|
pos, tmp = v3.processResponse(data, pos)
|
||||||
|
if ( tmp.type == MessageType.ParameterStatus ) then
|
||||||
|
table.insert(response.params, {name=tmp.key, value=tmp.value})
|
||||||
|
end
|
||||||
|
until pos >= data:len() or pos == -1
|
||||||
|
|
||||||
|
return true, response
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Sends a startup message to the server containing the username and database to connect to
|
||||||
|
--
|
||||||
|
-- @param socket socket already connected to the pgsql server
|
||||||
|
-- @param user string containing the name of the user
|
||||||
|
-- @param database string containing the name of the database
|
||||||
|
-- @return status true on success, false on failure
|
||||||
|
-- @return table containing a processed response from <code>processResponse</code>
|
||||||
|
-- string containing error message if status is false
|
||||||
|
sendStartup = function(socket, user, database )
|
||||||
|
local data, response, status, pos
|
||||||
|
local proto_ver, ptype, _, tmp
|
||||||
|
|
||||||
|
proto_ver = 0x0030000
|
||||||
|
data = bin.pack(">IzzzzH", proto_ver, "user", user, "database", database, 0)
|
||||||
|
data = bin.pack(">I", data:len() + 4) .. data
|
||||||
|
|
||||||
|
socket:send( data )
|
||||||
|
|
||||||
|
-- attempt to verify version
|
||||||
|
status, data = socket:receive_bytes( 2 )
|
||||||
|
|
||||||
|
if ( not(status) ) then
|
||||||
|
return false, "sendStartup failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
if ( not(status) or data:match("^EF") ) then
|
||||||
|
return false, "Incorrect version"
|
||||||
|
end
|
||||||
|
|
||||||
|
data = v3.readPacket(socket, data )
|
||||||
|
pos, response = v3.processResponse( data )
|
||||||
|
|
||||||
|
if ( pos < 0 or response.type == MessageType.Error) then
|
||||||
|
return false, response.error.message or "unknown error"
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, response
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
--- Sends a packet requesting SSL communication to be activated
|
||||||
|
--
|
||||||
|
-- @param socket socket already connected to the pgsql server
|
||||||
|
-- @return boolean true if request was accepted, false if request was denied
|
||||||
|
function requestSSL(socket)
|
||||||
|
-- SSLRequest
|
||||||
|
local ssl_req_code = 80877103
|
||||||
|
local data = bin.pack( ">I>I", 8, ssl_req_code)
|
||||||
|
local status, response
|
||||||
|
|
||||||
|
socket:send(data)
|
||||||
|
status, response = socket:receive_bytes(1)
|
||||||
|
|
||||||
|
if ( not(status) ) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if ( response == 'S' ) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a cryptographic hash to be used for login
|
||||||
|
--
|
||||||
|
-- @param string username
|
||||||
|
-- @param string password
|
||||||
|
-- @param string salt
|
||||||
|
-- @return string suitable for login request
|
||||||
|
function createMD5LoginHash(username, password, salt)
|
||||||
|
local md5_1 = select( 2, bin.unpack( "H16", openssl.md5(password..username) ) ):lower()
|
||||||
|
return "md5" .. select( 2, bin.unpack("H16", openssl.md5( md5_1 .. salt ) ) ):lower()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Prints the contents of the error table returned from the Error message decoder
|
||||||
|
--
|
||||||
|
-- @param dberror table containing the error
|
||||||
|
function printErrorMessage( dberror )
|
||||||
|
if not dberror then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for k, v in pairs(dberror) do
|
||||||
|
stdnse.print_debug( ("%s=%s"):format(k, v) )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Attempts to determine if the server supports v3 or v2 of the protocol
|
||||||
|
--
|
||||||
|
-- @param host table
|
||||||
|
-- @param port table
|
||||||
|
-- @return class v2 or v3
|
||||||
|
function detectVersion(host, port)
|
||||||
|
local status, response
|
||||||
|
local socket = nmap.new_socket()
|
||||||
|
|
||||||
|
socket:connect(host.ip, port.number, "tcp")
|
||||||
|
status, response = v3.sendStartup(socket, "versionprobe", "versionprobe")
|
||||||
|
socket:close()
|
||||||
|
|
||||||
|
if ( not(status) and response == 'Incorrect version' ) then
|
||||||
|
return v2
|
||||||
|
end
|
||||||
|
|
||||||
|
return v3
|
||||||
|
end
|
||||||
178
scripts/pgsql-brute.nse
Normal file
178
scripts/pgsql-brute.nse
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
description = [[
|
||||||
|
Performs password guessing against Postgresql
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -p 5432 --script pgsql-brute <host>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- 5432/tcp open pgsql
|
||||||
|
-- | pgsql-brute:
|
||||||
|
-- | root:<empty> => Login Correct
|
||||||
|
-- |_ test:test => Login Correct
|
||||||
|
--
|
||||||
|
-- @args pgsql.nossl If set to 1 or true disables SSL.
|
||||||
|
-- @args pgsql.version Force protocol version 2 or 3
|
||||||
|
--
|
||||||
|
-- SSL Encryption
|
||||||
|
-- --------------
|
||||||
|
-- We need to handle several cases of SSL support
|
||||||
|
-- o SSL can be supported on a server level
|
||||||
|
-- o SSL can be enforced per host or network level
|
||||||
|
-- o SSL can be denied per host or network level
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Patrik Karlsson"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"intrusive", "auth"}
|
||||||
|
|
||||||
|
require 'shortport'
|
||||||
|
require 'stdnse'
|
||||||
|
require 'unpwdb'
|
||||||
|
|
||||||
|
-- Version 0.3
|
||||||
|
-- Created 01/15/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
||||||
|
-- Revised 02/20/2010 - v0.2 - moved version detection to pgsql library
|
||||||
|
-- Revised 03/04/2010 - v0.3 - added code from ssh-hostkey.nse to check for SSL support
|
||||||
|
-- - added support for trusted authentication method
|
||||||
|
|
||||||
|
-- ripped from ssh-hostkey.nse
|
||||||
|
-- openssl is required for this script
|
||||||
|
if pcall(require,"openssl") then
|
||||||
|
require("pgsql")
|
||||||
|
else
|
||||||
|
portrule = function() return false end
|
||||||
|
action = function() end
|
||||||
|
stdnse.print_debug( 3, "Skipping %s script because OpenSSL is missing.", filename )
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
portrule = shortport.port_or_service(5432, "postgresql")
|
||||||
|
|
||||||
|
--- Connect a socket to the server with or without SSL
|
||||||
|
--
|
||||||
|
-- @param host table as received by the action function
|
||||||
|
-- @param port table as received by the action function
|
||||||
|
-- @param ssl boolean, if true connect using SSL
|
||||||
|
-- @return socket connected to server
|
||||||
|
local function connectSocket(host, port, ssl)
|
||||||
|
local socket = nmap.new_socket()
|
||||||
|
|
||||||
|
-- set a reasonable timeout value
|
||||||
|
socket:set_timeout(5000)
|
||||||
|
socket:connect(host.ip, port.number, "tcp")
|
||||||
|
|
||||||
|
-- let's be responsible and avoid sending communication in the clear
|
||||||
|
if ( ssl ) then
|
||||||
|
status = pgsql.requestSSL(socket)
|
||||||
|
if ( status ) then
|
||||||
|
socket:reconnect_ssl()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return socket
|
||||||
|
end
|
||||||
|
|
||||||
|
action = function( host, port )
|
||||||
|
|
||||||
|
local status, response, ssl_enable, output
|
||||||
|
local max_time = unpwdb.timelimit() ~= nil and unpwdb.timelimit() * 1000 or -1
|
||||||
|
local clock_start = nmap.clock_ms()
|
||||||
|
local result, response, status, aborted, nossl = {}, nil, nil, false, false
|
||||||
|
local valid_accounts = {}
|
||||||
|
local pg
|
||||||
|
|
||||||
|
if ( nmap.registry.args['pgsql.version'] ) then
|
||||||
|
if ( tonumber(nmap.registry.args['pgsql.version']) == 2 ) then
|
||||||
|
pg = pgsql.v2
|
||||||
|
elseif ( tonumber(nmap.registry.args['pgsql.version']) == 3 ) then
|
||||||
|
pg = pgsql.v3
|
||||||
|
else
|
||||||
|
stdnse.print_debug("pgsql-brute: Unsupported version %s", nmap.registry.args['pgsql.version'])
|
||||||
|
return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
pg = pgsql.detectVersion(host, port )
|
||||||
|
end
|
||||||
|
|
||||||
|
status, usernames = unpwdb.usernames()
|
||||||
|
if ( not(status) ) then return end
|
||||||
|
|
||||||
|
status, passwords = unpwdb.passwords()
|
||||||
|
if ( not(status) ) then return end
|
||||||
|
|
||||||
|
-- If the user explicitly does not disable SSL, enforce it
|
||||||
|
if ( ( nmap.registry.args['pgsql.nossl'] == 'true' ) or
|
||||||
|
( nmap.registry.args['pgsql.nossl'] == '1' ) ) then
|
||||||
|
nossl = true
|
||||||
|
end
|
||||||
|
|
||||||
|
for username in usernames do
|
||||||
|
ssl_enable = not(nossl)
|
||||||
|
for password in passwords do
|
||||||
|
if max_time>0 and nmap.clock_ms() - clock_start > max_time then
|
||||||
|
aborted=true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
stdnse.print_debug( string.format("Trying %s/%s ...", username, password ) )
|
||||||
|
socket = connectSocket( host, port, ssl_enable )
|
||||||
|
status, response = pg.sendStartup(socket, username, username)
|
||||||
|
|
||||||
|
-- if nossl is enforced by the user, we're done
|
||||||
|
if ( not(status) and nossl ) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
-- SSL failed, this can occure due to:
|
||||||
|
-- 1. The server does not do SSL
|
||||||
|
-- 2. SSL was denied on a per host or network level
|
||||||
|
--
|
||||||
|
-- Attempt SSL connection
|
||||||
|
if ( not(status) ) then
|
||||||
|
socket:close()
|
||||||
|
ssl_enable = false
|
||||||
|
socket = connectSocket( host, port, ssl_enable )
|
||||||
|
status, response = pg.sendStartup(socket, username, username)
|
||||||
|
if (not(status)) then
|
||||||
|
if ( response:match("no pg_hba.conf entry for host") ) then
|
||||||
|
stdnse.print_debug("The host was denied access to db \"%s\" as user \"%s\", aborting ...", username, username )
|
||||||
|
break
|
||||||
|
else
|
||||||
|
stdnse.print_debug("pgsql-brute: sendStartup returned: %s", response )
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do not attempt to authenticate if authentication type is trusted
|
||||||
|
if ( response.authtype ~= pgsql.AuthenticationType.Success ) then
|
||||||
|
status, response = pg.loginRequest( socket, response, username, password, response.salt)
|
||||||
|
end
|
||||||
|
|
||||||
|
if status then
|
||||||
|
-- Add credentials for other pgsql scripts to use
|
||||||
|
if nmap.registry.pgsqlusers == nil then
|
||||||
|
nmap.registry.pgsqlusers = {}
|
||||||
|
end
|
||||||
|
nmap.registry.pgsqlusers[username]=password
|
||||||
|
if ( response.authtype ~= pgsql.AuthenticationType.Success ) then
|
||||||
|
table.insert( valid_accounts, string.format("%s:%s => Login Correct", username, password:len()>0 and password or "<empty>" ) )
|
||||||
|
else
|
||||||
|
table.insert( valid_accounts, string.format("%s => Trusted authentication", username ) )
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
socket:close()
|
||||||
|
end
|
||||||
|
passwords("reset")
|
||||||
|
end
|
||||||
|
|
||||||
|
output = stdnse.format_output(true, valid_accounts)
|
||||||
|
if max_time > 0 and aborted then
|
||||||
|
output = output .. string.format(" \n\nscript aborted execution after %d seconds", max_time/1000 )
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
end
|
||||||
@@ -60,6 +60,7 @@ Entry { filename = "nfs-showmount.nse", categories = { "discovery", "safe", } }
|
|||||||
Entry { filename = "ntp-info.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "ntp-info.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "oracle-sid-brute.nse", categories = { "auth", "intrusive", } }
|
Entry { filename = "oracle-sid-brute.nse", categories = { "auth", "intrusive", } }
|
||||||
Entry { filename = "p2p-conficker.nse", categories = { "default", "safe", } }
|
Entry { filename = "p2p-conficker.nse", categories = { "default", "safe", } }
|
||||||
|
Entry { filename = "pgsql-brute.nse", categories = { "auth", "intrusive", } }
|
||||||
Entry { filename = "pjl-ready-message.nse", categories = { "intrusive", } }
|
Entry { filename = "pjl-ready-message.nse", categories = { "intrusive", } }
|
||||||
Entry { filename = "pop3-brute.nse", categories = { "auth", "intrusive", } }
|
Entry { filename = "pop3-brute.nse", categories = { "auth", "intrusive", } }
|
||||||
Entry { filename = "pop3-capabilities.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "pop3-capabilities.nse", categories = { "default", "discovery", "safe", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user