Module: Msf::Auxiliary::Nmap

Defined in:
lib/msf/core/auxiliary/nmap.rb

Overview

This module provides methods for interacting with nmap. Modules that include this should define their own nmap_build_args() function, and usually should have some method for dealing with the data yielded from nmap_hosts(). See auxiliary/scanner/oracle/oracle_login for an example implementation.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#nmap_argsObject

Returns the value of attribute nmap_args


20
21
22
# File 'lib/msf/core/auxiliary/nmap.rb', line 20

def nmap_args
  @nmap_args
end

#nmap_binObject

Returns the value of attribute nmap_bin


20
21
22
# File 'lib/msf/core/auxiliary/nmap.rb', line 20

def nmap_bin
  @nmap_bin
end

#nmap_logObject

Returns the value of attribute nmap_log


20
21
22
# File 'lib/msf/core/auxiliary/nmap.rb', line 20

def nmap_log
  @nmap_log
end

#nmap_pidObject (readonly)

Returns the value of attribute nmap_pid


21
22
23
# File 'lib/msf/core/auxiliary/nmap.rb', line 21

def nmap_pid
  @nmap_pid
end

#nmap_verObject (readonly)

Returns the value of attribute nmap_ver


21
22
23
# File 'lib/msf/core/auxiliary/nmap.rb', line 21

def nmap_ver
  @nmap_ver
end

Instance Method Details

#get_nmap_verObject


56
57
58
59
60
61
62
63
# File 'lib/msf/core/auxiliary/nmap.rb', line 56

def get_nmap_ver
  self.nmap_bin || (raise RuntimeError, "Cannot locate nmap binary")
  res = ""
  nmap_cmd = [self.nmap_bin]
  nmap_cmd << "--version"
  res << %x{#{nmap_cmd.join(" ")}} rescue nil
  res.gsub(/[\x0d\x0a]/n,"")
end

#initialize(info = {}) ⇒ Object


23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/msf/core/auxiliary/nmap.rb', line 23

def initialize(info = {})
  super

  register_options([
    OptAddressRange.new('RHOSTS', [ true, "The target address range or CIDR identifier"]),
    OptBool.new('NMAP_VERBOSE', [ false, 'Display nmap output', true]),
    OptString.new('RPORTS', [ false, 'Ports to target']), # RPORT supersedes RPORTS
  ], Auxiliary::Nmap)

  deregister_options("RPORT")
  @nmap_args = []
  @nmap_bin = nmap_binary_path
end

#nmap_add_portsObject

A helper to add in rport or rports as a -p argument


160
161
162
163
164
165
166
167
168
169
170
# File 'lib/msf/core/auxiliary/nmap.rb', line 160

def nmap_add_ports
  if not nmap_validate_rports
    raise RuntimeError, "Cannot continue without a valid port list."
  end
  port_arg = "-p \"#{datastore['RPORT'] || rports}\""
  if nmap_validate_arg(port_arg)
    self.nmap_args << port_arg
  else
    raise RunTimeError, "Argument is invalid"
  end
end

#nmap_append_arg(str) ⇒ Object


149
150
151
152
153
# File 'lib/msf/core/auxiliary/nmap.rb', line 149

def nmap_append_arg(str)
  if nmap_validate_arg(str)
    self.nmap_args << str
  end
end

#nmap_binary_pathObject


120
121
122
123
124
125
126
127
128
129
130
# File 'lib/msf/core/auxiliary/nmap.rb', line 120

def nmap_binary_path
  ret = Rex::FileUtils.find_full_path("nmap") || Rex::FileUtils.find_full_path("nmap.exe")
  if ret
    fullpath = ::File.expand_path(ret)
    if fullpath =~ /\s/ # Thanks, "Program Files"
      return "\"#{fullpath}\""
    else
      return fullpath
    end
  end
end

#nmap_build_argsObject

Raises:

  • (RuntimeError)

86
87
88
# File 'lib/msf/core/auxiliary/nmap.rb', line 86

def nmap_build_args
  raise RuntimeError, "nmap_build_args() not defined by #{self.refname}"
end

#nmap_hosts(&block) ⇒ Object

Takes a block, and yields back the host object as discovered by the Rex::Parser::NmapXMLStreamParser. It's up to the module to ferret out whatever's interesting in this host object.


239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/msf/core/auxiliary/nmap.rb', line 239

def nmap_hosts(&block)
  @nmap_bin || (raise RuntimeError, "Cannot locate the nmap binary.")
  fh = self.nmap_log[0]
  nmap_data = fh.read(fh.stat.size)
  # fh.unlink
  if Rex::Parser.nokogiri_loaded && framework.db.active
    wspace = framework.db.find_workspace(datastore['WORKSPACE'])
    wspace ||= framework.db.workspace
    import_args = { :data => nmap_data, :wspace => wspace }
    framework.db.import_nmap_noko_stream(import_args) { |type, data| yield type, data }
  else
    nmap_parser = Rex::Parser::NmapXMLStreamParser.new
    nmap_parser.on_found_host = Proc.new { |h|
      if (h["addrs"].has_key?("ipv4"))
        addr = h["addrs"]["ipv4"]
      elsif (h["addrs"].has_key?("ipv6"))
        addr = h["addrs"]["ipv6"]
      else
        # Can't do much with it if it doesn't have an IP
        next
      end
      yield h
    }
    REXML::Document.parse_stream(nmap_data, nmap_parser)
  end
end

#nmap_reset_argsObject


155
156
157
# File 'lib/msf/core/auxiliary/nmap.rb', line 155

def nmap_reset_args
  self.nmap_args = []
end

#nmap_runObject


90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/msf/core/auxiliary/nmap.rb', line 90

def nmap_run
  nmap_cmd = set_nmap_cmd
  begin
    nmap_pipe = ::Open3::popen3(nmap_cmd)
    @nmap_pid = nmap_pipe.last.pid
    print_status "Nmap: Starting nmap with pid #{@nmap_pid}"
    temp_nmap_threads = []
    temp_nmap_threads << framework.threads.spawn("Module(#{self.refname})-NmapStdout", false, nmap_pipe[1]) do |np_1|
      np_1.each_line do |nmap_out|
        next if nmap_out.strip.empty?
        print_status "Nmap: #{nmap_out.strip}" if datastore['NMAP_VERBOSE']
      end
    end

    temp_nmap_threads << framework.threads.spawn("Module(#{self.refname})-NmapStderr", false, nmap_pipe[2]) do |np_2|
      np_2.each_line do |nmap_err|
        next if nmap_err.strip.empty?
        print_status  "Nmap: '#{nmap_err.strip}'"
      end
    end

    temp_nmap_threads.map {|t| t.join rescue nil}
    nmap_pipe.each {|p| p.close rescue nil}
    if self.nmap_log[0].size.zero?
      print_error "Nmap Warning: Output file is empty, no useful results can be processed."
    end
  rescue ::IOError
  end
end

#nmap_saveObject

Saves the data from the nmap scan to a file in the MSF::Config.local_directory


267
268
269
270
271
272
273
# File 'lib/msf/core/auxiliary/nmap.rb', line 267

def nmap_save()
  print_status "Nmap: saving nmap log file"
  fh = self.nmap_log[0]
  nmap_data = fh.read(fh.stat.size)
  saved_path = store_local("nmap.scan.xml", "text/xml", nmap_data, "nmap_#{Time.now.utc.to_i}.xml")
  print_status "Saved NMAP XML results to #{saved_path}"
end

#nmap_set_logObject

Returns the [filehandle, pathname], and sets the same to self.nmap_log. Only supports XML format since that's the most useful.


135
136
137
138
139
140
141
142
143
# File 'lib/msf/core/auxiliary/nmap.rb', line 135

def nmap_set_log
  outfile = Rex::Quickfile.new("msf3-nmap-")
  if Rex::Compat.is_cygwin and self.nmap_bin =~ /cygdrive/i
    outfile_path = Rex::Compat.cygwin_to_win32(outfile.path)
  else
    outfile_path = outfile.path
  end
  self.nmap_log = [outfile,outfile_path]
end

#nmap_show_argsObject


145
146
147
# File 'lib/msf/core/auxiliary/nmap.rb', line 145

def nmap_show_args
  print_status self.nmap_args.join(" ")
end

#nmap_validate_arg(str) ⇒ Object

Validates an argument to be passed on the command line to nmap. Most special characters aren't allowed, and commas in arguments are only allowed inside a quoted argument.


207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/msf/core/auxiliary/nmap.rb', line 207

def nmap_validate_arg(str)
  # Check for existence
  if str.nil? || str.empty?
    print_error "Missing nmap argument"
    return false
  end
  # Check for quote balance
  if !(str.scan(/'/).size % 2).zero? or !(str.scan(/"/).size % 2).zero?
    print_error "Unbalanced quotes in nmap argument: #{str}"
    return false
  end
  # Check for characters that enable badness
  disallowed_characters = /([\x00-\x19\x21\x23-\x26\x28\x29\x3b\x3e\x60\x7b\x7c\x7d\x7e-\xff])/n
  badchar = str[disallowed_characters]
  if badchar
    print_error "Malformed nmap arguments (contains '#{badchar}'): #{str}"
    return false
  end
  # Check for commas outside of quoted arguments
  quoted_22 = /\x22[^\x22]*\x22/n
  requoted_str = str.gsub(/'/,"\"")
  if requoted_str.split(quoted_22).join[/,/]
    print_error "Malformed nmap arguments (unquoted comma): #{str}"
    return false
  end
  return true
end

#nmap_validate_rportsObject

Validates the correctness of ports passed to nmap's -p option. Note that this will not validate named ports (like 'http'), nor will it validate when brackets are specified. The acceptable formats for this is:

80 80-90 22,23 U:53,T:80 and combinations thereof.


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/msf/core/auxiliary/nmap.rb', line 182

def nmap_validate_rports
  # If there's an RPORT specified, use that instead.
  if datastore['RPORT'] && (datastore['RPORT'].kind_of?(Fixnum) || !datastore['RPORT'].empty?)
    return true
  end
  if rports.nil? || rports.empty?
    print_error "Missing RPORTS"
    return false
  end
  rports.split(/\s*,\s*/).each do |r|
    if r =~ /^([TU]:)?[0-9]*-?[0-9]*$/
      next
    else
      print_error "Malformed nmap port: #{r}"
      return false
    end
  end
  print_status "Using RPORTS range #{datastore['RPORTS']}"
  return true
end

#nmap_version_at_least?(test_ver = nil) ⇒ Boolean

Takes a version string in the form of Major.Minor and compares to the found version. It yells at you specifically if you try to compare a float b/c that's going to be a super common error. Comparing an Integer is okay, though.

Raises:

  • (ArgumentError)

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/msf/core/auxiliary/nmap.rb', line 69

def nmap_version_at_least?(test_ver=nil)
  raise ArgumentError, "Cannot compare a Float, use a String or Integer" if test_ver.kind_of? Float
  unless test_ver.to_s[/^([0-9]+(\x2e[0-9]+)?)/n]
    raise ArgumentError, "Bad Nmap comparison version: #{test_ver.inspect}"
  end
  test_ver_str = test_ver.to_s
  tnum_arr = $1.split(/\x2e/n)[0,2].map {|x| x.to_i}
  installed_ver = get_nmap_ver()
  vtag = installed_ver.split[2] # Should be ["Nmap", "version", "X.YZTAG", "(", "http..", ")"]
  return false if (vtag.nil? || vtag.empty?)
  return false unless (vtag =~ /^([0-9]+\x2e[0-9]+)/n) # Drop the tag.
  inum_arr = $1.split(/\x2e/n)[0,2].map {|x| x.to_i}
  return true if inum_arr[0] > tnum_arr[0]
  return false if inum_arr[0] < tnum_arr[0]
  inum_arr[1].to_i >= tnum_arr[1].to_i
end

#rportObject


41
42
43
# File 'lib/msf/core/auxiliary/nmap.rb', line 41

def rport
  datastore['RPORT']
end

#rportsObject


37
38
39
# File 'lib/msf/core/auxiliary/nmap.rb', line 37

def rports
  datastore['RPORTS']
end

#set_nmap_cmdObject


45
46
47
48
49
50
51
52
53
54
# File 'lib/msf/core/auxiliary/nmap.rb', line 45

def set_nmap_cmd
  self.nmap_bin || (raise RuntimeError, "Cannot locate nmap binary")
  nmap_set_log
  nmap_add_ports
  nmap_cmd = [self.nmap_bin]
  self.nmap_args.unshift("-oX #{self.nmap_log[1]}")
  nmap_cmd << self.nmap_args.join(" ")
  nmap_cmd << datastore['RHOSTS']
  nmap_cmd.join(" ")
end