Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/msf/ui/console/command_dispatcher/dns.rb
Views: 11784
# -*- coding: binary -*-12module Msf3module Ui4module Console5module CommandDispatcher67class DNS89include Msf::Ui::Console::CommandDispatcher1011ADD_USAGE = 'dns [add] [--index <insertion index>] [--rule <wildcard DNS entry>] [--session <session id>] <resolver> ...'.freeze12@@add_opts = Rex::Parser::Arguments.new(13['-i', '--index'] => [true, 'Index to insert at'],14['-r', '--rule'] => [true, 'Set a DNS wildcard entry to match against'],15['-s', '--session'] => [true, 'Force the DNS request to occur over a particular channel (override routing rules)']16)1718ADD_STATIC_USAGE = 'dns [add-static] <hostname> <IP address> ...'.freeze1920REMOVE_USAGE = 'dns [remove/del] -i <entry id> [-i <entry id> ...]'.freeze21@@remove_opts = Rex::Parser::Arguments.new(22['-i', '--index'] => [true, 'Index to remove at']23)2425REMOVE_STATIC_USAGE = 'dns [remove-static] <hostname> [<IP address> ...]'.freeze2627RESET_CONFIG_USAGE = 'dns [reset-config] [-y/--yes] [--system]'.freeze28@@reset_config_opts = Rex::Parser::Arguments.new(29['-y', '--yes'] => [false, 'Assume yes and do not prompt for confirmation before resetting'],30['--system'] => [false, 'Include the system resolver']31)3233RESOLVE_USAGE = 'dns [resolve] [-f <address family>] <hostname> ...'.freeze34@@resolve_opts = Rex::Parser::Arguments.new(35# same usage syntax as Rex::Post::Meterpreter::Ui::Console::CommandDispatcher::Stdapi36['-f'] => [true, 'Address family - IPv4 or IPv6 (default IPv4)']37)3839def initialize(driver)40super41end4243def name44'DNS'45end4647def commands48commands = {}4950if framework.features.enabled?(Msf::FeatureManager::DNS)51commands = {52'dns' => "Manage Metasploit's DNS resolving behaviour"53}54end55commands56end5758#59# Tab completion for the dns command60#61# @param str [String] the string currently being typed before tab was hit62# @param words [Array<String>] the previously completed words on the command line. The array63# contains at least one entry when tab completion has reached this stage since the command itself has been completed64def cmd_dns_tabs(str, words)65return if driver.framework.dns_resolver.nil?6667subcommands = %w[ add add-static delete flush-cache flush-entries flush-static help print query remove remove-static reset-config resolve ]68if words.length == 169return subcommands.select { |opt| opt.start_with?(str) }70end7172cmd = words[1]73case cmd74when 'add'75# We expect a repeating pattern of tag (e.g. -r) and then a value (e.g. *.metasploit.com)76# Once this pattern is violated, we're just specifying DNS servers at that point.77tag_is_expected = true78if words.length > 279words[2..-1].each do |word|80if tag_is_expected && !word.start_with?('-')81return82end83tag_is_expected = !tag_is_expected84end85end8687case words[-1]88when '-r', '--rule'89# Hard to auto-complete a rule with any meaningful value; just return90return91when '-s', '--session'92session_ids = driver.framework.sessions.keys.map { |k| k.to_s }93return session_ids.select { |id| id.start_with?(str) }94when /^-/95# Unknown tag96return97end9899options = @@add_opts.option_keys.select { |opt| opt.start_with?(str) }100options << '' # Prevent tab-completion of a dash, given they could provide an IP address at this point101return options102when 'add-static'103if words.length == 2104# tab complete existing hostnames because they can have more than one IP address105return resolver.static_hostnames.each.select { |hostname,_| hostname.downcase.start_with?(str.downcase) }.map { |hostname,_| hostname }106end107when 'help'108# These commands don't have any arguments109return subcommands.select { |sc| sc.start_with?(str) }110when 'remove','delete'111if words[-1] == '-i'112return113else114return @@remove_opts.option_keys.select { |opt| opt.start_with?(str) }115end116when 'remove-static'117if words.length == 2118return resolver.static_hostnames.each.select { |hostname,_| hostname.downcase.start_with?(str.downcase) }.map { |hostname,_| hostname }119elsif words.length > 2120hostname = words[2]121ip_addresses = resolver.static_hostnames.get(hostname, Dnsruby::Types::A) + resolver.static_hostnames.get(hostname, Dnsruby::Types::AAAA)122return ip_addresses.map(&:to_s).select { |ip_address| ip_address.start_with?(str) }123end124when 'reset-config'125@@reset_config_opts.option_keys.select { |opt| opt.start_with?(str) }126when 'resolve','query'127if words[-1] == '-f'128families = %w[ IPv4 IPv6 ] # The family argument is case-insensitive129return families.select { |family| family.downcase.start_with?(str.downcase) }130else131@@resolve_opts.option_keys.select { |opt| opt.start_with?(str) }132end133end134end135136def cmd_dns_help(*args)137if args.first.present?138handler = "#{args.first.gsub('-', '_')}_dns"139if respond_to?("#{handler}_help")140# if it is a valid command with dedicated help information141return send("#{handler}_help")142elsif respond_to?(handler)143# if it is a valid command without dedicated help information144print_error("No help menu is available for #{args.first}")145return146else147print_error("Invalid subcommand: #{args.first}")148end149end150151print_line "Manage Metasploit's DNS resolution behaviour"152print_line153print_line "USAGE:"154print_line " #{ADD_USAGE}"155print_line " #{ADD_STATIC_USAGE}"156print_line " #{REMOVE_USAGE}"157print_line " #{REMOVE_STATIC_USAGE}"158print_line " dns [flush-cache]"159print_line " dns [flush-entries]"160print_line " dns [flush-static]"161print_line " dns [print]"162print_line " #{RESET_CONFIG_USAGE}"163print_line " #{RESOLVE_USAGE}"164print_line " dns [help] [subcommand]"165print_line166print_line "SUBCOMMANDS:"167print_line " add - Add a DNS resolution entry to resolve certain domain names through a particular DNS resolver"168print_line " add-static - Add a statically defined hostname"169print_line " flush-cache - Remove all cached DNS answers"170print_line " flush-entries - Remove all configured DNS resolution entries"171print_line " flush-static - Remove all statically defined hostnames"172print_line " print - Show all configured DNS resolution entries"173print_line " remove - Delete a DNS resolution entry"174print_line " remove-static - Delete a statically defined hostname"175print_line " reset-config - Reset the DNS configuration"176print_line " resolve - Resolve a hostname"177print_line178print_line "EXAMPLES:"179print_line " Display help information for the 'add' subcommand"180print_line " dns help add"181print_line182end183184#185# Manage Metasploit's DNS resolution rules186#187def cmd_dns(*args)188if driver.framework.dns_resolver.nil?189print_warning("Run the #{Msf::Ui::Tip.highlight("save")} command and restart the console for this feature configuration to take effect.")190return191end192193args << 'print' if args.length == 0194# Short-circuit help195if args.delete("-h") || args.delete("--help")196subcommand = args.first197if subcommand && respond_to?("#{subcommand.gsub('-', '_')}_dns_help")198# if it is a valid command with dedicated help information199send("#{subcommand.gsub('-', '_')}_dns_help")200else201# otherwise print the top-level help information202cmd_dns_help203end204return205end206207action = args.shift208begin209case action210when "add"211add_dns(*args)212when "add-static"213add_static_dns(*args)214when "flush-entries"215flush_entries_dns216when "flush-cache"217flush_cache_dns218when "flush-static"219flush_static_dns220when "help"221cmd_dns_help(*args)222when "print"223print_dns224when "remove", "rm", "delete", "del"225remove_dns(*args)226when "remove-static"227remove_static_dns(*args)228when "reset-config"229reset_config_dns(*args)230when "resolve", "query"231resolve_dns(*args)232else233print_error("Invalid command. To view help: dns -h")234end235rescue ::ArgumentError => e236print_error(e.message)237end238end239240def add_dns(*args)241rules = ['*']242first_rule = true243comm = nil244resolvers = []245index = -1246@@add_opts.parse(args) do |opt, idx, val|247unless resolvers.empty? || opt.nil?248raise ::ArgumentError.new("Invalid command near #{opt}")249end250case opt251when '-i', '--index'252raise ::ArgumentError.new("Not a valid index: #{val}") unless val.to_i > 0253254index = val.to_i - 1255when '-r', '--rule'256raise ::ArgumentError.new('No rule specified') if val.nil?257258rules.clear if first_rule # if the user defines even one rule, clear the defaults259first_rule = false260rules << val261when '-s', '--session'262if val.nil?263raise ::ArgumentError.new('No session specified')264end265266unless comm.nil?267raise ::ArgumentError.new('Only one session can be specified')268end269270comm = val271when nil272val = 'black-hole' if val.casecmp?('blackhole')273resolvers << val274else275raise ::ArgumentError.new("Unknown flag: #{opt}")276end277end278279# The remaining args should be the DNS servers280if resolvers.length < 1281raise ::ArgumentError.new('You must specify at least one upstream DNS resolver')282end283284resolvers.each do |resolver|285unless Rex::Proto::DNS::UpstreamRule.valid_resolver?(resolver)286message = "Invalid DNS resolver: #{resolver}."287if (suggestions = Rex::Proto::DNS::UpstreamRule.spell_check_resolver(resolver)).present?288message << " Did you mean #{suggestions.first}?"289end290291raise ::ArgumentError.new(message)292end293end294295comm_obj = nil296297unless comm.nil?298raise ::ArgumentError.new("Not a valid session: #{comm}") unless comm =~ /\A-?[0-9]+\Z/299300comm_obj = driver.framework.sessions.get(comm.to_i)301raise ::ArgumentError.new("Session does not exist: #{comm}") unless comm_obj302raise ::ArgumentError.new("Socket Comm (Session #{comm}) does not implement Rex::Socket::Comm") unless comm_obj.is_a? ::Rex::Socket::Comm303304if resolvers.any? { |resolver| SPECIAL_RESOLVERS.include?(resolver.downcase) }305print_warning("The session argument will be ignored for the system resolver")306end307end308309rules.each_with_index do |rule, offset|310print_warning("DNS rule #{rule} does not contain wildcards, it will not match subdomains") unless rule.include?('*')311driver.framework.dns_resolver.add_upstream_rule(312resolvers,313comm: comm_obj,314wildcard: rule,315index: (index == -1 ? -1 : offset + index)316)317end318319print_good("#{rules.length} DNS #{rules.length > 1 ? 'entries' : 'entry'} added")320end321322def add_dns_help323print_line "USAGE:"324print_line " #{ADD_USAGE}"325print_line @@add_opts.usage326print_line "RESOLVERS:"327print_line " ipv4 / ipv6 address - The IP address of an upstream DNS server to resolve from"328print_line " #{Rex::Proto::DNS::UpstreamResolver::Type::BLACK_HOLE.to_s.ljust(19)} - Drop all queries"329print_line " #{Rex::Proto::DNS::UpstreamResolver::Type::STATIC.to_s.ljust(19) } - Reply with statically configured addresses (only for A/AAAA records)"330print_line " #{Rex::Proto::DNS::UpstreamResolver::Type::SYSTEM.to_s.ljust(19) } - Use the host operating systems DNS resolution functionality (only for A/AAAA records)"331print_line332print_line "EXAMPLES:"333print_line " Set the DNS server(s) to be used for *.metasploit.com to 192.168.1.10"334print_line " dns add --rule *.metasploit.com 192.168.1.10"335print_line336print_line " Add multiple entries at once"337print_line " dns add --rule *.metasploit.com --rule *.google.com 192.168.1.10 192.168.1.11"338print_line339print_line " Set the DNS server(s) to be used for *.metasploit.com to 192.168.1.10, but specifically to go through session 2"340print_line " dns add --session 2 --rule *.metasploit.com 192.168.1.10"341end342343def add_static_dns(*args)344if args.length < 2345raise ::ArgumentError.new('A hostname and IP address must be provided')346end347348hostname = args.shift349if !Rex::Proto::DNS::StaticHostnames.is_valid_hostname?(hostname)350raise ::ArgumentError.new("Invalid hostname: #{hostname}")351end352353ip_addresses = args354if (ip_address = ip_addresses.find { |a| !Rex::Socket.is_ip_addr?(a) })355raise ::ArgumentError.new("Invalid IP address: #{ip_address}")356end357358ip_addresses.each do |ip_address|359resolver.static_hostnames.add(hostname, ip_address)360print_status("Added static hostname mapping #{hostname} to #{ip_address}")361end362end363364def add_static_dns_help365print_line "USAGE:"366print_line " #{ADD_STATIC_USAGE}"367print_line368print_line "EXAMPLES:"369print_line " Define a static entry mapping localhost6 to ::1"370print_line " dns add-static localhost6 ::1"371end372373#374# Query a hostname using the configuration. This is useful for debugging and375# inspecting the active settings.376#377def resolve_dns(*args)378names = []379query_type = Dnsruby::Types::A380381@@resolve_opts.parse(args) do |opt, idx, val|382unless names.empty? || opt.nil?383raise ::ArgumentError.new("Invalid command near #{opt}")384end385case opt386when '-f'387case val.downcase388when 'ipv4'389query_type = Dnsruby::Types::A390when'ipv6'391query_type = Dnsruby::Types::AAAA392else393raise ::ArgumentError.new("Invalid family: #{val}")394end395when nil396names << val397else398raise ::ArgumentError.new("Unknown flag: #{opt}")399end400end401402if names.length < 1403raise ::ArgumentError.new('You must specify at least one hostname to resolve')404end405406tbl = Table.new(407Table::Style::Default,408'Header' => 'Host resolutions',409'Prefix' => "\n",410'Postfix' => "\n",411'Columns' => ['Hostname', 'IP Address', 'Rule #', 'Rule', 'Resolver', 'Comm channel'],412'ColProps' => { 'Hostname' => { 'Strip' => false } },413'SortIndex' => -1,414'WordWrap' => false415)416names.each do |name|417upstream_rule = resolver.upstream_rules.find { |ur| ur.matches_name?(name) }418if upstream_rule.nil?419tbl << [name, '[Failed To Resolve]', '', '', '', '']420next421end422423upstream_rule_idx = resolver.upstream_rules.index(upstream_rule) + 1424425begin426result = resolver.query(name, query_type)427rescue NoResponseError428tbl = append_resolver_cells!(tbl, upstream_rule, prefix: [name, '[Failed To Resolve]'], index: upstream_rule_idx)429else430if result.answer.empty?431tbl = append_resolver_cells!(tbl, upstream_rule, prefix: [name, '[Failed To Resolve]'], index: upstream_rule_idx)432else433result.answer.select do |answer|434answer.type == query_type435end.map(&:address).map(&:to_s).each do |address|436tbl = append_resolver_cells!(tbl, upstream_rule, prefix: [name, address], index: upstream_rule_idx)437end438end439end440end441print(tbl.to_s)442end443444def resolve_dns_help445print_line "USAGE:"446print_line " #{RESOLVE_USAGE}"447print_line @@resolve_opts.usage448print_line "EXAMPLES:"449print_line " Resolve a hostname to an IPv6 address using the current configuration"450print_line " dns resolve -f IPv6 www.metasploit.com"451print_line452end453454#455# Remove all matching user-configured DNS entries456#457def remove_dns(*args)458remove_ids = []459@@remove_opts.parse(args) do |opt, idx, val|460case opt461when '-i', '--index'462raise ::ArgumentError.new("Not a valid index: #{val}") unless val.to_i > 0463464remove_ids << val.to_i - 1465end466end467468if remove_ids.empty?469raise ::ArgumentError.new('At least one index to remove must be provided')470end471472removed = resolver.remove_ids(remove_ids)473print_warning('Some entries were not removed') unless removed.length == remove_ids.length474if removed.length > 0475print_good("#{removed.length} DNS #{removed.length > 1 ? 'entries' : 'entry'} removed")476print_dns_set('Deleted entries', removed, ids: [nil] * removed.length)477end478end479480def remove_dns_help481print_line "USAGE:"482print_line " #{REMOVE_USAGE}"483print_line(@@remove_opts.usage)484print_line "EXAMPLES:"485print_line " Delete the DNS resolution rule #3"486print_line " dns remove -i 3"487print_line488print_line " Delete multiple rules in one command"489print_line " dns remove -i 3 -i 4 -i 5"490print_line491end492493def remove_static_dns(*args)494if args.length < 1495raise ::ArgumentError.new('A hostname must be provided')496end497498hostname = args.shift499if !Rex::Proto::DNS::StaticHostnames.is_valid_hostname?(hostname)500raise ::ArgumentError.new("Invalid hostname: #{hostname}")501end502503ip_addresses = args504if ip_addresses.empty?505ip_addresses = resolver.static_hostnames.get(hostname, Dnsruby::Types::A) + resolver.static_hostnames.get(hostname, Dnsruby::Types::AAAA)506if ip_addresses.empty?507print_status("There are no definitions for hostname: #{hostname}")508end509elsif (ip_address = ip_addresses.find { |ip| !Rex::Socket.is_ip_addr?(ip) })510raise ::ArgumentError.new("Invalid IP address: #{ip_address}")511end512513ip_addresses.each do |ip_address|514resolver.static_hostnames.delete(hostname, ip_address)515print_status("Removed static hostname mapping #{hostname} to #{ip_address}")516end517end518519def remove_static_dns_help520print_line "USAGE:"521print_line " #{REMOVE_STATIC_USAGE}"522print_line523print_line "EXAMPLES:"524print_line " Remove all IPv4 and IPv6 addresses for 'localhost'"525print_line " dns remove-static localhost"526print_line527end528529def reset_config_dns(*args)530add_system_resolver = false531should_confirm = true532@@reset_config_opts.parse(args) do |opt, idx, val|533case opt534when '--system'535add_system_resolver = true536when '-y', '--yes'537should_confirm = false538end539end540541if should_confirm542print("Are you sure you want to reset the DNS configuration? [y/N]: ")543response = gets.downcase.chomp544return unless response =~ /^y/i545end546547resolver.reinit548print_status('The DNS configuration has been reset')549550if add_system_resolver551# if the user requested that we add the system resolver552system_resolver = Rex::Proto::DNS::UpstreamResolver.create_system553# first find the default, catch-all rule554default_rule = resolver.upstream_rules.find { |ur| ur.matches_all? }555if default_rule.nil?556resolver.add_upstream_rule([ system_resolver ])557else558# if the first resolver is for static hostnames, insert after that one559if default_rule.resolvers.first&.type == Rex::Proto::DNS::UpstreamResolver::Type::STATIC560index = 1561else562index = 0563end564default_rule.resolvers.insert(index, system_resolver)565end566end567568print_dns569570if ENV['PROXYCHAINS_CONF_FILE'] && !add_system_resolver571print_warning('Detected proxychains but the system resolver was not added')572end573end574575def reset_config_dns_help576print_line "USAGE:"577print_line " #{RESET_CONFIG_USAGE}"578print_line @@reset_config_opts.usage579print_line "EXAMPLES:"580print_line " Reset the configuration without prompting to confirm"581print_line " dns reset-config --yes"582print_line583end584585#586# Delete all cached DNS answers587#588def flush_cache_dns589resolver.cache.flush590print_good('DNS cache flushed')591end592593#594# Delete all user-configured DNS settings595#596def flush_entries_dns597resolver.flush598print_good('DNS entries flushed')599end600601def flush_static_dns602resolver.static_hostnames.flush603print_good('DNS static hostnames flushed')604end605606#607# Display the user-configured DNS settings608#609def print_dns610default_domain = 'N/A'611if resolver.defname? && resolver.domain.present?612default_domain = resolver.domain613end614print_line("Default search domain: #{default_domain}")615616searchlist = resolver.searchlist617case searchlist.length618when 0619print_line('Default search list: N/A')620when 1621print_line("Default search list: #{searchlist.first}")622else623print_line('Default search list:')624searchlist.each do |entry|625print_line(" * #{entry}")626end627end628print_line("Current cache size: #{resolver.cache.records.length}")629630upstream_rules = resolver.upstream_rules631print_dns_set('Resolver rule entries', upstream_rules, ids: (1..upstream_rules.length).to_a)632if upstream_rules.empty?633print_line634print_error('No DNS nameserver entries configured')635end636637tbl = Table.new(638Table::Style::Default,639'Header' => 'Static hostnames',640'Prefix' => "\n",641'Postfix' => "\n",642'Columns' => ['Hostname', 'IPv4 Address', 'IPv6 Address'],643'ColProps' => { 'Hostname' => { 'Strip' => false } },644'SortIndex' => -1,645'WordWrap' => false646)647resolver.static_hostnames.sort_by { |hostname, _| hostname }.each do |hostname, addresses|648ipv4_addresses = addresses.fetch(Dnsruby::Types::A, []).sort_by(&:to_i)649ipv6_addresses = addresses.fetch(Dnsruby::Types::AAAA, []).sort_by(&:to_i)650if (ipv4_addresses.length <= 1 && ipv6_addresses.length <= 1) && ((ipv4_addresses + ipv6_addresses).length > 0)651tbl << [hostname, ipv4_addresses.first, ipv6_addresses.first]652else653tbl << [hostname, '', '']6540.upto([ipv4_addresses.length, ipv6_addresses.length].max - 1) do |idx|655tbl << [TABLE_INDENT, ipv4_addresses[idx], ipv6_addresses[idx]]656end657end658end659print_line(tbl.to_s)660if resolver.static_hostnames.empty?661print_line('No static hostname entries are configured')662end663end664665private666667SPECIAL_RESOLVERS = [668Rex::Proto::DNS::UpstreamResolver::Type::BLACK_HOLE.to_s.downcase,669Rex::Proto::DNS::UpstreamResolver::Type::SYSTEM.to_s.downcase670].freeze671672TABLE_INDENT = " \\_ ".freeze673674#675# Get user-friendly text for displaying the session that this entry would go through676#677def prettify_comm(comm, upstream_resolver)678if !Rex::Socket.is_ip_addr?(upstream_resolver.destination)679'N/A'680elsif comm.nil?681channel = Rex::Socket::SwitchBoard.best_comm(upstream_resolver.destination)682if channel.nil?683nil684else685"Session #{channel.sid} (route)"686end687else688if comm.alive?689"Session #{comm.sid}"690else691"Closed session (#{comm.sid})"692end693end694end695696def print_dns_set(heading, result_set, ids: [])697return if result_set.length == 0698columns = ['#', 'Rule', 'Resolver', 'Comm channel']699col_props = { 'Rule' => { 'Strip' => false } }700701tbl = Table.new(702Table::Style::Default,703'Header' => heading,704'Prefix' => "\n",705'Postfix' => "\n",706'Columns' => columns,707'ColProps' => col_props,708'SortIndex' => -1,709'WordWrap' => false710)711result_set.each_with_index do |entry, index|712tbl = append_resolver_cells!(tbl, entry, index: ids[index])713end714715print(tbl.to_s) if tbl.rows.length > 0716end717718def append_resolver_cells!(tbl, entry, prefix: [], suffix: [], index: nil)719alignment_prefix = prefix.empty? ? [] : (['.'] * prefix.length)720721if entry.resolvers.length == 1722tbl << prefix + [index.to_s, entry.wildcard, entry.resolvers.first, prettify_comm(entry.comm, entry.resolvers.first)] + suffix723elsif entry.resolvers.length > 1724tbl << prefix + [index.to_s, entry.wildcard, '', ''] + suffix725entry.resolvers.each do |resolver|726tbl << alignment_prefix + ['.', TABLE_INDENT, resolver, prettify_comm(entry.comm, resolver)] + ([''] * suffix.length)727end728end729tbl730end731732def resolver733self.driver.framework.dns_resolver734end735end736737end738end739end740end741742743