1
0
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:
patrik
2011-10-26 21:36:37 +00:00
parent c9888b6596
commit 0270368e69
6 changed files with 571 additions and 0 deletions

View File

@@ -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
View 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
View 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
View 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
View 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

View File

@@ -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", } }