mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 12:41:29 +00:00
Cases where the format string does not contain any placeholders, but values are given anyway. Cases where string.format is used without any placeholders or arguments.
165 lines
5.0 KiB
Lua
165 lines
5.0 KiB
Lua
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"
|
|
|
|
description = [[
|
|
Attempts to enumerate RTSP media URLS by testing for common paths on 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 parallel 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"}
|
|
|
|
|
|
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.debug2("ERROR: Connecting to RTSP server url: %s", url)
|
|
table.insert(result, { url = url, status = -1 } )
|
|
break
|
|
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
|
|
|
|
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
|
|
|
|
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 = { name='An error occurred while testing the following URLs' }
|
|
|
|
-- urls that elicited 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
|