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/core/auxiliary/nmap.rb
Views: 11784
# -*- coding: binary -*-1require 'open3'23module Msf45###6#7# This module provides methods for interacting with nmap.8# Modules that include this should define their own nmap_build_args()9# function, and usually should have some method for dealing with10# the data yielded from nmap_hosts(). See auxiliary/scanner/oracle/oracle_login11# for an example implementation.12#13###1415module Auxiliary::Nmap1617attr_accessor :nmap_args, :nmap_bin, :nmap_log18attr_reader :nmap_pid, :nmap_ver1920def initialize(info = {})21super2223register_options([24Opt::RHOSTS,25OptBool.new('NMAP_VERBOSE', [ false, 'Display nmap output', true]),26OptString.new('RPORTS', [ false, 'Ports to target']), # RPORT supersedes RPORTS27], Auxiliary::Nmap)2829deregister_options("RPORT")30@nmap_args = []31@nmap_bin = nmap_binary_path32end3334def rports35datastore['RPORTS']36end3738def rport39datastore['RPORT']40end4142def set_nmap_cmd43self.nmap_bin || (raise "Cannot locate nmap binary")44nmap_set_log45nmap_add_ports46nmap_cmd = [self.nmap_bin]47self.nmap_args.unshift("-oX #{self.nmap_log[1]}")48nmap_cmd << self.nmap_args.join(" ")49nmap_cmd << datastore['RHOSTS']50nmap_cmd.join(" ")51end5253def get_nmap_ver54self.nmap_bin || (raise "Cannot locate nmap binary")55res = ""56nmap_cmd = [self.nmap_bin]57nmap_cmd << "--version"58res << %x{#{nmap_cmd.join(" ")}} rescue nil59res.gsub(/[\x0d\x0a]/n,"")60end6162# Takes a version string in the form of Major.Minor and compares to63# the found version. It yells at you specifically if you try to64# compare a float b/c that's going to be a super common error.65# Comparing an Integer is okay, though.66def nmap_version_at_least?(test_ver=nil)67raise ArgumentError, "Cannot compare a Float, use a String or Integer" if test_ver.kind_of? Float68unless test_ver.to_s[/^([0-9]+(\x2e[0-9]+)?)/n]69raise ArgumentError, "Bad Nmap comparison version: #{test_ver.inspect}"70end71test_ver_str = test_ver.to_s72tnum_arr = $1.split(/\x2e/n)[0,2].map {|x| x.to_i}73installed_ver = get_nmap_ver()74vtag = installed_ver.split[2] # Should be ["Nmap", "version", "X.YZTAG", "(", "http..", ")"]75return false if (vtag.nil? || vtag.empty?)76return false unless (vtag =~ /^([0-9]+\x2e[0-9]+)/n) # Drop the tag.77inum_arr = $1.split(/\x2e/n)[0,2].map {|x| x.to_i}78return true if inum_arr[0] > tnum_arr[0]79return false if inum_arr[0] < tnum_arr[0]80inum_arr[1].to_i >= tnum_arr[1].to_i81end8283def nmap_build_args84raise "nmap_build_args() not defined by #{self.refname}"85end8687def nmap_run88nmap_cmd = set_nmap_cmd89begin90nmap_pipe = ::Open3::popen3(nmap_cmd)91@nmap_pid = nmap_pipe.last.pid92print_status "Nmap: Starting nmap with pid #{@nmap_pid}"93temp_nmap_threads = []94temp_nmap_threads << framework.threads.spawn("Module(#{self.refname})-NmapStdout", false, nmap_pipe[1]) do |np_1|95np_1.each_line do |nmap_out|96next if nmap_out.strip.empty?97print_status "Nmap: #{nmap_out.strip}" if datastore['NMAP_VERBOSE']98end99end100101temp_nmap_threads << framework.threads.spawn("Module(#{self.refname})-NmapStderr", false, nmap_pipe[2]) do |np_2|102np_2.each_line do |nmap_err|103next if nmap_err.strip.empty?104print_status "Nmap: '#{nmap_err.strip}'"105end106end107108temp_nmap_threads.map {|t| t.join rescue nil}109nmap_pipe.each {|p| p.close rescue nil}110if self.nmap_log[0].size.zero?111print_error "Nmap Warning: Output file is empty, no useful results can be processed."112end113rescue ::IOError114end115end116117def nmap_binary_path118ret = Rex::FileUtils.find_full_path("nmap") || Rex::FileUtils.find_full_path("nmap.exe")119if ret120fullpath = ::File.expand_path(ret)121if fullpath =~ /\s/ # Thanks, "Program Files"122return "\"#{fullpath}\""123else124return fullpath125end126end127end128129# Returns the [filehandle, pathname], and sets the same130# to self.nmap_log.131# Only supports XML format since that's the most useful.132def nmap_set_log133outfile = Rex::Quickfile.new("msf3-nmap-")134if Rex::Compat.is_cygwin and self.nmap_bin =~ /cygdrive/i135outfile_path = Rex::Compat.cygwin_to_win32(outfile.path)136else137outfile_path = outfile.path138end139self.nmap_log = [outfile,outfile_path]140end141142def nmap_show_args143print_status self.nmap_args.join(" ")144end145146def nmap_append_arg(str)147if nmap_validate_arg(str)148self.nmap_args << str149end150end151152def nmap_reset_args153self.nmap_args = []154end155156# A helper to add in rport or rports as a -p argument157def nmap_add_ports158if not nmap_validate_rports159raise "Cannot continue without a valid port list."160end161port_arg = "-p \"#{datastore['RPORT'] || rports}\""162if nmap_validate_arg(port_arg)163self.nmap_args << port_arg164else165raise "Argument is invalid"166end167end168169# Validates the correctness of ports passed to nmap's -p170# option. Note that this will not validate named ports (like171# 'http'), nor will it validate when brackets are specified.172# The acceptable formats for this is:173#174# 80175# 80-90176# 22,23177# U:53,T:80178# and combinations thereof.179def nmap_validate_rports180# If there's an RPORT specified, use that instead.181if datastore['RPORT'] && (datastore['RPORT'].kind_of?(Integer) || !datastore['RPORT'].empty?)182return true183end184if rports.nil? || rports.empty?185print_error "Missing RPORTS"186return false187end188rports.split(/\s*,\s*/).each do |r|189if r =~ /^([TU]:)?[0-9]*-?[0-9]*$/190next191else192print_error "Malformed nmap port: #{r}"193return false194end195end196print_status "Using RPORTS range #{datastore['RPORTS']}"197return true198end199200# Validates an argument to be passed on the command201# line to nmap. Most special characters aren't allowed,202# and commas in arguments are only allowed inside a203# quoted argument.204def nmap_validate_arg(str)205# Check for existence206if str.nil? || str.empty?207print_error "Missing nmap argument"208return false209end210# Check for quote balance211if !(str.scan(/'/).size % 2).zero? or !(str.scan(/"/).size % 2).zero?212print_error "Unbalanced quotes in nmap argument: #{str}"213return false214end215# Check for characters that enable badness216disallowed_characters = /([\x00-\x19\x21\x23-\x26\x28\x29\x3b\x3e\x60\x7b\x7c\x7d\x7e-\xff])/n217badchar = str[disallowed_characters]218if badchar219print_error "Malformed nmap arguments (contains '#{badchar}'): #{str}"220return false221end222# Check for commas outside of quoted arguments223quoted_22 = /\x22[^\x22]*\x22/n224requoted_str = str.tr('\'','"')225if requoted_str.split(quoted_22).join[/,/]226print_error "Malformed nmap arguments (unquoted comma): #{str}"227return false228end229return true230end231232# Takes a block, and yields back the host object as discovered233# by the Rex::Parser::NmapXMLStreamParser. It's up to the234# module to ferret out whatever's interesting in this host235# object.236def nmap_hosts(&block)237@nmap_bin || (raise "Cannot locate the nmap binary.")238fh = self.nmap_log[0]239nmap_data = fh.read(fh.stat.size)240# fh.unlink241if Rex::Parser.nokogiri_loaded && framework.db.active242wspace = framework.db.find_workspace(datastore['WORKSPACE'])243wspace ||= framework.db.workspace244import_args = { :data => nmap_data, :workspace => wspace }245framework.db.import_nmap_noko_stream(import_args) { |type, data| yield type, data }246else247nmap_parser = Rex::Parser::NmapXMLStreamParser.new248nmap_parser.on_found_host = Proc.new { |h|249if (h["addrs"].has_key?("ipv4"))250addr = h["addrs"]["ipv4"]251elsif (h["addrs"].has_key?("ipv6"))252addr = h["addrs"]["ipv6"]253else254# Can't do much with it if it doesn't have an IP255next256end257yield h258}259REXML::Document.parse_stream(nmap_data, nmap_parser)260end261end262263#Saves the data from the nmap scan to a file in the MSF::Config.local_directory264def nmap_save()265print_status "Nmap: saving nmap log file"266fh = self.nmap_log[0]267nmap_data = fh.read(fh.stat.size)268saved_path = store_local("nmap.scan.xml", "text/xml", nmap_data, "nmap_#{Time.now.utc.to_i}.xml")269print_status "Saved NMAP XML results to #{saved_path}"270end271272end273end274275276277