From cd2088ee964afa89a7b58e0656917648bd063baa Mon Sep 17 00:00:00 2001 From: HD Moore Date: Tue, 28 Dec 2010 17:45:05 +0000 Subject: [PATCH] Import the OpenVAS bridge from Kost. Go see his talk: http://www.berlinsides.org/node/14 git-svn-id: file:///home/svn/framework3/trunk@11428 4d416f70-5f16-0410-b530-b9f4589650da --- lib/openvas/openvas-omp.rb | 899 +++++++++++++++++++++++++++++++++++++ plugins/openvas.rb | 737 ++++++++++++++++++++++++++++++ 2 files changed, 1636 insertions(+) create mode 100644 lib/openvas/openvas-omp.rb create mode 100644 plugins/openvas.rb diff --git a/lib/openvas/openvas-omp.rb b/lib/openvas/openvas-omp.rb new file mode 100644 index 0000000000..224b4f7e14 --- /dev/null +++ b/lib/openvas/openvas-omp.rb @@ -0,0 +1,899 @@ +# +# = openvas-omp.rb: communicate with OpenVAS manager over OMP +# +# Author:: Vlatko Kosturjak +# +# (C) Vlatko Kosturjak, Kost. Distributed under MIT license: +# http://www.opensource.org/licenses/mit-license.php +# +# == What is this library? +# +# This library is used for communication with OpenVAS manager over OMP +# You can start, stop, pause and resume scan. Watch progress and status of +# scan, download report, etc. +# +# == Requirements +# +# Required libraries are standard Ruby libraries: socket,timeout,openssl, +# rexml/document, rexml/text, base64 +# +# == Usage: +# +# require 'openvas-omp' + +require 'socket' +require 'timeout' +require 'openssl' +require 'rexml/document' +require 'rexml/text' +require 'base64' + +# OpenVASOMP module +# +# Usage: +# +# require 'openvas-omp' + +module OpenVASOMP + + class OMPError < :: RuntimeError + attr_accessor :req, :reason + def initialize(req, reason = '') + self.req = req + self.reason = reason + end + def to_s + "OpenVAS OMP: #{self.reason}" + 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 + + # Class which uses standard REXML to parse OpenVAS replies. + class OpenVASOMP + # initialize object: try to connect to OpenVAS using URL, user and password + # + # Usage: + # + # ov=OpenVASOMP.new(user=>'user',password=>'pass') + # # default: host=>'localhost', port=>'9390' + # + def initialize(p={}) + if p.has_key?("host") + @host=p["host"] + else + @host="localhost" + end + if p.has_key?("port") + @port=p["port"] + else + @port=9390 + end + if p.has_key?("user") + @user=p["user"] + else + @user="openvas" + end + if p.has_key?("password") + @password=p["password"] + else + @password="openvas" + end + if p.has_key?("bufsize") + @bufsize=p["bufsize"] + else + @bufsize=16384 + end + if p.has_key?("debug") + @debug=p["debug"] + else + @debug=0 + end + + if @debug>3 + puts "Host: "+@host + puts "Port: "+@port.to_s() + puts "User: "+@user + end + if @debug>99 + puts "Password: "+@password + end + @areq='' + @read_timeout=3 + if defined? p["noautoconnect"] and not p["noautoconnect"] + connect() + if defined? p["noautologin"] and not p["noautologin"] + login() + end + end + end + + # Sets debug level + # + # Usage: + # + # ov.debug(3) + # + def debug (level) + @debug=level + end + + # Low level method - Connect to SSL socket + # + # Usage: + # + # ov.connect() + # + def connect + @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 + + # Low level method - Disconnect SSL socket + # + # Usage: + # + # ov.disconnect() + # + def disconnect + if @socket + @socket.close + end + end + + # Low level method: Send request and receive response - socket + # + # Usage: + # + # ov.connect(); + # puts ov.sendrecv("") + # ov.disconnect(); + # + def sendrecv (tosend) + if not @socket + connect + end + + if @debug>3 then + puts "SENDING: "+tosend + end + @socket.puts(tosend) + + @rbuf='' + size=0 + begin + begin + timeout(@read_timeout) { + a = @socket.sysread(@bufsize) + size=a.length + # puts "sysread #{size} bytes" + @rbuf << a + } + rescue Timeout::Error + size=0 + rescue EOFError + raise OMPResponseError + end + end while size>=@bufsize + response=@rbuf + + if @debug>3 then + puts "RECEIVED: "+response + end + return response + end + + # get OMP version (you don't need to be authenticated) + # + # Usage: + # + # ov.version_get() + # + def version_get + vreq="" + resp=sendrecv(vreq) + resp = ""+resp+"" + begin + docxml = REXML::Document.new(resp) + version='' + version=docxml.root.elements['get_version_response'].elements['version'].text + return version + rescue + raise XMLParsingError + end + end + + # produce single XML element with attributes specified as hash + # low-level function + # + # Usage: + # + # ov.xml_attr() + # + def xml_attr(name, opts={}) + xml = REXML::Element.new(name) + opts.keys.each do |k| + xml.attributes[k] = opts[k] + end + return xml + end + + # produce multiple XML elements with text specified as hash + # low-level function + # + # Usage: + # + # ov.xml_ele() + # + def xml_ele(name, child={}) + xml = REXML::Element.new(name) + child.keys.each do |k| + xml.add_element(k) + xml.elements[k].text = child[k] + end + return xml + end + + # produce multiple XML elements with text specified as hash + # also produce multiple XML elements with attributes + # low-level function + # + # Usage: + # + # ov.xml_mix() + # + def xml_mix(name, child, attr, elem) + xml = REXML::Element.new(name) + child.keys.each do |k| + xml.add_element(k) + xml.elements[k].text = child[k] + end + elem.keys.each do |k| + xml.add_element(k) + xml.elements[k].attributes[attr] = elem[k] + end + return xml + end + + # login to OpenVAS server. + # if successful returns authentication XML for further usage + # if unsuccessful returns empty string + # + # Usage: + # + # ov.login() + # + def login + areq=""+xml_ele("credentials", {"username"=>@user, "password"=>@password}).to_s()+"" + resp=sendrecv(areq+"") + # wrap it inside tags, so rexml does not complain + resp = ""+resp+"" + + begin + docxml = REXML::Document.new(resp) + status=docxml.root.elements['authenticate_response'].attributes['status'].to_i() + rescue + raise XMLParsingError + end + if status == 200 + @areq=areq + else + raise OMPAuthError + end + end + + # check if we're successful logged in + # if successful returns true + # if unsuccessful returns false + # + # Usage: + # + # if ov.logged_in() then + # puts "logged in" + # end + # + def logged_in + if @areq == '' + return false + else + return true + end + end + + # logout from OpenVAS server. + # it actually just sets internal authentication XML to empty str + # (as in OMP you have to send username/password each time) + # (i.e. there is no session id) + # + # Usage: + # + # ov.logout() + # + def logout + disconnect() + @areq = '' + end + + # OMP low level method - Send string request wrapped with + # authentication XML and return response as string + # + # Usage: + # + # ov.request_xml("" + + begin + docxml = REXML::Document.new(resp) + status=docxml.root.elements['authenticate_response'].attributes['status'].to_i + if status<200 and status>299 + raise OMPAuthError + end + return docxml.root + rescue + raise XMLParsingError + 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 (p={}) + xmlreq=xml_ele("create_target", p).to_s() + + begin + xr=omp_request_xml(xmlreq) + id=xr.elements['create_target_response'].attributes['id'] + rescue + raise OMPResponseError + end + return id + end + + # OMP - Delete target + # + # Usage: + # + # ov.target_delete(target_id) + # + def target_delete (id) + xmlreq=xml_attr("delete_task",{"target_id" => id}).to_s() + begin + xr=omp_request_xml(xmlreq) + rescue + raise OMPResponseError + end + return xr + end + + # OMP - Get target for scanning and returns rexml object + # + # Usage: + # rexmlobject = target_get_raw("target_id"=>target_id) + # + def target_get_raw (p={}) + xmlreq=xml_attr("get_targets", p).to_s() + + begin + xr=omp_request_xml(xmlreq) + return xr + rescue + raise OMPResponseError + end + end + + # 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 (p={}) + begin + xr=target_get_raw(p) + list=Array.new + xr.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 + return list + rescue + raise OMPResponseError + end + end + + def target_get_byid (id) + begin + xr=target_get_raw("target_id"=>id) + xr.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 + return td + end + return list + rescue + raise OMPResponseError + end + end + + # OMP - get reports and returns raw rexml object as response + # + # Usage: + # + # rexmlobject=ov.report_get_raw("format"=>"PDF") + # + # rexmlobject=ov.report_get_raw( + # "report_id" => "", + # "format"=>"PDF") + # + def report_get_raw (p={}) + xmlreq=xml_attr("get_reports",p).to_s() + begin + xr=omp_request_xml(xmlreq) + rescue + raise OMPResponseError + end + return xr + end + + # OMP - get report by id and format, returns report + # (already base64 decoded if needed) + # + # format can be: HTML, NBE, PDF, ... + # + # Usage: + # + # pdf_content=ov.report_get_byid(id,"PDF") + # File.open('report.pdf', 'w') {|f| f.write(pdf_content) } + # + def report_get_byid (id,format) + decode=Array["HTML","NBE","PDF"] + xr=report_get_raw("report_id"=>id,"format"=>format) + resp=xr.elements['get_reports_response'].elements['report'].text + if decode.include?(format) + resp=Base64.decode64(resp) + end + return resp + end + + # OMP - get report all, returns report + # + # Usage: + # + # pdf_content=ov.report_get_all() + # + def report_get_all () + begin + xr=report_get_raw("format"=>"NBE") + list=Array.new + xr.elements.each('//get_reports_response/report') do |report| + 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 + return list + rescue + raise OMPResponseError + end + end + + # OMP - get reports and returns raw rexml object as response + # + # Usage: + # + # rexmlobject=ov.result_get_raw("notes"=>0) + # + def result_get_raw (p={}) + begin + xmlreq=xml_attr("get_results",p).to_s() + xr=omp_request_xml(xmlreq) + rescue + raise OMPResponseError + end + return xr + end + + # OMP - get configs and returns rexml object as response + # + # Usage: + # + # rexmldocument=ov.config_get_raw() + # + def config_get_raw (p={}) + xmlreq=xml_attr("get_configs",p).to_s() + begin + xr=omp_request_xml(xmlreq) + return xr + rescue + raise OMPResponseError + end + return false + end + + # 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 (p={}) + begin + xr=config_get_raw(p) + tc=Array.new + xr.elements.each('//get_configs_response/config') do |config| + c=Hash.new + c["id"]=config.attributes["id"] + c["name"]=config.elements["name"].text + c["comment"]=config.elements["comment"].text + tc.push c + end + return tc + rescue + raise OMPResponseError + end + return false + end + + # OMP - get configs and returns hash as response + # hash[config_id]=config_name + # + # Usage: + # + # all_configs_hash=ov.config.get() + # + # config_id=ov.config_get().index("Full and fast") + # + def config_get (p={}) + begin + xr=config_get_raw(p) + list=Hash.new + xr.elements.each('//get_configs_response/config') do |config| + id=config.attributes["id"] + name=config.elements["name"].text + list[id]=name + end + return list + rescue + raise OMPResponseError + end + return false + end + + # OMP - copy config with new name and returns new id + # + # Usage: + # + # new_config_id=config_copy(config_id,"new_name"); + # + def config_copy (config_id,name) + xmlreq=xml_attr("create_config", + {"copy"=>config_id,"name"=>name}).to_s() + begin + xr=omp_request_xml(xmlreq) + id=xr.elements['create_config_response'].attributes['id'] + return id + rescue + raise OMPResponseError + end + end + + # OMP - create config with specified RC file and returns new id + # name = name of new config + # rcfile = base64 encoded OpenVAS rcfile + # + # Usage: + # + # config_id=config_create("name",rcfile); + # + def config_create (name,rcfile) + xmlreq=xml_attr("create_config", + {"name"=>name,"rcfile"=>rcfile}).to_s() + begin + xr=omp_request_xml(xmlreq) + id=xr.elements['create_config_response'].attributes['id'] + return id + rescue + raise OMPResponseError + end + end + + # OMP - creates task and returns id of created task + # + # Parameters which usually fit in p hash and i hash: + # p = name,comment,rcfile + # i = config,target,escalator,schedule + # + # Usage: + # + # task_id=ov.task_create_raw() + # + def task_create_raw (p={}, i={}) + xmlreq=xml_mix("create_task",p,"id",i).to_s() + begin + xr=omp_request_xml(xmlreq) + id=xr.elements['create_task_response'].attributes['id'] + return id + rescue + raise OMPResponseError + end + end + + # OMP - creates task and returns id of created task + # + # parameters = name,comment,rcfile,config,target,escalator, + # schedule + # + # Usage: + # + # config_id=o.config_get().index("Full and fast") + # target_id=o.target_create( + # {"name"=>"localtarget", "hosts"=>"127.0.0.1", "comment"=>"t"}) + # task_id=ov.task_create( + # {"name"=>"testlocal","comment"=>"test", "target"=>target_id, + # "config"=>config_id} + # + def task_create (p={}) + specials=Array["config","target","escalator","schedule"] + ids = Hash.new + specials.each do |spec| + if p.has_key?(spec) + ids[spec]=p[spec] + p.delete(spec) + end + end + return task_create_raw(p,ids) + end + + # OMP - deletes task specified by task_id + # + # Usage: + # + # ov.task_delete(task_id) + # + def task_delete (task_id) + xmlreq=xml_attr("delete_task",{"task_id" => task_id}).to_s() + begin + xr=omp_request_xml(xmlreq) + rescue + raise OMPResponseError + end + return xr + end + + # OMP - get task and returns raw rexml object as response + # + # Usage: + # + # rexmlobject=ov.task_get_raw("details"=>"0") + # + def task_get_raw (p={}) + xmlreq=xml_attr("get_tasks",p).to_s() + begin + xr=omp_request_xml(xmlreq) + return xr + rescue + raise OMPResponseError + end + end + + # OMP - get all tasks and returns array with hashes with + # following content: + # id,name,comment,status,progress,first_report,last_report + # + # Usage: + # + # array_of_hashes=ov.task_get_all() + # + def task_get_all (p={}) + xr=task_get_raw(p) + t=Array.new + xr.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 + if defined? task.elements["first_report"].elements["report"].attributes["id"] then + td["firstreport"]=task.elements["first_report"].elements["report"].attributes["id"] + else + td["firstreport"]=nil + end + if defined? task.elements["last_report"].elements["report"].attributes["id"] then + td["lastreport"]=task.elements["last_report"].elements["report"].attributes["id"] + else + td["lastreport"]=nil + end + t.push td + end + return t + end + + # OMP - get task specified by task_id and returns hash with + # following content: + # id,name,comment,status,progress,first_report,last_report + # + # Usage: + # + # hash=ov.task_get_byid(task_id) + # + def task_get_byid (id) + xr=task_get_raw("task_id"=>id,"details"=>0) + xr.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 + if defined? task.elements["first_report"].elements["report"].attributes["id"] then + td["firstreport"]=task.elements["first_report"].elements["report"].attributes["id"] + else + td["firstreport"]=nil + end + if defined? task.elements["last_report"].elements["report"].attributes["id"] then + td["lastreport"]=task.elements["last_report"].elements["report"].attributes["id"] + else + td["lastreport"]=nil + end + return (td) + end + end + + # OMP - check if task specified by task_id is finished + # (it checks if task status is "Done" in OMP) + # + # Usage: + # + # if ov.task_finished(task_id) + # puts "Task finished" + # end + # + def task_finished (id) + xr=task_get_raw("task_id"=>id,"details"=>0) + xr.elements.each('//get_tasks_response/task') do |task| + if status=task.elements["status"].text == "Done" + return true + else + return false + end + end + end + + # OMP - check progress of task specified by task_id + # (OMP returns -1 if task is finished, not started, etc) + # + # Usage: + # + # print "Progress: " + # puts ov.task_progress(task_id) + # + def task_progress (id) + xr=task_get_raw("task_id"=>id,"details"=>0) + xr.elements.each('//get_tasks_response/task') do |task| + return task.elements["progress"].text.to_i() + end + end + + # OMP - starts task specified by task_id + # + # Usage: + # + # ov.task_start(task_id) + # + def task_start (task_id) + xmlreq=xml_attr("start_task",{"task_id" => task_id}).to_s() + begin + xr=omp_request_xml(xmlreq) + rescue + raise OMPResponseError + end + return xr + end + + # OMP - stops task specified by task_id + # + # Usage: + # + # ov.task_stop(task_id) + # + def task_stop (task_id) + xmlreq=xml_attr("stop_task",{"task_id" => task_id}).to_s() + begin + xr=omp_request_xml(xmlreq) + rescue + raise OMPResponseError + end + return xr + end + + # OMP - pauses task specified by task_id + # + # Usage: + # + # ov.task_pause(task_id) + # + def task_pause (task_id) + xmlreq=xml_attr("pause_task",{"task_id" => task_id}).to_s() + begin + xr=omp_request_xml(xmlreq) + rescue + raise OMPResponseError + end + return xr + end + + # OMP - resumes (or starts) task specified by task_id + # + # Usage: + # + # ov.task_resume_or_start(task_id) + # + def task_resume_or_start (task_id) + xmlreq=xml_attr("resume_or_start_task",{"task_id" => task_id}).to_s() + begin + xr=omp_request_xml(xmlreq) + rescue + raise OMPResponseError + end + return xr + end + + end # end of Class + +end # of Module + diff --git a/plugins/openvas.rb b/plugins/openvas.rb new file mode 100644 index 0000000000..837dc13b5b --- /dev/null +++ b/plugins/openvas.rb @@ -0,0 +1,737 @@ +#!/usr/bin/env ruby +# +# This plugin provides integration with OpenVAS. Written by kost. +# Distributed under MIT license: +# http://www.opensource.org/licenses/mit-license.php +# +# Typical usage: +# load openvas +# db_create +# openvas_connect test test localhost 9390 ok +# openvas_scan localhost + +require 'openvas/openvas-omp' + +module Msf +class Plugin::OpenVAS < Msf::Plugin + class OpenVASCommandDispatcher + include Msf::Ui::Console::CommandDispatcher + + def name + "OpenVAS" + end + + def commands + { + 'openvas_help' => "Displays help", + 'openvas_connect' => "Connect to a OpenVAS manager using OMP ( user:pass@host[:port] )", + 'openvas_task_list' => "Display list of tasks", + 'openvas_task_create' => "Creates task (name, comment, target, config)", + 'openvas_task_start' => "Start task by ID", + 'openvas_task_stop' => "Stop task by ID", + 'openvas_task_pause' => "Pause task by ID", + 'openvas_task_resume' => "Resume task by ID", + 'openvas_task_resume_or_start' => "Resume task or start task by ID", + 'openvas_task_status' => "Get status of task ID", + 'openvas_task_delete' => "Delete task by ID", + + 'openvas_target_create' => "Create target (name, hosts, comment)", + 'openvas_target_list' => "Display list of targets", + 'openvas_target_delete' => "Delete target by ID", + + 'openvas_config_list' => "Quickly display list of configs", + 'openvas_report_import' => "Import report specified by ID to framework", + 'openvas_report_save' => "Save report specified by ID and format to file", + + 'openvas_scan' => "Launch an automatic OpenVAS scan against a specific IP range and import the results", + + 'openvas_debug' => "Sets debug level", + 'openvas_disconnect' => "Disconnect from OpenVAS manager", + + } + end + + def cmd_openvas_help (*args) + usage=" +openvas_help Display this help + +CONNECTION +========== +openvas_connect Connects to OpenVAS +openvas_disconnect Disconnects from OpenVAS + +TARGETS +======= +openvas_target_list Lists targets +openvas_target_create Create target +openvas_target_delete Deletes target specified by ID + +TASKS +===== +openvas_task_list Lists tasks +openvas_task_create Create task +openvas_task_start Starts task specified by ID +openvas_task_stop Stops task specified by ID +openvas_task_pause Pauses task specified by ID +openvas_task_resume Resumes task specified by ID +openvas_task_resume_or_start Resumes or starts task specified by ID + +CONFIGS +======= +openvas_config_list Lists configs + +REPORTS +======= +openvas_report_import Imports OpenVAS report in framework spec. by ID +openvas_report_save Saves OpenVAS report specified by ID and format + +AUTO +==== +openvas_scan Launch an automatic OpenVAS scan against a specific IP range and import the results automatically with optional autopwn +" + print_status(usage) + end + + def openvas_verify + if ! @ov + print_error("No active OpenVAS instance has been configured, please use 'openvas_connect'") + return false + end + + if ! (framework.db and framework.db.usable) + print_error("No database has been configured, please use db_create/db_connect first") + return false + end + + true + end + + def cmd_openvas_debug(*args) + usagecmd="openvas_debug +Example: openvas_debug 99" + return if not openvas_verify + if(args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + level=args[0] + @ov.debug(level) + print_good("Command completed successfuly: "+level) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_task_start(*args) + usagecmd="openvas_task_start +Example: openvas_task_start 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if(args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=args[0] + @ov.task_start(id) + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_task_create(*args) + usagecmd="openvas_task_create +Example: openvas_task_create newtask MyNewTask abc12345-a234-46a1-c01c-123456789012 9abc0790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if(args.length != 4 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=@ov.task_create({"name"=>args[0],"comment"=>arg[1],"config"=>args[2],"target"=>args[3]}) + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_task_stop(*args) + usagecmd="openvas_task_stop +Example: openvas_task_stop 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if(args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=args[0] + @ov.task_stop(id) + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_task_pause(*args) + usagecmd="openvas_task_pause +Example: openvas_task_pause 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if(args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=args[0] + @ov.task_pause(id) + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_task_resume(*args) + usagecmd="openvas_task_resume +Example: openvas_task_resume 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if(args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=args[0] + @ov.task_resume(id) + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_task_resume_or_start(*args) + usagecmd="openvas_task_resume_or_start +Example: openvas_task_resume_or_start 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if(args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=args[0] + @ov.task_resume_or_start(id) + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_task_delete(*args) + usagecmd="openvas_task_delete +Example: openvas_task_delete 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if(args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=args[0] + @ov.task_delete(id) + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_task_status(*args) + usagecmd="openvas_task_status +Example: openvas_task_status 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if (args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=args[0] + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ "ID", "Name", "Status", "Progress", "Comment","First Report","Last Report" ] + ) + @ov.task_get_all("task_id"=>id).each do |task| + tbl << [ task["id"] , task["name"] , + task["status"], + task["progress"], + task["comment"], + task["firstreport"], + task["lastreport"] + ] + end + print_good("OpenVAS task status") + puts "\n" + puts tbl.to_s + "\n" + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_task_list(*args) + usagecmd="openvas_task_list [id] +Example: openvas_task_list +Example: openvas_task_list 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if (args[0] == "-h") then + print_status(usagecmd) + return + end + if not (args.length == 0 or args[0].empty?) then + id=args[0] + end + begin + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ "ID", "Name", "Status", "Progress" ] + ) + p={} + if id + p={"task_id"=>id} + end + @ov.task_get_all(p).each do |task| + tbl << [ task["id"] , task["name"] , + task["status"] , task["progress"] ] + end + print_good("OpenVAS list of tasks") + puts "\n" + puts tbl.to_s + "\n" + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_target_list(*args) + usagecmd="openvas_target_list [id] +Example: openvas_target_list +Example: openvas_target_list 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if (args[0] == "-h") then + print_status(usagecmd) + return + end + if not (args.length == 0 or args[0].empty?) then + id=args[0] + end + begin + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ "ID","Name","Hosts" ]) + p={} + if id + p={"target_id"=>id} + end + @ov.target_get_all(p).each do |target| + tbl << [target["id"], + target["name"], + target["hosts"] ] + end + print_good("OpenVAS list of targets") + puts "\n" + puts tbl.to_s + "\n" + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_target_create(*args) + usagecmd="openvas_target_create + +Example: openvas_target_create mylocalhost 127.0.0.1 MyLocalHostComment" + return if not openvas_verify + if(args.length != 3 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=@ov.target_create({"name"=>args[0],"comment"=>arg[2],"hosts"=>args[1]}) + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_target_delete(*args) + usagecmd="openvas_target_delete +Example: openvas_target_delete 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if(args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=args[0] + @ov.target_delete(id) + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_connect(*args) + usagecmd = "Usage: + openvas_connect username:password@host[:port] + -OR- + openvas_connect username password host port " + + if(args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + + user = pass = host = port = sslv = nil + + case args.length + when 1,2 + cred,targ = args[0].split('@', 2) + user,pass = cred.split(':', 2) + targ ||= '127.0.0.1:9390' + host,port = targ.split(':', 2) + port ||= '9390' + sslv = args[1] + when 4,5 + user,pass,host,port,sslv = args + else + print_status(usagecmd) + return + end + + + if ! ((user and user.length > 0) and (host and host.length > 0) and (port and port.length > 0 and port.to_i > 0) and (pass and pass.length > 0)) + print_status(usagecmd) + return + end + + # taken from NeXpose WARNING + if(host != "localhost" and host != "127.0.0.1" and sslv != "ok") + print_error("Warning: SSL connections are not verified in this release, it is possible for an attacker") + print_error(" with the ability to man-in-the-middle the OpenVAS traffic to capture the OpenVAS") + print_error(" credentials. If you are running this on a trusted network, please pass in 'ok'") + print_error(" as an additional parameter to this command.") + return + end + + # Wrap this so a duplicate session doesnt prevent a new login + begin + cmd_openvas_disconnect + rescue ::Interrupt + raise $! + rescue ::Exception + end + + begin + print_status("Connecting to OpenVAS instance at #{host}:#{port} with username #{user}...") + ov = OpenVASOMP::OpenVASOMP.new("user"=>user,"password"=>pass,"host"=>host,"port"=>port) + rescue OpenVASOMP::OMPAuthError => e + print_error("Connection failed: #{e.reason}") + return + end + + @ov = ov + end + + def cmd_openvas_config_list(*args) + usagecmd="openvas_config_list [id] +Example: openvas_config_list +Example: openvas_config_list 9fd90790-a79b-49e0-b08e-6912afde72f4" + return if not openvas_verify + if (args[0] == "-h") then + print_status(usagecmd) + return + end + if not (args.length == 0 or args[0].empty?) then + id=args[0] + end + begin + tbl = Rex::Ui::Text::Table.new( + 'Columns' => + [ "ID", "Name", "Comments" ] + ) + p={} + if id + p={"config_id"=>id} + end + @ov.config_get_all(p).each do |config| + tbl << [ config["id"], config["name"], + config["comment"] ] + end + print_good("OpenVAS list of configs") + puts "\n" + puts tbl.to_s + "\n" + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_report_import(*args) + usagecmd="openvas_report_import +Example: openvas_report_import 9fd90790-a79b-49e0-b08e-6912afde72f4 +Note: gets NBE report from the OpenVAS and tries to import it into framework +" + return if not openvas_verify + if(args.length == 0 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=args[0] + content=@ov.report_get_byid(id,'NBE') + framework.db.import({:data => content}) + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_report_save(*args) + usagecmd="openvas_report_save +Example: openvas_report_save 9fd90790-a79b-49e0-b08e-6912afde72f4 PDF /tmp/a.pdf" + return if not openvas_verify + if(args.length != 3 or args[0].empty? or args[0] == "-h") + print_status(usagecmd) + return + end + begin + id=args[0] + content=@ov.report_get_byid(id,args[1]) + File.open(args[2], 'w') {|f| f.write(content) } + print_good("Command completed successfuly: "+id) + rescue ::Exception + print_error("Error executing") + end + end + + def cmd_openvas_scan(*args) + opts = Rex::Parser::Arguments.new( + "-h" => [ false, "This help menu"], + "-t" => [ true, "The scan template to use (default:Full and fast )"], + "-P" => [ false, "Leave the scan data on the server when it completes (this counts against the maximum licensed IPs)"], + "-x" => [ false, "Automatically launch all exploits by matching reference after the scan completes (unsafe)"], + "-X" => [ false, "Automatically launch all exploits by matching reference and port after the scan completes (unsafe)"], + "-d" => [ false, "Scan hosts based on the contents of the existing database"], + "-v" => [ false, "Display diagnostic information about the scanning process"], + "-I" => [ true, "Only scan systems with an address within the specified range"], + "-E" => [ true, "Exclude hosts in the specified range from the scan"], + "-R" => [ true, "Specify a minimum exploit rank to use for automated exploitation"] + ) + + opt_template = "Full and fast" + opt_verbose = false + opt_preserve = false + opt_autopwn = false + opt_rescandb = false + opt_addrinc = nil + opt_addrexc = nil + opt_scanned = [] + opt_minrank = "manual" + + opt_ranges = [] + + opts.parse(args) do |opt, idx, val| + case opt + when "-h" + print_line("Usage: openvas_scan [options] ") + print_line(opts.usage) + return + when "-v" + opt_verbose = true + when "-t" + opt_template = val + when "-P" + opt_preserve = true + when "-X" + opt_autopwn = "-p -x" + when "-x" + opt_autopwn = "-x" unless opt_autopwn + when "-d" + opt_rescandb = true + when '-I' + opt_addrinc = OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(val) + when '-E' + opt_addrexc = OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(val) + else + opt_ranges << val + end + end + + return if not openvas_verify + + # Include all database hosts as scan targets if specified + if(opt_rescandb) + print_status("Loading scan targets from the active database...") if opt_verbose + framework.db.hosts.each do |host| + next if host.state != ::Msf::HostState::Alive + opt_ranges << host.address + end + end + + opt_ranges = opt_ranges.join(',') + + if(opt_ranges.strip.empty?) + print_line("Usage: openvas_scan [options] ") + print_line(opts.usage) + return + end + + if(opt_verbose) + print_status("Creating a new scan using config #{opt_template} against #{opt_ranges}") + end + + range_inp = ::Msf::OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(opt_ranges) + range = ::Rex::Socket::RangeWalker.new(range_inp) + include_range = opt_addrinc ? ::Rex::Socket::RangeWalker.new(opt_addrinc) : nil + exclude_range = opt_addrexc ? ::Rex::Socket::RangeWalker.new(opt_addrexc) : nil + + completed = 0 + total = range.num_ips + count = 0 + + print_status("Scanning #{total} addresses with config #{opt_template}") + + while(completed < total) + count += 1 + queue = [] + + while(ip = range.next_ip ) + + if(exclude_range and exclude_range.include?(ip)) + print_status(" >> Skipping host #{ip} due to exclusion") if opt_verbose + next + end + + if(include_range and ! include_range.include?(ip)) + print_status(" >> Skipping host #{ip} due to inclusion filter") if opt_verbose + next + end + + opt_scanned << ip + queue << ip + end + + break if queue.empty? + print_status("Scanning #{queue[0]}-#{queue[-1]}...") if opt_verbose + + msfid = Time.now.to_i + + ipstr='' + queue.each do |ip| + ipstr=ipstr+","+ip + end + # Create a temporary site + mname="Metasploit-#{msfid}" + mcomment="Autocreated by the Metasploit Framework" + mtarget=@ov.target_create({"name"=>mname, "hosts"=>ipstr, "comment"=>mcomment}) + + print_status(" >> Created temporary target #{mname} with id #{mtarget}") if opt_verbose + + mconfig=@ov.config_get().index(opt_template) + + if mconfig + print_status(" >> Found config #{opt_template} with id #{mconfig}") if opt_verbose + else + print_error("Config not found") + break + end + + # Create temporary task + mtask = @ov.task_create({"name"=>mname,"comment"=>mcomment, "target"=>mtarget, "config"=>mconfig}) + if mtask + print_status(" >> Created task #{mname} with id #{mtask}") if opt_verbose + else + print_error("Task could not be created") + break + end + + + @ov.task_start(mtask) + + print_status(" >> Scan has been launched with ID #{mtask}") if opt_verbose + + rep = true + begin + prev = nil + while(true) + stat = @ov.task_get_byid(mtask) + break if stat["status"] == "Done" + percent=stat["progress"] + + stat = "Progress: #{percent} %" + if(stat != prev) + print_status(" >> #{stat}") if opt_verbose + end + prev = stat + select(nil, nil, nil, 5.0) + end + print_status(" >> Scan has been completed with task ID #{mtask}") if opt_verbose + rescue ::Interrupt + rep = false + print_status(" >> Terminating task ID #{mtask} due to console interupt") if opt_verbose + @ov.task_stop(mtask) + break + end + + # Wait for the automatic report generation to complete + if(rep) + print_status(" >> Getting report...") if opt_verbose + stat = @ov.task_get_byid(mtask) + + if stat["status"] != "Done" + content=@ov.report_get_byid(stat["lastreport"],'NBE') + print_status(" >> Importing the report data from OpenVAS...") if opt_verbose + framework.db.import({:data => content}) + + end + + if ! opt_preserve + print_status(" >> Deleting the temporary task and target...") if opt_verbose + @ov.task_delete(mtask) + @ov.target_delete(mtarget) + end + end + + print_status("Completed the scan of #{total} addresses") + + if(opt_autopwn) + print_status("Launching an automated exploitation session") + driver.run_single("db_autopwn -q -r -e -t #{opt_autopwn} -R #{opt_minrank} -I #{opt_scanned.join(",")}") + end + end + end + + def cmd_openvas_disconnect(*args) + @ov.logout if @ov + @ov = nil + end + end + + # + # Plugin initialization + # + + def initialize(framework, opts) + super + add_console_dispatcher(OpenVASCommandDispatcher) + print "Welcome to OpenVAS integration by kost\n\n" + print "Use openvas_help for list of commands you can use.\n" + print "Note that you should have database ready. After that you should connect with:\n" + print "openvas_connect (try with -h for help on connecting)\n" + print_status("OpenVAS integration has been activated") + end + + def cleanup + remove_console_dispatcher('OpenVAS') + end + + def name + "OpenVAS" + end + + def desc + "Integrates with the OpenVAS - open source vulnerability management" + end +end +end +