nexty 0.20
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/.rvmrc +1 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +20 -0
- data/README.md +29 -0
- data/Rakefile +8 -0
- data/bin/nexty +107 -0
- data/conf/config.yaml.sample +6 -0
- data/lib/nexty/config.rb +19 -0
- data/lib/nexty/connector.rb +14 -0
- data/lib/nexty/device.rb +29 -0
- data/lib/nexty/report.rb +77 -0
- data/lib/nexty/site.rb +56 -0
- data/lib/nexty/version.rb +3 -0
- data/lib/nexty.rb +5 -0
- data/nexty.gemspec +18 -0
- data/spec/data/sites.txt +5 -0
- data/spec/nexty_site_spec.rb +15 -0
- data/spec/spec_helper.rb +1 -0
- metadata +71 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@nexty
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010-2012 Paolo Perego
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Nexty
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'nexty'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install nexty
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/nexty
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
class String
|
4
|
+
def starts_with?(pattern)
|
5
|
+
! self.match(/^#{pattern}/).nil?
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Numeric
|
10
|
+
def duration
|
11
|
+
secs = self.to_int
|
12
|
+
mins = secs / 60
|
13
|
+
hours = mins / 60
|
14
|
+
days = hours / 24
|
15
|
+
|
16
|
+
return "#{days} days and #{hours % 24} hours" if days > 0
|
17
|
+
return "#{hours} hours and #{mins % 60} minutes" if days == 0 and hours > 0
|
18
|
+
return "#{mins} minutes and #{secs % 60} seconds" if days == 0 and hours == 0 and mins > 0
|
19
|
+
return "#{secs} seconds" if days == 0 and hours == 0 and mins == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if RUBY_VERSION.split('.')[1] == "8"
|
24
|
+
require 'rubygems'
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'time'
|
28
|
+
require 'rainbow'
|
29
|
+
require 'nexpose'
|
30
|
+
require 'nexty'
|
31
|
+
require 'getoptlong'
|
32
|
+
require 'csv'
|
33
|
+
|
34
|
+
opts = GetoptLong.new(
|
35
|
+
[ '--report', '-r', GetoptLong::REQUIRED_ARGUMENT ],
|
36
|
+
[ '--host', '-H', GetoptLong::REQUIRED_ARGUMENT ],
|
37
|
+
[ '--full-export', '-f', GetoptLong::REQUIRED_ARGUMENT ],
|
38
|
+
[ '--download', '-d', GetoptLong::REQUIRED_ARGUMENT ]
|
39
|
+
)
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
trap("INT") { puts '['+'INTERRUPTED'.color(:red)+']'; exit -1 }
|
44
|
+
|
45
|
+
|
46
|
+
# templates = nsc.report_template_listing
|
47
|
+
#
|
48
|
+
# templates.each do |template|
|
49
|
+
# p template[:template_id]
|
50
|
+
# end
|
51
|
+
|
52
|
+
conn = Nexty::Connector.new
|
53
|
+
nsc = conn.nsc
|
54
|
+
|
55
|
+
begin
|
56
|
+
printf "Connecting to #{conn.config["config"]["host"]}:#{conn.config["config"]["port"]} - ".color(:white)
|
57
|
+
nsc.login
|
58
|
+
rescue ::Nexpose::APIError => e
|
59
|
+
printf "failed\n".color(:red)
|
60
|
+
$stderr.puts("#{e.reason}")
|
61
|
+
exit(1)
|
62
|
+
end
|
63
|
+
printf "succeded\n".color(:green)
|
64
|
+
|
65
|
+
|
66
|
+
opts.each do |opt, arg|
|
67
|
+
case opt
|
68
|
+
|
69
|
+
when '--download'
|
70
|
+
|
71
|
+
file_name = "export_#{Time.now.strftime("%Y%m%d%H%M%s")}.csv"
|
72
|
+
puts "Report saved: #{Nexty::Report.download(arg, file_name, nsc)}".color(:white)
|
73
|
+
|
74
|
+
when '--full-export'
|
75
|
+
|
76
|
+
fn = Nexty::Report.generate_from_a_template(arg, nsc)
|
77
|
+
file_name = "export_#{Time.now.strftime("%Y%m%d%H%M%s")}.csv"
|
78
|
+
nsc.logout
|
79
|
+
begin
|
80
|
+
printf "Connecting to #{conn.config["config"]["host"]}:#{conn.config["config"]["port"]} - ".color(:white)
|
81
|
+
nsc.login
|
82
|
+
rescue ::Nexpose::APIError => e
|
83
|
+
printf "failed\n".color(:red)
|
84
|
+
$stderr.puts("#{e.reason}")
|
85
|
+
exit(1)
|
86
|
+
end
|
87
|
+
printf "succeded\n".color(:green)
|
88
|
+
|
89
|
+
puts "Report saved: #{Nexty::Report.download(fn, file_name, nsc)}".color(:white)
|
90
|
+
|
91
|
+
when '--report'
|
92
|
+
fn = Nexty::Report.generate_from_a_list_of_sites(arg, nsc)
|
93
|
+
puts "Report saved: #{fn}".color(:white)
|
94
|
+
|
95
|
+
when '--host'
|
96
|
+
|
97
|
+
dev= Nexty::Device.find_by_address(nsc, arg)
|
98
|
+
if ! dev.nil?
|
99
|
+
puts "#{dev.address} found @ site #{dev.site_id}. Risk score = #{dev.riskscore} * #{dev.riskfactor}".color(:green)
|
100
|
+
else
|
101
|
+
puts "#{arg} not found".color{:red}
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
nsc.logout
|
data/lib/nexty/config.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Nexty
|
5
|
+
class Config
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def read(file)
|
9
|
+
if file.nil? or !File.exists?(file)
|
10
|
+
return {"config" => {"host"=>"127.0.0.1", "port"=>"3790", "user"=>"nxadmin", "pass"=>"nxadmin"}}
|
11
|
+
end
|
12
|
+
config = YAML.load_file(file)
|
13
|
+
|
14
|
+
return config
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'nexty/config'
|
2
|
+
module Nexty
|
3
|
+
class Connector
|
4
|
+
|
5
|
+
attr_reader :nsc, :config
|
6
|
+
def initialize(file="./conf/config.yaml")
|
7
|
+
@config = Nexty::Config.instance.read(file)
|
8
|
+
@nsc = Nexpose::Connection.new(@config["config"]["host"],
|
9
|
+
@config["config"]["user"],
|
10
|
+
@config["config"]["pass"],
|
11
|
+
@config["config"]["port"])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/nexty/device.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Nexty
|
2
|
+
class Device
|
3
|
+
def self.all(connection)
|
4
|
+
@devices = []
|
5
|
+
|
6
|
+
r = connection.execute('<SiteDeviceListingRequest session-id="' + connection.session_id + '"/>')
|
7
|
+
if (r.success)
|
8
|
+
r.res.elements.each('SiteDeviceListingResponse/SiteDevices') do |rr|
|
9
|
+
@sid = rr.attribute("site-id")
|
10
|
+
rr.elements.each('device') do |d|
|
11
|
+
@devices.push(Nexpose::Device.new(d.attributes['id'], @sid, d.attributes["address"], d.attributes["riskfactor"], d.attributes['riskscore']))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
@devices
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_by_address(connection, address)
|
19
|
+
devices = Nexty::Device.all(connection)
|
20
|
+
devices.each do |d|
|
21
|
+
if d.address == address
|
22
|
+
return d
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
return nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/nexty/report.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
module Nexty
|
2
|
+
class Report
|
3
|
+
|
4
|
+
# def self.generate_from_ip_list(ip_list, nsc)
|
5
|
+
# ips = Nexty::Sites.load_from_file(ip_list)
|
6
|
+
|
7
|
+
# report = Nexpose::ReportAdHoc.new(nsc, 'SOX Vulns', 'csv')
|
8
|
+
# ips.each do |ip|
|
9
|
+
# report.addFilter('device',addFilter('device',
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
|
13
|
+
|
14
|
+
def self.download(url, filename, nsc)
|
15
|
+
return nil if url.nil? or url.empty?
|
16
|
+
uri = URI.parse(url)
|
17
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
18
|
+
http.use_ssl = true
|
19
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX: security issue
|
20
|
+
headers = {'Cookie' => "nexposeCCSessionID=#{nsc.session_id}"}
|
21
|
+
resp = http.get(uri.path, headers)
|
22
|
+
|
23
|
+
file = File.open(filename, "w")
|
24
|
+
file.write(resp.body)
|
25
|
+
file.close
|
26
|
+
|
27
|
+
filename
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.generate(nsc, list, options={:template=>'', :filename=> '', :format=>"csv", :scan_to_include=>1})
|
31
|
+
|
32
|
+
options[:filename] = "export_#{Time.now.strftime("%Y%m%d%H%M%s")}.csv" if options[:filename].nil? or options[:filename].empty?
|
33
|
+
report = Nexpose::ReportConfig.new(nsc)
|
34
|
+
report.set_name(options[:filename])
|
35
|
+
report.set_template_id(options[:template])
|
36
|
+
report.set_format(options[:format])
|
37
|
+
|
38
|
+
list.each do |item|
|
39
|
+
site_config = Nexpose::SiteConfig.new
|
40
|
+
site_config.getSiteConfig(nsc, item[:site_id])
|
41
|
+
scan_history = nsc.site_scan_history(item[:site_id])
|
42
|
+
scan_history.sort! { |a,b| b[:start_time] <=> a[:start_time]}
|
43
|
+
scan_history.take(options[:scan_to_include]).each do |scan|
|
44
|
+
report.addFilter('scan', scan[:scan_id])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
report.saveReport
|
49
|
+
|
50
|
+
url = nil
|
51
|
+
while not url
|
52
|
+
url = nsc.report_last(report.config_id)
|
53
|
+
select(nil, nil, nil, 10)
|
54
|
+
end
|
55
|
+
|
56
|
+
full_url="https://#{nsc.host}:#{nsc.port}#{url}"
|
57
|
+
|
58
|
+
{:url=>full_url, :filename=>options[:filename]}
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.generate_from_a_template(template_name, nsc)
|
62
|
+
sites=nsc.site_listing
|
63
|
+
result = Nexty::Report.generate(nsc, sites, {:template=>template_name, :filename=>nil, :format=>'csv', :scan_to_include=>1})
|
64
|
+
result[:url]
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.generate_from_a_list_of_sites(site_list=nil, nsc)
|
68
|
+
sites=Nexty::Sites.load_from_file(site_list)
|
69
|
+
s = []
|
70
|
+
sites.each do |site|
|
71
|
+
s << nsc.find_site_by_name(site)
|
72
|
+
end
|
73
|
+
result = Nexty::Report.generate(nsc, s, {:template=>"template-per-vulnerability-meeting", :format=>'csv', :filename=>nil, :scan_to_include=>4})
|
74
|
+
Nexty::Report.download(result[:url], result[:filename], nsc)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/nexty/site.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Nexty
|
2
|
+
class Sites
|
3
|
+
# Public: load site names from a text file returning it in an array
|
4
|
+
#
|
5
|
+
# file - the filename
|
6
|
+
#
|
7
|
+
# Example
|
8
|
+
# Nexty::Sites.load_from_file('./important_sites.txt')
|
9
|
+
# # => ['www', 'mail', 'dns']
|
10
|
+
#
|
11
|
+
# Nexty::Sites.load_from_file('./doesnt_exists_file.foo')
|
12
|
+
# # => []
|
13
|
+
#
|
14
|
+
# Returns an Array containing the site names or an empty Array if the file
|
15
|
+
# doesn't exist.
|
16
|
+
def self.load_from_file(file)
|
17
|
+
if !File.exists?(file)
|
18
|
+
return []
|
19
|
+
end
|
20
|
+
|
21
|
+
ret = []
|
22
|
+
File.open(file).each_line{ |s|
|
23
|
+
ret << s.chomp
|
24
|
+
}
|
25
|
+
|
26
|
+
ret
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Nexpose
|
33
|
+
module NexposeAPI
|
34
|
+
include XMLUtils
|
35
|
+
|
36
|
+
def find_site_by_name(name)
|
37
|
+
r = execute(make_xml('SiteListingRequest', {}))
|
38
|
+
res = {}
|
39
|
+
|
40
|
+
if (r.success)
|
41
|
+
r.res.elements.each("//SiteSummary") do |site|
|
42
|
+
if (site.attributes['name'] == name)
|
43
|
+
res = {
|
44
|
+
:site_id => site.attributes['id'].to_i,
|
45
|
+
:name => site.attributes['name'].to_s,
|
46
|
+
:risk_factor => site.attributes['riskfactor'].to_f,
|
47
|
+
:risk_score => site.attributes['riskscore'].to_f,
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
res
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
data/lib/nexty.rb
ADDED
data/nexty.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/nexty/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Paolo Perego"]
|
6
|
+
gem.email = ["thesp0nge@gmail.com"]
|
7
|
+
gem.description = %q{A command line interface to your Nexpose VA tool}
|
8
|
+
gem.summary = %q{A command line interface to your Nexpose VA tool}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "nexty"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Nexty::VERSION
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Site loading from file " do
|
4
|
+
it "should return an empty array if the file doesn't exist" do
|
5
|
+
a = Nexty::Sites.load_from_file('./spec/data/sites_non_existant.txt')
|
6
|
+
a.should be_empty
|
7
|
+
a.should_not be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return an array with site names" do
|
11
|
+
a = Nexty::Sites.load_from_file('./spec/data/sites.txt')
|
12
|
+
a.should_not be_empty
|
13
|
+
a.should include 'a'
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'nexty'
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nexty
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.20'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Paolo Perego
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-04 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: A command line interface to your Nexpose VA tool
|
15
|
+
email:
|
16
|
+
- thesp0nge@gmail.com
|
17
|
+
executables:
|
18
|
+
- nexty
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- .rvmrc
|
24
|
+
- Gemfile
|
25
|
+
- LICENSE.txt
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- bin/nexty
|
29
|
+
- conf/config.yaml.sample
|
30
|
+
- lib/nexty.rb
|
31
|
+
- lib/nexty/config.rb
|
32
|
+
- lib/nexty/connector.rb
|
33
|
+
- lib/nexty/device.rb
|
34
|
+
- lib/nexty/report.rb
|
35
|
+
- lib/nexty/site.rb
|
36
|
+
- lib/nexty/version.rb
|
37
|
+
- nexty.gemspec
|
38
|
+
- spec/data/sites.txt
|
39
|
+
- spec/nexty_site_spec.rb
|
40
|
+
- spec/spec_helper.rb
|
41
|
+
homepage: ''
|
42
|
+
licenses: []
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
hash: -671521822909928412
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.8.24
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: A command line interface to your Nexpose VA tool
|
68
|
+
test_files:
|
69
|
+
- spec/data/sites.txt
|
70
|
+
- spec/nexty_site_spec.rb
|
71
|
+
- spec/spec_helper.rb
|