diff --git a/scripts/rtsp-url-brute.nse b/scripts/rtsp-url-brute.nse
index ab622e5809..7748d70cd6 100644
--- a/scripts/rtsp-url-brute.nse
+++ b/scripts/rtsp-url-brute.nse
@@ -8,6 +8,11 @@ local table = require "table"
description = [[
Attempts to enumerate RTSP media URLS by testing for common paths on devices such as surveillance IP cameras.
+
+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.
+
]]
---
@@ -18,12 +23,20 @@ Attempts to enumerate RTSP media URLS by testing for common paths on devices suc
-- 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.
+-- | discovered:
+-- | rtsp://camera.example.com/mpeg4
+-- | other responses:
+-- | 401:
+-- |_ rtsp://camera.example.com/live/mpeg4
+-- @xmloutput
+--
+-- rtsp://camera.example.com/mpeg4
+--
+--
+--
+-- rtsp://camera.example.com/live/mpeg4
+--
+--
--
-- @args rtsp-url-brute.urlfile sets an alternate URL dictionary file
-- @args rtsp-url-brute.threads sets the maximum number of parallel threads to run
@@ -57,6 +70,26 @@ urlIterator = function(fd)
return coroutine.wrap( getNextUrl )
end
+local function fetch_url(host, port, url)
+ local helper = rtsp.Helper:new(host, port)
+ local status = helper:connect()
+
+ if not status then
+ stdnse.debug2("ERROR: Connecting to RTSP server url: %s", url)
+ return nil
+ end
+
+ local response
+ status, response = helper:describe(url)
+ if not status then
+ stdnse.debug2("ERROR: Sending DESCRIBE request to url: %s", url)
+ return nil, response
+ end
+
+ helper:close()
+ return true, response
+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
@@ -65,30 +98,16 @@ end
-- @param result table containing the urls that were successfully retrieved
local function processURL(host, port, url_iter, result)
local condvar = nmap.condvar(result)
+ local name = stdnse.get_hostname(host)
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.debug2("ERROR: Connecting to RTSP server url: %s", url)
+ local status, response = fetch_url(host, port, url)
+ if not status then
table.insert(result, { url = url, status = -1 } )
break
+ else
+ table.insert(result, { url = url, status = response.status } )
end
-
- local response
- status, response = helper:describe(url)
- if ( not(status) ) then
- stdnse.debug2("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
@@ -118,6 +137,16 @@ action = function(host, port)
return stdnse.format_output(false, ("Could not open the URL dictionary: %s"):format(f))
end
+ -- Try to see what a nonexistent URL looks like
+ local status, response = fetch_url(
+ host, port, ("rtsp://%s/%s"):format(
+ stdnse.get_hostname(host), stdnse.generate_random_string(14))
+ )
+ local status_404 = 404
+ if status then
+ local status_404 = response.status
+ end
+
local threads = {}
for t=1, threadcount do
local co = stdnse.new_thread(processURL, host, port, url_iter, result)
@@ -135,30 +164,38 @@ action = function(host, port)
-- 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 occurred while testing the following URLs' }
+ local failure_urls = {}
-- urls that elicited a 200 OK response
- local success_urls = { name='Discovered URLs' }
+ local success_urls = {}
- -- urls requiring authentication
- -- local auth_urls = { name='URL requiring authentication' }
+ -- urls that got some non-404-type response
+ local urls_by_code = {}
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 )
+ elseif r.status ~= status_404 then
+ local s = tostring(r.status)
+ urls_by_code[s] = urls_by_code[s] or {}
+ table.insert(urls_by_code[s], 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
+ local output = stdnse.output_table()
+ if next(failure_urls) then
+ output.errors = failure_urls
+ end
+ if next(success_urls) then
+ output.discovered = success_urls
+ end
+ if next(urls_by_code) then
+ output["other responses"] = urls_by_code
+ end
- return stdnse.format_output(true, result )
+ if #output > 0 then
+ return output
+ end
end