From 46e4ee4c5b68216ae9c50e79eb9d3f53c2de2aec Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Sun, 14 Aug 2016 17:57:33 +0200 Subject: [PATCH] Start using gem instead of obsolete library/tool Rationale is following: nessus-cli is obsolete nessus is using json rest api instead of xmlrpc xmlrpc name is therefore obsolete Solution: with minimal changes start using nessus_rest gem. --- lib/nessus/README | 66 ------ lib/nessus/TODO | 1 - lib/nessus/nessus-cli.rb | 382 ----------------------------------- lib/nessus/nessus-xmlrpc.rb | 308 ---------------------------- metasploit-framework.gemspec | 2 + plugins/nessus.rb | 32 ++- 6 files changed, 26 insertions(+), 765 deletions(-) delete mode 100644 lib/nessus/README delete mode 100644 lib/nessus/TODO delete mode 100755 lib/nessus/nessus-cli.rb delete mode 100644 lib/nessus/nessus-xmlrpc.rb diff --git a/lib/nessus/README b/lib/nessus/README deleted file mode 100644 index b16d2ad767..0000000000 --- a/lib/nessus/README +++ /dev/null @@ -1,66 +0,0 @@ -Nessus XML RPC library and Nessus Command Line interface to XML RPC - -(C) Vlatko Kosturjak, Kost. Distributed under GPL and BSD (dual licensed). - -Requirements -============ -Requirements are quite standard Ruby libraries for HTTPS and XML -parsing: -require 'uri' -require 'net/https' -require 'rexml/document' - -nessus-cli.rb -============= -Nessus command line interface for XML-RPC. - -Type ./nessus-cli.rb --help for command line options. - -Examples: ---------- - -./nessus-cli.rb --user john --password doe --scan scan-localhost --wait --output report.xml --target localhost - -./nessus-cli.rb --user user --password pass --scan localhost-scan --wait 5 -D --output report-localhost.xml --target localhost --verbose - -./nessus-cli.rb --user user --password pass --scan localhost-scan --wait 5 -D --output report-localhost.xml --target 127.0.0.1 --verbose --policy mypolicy --url https://localhost:8834 - -Or if you want to have detached scans: --------------------------------------- - -./nessus-cli.rb --user user --password pass --scan localhost-scan --target 127.0.0.1 --policy mypolicy - -./nessus-cli.rb --user user --password pass --list-scans - -./nessus-cli.rb --user user --password pass --pause 5329fae9-fb1d-0c67-a401-a0db12637c0d5bcd67900d34e00e - -./nessus-cli.rb --user user --password pass --resume 5329fae9-fb1d-0c67-a401-a0db12637c0d5bcd67900d34e00e - -./nessus-cli.rb --user user --password pass --stop 5329fae9-fb1d-0c67-a401-a0db12637c0d5bcd67900d34e00e - -./nessus-cli.rb --user user --password pass --stop-all - -./nessus-cli.rb --user user --password pass --report 5329fae9-fb1d-0c67-a401-a0db12637c0d5bcd67900d34e00e --output report.xml - -nessus-xmlrpc.rb -================ -communicate with Nessus(4.2+) over XML RPC interface - -Simple example: - -require 'nessus-xmlrpc' -n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); -# n=NessusXMLRPC::NessusXMLRPC.new('','user','pass'); # it's same -if n.logged_in - id,name = n.policy_get_first - puts "using policy ID: " + id + " with name: " + name - uid=n.scan_new(id,"textxmlrpc","127.0.0.1") - puts "status: " + n.scan_status(uid) - while not n.scan_finished(uid) - sleep 10 - end - content=n.report_file_download(uid) - File.open('report.xml', 'w') {|f| f.write(content) } -end - -Take a look at nessus-cli.rb for more advanced examples. diff --git a/lib/nessus/TODO b/lib/nessus/TODO deleted file mode 100644 index 9901b174ca..0000000000 --- a/lib/nessus/TODO +++ /dev/null @@ -1 +0,0 @@ -- Error handling, no puts diff --git a/lib/nessus/nessus-cli.rb b/lib/nessus/nessus-cli.rb deleted file mode 100755 index f6d62a8a52..0000000000 --- a/lib/nessus/nessus-cli.rb +++ /dev/null @@ -1,382 +0,0 @@ -#!/usr/bin/env ruby -# = nessus-cli.rb: Nessus command line interface for XML-RPC -# Author:: Vlatko Kosturjak -# -# (C) Vlatko Kosturjak, Kost. Distributed under GPL and BSD (dual licensed). - -require 'nessus-xmlrpc' -require 'getoptlong' - -verbose = 0 -debug = 0 -operation = '' -targets = '' -deletereport = false -user = '' -password = '' -scanname = '' -output = '' -output1 = '' -wait = '' -policy = '' -url = '' - -def intro - $stderr.print $0 + ": Nessus command line interface for XML-RPC\n" - $stderr.print "(C) Vlatko Kosturjak, Kost. Distributed under GPL.\n" - $stderr.print "\n" -end - -intro - -def give_help - puts <<-EOF ---user user for login to Nessus server ---password

password for login to Nessus server ---scan start scan with name ---target specify list of targets, separated by comma ---policy specify policy to use (name of policy) ---url url of Nessus server (default: localhost:8834) ---wait [t] wait scan to finish (ask in regular periods of for status) ---output output report XML to file ---output1 output report XML v1 to file ---reportdelete delete report after finish or delete report by id (if alone) ---stop stop scan identified by ---stop-all stop all scans ---pause pause scan identified by ---pause-all pause all scans ---resume resume scan identified by ---resume-all resume all scans ---report download report identified by ---list-scans list scans ---list-policy list policies ---status get status of scan by ---verbose be verbose ---debug be even more verbose ---help this help - -Examples: -#{$0} --user john --password doe --scan scan-localhost --wait --output report.xml --target localhost -EOF - exit 0 -end - -if ARGV.length < 1 - give_help -end - -opt = GetoptLong.new( - ["--help", "-h", GetoptLong::NO_ARGUMENT], - ["--verbose", "-v", GetoptLong::OPTIONAL_ARGUMENT], - ["--target", "-t", GetoptLong::REQUIRED_ARGUMENT], - ["--user", "-u", GetoptLong::REQUIRED_ARGUMENT], - ["--password", "-p", GetoptLong::REQUIRED_ARGUMENT], - ["--policy", "-P", GetoptLong::REQUIRED_ARGUMENT], - ["--url", "-U", GetoptLong::REQUIRED_ARGUMENT], - ["--deletereport", "-D", GetoptLong::OPTIONAL_ARGUMENT], - ["--wait", "-w", GetoptLong::OPTIONAL_ARGUMENT], - ["--scan", "-s", GetoptLong::REQUIRED_ARGUMENT], - ["--list-scans", "-l", GetoptLong::NO_ARGUMENT], - ["--list-policy", "-L", GetoptLong::NO_ARGUMENT], - ["--status", "-W", GetoptLong::REQUIRED_ARGUMENT], - ["--stop", "-S", GetoptLong::REQUIRED_ARGUMENT], - ["--stop-all", "-a", GetoptLong::NO_ARGUMENT], - ["--pause", "-q", GetoptLong::REQUIRED_ARGUMENT], - ["--pause-all", "-Q", GetoptLong::NO_ARGUMENT], - ["--resume", "-e", GetoptLong::REQUIRED_ARGUMENT], - ["--resume-all", "-E", GetoptLong::NO_ARGUMENT], - ["--report", "-r", GetoptLong::REQUIRED_ARGUMENT], - ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT], - ["--output1", "-1", GetoptLong::REQUIRED_ARGUMENT] -) - -def give_error - $stderr.print "You used incompatible options, probably you mixed --scan with --stop" - $stderr.print "or something similar." - exit 0 -end - -opt.each do |opt,arg| - case opt - when '--help' - give_help - when '--user' - user = arg - when '--password' - password = arg - when '--stop' - if operation == '' - operation = "stop" - scanname = arg - else - give_error - end - when '--pause' - if operation == '' - operation = "pause" - scanname = arg - else - give_error - end - when '--resume' - if operation == '' - operation = "resume" - scanname = arg - else - give_error - end - when '--stop-all' - if operation == '' - operation = "stop-all" - else - give_error - end - when '--pause-all' - if operation == '' - operation = "pause-all" - else - give_error - end - when '--resume-all' - if operation == '' - operation = "resume-all" - else - give_error - end - when '--report' - if operation == '' - operation = "report" - scanname = arg - else - give_error - end - when '--scan' - if operation == '' - operation = "scan" - scanname = arg - else - give_error - end - when '--target' - if arg[0..6] == 'file://' - f = File.open(arg[7..-1], "r") - f.each_line do |line| - line=line.chomp - line=line.strip - unless line == '' or line == nil - if targets == '' - targets = line - else - targets = targets + "," + line - end - end - end - f.close - else - # if there's multiple target options, add comma - if targets == '' - targets = arg - - else - targets = targets + "," + arg - end - end - when '--wait' - if arg == '' - wait = 15 - else - wait = arg.to_i - end - when '--reportdelete' - if arg == '' - deletereport=true - else - operation = "reportdelete" - scanname = arg - end - - when '--output' - output = arg - when '--output1' - output1 = arg - when '--policy' - policy = arg - when '--status' - if operation == '' - operation = "status" - scanname = arg - else - give_error - end - when '--url' - url = arg - when '--verbose' - if arg == '' - verbose += 1 - else - verbose = arg.to_i - end - when '--debug' - if arg == '' - debug += 1 - else - debug = arg.to_i - end - when '--list-scans' - if operation == '' - operation = "list-scans" - scanname = arg - else - give_error - end - when '--list-policy' - if operation == '' - operation = "list-policy" - scanname = arg - else - give_error - end - end -end - -if (user == '') or (password == '') - $stderr.print "User and password is required to login to Nessus server" - $stderr.print "Try --help!" - exit 1 -end - -$stderr.print "[i] Targets: " + targets +"\n" if verbose > 0 -$stderr.print "[i] Connecting to nessus server: " if verbose > 0 -n=NessusXMLRPC::NessusXMLRPC.new(url,user,password) -if n.logged_in - $stderr.print "OK!\n" if verbose > 0 -else - $stderr.print "[e] Error connecting/logging to the server!\n" - exit 2 -end - -case operation - when "scan" - if policy == '' - $stderr.print "[w] Policy not defined, using first served from the server\n" - pid,name = n.policy_get_first - $stderr.print "[w] using policy id " + pid + " with name " + name + "\n" - else - pid=n.policy_get_id(policy) - if pid == '' - $stderr.print "[e] policy doesn't exit: " + policy + "\n" - exit 3 - end - end - if targets == '' - $stderr.print "[w] Targets not defined, using localhost as target\n" - targets = '127.0.0.1' - end - $stderr.print "[i] Initiating scan with targets: "+targets+': ' if verbose > 0 - uid=n.scan_new(pid,scanname,targets) - $stderr.print "done\n" if verbose > 0 - unless wait == '' - while not n.scan_finished(uid) - $stderr.print "[v] Sleeping for " + wait.to_s() + ": " if verbose > 1 - sleep wait - $stderr.print "done\n" if verbose > 1 - stat = n.scan_status(uid) - print "\r" + stat if verbose > 0 - end - else - puts uid - exit 0 - end - unless output == '' - $stderr.print "[i] Output XML report to file: "+output if verbose > 0 - content=n.report_file_download(uid) - File.open(output, 'w') {|f| f.write(content) } - $stderr.print ": done\n" if verbose > 0 - end - unless output1 == '' - $stderr.print "[i] Output XML1 report to file: "+output1 if verbose > 0 - content=n.report_file1_download(uid) - File.open(output, 'w') {|f| f.write(content) } - $stderr.print ": done\n" if verbose > 0 - end - if deletereport - $stderr.print "[i] Deleting report: " if verbose > 0 - n.report_delete(uid) - $stderr.print "done\n" if verbose > 0 - end - when "report" - uid=scanname - if (output == '') and (output1 == '') - $stderr.print "[e] You want report, but specify filename with --output or output1\n" - end - unless output == '' - $stderr.print "[i] Output XML report to file: "+output if verbose > 0 - content=n.report_file_download(uid) - File.open(output, 'w') {|f| f.write(content) } - $stderr.print ": done\n" if verbose > 0 - end - unless output1 == '' - $stderr.print "[i] Output XML1 report to file: "+output1 if verbose > 0 - content=n.report1_file_download(uid) - File.open(output, 'w') {|f| f.write(content) } - $stderr.print ": done\n" if verbose > 0 - end - if deletereport - $stderr.print "[i] Deleting report: " if verbose > 0 - n.report_delete(uid) - $stderr.print "done\n" if verbose > 0 - end - when "stop" - $stderr.print "[i] Stopping scan: " + scanname if verbose > 0 - n.scan_stop(scanname) - $stderr.print "done\n" if verbose > 0 - when "stop-all" - $stderr.print "[i] Stopping all scans: " if verbose > 0 - list=n.scan_stop_all - $stderr.print "done\n" if verbose > 0 - if verbose > 1 - list.each {|uuid| puts "[v] Stop all: " + uuid } - end - when "pause" - $stderr.print "[i] Pausing scan: " + scanname if verbose > 0 - n.scan_pause(scanname) - $stderr.print "done\n" if verbose > 0 - when "pause-all" - $stderr.print "[i] Pausing all scans: " if verbose > 0 - list=n.scan_pause_all - $stderr.print "done\n" if verbose > 0 - if verbose > 1 - list.each {|uuid| puts "[v] Pause all: " + uuid } - end - when "resume" - $stderr.print "[i] Resuming scan: " + scanname if verbose > 0 - n.scan_resume(scanname) - $stderr.print "done\n" if verbose > 0 - when "resume-all" - $stderr.print "[i] Resuming all scans: " if verbose > 0 - list=n.scan_resume_all - $stderr.print "done\n" if verbose > 0 - if verbose > 1 - list.each {|uuid| puts "[v] Resume all: " + uuid } - end - when "reportdelete" - $stderr.print "[i] Deleting report: " + scanname if verbose > 0 - n.report_delete(scanname) - $stderr.print "done\n" if verbose > 0 - when "status" - puts "status: " + n.scan_status(scanname) - when "list-scans" - list=n.scan_list_hash - list.each {|scan| - puts scan['id']+":"+scan['name']+":"+ \ - scan['current']+"/"+scan['total'] - } - when "list-policy" - list=n.policy_list_names - list.each {|policy| - puts policy - } - -end - -$stderr.print "[v] End reached.\n" if verbose > 1 diff --git a/lib/nessus/nessus-xmlrpc.rb b/lib/nessus/nessus-xmlrpc.rb deleted file mode 100644 index 58e48e15c0..0000000000 --- a/lib/nessus/nessus-xmlrpc.rb +++ /dev/null @@ -1,308 +0,0 @@ -require 'net/http' - -module Nessus - class Client - class << self - @connection - @token - end - - def initialize(host, username = nil, password = nil, ssl_option = nil) - uri = URI.parse(host) - @connection = Net::HTTP.new(uri.host, uri.port) - @connection.use_ssl = true - if ssl_option == "ssl_verify" - @connection.verify_mode = OpenSSL::SSL::VERIFY_PEER - else - @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - - yield @connection if block_given? - authenticate(username, password) if username && password - end - - def authenticate(username, password) - payload = { - :username => username, - :password => password, - :json => 1 - } - res = http_post(:uri=>"/session", :data=>payload) - if res['token'] - @token = "token=#{res['token']}" - return true - else - false - end - end - - def x_cookie - {'X-Cookie'=>@token} - end - - alias_method :login, :authenticate - - def authenticated - if (@token && @token.include?('token=')) - return true - else - return false - end - end - - def get_server_properties - http_get(:uri=>"/server/properties", :fields=>x_cookie) - end - - def user_add(username, password, permissions, type) - payload = { - :username => username, - :password => password, - :permissions => permissions, - :type => type, - :json => 1 - } - http_post(:uri=>"/users", :fields=>x_cookie, :data=>payload) - end - - def user_delete(user_id) - res = http_delete(:uri=>"/users/#{user_id}", :fields=>x_cookie) - return res.code - end - - def user_chpasswd(user_id, password) - payload = { - :password => password, - :json => 1 - } - res = http_put(:uri=>"/users/#{user_id}/chpasswd", :data=>payload, :fields=>x_cookie) - return res.code - end - - def user_logout - res = http_delete(:uri=>"/session", :fields=>x_cookie) - return res.code - end - - def list_policies - http_get(:uri=>"/policies", :fields=>x_cookie) - end - - def list_users - http_get(:uri=>"/users", :fields=>x_cookie) - end - - def list_folders - http_get(:uri=>"/folders", :fields=>x_cookie) - end - - def list_scanners - http_get(:uri=>"/scanners", :fields=>x_cookie) - end - - def list_families - http_get(:uri=>"/plugins/families", :fields=>x_cookie) - end - - def list_plugins(family_id) - http_get(:uri=>"/plugins/families/#{family_id}", :fields=>x_cookie) - end - - def list_template(type) - res = http_get(:uri=>"/editor/#{type}/templates", :fields=>x_cookie) - end - - def plugin_details(plugin_id) - http_get(:uri=>"/plugins/plugin/#{plugin_id}", :fields=>x_cookie) - end - - def is_admin - res = http_get(:uri=>"/session", :fields=>x_cookie) - if res['permissions'] == 128 - return true - else - return false - end - end - - def server_properties - http_get(:uri=>"/server/properties", :fields=>x_cookie) - end - - def scan_create(uuid, name, description, targets) - payload = { - :uuid => uuid, - :settings => { - :name => name, - :description => description, - :text_targets => targets - }, - :json => 1 - }.to_json - http_post(:uri=>"/scans", :body=>payload, :fields=>x_cookie, :ctype=>'application/json') - end - - def scan_launch(scan_id) - http_post(:uri=>"/scans/#{scan_id}/launch", :fields=>x_cookie) - end - - def server_status - http_get(:uri=>"/server/status", :fields=>x_cookie) - end - - def scan_list - http_get(:uri=>"/scans", :fields=>x_cookie) - end - - def scan_details(scan_id) - http_get(:uri=>"/scans/#{scan_id}", :fields=>x_cookie) - end - - def scan_pause(scan_id) - http_post(:uri=>"/scans/#{scan_id}/pause", :fields=>x_cookie) - end - - def scan_resume(scan_id) - http_post(:uri=>"/scans/#{scan_id}/resume", :fields=>x_cookie) - end - - def scan_stop(scan_id) - http_post(:uri=>"/scans/#{scan_id}/stop", :fields=>x_cookie) - end - - def scan_export(scan_id, format) - payload = { - :format => format - }.to_json - http_post(:uri=>"/scans/#{scan_id}/export", :body=>payload, :ctype=>'application/json', :fields=>x_cookie) - end - - def scan_export_status(scan_id, file_id) - request = Net::HTTP::Get.new("/scans/#{scan_id}/export/#{file_id}/status") - request.add_field("X-Cookie", @token) - res = @connection.request(request) - if res.code == "200" - return "ready" - else - res = JSON.parse(res.body) - return res - end - end - - def policy_delete(policy_id) - res = http_delete(:uri=>"/policies/#{policy_id}", :fields=>x_cookie) - return res.code - end - - def host_detail(scan_id, host_id) - res = http_get(:uri=>"/scans/#{scan_id}/hosts/#{host_id}", :fields=>x_cookie) - end - - def report_download(scan_id, file_id) - res = http_get(:uri=>"/scans/#{scan_id}/export/#{file_id}/download", :raw_content=> true, :fields=>x_cookie) - end - - private - - def http_put(opts={}) - uri = opts[:uri] - data = opts[:data] - fields = opts[:fields] || {} - res = nil - - req = Net::HTTP::Put.new(uri) - req.set_form_data(data) unless data.blank? - fields.each_pair do |name, value| - req.add_field(name, value) - end - - begin - res = @connection.request(req) - rescue URI::InvalidURIError - return res - end - - res - end - - def http_delete(opts={}) - uri = opts[:uri] - fields = opts[:fields] || {} - res = nil - - req = Net::HTTP::Delete.new(uri) - - fields.each_pair do |name, value| - req.add_field(name, value) - end - - begin - res = @connection.request(req) - rescue URI::InvalidURIError - return res - end - - res - end - - def http_get(opts={}) - uri = opts[:uri] - fields = opts[:fields] || {} - raw_content = opts[:raw_content] || false - json = {} - - req = Net::HTTP::Get.new(uri) - fields.each_pair do |name, value| - req.add_field(name, value) - end - - begin - res = @connection.request(req) - rescue URI::InvalidURIError - return json - end - if !raw_content - parse_json(res.body) - else - res.body - end - end - - def http_post(opts={}) - uri = opts[:uri] - data = opts[:data] - fields = opts[:fields] || {} - body = opts[:body] - ctype = opts[:ctype] - json = {} - - req = Net::HTTP::Post.new(uri) - req.set_form_data(data) unless data.blank? - req.body = body unless body.blank? - req['Content-Type'] = ctype unless ctype.blank? - fields.each_pair do |name, value| - req.add_field(name, value) - end - - begin - res = @connection.request(req) - rescue URI::InvalidURIError - return json - end - - parse_json(res.body) - end - - def parse_json(body) - buf = {} - - begin - buf = JSON.parse(body) - rescue JSON::ParserError - end - - buf - end - - end -end diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 52abbba707..57f81b522e 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 by metasploit nessus bridge + spec.add_runtime_dependency 'nessus_rest' end diff --git a/plugins/nessus.rb b/plugins/nessus.rb index 07f24be884..b38b4ad9f4 100644 --- a/plugins/nessus.rb +++ b/plugins/nessus.rb @@ -1,5 +1,5 @@ # $Id$ $Revision$ -require 'nessus/nessus-xmlrpc' +require 'nessus_rest' require 'rex/parser/nessus_xml' module Msf @@ -44,7 +44,6 @@ module Msf "nessus_logout" => "Terminate the session", "nessus_server_status" => "Check the status of your Nessus server", "nessus_server_properties" => "Nessus server properties such as feed type, version, plugin set and server UUID", - "nessus_scanner_list" => "List all the scanners configured on the Nessus server", "nessus_report_download" => "Download a report from the nessus server in either Nessus, HTML, PDF, CSV, or DB format", "nessus_report_vulns" => "Get list of vulns from a report", "nessus_report_hosts" => "Get list of hosts from a report", @@ -158,7 +157,11 @@ module Msf end @url = "https://#{@host}:#{@port}/" print_status("Connecting to #{@url} as #{@user}") - @n = Nessus::Client.new(@url, @user, @pass,@sslv) + verify_ssl=false + if @sslv == "verify_ssl" then + verify_ssl=true + end + @n = NessusREST::Client.new(:url=>@url,:username=>@user,:password=>@pass,:ssl_verify=>verify_ssl) if @n.authenticated print_status("User #{@user} authenticated successfully.") @token = 1 @@ -791,7 +794,7 @@ module Msf print_status("Report downloaded to #{msf_local} directory") end else - print_error("Only completed scans ca be downloaded") + print_error("Only completed scans can be downloaded") end else print_status("Usage: ") @@ -963,7 +966,14 @@ module Msf end if valid_policy(uuid) print_status("Creating scan from policy number #{uuid}, called #{scan_name} - #{description} and scanning #{targets}") - scan = @n.scan_create(uuid, scan_name, description, targets) + et=Hash.new + et['enabled']=false + et['launch']='ONETIME' + et['name']=scan_name + et['text_targets']=targets + et['description']=description + et['launch_now']=false + scan = @n.scan_create(uuid, et) tbl = Rex::Text::Table.new( 'Columns' => [ "Scan ID", @@ -1065,11 +1075,17 @@ module Msf end targets.chop! print_status("Creating scan from policy #{policy_id}, called \"#{name}\" and scanning all hosts in all the workspaces") - scan = @n.scan_create(policy_id, name, desc, targets) + et=Hash.new + et['enabled']=false + et['launch']='ONETIME' + et['name']=name + et['text_targets']=targets + et['description']=desc + et['launch_now']=true + scan = @n.scan_create(policy_id, et) if !scan["error"] scan = scan["scan"] - print_status("Scan ID #{scan['id']} successfully created") - print_status("Run nessus_scan_launch #{scan['id']} to launch the scan") + print_status("Scan ID #{scan['id']} successfully created and launched") else print_error(JSON.pretty_generate(scan)) end