From 23ed953352ddc9120265629347359d9bd5d8f936 Mon Sep 17 00:00:00 2001 From: dmiller Date: Tue, 16 Aug 2016 04:52:11 +0000 Subject: [PATCH] Check for non-200 responses that may indicate url is present. --- scripts/rtsp-url-brute.nse | 113 ++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 38 deletions(-) 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