mirror of
https://github.com/nmap/nmap.git
synced 2025-12-18 13:39:02 +00:00
o [NSE] Added the scripts rtsp-url-brute, rtsp-methods and the supporting rtsp
library. The scripts check the supported RTSP methods and attempt to brute force valid RTSP urls. [Patrik]
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added the scripts rtsp-url-brute, rtsp-methods and the supporting rtsp
|
||||
library. The scripts check the supported RTSP methods and attempt to brute
|
||||
force valid RTSP urls. [Patrik]
|
||||
|
||||
o [NSE] Added the http-robtex-reverse-ip script that uses the Robtex service to
|
||||
perform a reverse lookup in order to discover all names associated with the
|
||||
IP. [riemann]
|
||||
|
||||
76
nselib/data/rtsp-urls.txt
Normal file
76
nselib/data/rtsp-urls.txt
Normal file
@@ -0,0 +1,76 @@
|
||||
#!comment: The following dictionary contains a list of well-known RTSP URLs
|
||||
#!comment: used by common video surveillance equipment.
|
||||
/
|
||||
/1.AMP
|
||||
/1/stream1
|
||||
/CAM_ID.password.mp2
|
||||
/CH001.sdp
|
||||
/GetData.cgi
|
||||
/MediaInput/h264
|
||||
/MediaInput/mpeg4
|
||||
/VideoInput/1/h264/1
|
||||
/access_code
|
||||
/access_name_for_stream_1_to_5
|
||||
/av0_0
|
||||
/av2
|
||||
/avn=2
|
||||
/axis-media/media.amp
|
||||
/cam
|
||||
/cam0_0
|
||||
/cam0_1
|
||||
/cam1/h264
|
||||
/cam1/h264/multicast
|
||||
/cam1/mjpeg
|
||||
/cam1/mpeg4
|
||||
/camera.stm
|
||||
/ch0
|
||||
/ch001.sdp
|
||||
/ch0_unicast_firststream
|
||||
/ch0_unicast_secondstream
|
||||
/channel1
|
||||
/h264
|
||||
/h264/media.amp
|
||||
/image.mpg
|
||||
/img/media.sav
|
||||
/img/video.asf
|
||||
/img/video.sav
|
||||
/ioImage/1
|
||||
/ipcam.sdp
|
||||
/ipcam_h264.sdp
|
||||
/live.sdp
|
||||
/live/h264
|
||||
/live/mpeg4
|
||||
/live_mpeg4.sdp
|
||||
/livestream
|
||||
/livestream/
|
||||
/media/media.amp
|
||||
/media/video1
|
||||
/mjpeg/media.smp
|
||||
/mp4
|
||||
/mpeg4
|
||||
/mpeg4/1/media.amp
|
||||
/mpeg4/media.amp
|
||||
/mpeg4/media.smp
|
||||
/mpeg4unicast
|
||||
/mpg4/rtsp.amp
|
||||
/multicaststream
|
||||
/now.mp4
|
||||
/nph-h264.cgi
|
||||
/nphMpeg4/g726-640x
|
||||
/nphMpeg4/g726-640x480
|
||||
/nphMpeg4/nil-320x240
|
||||
/play1.sdp
|
||||
/play2.sdp
|
||||
/rtpvideo1.sdp
|
||||
/rtsp_tunnel
|
||||
/rtsph264
|
||||
/stream1
|
||||
/user.pin.mp2
|
||||
/user_defined
|
||||
/video
|
||||
/video.3gp
|
||||
/video.mp4
|
||||
/video1
|
||||
/video1+audio1
|
||||
/vis
|
||||
/wfov
|
||||
286
nselib/rtsp.lua
Normal file
286
nselib/rtsp.lua
Normal file
@@ -0,0 +1,286 @@
|
||||
---
|
||||
-- This Real Time Streaming Protocol (RTSP) library implements only a minimal
|
||||
-- subset of the protocol needed by the current scripts.
|
||||
--
|
||||
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
|
||||
-- @author Patrik Karlsson <patrik@cqure.net>
|
||||
--
|
||||
-- The library contains the following classes:
|
||||
--
|
||||
-- * <code>Request</code>
|
||||
-- ** This class contains the functions needed to create the RTSP request
|
||||
--
|
||||
-- * <code>Response</code>
|
||||
-- ** This class contains the functions needed to parse the RTSP response
|
||||
--
|
||||
-- * <code>Client</code>
|
||||
-- ** This class contains the RTSP client, a class responsible for sending
|
||||
-- and receiving requests and responses to/from the server
|
||||
--
|
||||
-- * <code>Helper</code>
|
||||
-- ** This class serves as the main interface for script writers
|
||||
--
|
||||
-- The following sample code shows how to use the library:
|
||||
-- <code>
|
||||
-- local helper = rtsp.Helper:new(host, port)
|
||||
-- local status = helper:connect()
|
||||
-- local response
|
||||
-- status, response = helper:describe(url)
|
||||
-- helper:close()
|
||||
-- </code>
|
||||
|
||||
--
|
||||
-- Version 0.1
|
||||
-- Created 10/23/2011 - v0.1 - Created by Patrik Karlsson
|
||||
--
|
||||
|
||||
module(... or "rtsp", package.seeall)
|
||||
|
||||
-- The RTSP Request object
|
||||
Request = {
|
||||
|
||||
--- Creates a new Request instance
|
||||
-- @return o instance of Request
|
||||
new = function(self, url, headers)
|
||||
local o = { url = url, req = {}, headers = headers or {} }
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Sets the RTSP Request method
|
||||
-- @param method string containing the RTSP method
|
||||
setMethod = function(self, method)
|
||||
self.method = method
|
||||
end,
|
||||
|
||||
--- Sets the RTSP sequence number
|
||||
-- @param cseq number containing the sequence number
|
||||
setCSeq = function(self, cseq)
|
||||
self.cseq = cseq
|
||||
end,
|
||||
|
||||
--- Adds an optional header to the RTSP request
|
||||
-- @param header string containing the header name
|
||||
-- @param value string containing the header value
|
||||
addHeader = function(self, header, value)
|
||||
table.insert( self.headers, { header = value } )
|
||||
end,
|
||||
|
||||
--- Converts the Request to a string
|
||||
--
|
||||
-- @return req string containing the request as a string
|
||||
__tostring = function(self)
|
||||
assert(self.cseq, "Request is missing required header CSeq")
|
||||
assert(self.url, "Request is missing URL")
|
||||
|
||||
local req = stdnse.strjoin("\r\n", {
|
||||
("%s %s RTSP/1.0"):format(self.method, self.url),
|
||||
("CSeq: %d"):format(self.cseq)
|
||||
} ) .. "\r\n"
|
||||
if ( #self.headers > 0 ) then
|
||||
req = req .. stdnse.strjoin("\r\n", self.headers) .. "\r\n"
|
||||
end
|
||||
|
||||
return req .. "\r\n"
|
||||
end,
|
||||
}
|
||||
|
||||
-- The RTSP response instance
|
||||
Response = {
|
||||
|
||||
--- Creates a new Response instance
|
||||
-- @param data string containing the unparsed data
|
||||
new = function(self, data)
|
||||
assert(data, "No data was supplied")
|
||||
local o = {
|
||||
raw = data,
|
||||
status = tonumber(data:match("^RTSP%/1%.0 (%d*) "))
|
||||
}
|
||||
|
||||
-- Split the response into a temporary array
|
||||
local tmp = stdnse.strsplit("\r\n", data)
|
||||
if ( not(tmp) ) then return nil end
|
||||
|
||||
-- we should have atleas one entry
|
||||
if ( #tmp > 1 ) then
|
||||
o.headers = {}
|
||||
for i=2, #tmp do
|
||||
-- if we have an empty line, this should be the end of headers
|
||||
if ( #tmp[i] == 0 ) then break end
|
||||
local key, val = tmp[i]:match("^(.-): (.*)$")
|
||||
-- create a key per header name
|
||||
o.headers[key] = val
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
}
|
||||
|
||||
|
||||
-- RTSP Client class
|
||||
Client = {
|
||||
|
||||
-- Creates a new Client instance
|
||||
-- @param host table as received by the action method
|
||||
-- @param port table as received by the action method
|
||||
-- @return o instance of Client
|
||||
new = function(self, host, port)
|
||||
local o = {
|
||||
host = host,
|
||||
port = port,
|
||||
cseq = 0,
|
||||
headers = { },
|
||||
retries = 3,
|
||||
timeout = 10 * 1000,
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Sets the number of retries for socket reads
|
||||
-- @param retries number containing the number of retries
|
||||
setRetries = function(self, retries) self.retries = retries end,
|
||||
|
||||
--- Sets the socket connection timeout in ms
|
||||
-- @param timeout number containing the timeout in ms
|
||||
setTimeout = function(self, timeout) self.timeout = timeout end,
|
||||
|
||||
--- Adds a RTSP header to the request
|
||||
-- @param header string containing the header name
|
||||
-- @param value string containing the header value
|
||||
addHeader = function(self, header, value)
|
||||
table.insert(self.headers, { ("%s: %s"):format(header,value) } )
|
||||
end,
|
||||
|
||||
--- Connects to the RTSP server
|
||||
-- @return status true on success, false on failure
|
||||
-- @return err string containing the error message on failure
|
||||
connect = function(self)
|
||||
self.socket = nmap.new_socket()
|
||||
self.socket:set_timeout(self.timeout)
|
||||
local status = self.socket:connect(self.host, self.port)
|
||||
if ( not(status) ) then
|
||||
stdnse.print_debug(2, "Failed to connect to the server: %s", self.host.ip)
|
||||
return false, ("Failed to connect to the server: %s"):format(self.host.ip)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
|
||||
--- Sends a DESCRIBE request to the server and receives the response
|
||||
-- @param url string containing the RTSP URL
|
||||
-- @return status true on success, false on failure
|
||||
-- @return response Response instance on success
|
||||
-- err string containing the error message on failure
|
||||
describe = function(self, url)
|
||||
local req = Request:new(url, self.headers)
|
||||
req:setMethod("DESCRIBE")
|
||||
return self:exch(req)
|
||||
end,
|
||||
|
||||
options = function(self, url)
|
||||
local req = Request:new(url, self.headers)
|
||||
req:setMethod("OPTIONS")
|
||||
return self:exch(req)
|
||||
end,
|
||||
|
||||
--- Sends a request to the server and receives the response and attempts
|
||||
-- to retry if either send or receive fails.
|
||||
-- @param request instance of Request
|
||||
-- @return status true on success, false on failure
|
||||
-- @return response Response instance on success
|
||||
-- err string containing the error message on failure
|
||||
exch = function(self, req)
|
||||
local retries = self.retries
|
||||
local status, data
|
||||
self.cseq = self.cseq + 1
|
||||
req:setCSeq( self.cseq )
|
||||
|
||||
repeat
|
||||
local err
|
||||
status, err = self.socket:send( tostring(req) )
|
||||
-- check if send was successfull, in case it wasn't AND
|
||||
-- this is our last retry, ABORT
|
||||
if ( not(status) and 0 == retries - 1 ) then
|
||||
stdnse.print_debug(2, "Failed to send request to server (%s)", err)
|
||||
return false, ("Failed to send request to server (%s)"):format(err)
|
||||
-- if send was successfull, attempt to receive the response
|
||||
elseif ( status ) then
|
||||
status, data = self.socket:receive()
|
||||
-- if we got the response allright, break out of retry loop
|
||||
if ( status ) then break end
|
||||
end
|
||||
-- if either send or receive fails, re-connect the socket
|
||||
if ( not(status) ) then
|
||||
self:close()
|
||||
local status, err = self:connect()
|
||||
-- if re-connect fails, BAIL out of here
|
||||
if ( not(status) ) then
|
||||
stdnse.print_debug(2, "Failed to reconnect socket to server (%s)", err)
|
||||
return false, ("Failed to reconnect socket to server (%s)"):format(err)
|
||||
end
|
||||
end
|
||||
retries = retries - 1
|
||||
until( status or retries == 0 )
|
||||
|
||||
if( not(status) ) then
|
||||
stdnse.print_debug(2, "Failed to receive response from server (%s)", data)
|
||||
return false, ("Failed to receive response from server (%s)"):format(data)
|
||||
end
|
||||
|
||||
return true, Response:new(data)
|
||||
end,
|
||||
|
||||
--- Closes the RTSP socket with the server
|
||||
close = function(self)
|
||||
return self.socket:close()
|
||||
end,
|
||||
|
||||
}
|
||||
|
||||
-- The Helper class is the main script interface
|
||||
Helper = {
|
||||
|
||||
-- Creates a new Helper instance
|
||||
-- @param host table as received by the action method
|
||||
-- @param port table as received by the action method
|
||||
-- @return o instance of Client
|
||||
new = function(self, host, port)
|
||||
local o = { host = host, port = port, client = Client:new(host, port) }
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Connects to the RTSP server
|
||||
-- @return status true on success, false on failure
|
||||
-- @return err string containing the error message on failure
|
||||
connect = function(self)
|
||||
return self.client:connect()
|
||||
end,
|
||||
|
||||
-- Closes the RTSP socket with the server
|
||||
close = function(self)
|
||||
return self.client:close()
|
||||
end,
|
||||
|
||||
-- Sends a DESCRIBE request to the server and receives the response
|
||||
--
|
||||
-- @param url string containing the RTSP URL
|
||||
-- @return status true on success, false on failure
|
||||
-- @return response string containing the unparsed RTSP response on success
|
||||
-- err string containing the error message on failure
|
||||
describe = function(self, url)
|
||||
return self.client:describe(url)
|
||||
end,
|
||||
|
||||
options = function(self, url)
|
||||
return self.client:options(url)
|
||||
end,
|
||||
|
||||
}
|
||||
47
scripts/rtsp-methods.nse
Normal file
47
scripts/rtsp-methods.nse
Normal file
@@ -0,0 +1,47 @@
|
||||
description = [[
|
||||
Finds out what methods are supported by the RTSP server.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap -p 554 --script rtsp-methods <ip>
|
||||
--
|
||||
-- @output
|
||||
-- PORT STATE SERVICE
|
||||
-- 554/tcp open rtsp
|
||||
-- | rtsp-methods:
|
||||
-- |_ DESCRIBE, SETUP, PLAY, TEARDOWN, OPTIONS
|
||||
--
|
||||
-- @args rtsp-methods.path the path to query, defaults to "*" which queries
|
||||
-- the server itself, rather than a specific url.
|
||||
--
|
||||
|
||||
--
|
||||
-- Version 0.1
|
||||
-- Created 23/10/2011 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
||||
--
|
||||
author = "Patrik Karlsson <patrik@cqure.net>"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"default", "safe"}
|
||||
|
||||
require 'rtsp'
|
||||
require 'shortport'
|
||||
|
||||
portrule = shortport.port_or_service(554, "rtsp", "tcp", "open")
|
||||
|
||||
action = function(host, port)
|
||||
local path = stdnse.get_script_args('rtsp-methods.path') or '*'
|
||||
local helper = rtsp.Helper:new(host, port)
|
||||
local status = helper:connect()
|
||||
if ( not(status) ) then
|
||||
stdnse.print_debug(2, "ERROR: Failed to connect to RTSP server")
|
||||
return
|
||||
end
|
||||
|
||||
local response
|
||||
status, response = helper:options(path)
|
||||
helper:close()
|
||||
if ( status ) then
|
||||
return stdnse.format_output(true, response.headers['Public'])
|
||||
end
|
||||
end
|
||||
156
scripts/rtsp-url-brute.nse
Normal file
156
scripts/rtsp-url-brute.nse
Normal file
@@ -0,0 +1,156 @@
|
||||
description = [[
|
||||
Attempts to brute common RTSP media URLs for devices such as surveillance IP
|
||||
cameras.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap --script rtsp-url-brute -p 554 <ip>
|
||||
--
|
||||
-- @output
|
||||
-- PORT STATE SERVICE
|
||||
-- 554/tcp open rtsp
|
||||
-- | rtsp-url-brute:
|
||||
-- | Discovered URLs
|
||||
-- |_ rtsp://camera.example.com/mpeg4
|
||||
--
|
||||
-- The script attempts to discover valid RTSP URLs by sending a DESCRIBE
|
||||
-- request for each URL in the dictionary. It then parses the response, based
|
||||
-- on which it determines whether the URL is valid or not.
|
||||
--
|
||||
-- @args rtsp-url-brute.urlfile sets an alternate URL dictionary file
|
||||
-- @args rtsp-url-brute.threads sets the maximum number of parallell threads to run
|
||||
|
||||
--
|
||||
-- Version 0.1
|
||||
-- Created 23/10/2011 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
||||
--
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"brute", "intrusive"}
|
||||
|
||||
require 'rtsp'
|
||||
require 'shortport'
|
||||
|
||||
portrule = shortport.port_or_service(554, "rtsp", "tcp", "open")
|
||||
|
||||
--- Retrieves the next RTSP relative URL from the datafile
|
||||
-- @param filename string containing the name of the file to read from
|
||||
-- @return url string containing the relative RTSP url
|
||||
urlIterator = function(fd)
|
||||
local function getNextUrl ()
|
||||
repeat
|
||||
local line = fd:read()
|
||||
if ( line and not(line:match('^#!comment:')) ) then
|
||||
coroutine.yield(line)
|
||||
end
|
||||
until(not(line))
|
||||
fd:close()
|
||||
while(true) do coroutine.yield(nil) end
|
||||
end
|
||||
return coroutine.wrap( getNextUrl )
|
||||
end
|
||||
|
||||
-- Fetches the next url from the iterator, creates an absolute url and tries
|
||||
-- to fetch it from the RTSP service.
|
||||
-- @param host table containing the host table as received by action
|
||||
-- @param port table containing the port table as received by action
|
||||
-- @param url_iter function containing the url iterator
|
||||
-- @param result table containing the urls that were successfully retrieved
|
||||
local function processURL(host, port, url_iter, result)
|
||||
local condvar = nmap.condvar(result)
|
||||
for u in url_iter do
|
||||
local name = ( host.targetname and #host.targetname > 0 ) and host.targetname or
|
||||
( host.name and #host.name > 0 ) and host.name or
|
||||
host.ip
|
||||
local url = ("rtsp://%s%s"):format(name, u)
|
||||
local helper = rtsp.Helper:new(host, port)
|
||||
local status = helper:connect()
|
||||
|
||||
if ( not(status) ) then
|
||||
stdnse.print_debug(2, "ERROR: Connecting to RTSP server url: %s", url)
|
||||
table.insert(result, { url = url, status = -1 } )
|
||||
break
|
||||
end
|
||||
|
||||
status, response = helper:describe(url)
|
||||
if ( not(status) ) then
|
||||
stdnse.print_debug(2, "ERROR: Sending DESCRIBE request to url: %s", url)
|
||||
table.insert(result, { url = url, status = -1 } )
|
||||
break
|
||||
end
|
||||
|
||||
table.insert(result, { url = url, status = response.status } )
|
||||
helper:close()
|
||||
end
|
||||
condvar "signal"
|
||||
end
|
||||
|
||||
action = function(host, port)
|
||||
|
||||
local response
|
||||
local result = {}
|
||||
local condvar = nmap.condvar(result)
|
||||
local threadcount = stdnse.get_script_args('rtsp-url-brute.threads') or 10
|
||||
local filename = stdnse.get_script_args('rtsp-url-brute.urlfile') or
|
||||
nmap.fetchfile("nselib/data/rtsp-urls.txt")
|
||||
|
||||
threadcount = tonumber(threadcount)
|
||||
|
||||
if ( not(filename) ) then
|
||||
return stdnse.format_output(false, "No dictionary could be loaded")
|
||||
end
|
||||
|
||||
local f = io.open(filename)
|
||||
if ( not(f) ) then
|
||||
return stdnse.format_output(false, ("Failed to open dictionary file: %s"):format(filename))
|
||||
end
|
||||
|
||||
local url_iter = urlIterator(f)
|
||||
if ( not(url_iter) ) then
|
||||
return stdnse.format_output(false, ("Could not open the URL dictionary: "):format(f))
|
||||
end
|
||||
|
||||
local threads = {}
|
||||
for t=1, threadcount do
|
||||
local co = stdnse.new_thread(processURL, host, port, url_iter, result)
|
||||
threads[co] = true
|
||||
end
|
||||
|
||||
repeat
|
||||
condvar "wait"
|
||||
for t in pairs(threads) do
|
||||
if ( coroutine.status(t) == "dead" ) then threads[t] = nil end
|
||||
end
|
||||
until( next(threads) == nil )
|
||||
|
||||
-- urls that could not be retrieved due to low level errors, such as
|
||||
-- failure in socket send or receive
|
||||
local failure_urls = { name='An error occured while testing the following URLs' }
|
||||
|
||||
-- urls that illicited a 200 OK response
|
||||
local success_urls = { name='Discovered URLs' }
|
||||
|
||||
-- urls requiring authentication
|
||||
-- local auth_urls = { name='URL requiring authentication' }
|
||||
|
||||
for _, r in ipairs(result) do
|
||||
if ( r.status == -1 ) then
|
||||
table.insert(failure_urls, r.url)
|
||||
elseif ( r.status == 200 ) then
|
||||
table.insert(success_urls, r.url)
|
||||
-- elseif ( r.status == 401 ) then
|
||||
-- table.insert(auth_urls, r.url )
|
||||
end
|
||||
end
|
||||
|
||||
local result = { success_urls, failure_urls }
|
||||
|
||||
-- -- insert our URLs requiring auth ONLY if not ALL urls returned auth
|
||||
-- if (#result > #auth_urls) then
|
||||
-- table.insert(result, 2, auth_urls)
|
||||
-- end
|
||||
|
||||
return stdnse.format_output(true, result )
|
||||
end
|
||||
@@ -187,6 +187,8 @@ Entry { filename = "realvnc-auth-bypass.nse", categories = { "auth", "default",
|
||||
Entry { filename = "resolveall.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "rmi-dumpregistry.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "rpcinfo.nse", categories = { "default", "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", } }
|
||||
Entry { filename = "sip-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "sip-enum-users.nse", categories = { "auth", "intrusive", } }
|
||||
|
||||
Reference in New Issue
Block a user