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


18
19
20
# File 'lib/msf/core/auxiliary/nmap.rb', line 18

def nmap_args
  @nmap_args
end

#nmap_binObject

Returns the value of attribute nmap_bin


18
19
20
# File 'lib/msf/core/auxiliary/nmap.rb', line 18

def nmap_bin
  @nmap_bin
end

#nmap_logObject

Returns the value of attribute nmap_log


18
19
20
# File 'lib/msf/core/auxiliary/nmap.rb', line 18

def nmap_log
  @nmap_log
end

#nmap_pidObject (readonly)

Returns the value of attribute nmap_pid


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

def nmap_pid
  @nmap_pid
end

#nmap_verObject (readonly)

Returns the value of attribute nmap_ver


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

def nmap_ver
  @nmap_ver
end

Instance Method Details

#get_nmap_verObject


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

def get_nmap_ver
  self.nmap_bin || (raise "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


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

def initialize(info = {})
  super

  register_options([
    Opt::RHOSTS,
    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


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

def nmap_add_ports
  if not nmap_validate_rports
    raise "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 "Argument is invalid"
  end
end

#nmap_append_arg(str) ⇒ Object


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

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

#nmap_binary_pathObject


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

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


84
85
86
# File 'lib/msf/core/auxiliary/nmap.rb', line 84

def nmap_build_args
  raise "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.


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

def nmap_hosts(&block)
  @nmap_bin || (raise "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, :workspace => 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


153
154
155
# File 'lib/msf/core/auxiliary/nmap.rb', line 153

def nmap_reset_args
  self.nmap_args = []
end

#nmap_runObject


88
89
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
# File 'lib/msf/core/auxiliary/nmap.rb', line 88

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


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

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.


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

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


143
144
145
# File 'lib/msf/core/auxiliary/nmap.rb', line 143

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.


205
206
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
# File 'lib/msf/core/auxiliary/nmap.rb', line 205

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.tr('\'','"')
  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.


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

def nmap_validate_rports
  # If there's an RPORT specified, use that instead.
  if datastore['RPORT'] && (datastore['RPORT'].kind_of?(Integer) || !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.

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

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

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


39
40
41
# File 'lib/msf/core/auxiliary/nmap.rb', line 39

def rport
  datastore['RPORT']
end

#rportsObject


35
36
37
# File 'lib/msf/core/auxiliary/nmap.rb', line 35

def rports
  datastore['RPORTS']
end

#set_nmap_cmdObject


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

def set_nmap_cmd
  self.nmap_bin || (raise "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