Share
## https://sploitus.com/exploit?id=PACKETSTORM:180933
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Auxiliary::Report  
include Msf::Auxiliary::Scanner  
include Msf::Exploit::Capture  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'URGENT/11 Scanner, Based on Detection Tool by Armis',  
'Description' => %q{  
This module detects VxWorks and the IPnet IP stack, along with devices  
vulnerable to CVE-2019-12258.  
},  
'Author' => [  
'Ben Seri', # Upstream tool  
'Brent Cook', # Metasploit module  
'wvu' # Metasploit module  
],  
'References' => [  
['CVE', '2019-12258'],  
['URL', 'https://armis.com/urgent11'],  
['URL', 'https://github.com/ArmisSecurity/urgent11-detector']  
],  
'DisclosureDate' => '2019-08-09', # NVD entry publication  
'License' => MSF_LICENSE,  
'Notes' => {'Stability' => [CRASH_SAFE]}  
))  
  
register_options([  
OptString.new('RPORTS', required: true, default: "21 22 23 80 443", desc: 'Target ports for TCP detections')  
])  
  
register_advanced_options([  
OptInt.new('RetransmissionRate', required: true, default: 3, desc: 'Send n TCP packets')  
])  
  
deregister_options('PCAPFILE', 'FILTER')  
end  
  
#  
# Utility methods  
#  
  
def rports  
datastore['RPORTS'].split(/[\s,]/).collect{|i| (i.to_i.to_s == i) ? i.to_i : nil}.compact  
end  
  
def filter(ip)  
"src host #{ip} and dst host #{Rex::Socket.source_address(ip)}"  
end  
  
#  
# Scanner methods  
#  
  
def run_host(ip)  
# XXX: Configuring Ethernet and IP headers sends a UDP packet!  
@config = PacketFu::Utils.whoami?(target: ip)  
  
open_pcap  
capture.setfilter(filter(ip))  
  
port_open = false  
rports.each do |rport|  
port_open |= run_detections(ip, rport)  
end  
raise RuntimeError.new("No ports open on #{ip} from #{datastore['RPORTS']}") if !port_open  
rescue RuntimeError => e  
fail_with(Failure::BadConfig, e.message)  
ensure  
close_pcap  
end  
  
def detections  
%w[  
tcp_dos_detection  
tcp_malformed_options_detection  
icmp_code_detection  
icmp_timestamp_detection  
]  
end  
  
def run_detections(ip, port)  
print_status("#{ip}:#{port} being checked")  
  
final_ipnet_score = 0  
final_vxworks_score = 0  
affected_vulnerabilities = []  
  
begin  
sock = Rex::Socket::Tcp.create(  
'PeerHost' => ip,  
'PeerPort' => port  
)  
rescue  
vprint_bad("Could not connect to #{ip}:#{port}, cannot verify vulnerability")  
return false  
end  
  
detections.each do |detection|  
@ipnet_score = 0  
@vxworks_score = 0  
@vulnerable_cves = []  
  
detection_name = detection.camelize  
  
begin  
send(detection, sock, ip, port)  
rescue StandardError => e  
vprint_error("#{detection_name} failed: #{e.message}")  
next  
end  
  
vprint_status(  
"\t#{detection_name.ljust(30)}" \  
"\tVxWorks: #{@vxworks_score}" \  
"\tIPnet: #{@ipnet_score}"  
)  
  
final_ipnet_score += @ipnet_score  
final_vxworks_score += @vxworks_score  
affected_vulnerabilities += @vulnerable_cves  
end  
  
sock.close  
  
if final_ipnet_score > 0  
vprint_good("#{ip}:#{port} detected as IPnet")  
elsif final_ipnet_score < 0  
vprint_error("#{ip}:#{port} detected as NOT IPnet")  
end  
  
if final_vxworks_score > 100  
vprint_good("#{ip}:#{port} detected as VxWorks")  
elsif final_vxworks_score < 0  
vprint_error("#{ip}:#{port} detected as NOT VxWorks")  
end  
  
affected_vulnerabilities.each do |vuln|  
msg = "#{ip}:#{port} affected by #{vuln}"  
print_good(msg)  
report_vuln(  
host: ip,  
name: name,  
refs: references,  
info: msg  
)  
end  
true  
end  
  
#  
# TCP detection methods  
#  
  
def tcp_malformed_options_detection(sock, ip, port)  
pkt = PacketFu::TCPPacket.new(config: @config)  
  
# IP destination address  
pkt.ip_daddr = ip  
  
# TCP SYN with malformed options  
pkt.tcp_dst = port  
pkt.tcp_flags.syn = 1  
pkt.tcp_opts = [2, 4, 1460].pack('CCn') + # MSS  
[1].pack('C') + # NOP  
[3, 2].pack('CC') + # WSCALE with invalid length  
[3, 3, 0].pack('CCC') # WSCALE with valid length  
pkt.recalc  
  
res = nil  
  
datastore['RetransmissionRate'].times do  
pkt.to_w  
res = inject_reply(:tcp)  
  
break unless res  
end  
  
unless res  
return @vxworks_score = 0,  
@ipnet_score = 50  
end  
  
if res.tcp_flags.rst == 1 &&  
res.tcp_dst == pkt.tcp_src && res.tcp_dst == pkt.tcp_src  
  
return @vxworks_score = 100,  
@ipnet_score = 100  
end  
  
return @vxworks_score = -100,  
@ipnet_score = -100  
end  
  
def tcp_dos_detection(sock, ip, port)  
pkt = PacketFu::TCPPacket.new(config: @config)  
  
# IP destination address  
pkt.ip_daddr = ip  
  
# TCP SYN with malformed (truncated) WS option  
pkt.tcp_src = sock.getlocalname.last  
pkt.tcp_dst = sock.peerport  
pkt.tcp_seq = rand(0xffffffff + 1)  
pkt.tcp_ack = rand(0xffffffff + 1)  
pkt.tcp_flags.syn = 1  
pkt.tcp_opts = [3, 2].pack('CC') + # WSCALE with invalid length  
[1, 0].pack('CC') # NOP + EOL  
pkt.recalc  
  
res = nil  
  
datastore['RetransmissionRate'].times do  
pkt.to_w  
res = inject_reply(:tcp)  
  
break unless res  
end  
  
unless res  
return @vxworks_score = 0,  
@ipnet_score = 0  
end  
  
if res.tcp_flags.rst == 1 &&  
res.tcp_dst == pkt.tcp_src && res.tcp_dst == pkt.tcp_src  
  
return @vxworks_score = 100,  
@ipnet_score = 100,  
@vulnerable_cves = ['CVE-2019-12258']  
end  
  
return @vxworks_score = 0,  
@ipnet_score = 0  
end  
  
#  
# ICMP detection methods  
#  
  
def icmp_code_detection(sock, ip, _port = nil)  
pkt = PacketFu::ICMPPacket.new(config: @config)  
  
# IP destination address  
pkt.ip_daddr = ip  
  
# ICMP echo request with non-zero code  
pkt.icmp_type = 8  
pkt.icmp_code = rand(0x01..0xff)  
pkt.payload = capture_icmp_echo_pack  
pkt.recalc  
  
pkt.to_w  
res = inject_reply(:icmp)  
  
unless res  
return @ipnet_score = 0  
end  
  
# Echo reply with zeroed code  
if res.icmp_type == 0 && res.icmp_code == 0  
return @ipnet_score = 20  
end  
  
@ipnet_score = -20  
end  
  
def icmp_timestamp_detection(sock, ip, _port = nil)  
pkt = PacketFu::ICMPPacket.new(config: @config)  
  
# IP destination address  
pkt.ip_daddr = ip  
  
# Truncated ICMP timestamp request  
pkt.icmp_type = 13  
pkt.icmp_code = 0  
pkt.payload = "\x00" * 4  
pkt.recalc  
  
pkt.to_w  
res = inject_reply(:icmp)  
  
unless res  
return @ipnet_score = 0  
end  
  
# Timestamp reply  
if res.icmp_type == 14  
return @ipnet_score = 90  
end  
  
@ipnet_score = -30  
end  
  
end