From 0d47952611198ef6b1163f366dc03922d20b1475 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 09:42:04 +0200 Subject: Adding upstream version 7.94+git20230807.3be01efb1+dfsg. Signed-off-by: Daniel Baumann --- scripts/rtsp-url-brute.nse | 202 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 scripts/rtsp-url-brute.nse (limited to 'scripts/rtsp-url-brute.nse') diff --git a/scripts/rtsp-url-brute.nse b/scripts/rtsp-url-brute.nse new file mode 100644 index 0000000..7a96c9a --- /dev/null +++ b/scripts/rtsp-url-brute.nse @@ -0,0 +1,202 @@ +local coroutine = require "coroutine" +local io = require "io" +local nmap = require "nmap" +local rtsp = require "rtsp" +local shortport = require "shortport" +local stdnse = require "stdnse" +local table = require "table" +local rand = require "rand" + +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. + +]] + +--- +-- @usage +-- nmap --script rtsp-url-brute -p 554 +-- +-- @output +-- PORT STATE SERVICE +-- 554/tcp open rtsp +-- | rtsp-url-brute: +-- | 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 + +-- +-- Version 0.1 +-- Created 23/10/2011 - v0.1 - created by Patrik Karlsson +-- + +author = "Patrik Karlsson" +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"brute", "intrusive"} + + +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 + +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 +-- @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) + local name = stdnse.get_hostname(host) + for u in url_iter do + local url = ("rtsp://%s%s"):format(name, u) + 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 + 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: %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), rand.random_alpha(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) + threads[co] = true + end + + repeat + for t in pairs(threads) do + if ( coroutine.status(t) == "dead" ) then threads[t] = nil end + end + if ( next(threads) ) then + condvar "wait" + 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 = {} + + -- urls that elicited a 200 OK response + local success_urls = {} + + -- 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 ~= 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 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 + + if #output > 0 then + return output + end +end -- cgit v1.2.3