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.
bug/bundler_fix
Vlatko Kosturjak 2016-08-14 17:57:33 +02:00
parent d34579f1f0
commit 46e4ee4c5b
6 changed files with 26 additions and 765 deletions

View File

@ -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.

View File

@ -1 +0,0 @@
- Error handling, no puts

View File

@ -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> user for login to Nessus server
--password <p> password for login to Nessus server
--scan <name> start scan with name
--target <ip> specify list of targets, separated by comma
--policy <pol> specify policy to use (name of policy)
--url <url> url of Nessus server (default: localhost:8834)
--wait [t] wait scan to finish (ask in regular periods of <t> for status)
--output <f> output report XML to file <f>
--output1 <f> output report XML v1 to file <f>
--reportdelete delete report after finish or delete report by id (if alone)
--stop <id> stop scan identified by <id>
--stop-all stop all scans
--pause <id> pause scan identified by <id>
--pause-all pause all scans
--resume <id> resume scan identified by <id>
--resume-all resume all scans
--report <id> download report identified by <id>
--list-scans list scans
--list-policy list policies
--status <id> get status of scan by <id>
--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

View File

@ -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

View File

@ -151,4 +151,6 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'tzinfo' spec.add_runtime_dependency 'tzinfo'
# Needed so that disk size output isn't horrible # Needed so that disk size output isn't horrible
spec.add_runtime_dependency 'filesize' spec.add_runtime_dependency 'filesize'
# Needed by metasploit nessus bridge
spec.add_runtime_dependency 'nessus_rest'
end end

View File

@ -1,5 +1,5 @@
# $Id$ $Revision$ # $Id$ $Revision$
require 'nessus/nessus-xmlrpc' require 'nessus_rest'
require 'rex/parser/nessus_xml' require 'rex/parser/nessus_xml'
module Msf module Msf
@ -44,7 +44,6 @@ module Msf
"nessus_logout" => "Terminate the session", "nessus_logout" => "Terminate the session",
"nessus_server_status" => "Check the status of your Nessus server", "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_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_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_vulns" => "Get list of vulns from a report",
"nessus_report_hosts" => "Get list of hosts from a report", "nessus_report_hosts" => "Get list of hosts from a report",
@ -158,7 +157,11 @@ module Msf
end end
@url = "https://#{@host}:#{@port}/" @url = "https://#{@host}:#{@port}/"
print_status("Connecting to #{@url} as #{@user}") 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 if @n.authenticated
print_status("User #{@user} authenticated successfully.") print_status("User #{@user} authenticated successfully.")
@token = 1 @token = 1
@ -791,7 +794,7 @@ module Msf
print_status("Report downloaded to #{msf_local} directory") print_status("Report downloaded to #{msf_local} directory")
end end
else else
print_error("Only completed scans ca be downloaded") print_error("Only completed scans can be downloaded")
end end
else else
print_status("Usage: ") print_status("Usage: ")
@ -963,7 +966,14 @@ module Msf
end end
if valid_policy(uuid) if valid_policy(uuid)
print_status("Creating scan from policy number #{uuid}, called #{scan_name} - #{description} and scanning #{targets}") 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( tbl = Rex::Text::Table.new(
'Columns' => [ 'Columns' => [
"Scan ID", "Scan ID",
@ -1065,11 +1075,17 @@ module Msf
end end
targets.chop! targets.chop!
print_status("Creating scan from policy #{policy_id}, called \"#{name}\" and scanning all hosts in all the workspaces") 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"] if !scan["error"]
scan = scan["scan"] scan = scan["scan"]
print_status("Scan ID #{scan['id']} successfully created") print_status("Scan ID #{scan['id']} successfully created and launched")
print_status("Run nessus_scan_launch #{scan['id']} to launch the scan")
else else
print_error(JSON.pretty_generate(scan)) print_error(JSON.pretty_generate(scan))
end end