diff --git a/Gemfile.lock b/Gemfile.lock index 38db437011..cb4ef86add 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,6 +23,7 @@ PATH nokogiri octokit openssl-ccm + openvas-omp packetfu patch_finder pcaprub @@ -186,6 +187,7 @@ GEM octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) openssl-ccm (1.2.1) + openvas-omp (0.0.4) packetfu (1.1.11) network_interface (~> 0.0) pcaprub (~> 0.12) diff --git a/lib/openvas/openvas-omp.rb b/lib/openvas/openvas-omp.rb deleted file mode 100644 index fb7e3b7a51..0000000000 --- a/lib/openvas/openvas-omp.rb +++ /dev/null @@ -1,640 +0,0 @@ -#!/usr/bin/env ruby -# -# This plugin provides integration with OpenVAS. Written by kost and -# averagesecurityguy. -# -# Distributed under MIT license: -# http://www.opensource.org/licenses/mit-license.php -# - -require 'socket' -require 'timeout' -require 'openssl' -require 'rexml/document' -require 'rexml/text' -require 'base64' - -# OpenVASOMP module -# -# Usage: require 'openvas-omp' - -module OpenVASOMP - -#------------------------------ -# Error Classes -#------------------------------ - class OMPError < :: RuntimeError - attr_accessor :reason - def initialize(reason = '') - self.reason = reason - end - def to_s - "OpenVAS OMP: #{self.reason}" - end - end - - class OMPConnectionError < OMPError - def initialize - self.reason = "Could not connect to server" - end - end - - class OMPResponseError < OMPError - def initialize - self.reason = "Error in OMP request/response" - end - end - - class OMPAuthError < OMPError - def initialize - self.reason = "Authentication failed" - end - end - - class XMLParsingError < OMPError - def initialize - self.reason = "XML parsing failed" - end - end - - -#------------------------------ -# Connection Class -#------------------------------ - class OpenVASConnection - attr_accessor :socket, :bufsize, :debug - - def initialize(host="127.0.0.1", port=9390, debug=false) - @host = host - @port = port - @socket = nil - @bufsize = 16384 - @debug = debug - end - - def connect - if @debug then puts "Connecting to server #{@host} on port #{@port}" end - plain_socket = TCPSocket.open(@host, @port) - ssl_context = OpenSSL::SSL::SSLContext.new() - @socket = OpenSSL::SSL::SSLSocket.new(plain_socket, ssl_context) - @socket.sync_close = true - @socket.connect - end - - def disconnect - if @debug then - puts "Closing connection to server #{@host} on port #{@port}" end - if @socket then @socket.close end - end - - def sendrecv(data) - # Send the data - if @debug then puts "Preparing to send data" end - if not @socket then connect end - if @debug then puts "SENDING: " + data end - @socket.puts(data) - - # Receive the response - resp = '' - size = 0 - begin - begin - timeout(@read_timeout) { - a = @socket.sysread(@bufsize) - size = a.length - resp << a - } - rescue Timeout::Error - size = 0 - rescue EOFError - raise OMPResponseError - end - end while size >= @bufsize - - if @debug then puts "RECEIVED: " + resp end - return resp - end - end - - -#------------------------------ -# OpenVASOMP class -#------------------------------ - class OpenVASOMP - # initialize object: try to connect to OpenVAS using URL, user and password - attr_reader :targets, :tasks, :configs, :formats, :reports - - def initialize(user="openvas", pass="openvas", host="localhost", port=9392, debug=false) - @debug = debug - @token = '' - @server = OpenVASConnection.new(host, port, debug) - @server.connect - login(user, pass) - @configs = nil - @tasks = nil - @targets = nil - @formats = nil - @reports = nil - config_get_all - task_get_all - target_get_all - format_get_all - report_get_all - end - - #-------------------------- - # Low level commands. Only - # used by OpenVASOMP class. - #-------------------------- - # Nests a string inside an XML tag specified by root - def xml_str(root, str) - return "<#{root}>#{str}" - end - - # Creates an XML root with child elements specified by a hash - def xml_elems(root, elems) - xml = REXML::Element.new(root) - elems.each do |key, val| - e = xml.add_element(key) - e.text = val - end - return xml.to_s - end - - # Creates and XML element with attributes specified by a hash - def xml_attrs(elem, attribs) - xml = REXML::Element.new(elem) - attribs.each do |key, val| - xml.attributes[key] = val - end - return xml.to_s - end - - # Send authentication string and return an XML object (authentication token) - def auth_request_xml(request) - if @debug - puts "Sending Request: #{request}" - end - resp = @server.sendrecv(request) - begin - docxml = REXML::Document.new(resp) - status = docxml.root.attributes['status'].to_i - status_text = docxml.root.attributes['status_text'] - if @debug - puts "Status: #{status}" - puts "Status Text: #{status_text}" - end - rescue - raise XMLParsingError - end - - return status, status_text - end - - # Send string request wrapped with authentication XML and return - # an XML object - def omp_request_xml(request) - if @debug - puts "Sending Request: #{request}" - end - resp = @server.sendrecv(@token + request) - begin - # Wrap the response in XML tags to use next_element properly. - docxml = REXML::Document.new("" + resp + "") - resp = docxml.root.elements['authenticate_response'].next_element - status = resp.attributes['status'].to_i - status_text = resp.attributes['status_text'] - if @debug - puts "Status: #{status}" - puts "Status Text: #{status_text}" - end - rescue - raise XMLParsingError - end - - return status, status_text, resp - end - - #-------------------------- - # Class API methods. - #-------------------------- - # Sets debug level - def debug(value) - if value == 0 - @debug = false - @server.debug = false - return "Debug is deactivated." - else - @debug = true - @server.debug = true - return "Debug is activated." - end - end - - # get OMP version (you don't need to be authenticated) - def get_version - status, status_text, resp = omp_request_xml("") - begin - version = resp.elements['version'].text - return version - rescue - raise XMLParsingError - end - end - - # login to OpenVAS server. - # if successful returns authentication XML for further usage - # if unsuccessful returns empty string - def login(user, pass) - creds = xml_elems("credentials", {"username"=> user, "password" => pass}) - req = xml_str("authenticate", creds) - status, status_text = auth_request_xml(req) - - if status == 200 - @token = req - else - raise OMPAuthError - end - end - - # Logout by disconnecting from the server and deleting the - # authentication string. There are no sessions in OMP, must - # send the credentials every time. - def logout - @server.disconnect() - @token = '' - end - -#------------------------------ -# Target Functions -#------------------------------ - - # OMP - Get all targets for scanning and returns array of hashes - # with following keys: id,name,comment,hosts,max_hosts,in_use - # - # Usage: - # array_of_hashes = target_get_all() - # - def target_get_all() - begin - status, status_text, resp = omp_request_xml("") - - list = Array.new - resp.elements.each('//get_targets_response/target') do |target| - td = Hash.new - td["id"] = target.attributes["id"] - td["name"] = target.elements["name"].text - td["comment"] = target.elements["comment"].text - td["hosts"] = target.elements["hosts"].text - td["max_hosts"] = target.elements["max_hosts"].text - td["in_use"] = target.elements["in_use"].text - list.push td - end - @targets = list - return list - rescue - raise OMPResponseError - end - end - - # OMP - Create target for scanning - # - # Usage: - # - # target_id = ov.target_create("name"=>"localhost", - # "hosts"=>"127.0.0.1","comment"=>"yes") - # - def target_create(name, hosts, comment) - req = xml_elems("create_target", {"name"=>name, "hosts"=>hosts, "comment"=>comment}) - - begin - status, status_text, resp = omp_request_xml(req) - target_get_all - return "#{status_text}: #{resp.attributes['id']}" - rescue - raise OMPResponseError - end - end - - # OMP - Delete target - # - # Usage: - # - # ov.target_delete(target_id) - # - def target_delete(id) - target = @targets[id.to_i] - if not target - raise OMPError.new("Invalid target id.") - end - req = xml_attrs("delete_target",{"target_id" => target["id"]}) - begin - status, status_text, resp = omp_request_xml(req) - target_get_all - return status_text - rescue - raise OMPResponseError - end - end - - #-------------------------- - # Task Functions - #-------------------------- - # In short: Create a task. - # - # The client uses the create_task command to create a new task. - # - def task_create(name, comment, config_id, target_id) - config = @configs[config_id.to_i] - target = @targets[target_id.to_i] - config = xml_attrs("config", {"id"=>config["id"]}) - target = xml_attrs("target", {"id"=>target["id"]}) - namestr = xml_str("name", name) - commstr = xml_str("comment", comment) - - req = xml_str("create_task", namestr + commstr + config + target) - - begin - status, status_text, resp = omp_request_xml(req) - task_get_all - return "#{status_text}: #{resp.attributes['id']}" - rescue - raise OMPResponseError - end - end - - # In short: Delete a task. - # - # The client uses the delete_task command to delete an existing task, - # including all reports associated with the task. - # - def task_delete(task_id) - task = @tasks[task_id.to_i] - if not task - raise OMPError.new("Invalid task id.") - end - req = xml_attrs("delete_task",{"task_id" => task["id"]}) - begin - status, status_text, resp = omp_request_xml(req) - task_get_all - return status_text - rescue - raise OMPResponseError - end - end - - # In short: Get all tasks. - # - # The client uses the get_tasks command to get task information. - # - def task_get_all() - begin - status, status_text, resp = omp_request_xml("") - - list = Array.new - resp.elements.each('//get_tasks_response/task') do |task| - td = Hash.new - td["id"] = task.attributes["id"] - td["name"] = task.elements["name"].text - td["comment"] = task.elements["comment"].text - td["status"] = task.elements["status"].text - td["progress"] = task.elements["progress"].text - list.push td - end - @tasks = list - return list - rescue - raise OMPResponseError - end - end - - # In short: Manually start an existing task. - # - # The client uses the start_task command to manually start an existing - # task. - # - def task_start(task_id) - task = @tasks[task_id.to_i] - if not task - raise OMPError.new("Invalid task id.") - end - req = xml_attrs("start_task",{"task_id" => task["id"]}) - begin - status, status_text, resp = omp_request_xml(req) - return status_text - rescue - raise OMPResponseError - end - end - - # In short: Stop a running task. - # - # The client uses the stop_task command to manually stop a running - # task. - # - def task_stop(task_id) - task = @tasks[task_id.to_i] - if not task - raise OMPError.new("Invalid task id.") - end - req = xml_attrs("stop_task",{"task_id" => task["id"]}) - begin - status, status_text, resp = omp_request_xml(req) - return status_text - rescue - raise OMPResponseError - end - end - - # In short: Pause a running task. - # - # The client uses the pause_task command to manually pause a running - # task. - # - def task_pause(task_id) - task = @tasks[task_id.to_i] - if not task - raise OMPError.new("Invalid task id.") - end - req = xml_attrs("pause_task",{"task_id" => task["id"]}) - begin - status, status_text, resp = omp_request_xml(req) - return status_text - rescue - raise OMPResponseError - end - end - - # In short: Resume task if stopped, else start task. - # - # The client uses the resume_or_start_task command to manually start - # an existing task, ensuring that the task will resume from its - # previous position if the task is in the Stopped state. - # - def task_resume_or_start(task_id) - task = @tasks[task_id.to_i] - if not task - raise OMPError.new("Invalid task id.") - end - req = xml_attrs("resume_or_start_task",{"task_id" => task["id"]}) - begin - status, status_text, resp = omp_request_xml(req) - return status_text - rescue - raise OMPResponseError - end - end - - # In short: Resume a puased task - # - # The client uses the resume_paused_task command to manually resume - # a paused task. - # - def task_resume_paused(task_id) - task = @tasks[task_id.to_i] - if not task - raise OMPError.new("Invalid task id.") - end - req = xml_attrs("resume_paused_task",{"task_id" => task["id"]}) - begin - status, status_text, resp = omp_request_xml(req) - return status_text - rescue - raise OMPResponseError - end - end - - #-------------------------- - # Config Functions - #-------------------------- - # OMP - get configs and returns hash as response - # hash[config_id]=config_name - # - # Usage: - # - # array_of_hashes=ov.config_get_all() - # - def config_get_all() - begin - status, status_text, resp = omp_request_xml("") - - list = Array.new - resp.elements.each('//get_configs_response/config') do |config| - c = Hash.new - c["id"] = config.attributes["id"] - c["name"] = config.elements["name"].text - list.push c - end - @configs = list - return list - rescue - raise OMPResponseError - end - end - - - #-------------------------- - # Format Functions - #-------------------------- - # Get a list of report formats - def format_get_all() - begin - status, status_text, resp = omp_request_xml("") - if @debug then print resp end - - list = Array.new - resp.elements.each('//get_report_formats_response/report_format') do |report| - td = Hash.new - td["id"] = report.attributes["id"] - td["name"] = report.elements["name"].text - td["extension"] = report.elements["extension"].text - td["summary"] = report.elements["summary"].text - list.push td - end - @formats = list - return list - rescue - raise OMPResponseError - end - end - - - #-------------------------- - # Report Functions - #-------------------------- - # Get a list of reports - def report_get_all() - begin - status, status_text, resp = omp_request_xml("") - - list = Array.new - resp.elements.each('//get_reports_response/report') do |report| - td = Hash.new - td["id"] = report.attributes["id"] - td["task"] = report.elements["report/task/name"].text - td["start_time"] = report.elements["report/scan_start"].text - td["stop_time"] = report.elements["report/scan_end"].text - list.push td - end - @reports = list - return list - rescue - raise OMPResponseError - end - end - - def report_delete(report_id) - report = @reports[report_id.to_i] - if not report - raise OMPError.new("Invalid report id.") - end - req = xml_attrs("delete_report",{"report_id" => report["id"]}) - begin - status, status_text, resp = omp_request_xml(req) - report_get_all - return status_text - rescue - raise OMPResponseError - end - end - - # Get a report by id. Must also specify the format_id - def report_get_by_id(report_id, format_id) - report = @reports[report_id.to_i] - if not report - raise OMPError.new("Invalid report id.") - end - - format = @formats[format_id.to_i] - if not format - raise OMPError.new("Invalid format id.") - end - - req = xml_attrs("get_reports", {"report_id"=>report["id"], "format_id"=>format["id"]}) - begin - status, status_text, resp = omp_request_xml(req) - rescue - raise OMPResponseError - end - - if status == "404" - raise OMPError.new(status_text) - end - - content_type = resp.elements["report"].attributes["content_type"] - report = resp.elements["report"].to_s - - if report == nil - raise OMPError.new("The report is empty.") - end - - # XML reports are in XML format, everything else is base64 encoded. - if content_type == "text/xml" - return report - else - return Base64.decode64(report) - end - end - - end -end diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 52abbba707..462ec6a2e4 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -151,4 +151,6 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'tzinfo' # Needed so that disk size output isn't horrible spec.add_runtime_dependency 'filesize' + # Needed for openvas plugin + spec.add_runtime_dependency 'openvas-omp' end diff --git a/plugins/openvas.rb b/plugins/openvas.rb index 86bf29b147..0cea636cac 100644 --- a/plugins/openvas.rb +++ b/plugins/openvas.rb @@ -10,7 +10,7 @@ # http://www.opensource.org/licenses/mit-license.php # -require 'openvas/openvas-omp' +require 'openvas-omp' module Msf class Plugin::OpenVAS < Msf::Plugin @@ -149,7 +149,7 @@ class Plugin::OpenVAS < Msf::Plugin return unless openvas? begin - ver = @ov.get_version + ver = @ov.version_get print_good("Using OMP version #{ver}") rescue OpenVASOMP::OMPError => e print_error(e.to_s) @@ -189,7 +189,7 @@ class Plugin::OpenVAS < Msf::Plugin begin print_status("Connecting to OpenVAS instance at #{host}:#{port} with username #{user}...") - ov = OpenVASOMP::OpenVASOMP.new(user, pass, host, port) + ov = OpenVASOMP::OpenVASOMP.new('user' => user, 'password' => pass, 'host' => host, 'port' => port) rescue OpenVASOMP::OMPAuthError => e print_error("Authentication failed: #{e.reason}") return @@ -222,7 +222,7 @@ class Plugin::OpenVAS < Msf::Plugin if args?(args, 3) begin - resp = @ov.target_create(args[0], args[1], args[2]) + resp = @ov.target_create('name' => args[0], 'hosts' => args[1], 'comment' => args[2]) print_status(resp) cmd_openvas_target_list rescue OpenVASOMP::OMPError => e @@ -279,7 +279,7 @@ class Plugin::OpenVAS < Msf::Plugin if args?(args, 4) begin - resp = @ov.task_create(args[0], args[1], args[2], args[3]) + resp = @ov.task_create('name' => args[0], 'comment' => args[1], 'config' => args[2], 'target'=> args[3]) print_status(resp) cmd_openvas_task_list rescue OpenVASOMP::OMPError => e @@ -422,7 +422,7 @@ class Plugin::OpenVAS < Msf::Plugin 'Columns' => [ "ID", "Name" ]) id = 0 - @ov.configs.each do |config| + @ov.config_get_all.each do |config| tbl << [ id, config["name"] ] id += 1 end @@ -445,7 +445,7 @@ class Plugin::OpenVAS < Msf::Plugin tbl = Rex::Text::Table.new( 'Columns' => ["ID", "Name", "Extension", "Summary"]) id = 0 - @ov.formats.each do |format| + format_get_all.each do |format| tbl << [ id, format["name"], format["extension"], format["summary"] ] id += 1 end @@ -468,8 +468,23 @@ class Plugin::OpenVAS < Msf::Plugin tbl = Rex::Text::Table.new( 'Columns' => ["ID", "Task Name", "Start Time", "Stop Time"]) id = 0 - @ov.report_get_all().each do |report| - tbl << [ id, report["task"], report["start_time"], report["stop_time"] ] + resp = @ov.report_get_raw + + resp.elements.each("//get_reports_response/report") do |report| + report_task = nil + report_start_time = nil + report_stop_time = nil + + report.elements.each("//task/name") do |task_name| + report_task = task_name.get_text + end + report.elements.each("//creation_time") do |creation_time| + report_start_time = creation_time.get_text + end + report.elements.each("//modification_time") do |mod_time| + report_stop_time = mod_time.get_text + end + tbl << [ id, report_task, report_start_time, report_stop_time ] id += 1 end print_good("OpenVAS list of reports") @@ -502,12 +517,14 @@ class Plugin::OpenVAS < Msf::Plugin if args?(args, 4) begin - report = @ov.report_get_by_id(args[0], args[1]) + report = @ov.report_get_raw("report_id"=>args[0],"format"=>args[1]) ::FileUtils.mkdir_p(args[2]) name = ::File.join(args[2], args[3]) print_status("Saving report to #{name}") output = ::File.new(name, "w") - output.puts(report) + data = nil + report.elements.each("//get_reports_response"){|r| data = r.to_s} + output.puts(data) output.close rescue OpenVASOMP::OMPError => e print_error(e.to_s) @@ -522,9 +539,11 @@ class Plugin::OpenVAS < Msf::Plugin if args?(args, 2) begin - report = @ov.report_get_by_id(args[0], args[1]) + report = @ov.report_get_raw("report_id"=>args[0],"format"=>args[1]) + data = nil + report.elements.each("//get_reports_response"){|r| data = r.to_s} print_status("Importing report to database.") - framework.db.import({:data => report}) + framework.db.import({:data => data}) rescue OpenVASOMP::OMPError => e print_error(e.to_s) end @@ -534,6 +553,33 @@ class Plugin::OpenVAS < Msf::Plugin end end + + + #-------------------------- + # Format Functions + #-------------------------- + # Get a list of report formats + def format_get_all + begin + resp = @ov.omp_request_xml("") + if @debug then print resp end + + list = Array.new + resp.elements.each('//get_report_formats_response/report_format') do |report| + td = Hash.new + td["id"] = report.attributes["id"] + td["name"] = report.elements["name"].text + td["extension"] = report.elements["extension"].text + td["summary"] = report.elements["summary"].text + list.push td + end + @formats = list + return list + rescue + raise OMPResponseError + end + end + end # End OpenVAS class #------------------------------ @@ -551,6 +597,7 @@ class Plugin::OpenVAS < Msf::Plugin print_status @ov = nil @formats = nil + @debug = nil end def cleanup