Class: Rex::Proto::DHCPv6::Packet

Inherits:
BinData::Record
  • Object
show all
Defined in:
lib/rex/proto/dhcpv6/packet.rb

Overview

A DHCPv6 client/server message (RFC 8415 section 8): a one-byte message type, a three-byte transaction id, and a list of TLV options. This parses and builds the wire format and provides helpers for assembling the rogue server responses used to make a client adopt the attacker as its DNS server.

Relay messages (RELAY-FORW / RELAY-REPL) have a different layout and are not modelled here; only direct client/server messages are handled.

Defined Under Namespace

Classes: Dhcpv6Option

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.build_response(request:, server_duid:, dns_servers:, assigned_address: nil, preferred_lifetime: 300, valid_lifetime: 600, domain_list: nil) ⇒ Rex::Proto::DHCPv6::Packet?

Assemble a rogue server response that advertises the attacker as the client's DNS server. Echoes the client's transaction id and Client ID, and (for stateful requests carrying an IA_NA) leases the given address.

Parameters:

  • request (Rex::Proto::DHCPv6::Packet) —

    the parsed client message

  • server_duid (String) —

    the encoded server DUID (see duid_ll)

  • dns_servers (Array<String>) —

    DNS server IPv6 addresses to hand out

  • assigned_address (String, nil) (defaults to: nil) —

    address to lease, if answering statefully

  • preferred_lifetime (Integer) (defaults to: 300) —

    lease preferred lifetime, seconds

  • valid_lifetime (Integer) (defaults to: 600) —

    lease valid lifetime, seconds

  • domain_list (Array<String>, nil) (defaults to: nil) —

    optional DNS search domains

Returns:



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
# File 'lib/rex/proto/dhcpv6/packet.rb', line 140

def build_response(request:, server_duid:, dns_servers:, assigned_address: nil,
                   preferred_lifetime: 300, valid_lifetime: 600, domain_list: nil)
  response_type = response_type_for(request)
  return nil if response_type.nil?

  opts = [
    option(Constants::OptionCode::SERVERID, server_duid)
  ]

  client_id = request.find_option(Constants::OptionCode::CLIENTID)
  opts << option(Constants::OptionCode::CLIENTID, client_id.data.to_binary_s) unless client_id.nil?

  iaid = request_iaid(request)
  if !iaid.nil? && !assigned_address.nil?
    opts << ia_na_option(
      iaid: iaid,
      address: assigned_address,
      preferred_lifetime: preferred_lifetime,
      valid_lifetime: valid_lifetime,
      t1: preferred_lifetime / 2,
      t2: (preferred_lifetime * 4) / 5
    )
  end

  opts << dns_servers_option(dns_servers)
  opts << domain_list_option(domain_list) unless domain_list.nil? || domain_list.empty?
  opts << option(Constants::OptionCode::RAPID_COMMIT, '') if request.rapid_commit?

  new(
    msg_type: response_type,
    transaction_id: request.transaction_id.to_binary_s,
    options: opts.map { |o| { code: o.code, data: o.data.to_binary_s } }
  )
end

.dns_servers_option(addresses) ⇒ Rex::Proto::DHCPv6::Packet::Dhcpv6Option

Build a DNS Recursive Name Server option (RFC 3646): the list of IPv6 addresses the client should use as DNS servers.

Parameters:

  • addresses (Array<String>) —

    IPv6 addresses in presentation form

Returns:



66
67
68
# File 'lib/rex/proto/dhcpv6/packet.rb', line 66

def dns_servers_option(addresses)
  option(Constants::OptionCode::DNS_SERVERS, addresses.map { |a| Rex::Socket.addr_aton(a) }.join)
end

.domain_list_option(domains) ⇒ Rex::Proto::DHCPv6::Packet::Dhcpv6Option

Build a Domain Search List option (RFC 3646), DNS-name encoded.

Parameters:

  • domains (Array<String>) —

    search domains

Returns:



74
75
76
# File 'lib/rex/proto/dhcpv6/packet.rb', line 74

def domain_list_option(domains)
  option(Constants::OptionCode::DOMAIN_LIST, domains.map { |d| encode_dns_name(d) }.join)
end

.duid_ll(mac) ⇒ String

Build a DUID-LL (link-layer address DUID, RFC 8415 section 11.4).

Parameters:

  • mac (String) —

    the link-layer address, as "aa:bb:cc:dd:ee:ff" or 6 raw bytes

Returns:

  • (String) —

    the encoded DUID



57
58
59
# File 'lib/rex/proto/dhcpv6/packet.rb', line 57

def duid_ll(mac)
  [Constants::DuidType::LL, Constants::HARDWARE_TYPE_ETHERNET].pack('nn') + mac_to_bytes(mac)
end

.ia_na_option(iaid:, address:, preferred_lifetime:, valid_lifetime:, t1:, t2:) ⇒ Rex::Proto::DHCPv6::Packet::Dhcpv6Option

Build an IA_NA (Identity Association for Non-temporary Addresses) option carrying a single leased address, echoing the client's IAID.

Parameters:

  • iaid (Integer) —

    the client's IAID (from its IA_NA request)

  • address (String) —

    the IPv6 address to lease, in presentation form

  • preferred_lifetime (Integer) —

    seconds

  • valid_lifetime (Integer) —

    seconds

  • t1 (Integer) —

    renew timer, seconds

  • t2 (Integer) —

    rebind timer, seconds

Returns:



88
89
90
91
92
93
94
# File 'lib/rex/proto/dhcpv6/packet.rb', line 88

def ia_na_option(iaid:, address:, preferred_lifetime:, valid_lifetime:, t1:, t2:)
  iaaddr = option(
    Constants::OptionCode::IAADDR,
    Rex::Socket.addr_aton(address) + [preferred_lifetime, valid_lifetime].pack('NN')
  )
  option(Constants::OptionCode::IA_NA, [iaid, t1, t2].pack('NNN') + iaaddr.to_binary_s)
end

.option(code, data) ⇒ Rex::Proto::DHCPv6::Packet::Dhcpv6Option

Build a DHCPv6 option.

Parameters:

  • code (Integer) —

    the option code

  • data (String) —

    the option payload (already encoded)

Returns:



49
50
51
# File 'lib/rex/proto/dhcpv6/packet.rb', line 49

def option(code, data)
  Dhcpv6Option.new(code: code, data: data.b)
end

.request_iaid(request) ⇒ Integer?

Read the IAID out of a request's IA_NA option, if any.

Parameters:

Returns:

  • (Integer, nil)


100
101
102
103
104
105
# File 'lib/rex/proto/dhcpv6/packet.rb', line 100

def request_iaid(request)
  ia_na = request.find_option(Constants::OptionCode::IA_NA)
  return nil if ia_na.nil?

  ia_na.data.to_binary_s[0, 4].unpack1('N')
end

.response_type_for(request) ⇒ Integer?

The message type to answer a given request with (RFC 8415 section 18.3): a Solicit is answered with an Advertise, unless Rapid Commit is requested, in which case the exchange collapses to a Reply. Everything else we handle (Request/Renew/Rebind/Confirm/Information-Request) is answered with a Reply.

Parameters:

Returns:

  • (Integer, nil) —

    the response message type, or nil if we do not answer it



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/rex/proto/dhcpv6/packet.rb', line 114

def response_type_for(request)
  case request.msg_type
  when Constants::MessageType::SOLICIT
    request.rapid_commit? ? Constants::MessageType::REPLY : Constants::MessageType::ADVERTISE
  when Constants::MessageType::REQUEST,
       Constants::MessageType::RENEW,
       Constants::MessageType::REBIND,
       Constants::MessageType::CONFIRM,
       Constants::MessageType::INFORMATION_REQUEST
    Constants::MessageType::REPLY
  end
end

Instance Method Details

#find_option(code) ⇒ Rex::Proto::DHCPv6::Packet::Dhcpv6Option?

Returns the first option with the given code, if present.

Returns:



33
34
35
# File 'lib/rex/proto/dhcpv6/packet.rb', line 33

def find_option(code)
  options.find { |opt| opt.code == code }
end

#rapid_commit? ⇒ Boolean

Returns whether the client asked for a Rapid Commit (a two-message Solicit/Reply exchange rather than the four-message default).

Returns:

  • (Boolean) —

    whether the client asked for a Rapid Commit (a two-message Solicit/Reply exchange rather than the four-message default).



39
40
41
# File 'lib/rex/proto/dhcpv6/packet.rb', line 39

def rapid_commit?
  !find_option(Constants::OptionCode::RAPID_COMMIT).nil?
end