metasploit-framework/modules/auxiliary/admin/upnp/soap_portmapping.rb

141 lines
4.7 KiB
Ruby

##
# encoding: utf-8
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'nokogiri'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
def initialize
super(
'Name' => 'UPnP IGD SOAP Port Mapping Utility',
'Description' => %q{
Manage port mappings on UPnP IGD-capable device using the AddPortMapping and
DeletePortMapping SOAP requests
},
'Author' =>
[
'St0rn <fabien[at]anbu-pentest.com>', # initial module
'Jon Hart <jon_hart[at]rapid7.com>' # module cleanup and refactoring
],
'License' => MSF_LICENSE,
'References' => [['URL', 'http://www.upnp-hacks.org/igd.html']],
'DefaultAction' => 'ADD',
'Actions' =>
[
[ 'ADD',
{
'Description' => 'Use the AddPortMapping SOAP command to open and forward a port',
'SOAP_ACTION' => 'AddPortMapping'
}
],
[ 'DELETE',
{
'Description' => 'Use the DeletePortMapping SOAP command to remove a port forwarding',
'SOAP_ACTION' => 'DeletePortMapping'
}
]
],
)
register_options(
[
OptString.new('TARGETURI', [true, 'UPnP control URL', '/' ]),
OptAddress.new('INTERNAL_CLIENT', [false, 'Internal client hostname/IP']),
OptAddress.new('EXTERNAL_CLIENT', [false, 'External client hostname/IP']),
OptEnum.new('PROTOCOL', [true, 'Transport level protocol to map', 'TCP', %w(TCP UDP)]),
OptInt.new('INTERNAL_PORT', [false, 'Internal port']),
OptInt.new('EXTERNAL_PORT', [true, 'External port']),
OptInt.new('LEASE_DURATION', [false, 'Lease time for mapping, in seconds', 3600])
],
self.class
)
end
def internal_port
@internal_port ||= datastore['INTERNAL_PORT']
end
def internal_client
@internal_client ||= datastore['INTERNAL_CLIENT']
end
def external_port
@external_port ||= datastore['EXTERNAL_PORT']
end
def external_client
@external_client ||= datastore['EXTERNAL_CLIENT']
end
def lease_duration
@lease_duration ||= datastore['LEASE_DURATION']
end
def protocol
@protocol ||= datastore['PROTOCOL']
end
def soap_action
@soap_action ||= action.opts['SOAP_ACTION']
end
def build_soap
builder = ::Nokogiri::XML::Builder.new do |xml|
xml['SOAP-ENV'].Envelope('xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope', 'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/') do
xml['SOAP-ENV'].Body do
xml['m'].send(soap_action, 'xmlns:m' => 'urn:schemas-upnp-org:service:WANIPConnection:1') do
case action.name
when 'ADD'
xml.NewPortMappingDescription(Rex::Text.rand_text_alpha(8)) { xml.parent.namespace = nil }
xml.NewLeaseDuration(lease_duration) { xml.parent.namespace = nil }
xml.NewInternalClient(internal_client) { xml.parent.namespace = nil }
xml.NewEnabled(1) { xml.parent.namespace = nil }
xml.NewExternalPort(external_port) { xml.parent.namespace = nil }
xml.NewRemoteHost(external_client) { xml.parent.namespace = nil }
xml.NewProtocol(protocol) { xml.parent.namespace = nil }
xml.NewInternalPort(internal_port) { xml.parent.namespace = nil }
when 'DELETE'
xml.NewExternalPort(external_port) { xml.parent.namespace = nil }
xml.NewRemoteHost(external_client) { xml.parent.namespace = nil }
xml.NewProtocol(protocol) { xml.parent.namespace = nil }
end
end
end
end
end
builder.to_xml
end
def run
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path),
'method' => 'POST',
'content-type' => 'text/xml;charset="utf-8"',
'data' => build_soap,
'headers' => {
'SoapAction' => "urn:schemas-upnp-org:service:WANIPConnection:1##{soap_action}"
}
)
external_map = "#{external_client ? external_client : 'any'}:#{external_port}/#{protocol}"
internal_map = "#{internal_client ? internal_client : 'any'}:#{internal_port}/#{protocol}"
map = "#{external_map} -> #{internal_map}"
if res
if res.code == 200
print_good("#{peer} #{map} #{action.name} succeeded")
else
print_error("#{peer} #{map} #{action.name} failed with response code #{res.code}")
vprint_status("#{res.body}")
end
else
print_error("#{peer} no response for #{map} #{action.name}")
end
end
end