├── README.md
├── Nexpose-Lieberman-Integration
├── Integration Guide Lieberman.pdf
├── nexpose_integration.rb
└── lieberman_integration.rb
├── conf
├── vulnotify.yaml
└── nexpose.yaml
├── templateVulnList.rb
├── delete_devices_from_group.rb
├── regex_dag.rb
├── nexposeBackup.rb
├── report_distlist.rb
├── listEngines.rb
├── dhcp_heal.rb
├── scanAssetGroup.rb
├── scan_asset_group.rb
├── export_running_log.rb
├── stopScansForEngine.rb
├── moveEngineToPool.rb
├── openPortQuery.rb
├── add_global_exclusion.rb
├── discoveryCount.rb
├── search_ip.rb
├── powershell
├── alter_credential.ps1
├── example_site_get.ps1
├── alter_credential_build.ps1
├── create_asset_group.ps1
└── example_start_scan_post.ps1
├── nexposeListBackups.rb
├── create_asset_group.rb
├── addToAssetGroup.rb
├── createAssetGroup.rb
├── deleteStaleAssets.rb
├── stopPausedScans.rb
├── dbMaint.rb
├── discoveryCountCSV.rb
├── updateEmailAlerts.rb
├── vulnIDQuery.rb
├── assetGroupQuery.rb
├── massMaxDurationMod.rb
├── createSyslogAlerts.rb
├── createEmailAlerts.rb
├── calGen.rb
├── scan_mailer.rb
├── scanCleanup.rb
├── scan_by_iplist.rb
├── adhocScanGen.rb
├── smartCleanup.rb
├── systemCheck.rb
├── vulnReporter.rb
└── logtime.py
/README.md:
--------------------------------------------------------------------------------
1 | nexpose
2 | =======
3 |
4 | generic scripts for managing nexpose
5 |
6 | See: https://github.com/BrianWGray/nexpose/wiki
--------------------------------------------------------------------------------
/Nexpose-Lieberman-Integration/Integration Guide Lieberman.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianWGray/nexpose/HEAD/Nexpose-Lieberman-Integration/Integration Guide Lieberman.pdf
--------------------------------------------------------------------------------
/conf/vulnotify.yaml:
--------------------------------------------------------------------------------
1 | ############################################################
2 | #
3 | # Vulnerabilities to take action on and the action to take
4 | #
5 | ############################################################
6 |
7 | ---
8 | - vuln: "cmty-http-ricoh-no-password"
9 | vulnId: "cmty-http-ricoh-no-password"
10 | reporter_types: [email]
11 |
12 | - vuln: "apache-tomcat-default-password"
13 | vulnId: "apache-tomcat-default-password"
14 | reporter_types: [email]
15 |
16 | - vuln: "windows-hotfix-ms08-067"
17 | vulnId: "windows-hotfix-ms08-067"
18 | reporter_types: [email]
19 |
20 | - vuln: "http-openssl-cve-2014-0160"
21 | vulnId: "http-openssl-cve-2014-0160"
22 | reporter_types: [email]
23 |
24 | - vuln: "cmty-ssh-default-account-"
25 | vulnId: "cmty-ssh-default-account-%"
26 | reporter_types: [email]
27 |
28 | - vuln: "cmty-telnet-default-account-"
29 | vulnId: "cmty-telnet-default-account-%"
30 | reporter_types: [email]
--------------------------------------------------------------------------------
/conf/nexpose.yaml:
--------------------------------------------------------------------------------
1 | # Nexpose-Client Script Configurations
2 |
3 |
4 | hostname: nexpose.example.com
5 | username: apiuser
6 | passwordkey: "apiuserpassword"
7 | port: "3780"
8 | logserver: "localhost"
9 | logport: 514
10 | alertFail: 1
11 | alertPause: 1
12 | alertResume: 1
13 | alertStart: 1
14 | alertStop: 1
15 | alertConfirmed: 1
16 | alertPotential: 1
17 | alertSeverity: 1
18 | alertUnconfirmed: 1
19 | adhocscantemplate: "full-audit"
20 | adhocscanengine: 2
21 | netconfigoutfile: "./conf/netconfig.yaml"
22 | servicetimeout: 600
23 | cleanupqueue: 5
24 | cleanupwaittime: 60
25 | nexposeajaxtimeout: 1200000000
26 | icsfilename: "NexposeScanSchedule.ics"
27 | icsitterations: 4
28 | staledays: 60
29 |
30 | # Vulnerability Reporter
31 | vulnReporterThreads: 1
32 | ageInterval: 24
33 | vrDebug: "false"
34 |
35 | # Mail Notification Defaults
36 | mailFrom: "Notifier@example.com"
37 | mailServer: "relay@example.com"
38 | mailPort: "25"
39 | mailDomain: "example.com"
40 | defaultEmail: "security@example.com"
41 |
--------------------------------------------------------------------------------
/templateVulnList.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray 11/12/2014
3 |
4 | require 'yaml'
5 | require 'csv'
6 | require 'nexpose'
7 | require 'pp'
8 |
9 | include Nexpose
10 |
11 | # Default Values
12 | # Default Values from yaml file
13 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
14 | config = YAML.load_file(config_path)
15 |
16 | @host = config["hostname"]
17 | @userid = config["username"]
18 | @password = config["passwordkey"]
19 | @port = config["port"]
20 |
21 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
22 | puts 'logging into Nexpose'
23 |
24 | begin
25 | nsc.login
26 | rescue ::Nexpose::APIError => err
27 | $stderr.puts("Connection failed: #{err.reason}")
28 | exit(1)
29 | end
30 |
31 | puts 'logged into Nexpose'
32 | at_exit { nsc.logout }
33 |
34 |
35 |
36 | begin
37 |
38 | templates = nsc.list_scan_templates
39 |
40 | templates.each do |templateInfo|
41 | puts "TemplateID: #{templateInfo.id}, TemplateName: #{templateInfo.name}"
42 | scanTemplateInfo = Nexpose::ScanTemplate.load(nsc,"#{templateInfo.id}")
43 |
44 | pp scanTemplateInfo
45 | end
46 | end
47 |
48 | exit
49 |
--------------------------------------------------------------------------------
/delete_devices_from_group.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # BrianWGray
3 | # 03.19.2018
4 |
5 | # Script Purpose
6 | ## Purge assets listed within a specified group ID.
7 |
8 | ## written as an example for https://kb.help.rapid7.com/discuss/5aaabb3e311eea001e60e862
9 |
10 | require 'yaml'
11 | require 'nexpose'
12 |
13 | include Nexpose
14 |
15 | # Default Values
16 | # Group ID to purge assets from
17 | groupID = 53
18 |
19 | # Default Values from yaml file
20 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
21 | config = YAML.load_file(config_path)
22 |
23 | @host = config["hostname"]
24 | @userid = config["username"]
25 | @password = config["passwordkey"]
26 | @port = config["port"]
27 |
28 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
29 |
30 | begin
31 | nsc.login
32 | rescue ::Nexpose::APIError => err
33 | $stderr.puts("Connection failed: #{err.reason}")
34 | exit(1)
35 | end
36 |
37 | at_exit { nsc.logout if nsc.session_id }
38 |
39 | # load the specified asset group to purge and iterate through devices
40 | assetGroup = Nexpose::AssetGroup.load(nsc, groupID)
41 |
42 | assetGroup.assets.each do |device|
43 | puts "Deleting #{device.address} [Device ID: #{device.id}] Site ID: #{device.site_id} Risk Score: #{device.risk_score}"
44 | nsc.delete_device(device.id)
45 | end
46 |
47 |
48 |
--------------------------------------------------------------------------------
/regex_dag.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Date Created: 05.17.2018
4 | # Written by: BrianWGray
5 |
6 | # Written for
7 | # https://kb.help.rapid7.com/v1.0/discuss/5afd940ccbdae50003fe0e2e
8 |
9 | ## Script performs the following tasks
10 | ## 1.) Demonstrate creating a DAG using criterion
11 | ## 2.) Create new asset group
12 | ## 3.) Add addresses to the created asset group based on a regex search.
13 |
14 | require 'yaml'
15 | require 'nexpose'
16 | require 'pp'
17 | include Nexpose
18 |
19 | # Default Values from yaml file
20 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
21 | config = YAML.load_file(config_path)
22 |
23 | host = config["hostname"]
24 | userid = config["username"]
25 | password = config["passwordkey"]
26 | port = config["port"]
27 |
28 | nsc = Nexpose::Connection.new(host, userid, password, port)
29 |
30 | begin
31 | nsc.login
32 | rescue ::Nexpose::APIError => err
33 | $stderr.puts("Connection failed: #{err.reason}")
34 | exit(1)
35 | end
36 | at_exit { nsc.logout }
37 |
38 | assetarray = []
39 | assetarray << Criterion.new(Search::Field::IP_ADDRESS,Search::Operator::LIKE,"^10\\\.\\\d{1,3}\\\.\\\d{1,3}\\\.11$")
40 |
41 |
42 | crag = Criteria.new(assetarray,"OR")
43 | dag = DynamicAssetGroup.new('test_dag',crag,'test description')
44 |
45 | dag.save(nsc)
46 |
47 | pp(dag)
48 |
49 | exit
--------------------------------------------------------------------------------
/nexposeBackup.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 07.14.2014
4 |
5 | ## Script generates a Platform Independent application backup.
6 |
7 | require 'yaml'
8 | require 'nexpose'
9 |
10 | include Nexpose
11 |
12 | # Default Values
13 |
14 | config = YAML.load_file("conf/nexpose.yaml") # From file
15 |
16 | @host = config["hostname"]
17 | @userid = config["username"]
18 | @password = config["passwordkey"]
19 | @port = config["port"]
20 |
21 |
22 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
23 | puts 'logging into Nexpose'
24 |
25 | begin
26 | nsc.login
27 | rescue ::Nexpose::APIError => err
28 | $stderr.puts("Connection failed: #{e.reason}")
29 | exit(1)
30 | end
31 |
32 | puts 'logged into Nexpose'
33 | at_exit { nsc.logout }
34 |
35 | begin
36 | # Check scan activity wait until there are no scans running
37 | active_scans = nsc.scan_activity
38 | if active_scans.any?
39 | puts "Current scan status: #{active_scans.to_s}"
40 | sleep(15)
41 | end
42 | end while active_scans.any?
43 |
44 | time = Time.new
45 | backupDescription = time.strftime("%Y%m%d")+"_PI_Weekly"
46 |
47 | # Base backup code use from https://community.rapid7.com/thread/4687
48 | # Start the backup
49 | if active_scans.empty?
50 | platform_independent = true
51 | puts "Initiating Platform Independent backup to local disk"
52 | nsc.backup(platform_independent, backupDescription)
53 | else
54 |
55 | end
56 |
57 |
58 | puts 'Logging out'
59 | exit
60 |
--------------------------------------------------------------------------------
/report_distlist.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Date Created: 05.21.2018
4 | # Written by: BrianWGray
5 |
6 | # Written for
7 | # https://kb.help.rapid7.com/discuss/5b031d8a01b0ff00038d8b9b
8 |
9 | ## Script performs the following tasks
10 | ## 1.) pull report configuration
11 | ## 2.) generate list of report recipients
12 |
13 | require 'yaml'
14 | require 'nexpose'
15 | require 'pp'
16 | include Nexpose
17 |
18 | # Default Values from yaml file
19 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
20 | config = YAML.load_file(config_path)
21 |
22 | host = config["hostname"]
23 | userid = config["username"]
24 | password = config["passwordkey"]
25 | port = config["port"]
26 |
27 | nsc = Nexpose::Connection.new(host, userid, password, port)
28 |
29 | begin
30 | nsc.login
31 | rescue ::Nexpose::APIError => err
32 | $stderr.puts("Connection failed: #{err.reason}")
33 | exit(1)
34 | end
35 | at_exit { nsc.logout }
36 |
37 | # Pull a list of all reports
38 | reportList = nsc.list_reports
39 |
40 | # Iterate through each report
41 | reportList.each do |reportDetails|
42 | # Load report information for each report
43 | reportInfo = ReportConfig.load(nsc, reportDetails.config_id)
44 |
45 | # If the report has external recipients configured, print the recipient list
46 | if(reportInfo.delivery.email.respond_to? :recipients) then
47 | puts("Report: #{reportInfo.name}")
48 | reportInfo.delivery.email.recipients.each do |eachRecipient|
49 | puts(eachRecipient)
50 | end
51 | end
52 | end
53 |
54 |
55 | exit
--------------------------------------------------------------------------------
/listEngines.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 06.09.2015
4 |
5 |
6 | # List Engines and data associated with each.
7 |
8 | require 'yaml'
9 | require 'nexpose'
10 | require 'pp'
11 | include Nexpose
12 |
13 |
14 |
15 | #Default Values from yaml file
16 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
17 | config = YAML.load_file(config_path)
18 |
19 | @host = config["hostname"]
20 | @userid = config["username"]
21 | @password = config["passwordkey"]
22 | @port = config["port"]
23 |
24 |
25 | begin
26 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
27 | puts 'logging into Nexpose'
28 |
29 | begin
30 | nsc.login
31 | rescue ::Nexpose::APIError => err
32 | $stderr.puts("Connection failed: #{err.reason}")
33 | exit(1)
34 | end
35 |
36 | puts 'logged into Nexpose'
37 | at_exit { nsc.logout }
38 |
39 | nsc.engines.each do |engine|
40 | engineLoad = Engine.load(nsc,engine.id)
41 | # pp(engineLoad)
42 | puts("Engine: #{engine.name}-#{engine.id} Status: #{engine.status}")
43 | engineLoad.sites.each {|siteData|
44 | siteInfoID = siteData.id
45 | siteDetail = Site.load(nsc, siteInfoID)
46 | # pp(siteDetail)
47 | siteName = siteDetail.name
48 | puts " Site ID: #{siteInfoID} Site Name: #{siteName}"
49 | }
50 | end
51 |
52 | =begin
53 | @nsc.list_engine_pools.each do |engine|
54 | puts(" EnginePool: #{engine.name}-#{engine.id}")
55 | engineLoad = Engine.load(@nsc,engine.id)
56 | pp(engineLoad)
57 | end
58 | =end
59 |
60 |
61 | end
62 |
--------------------------------------------------------------------------------
/Nexpose-Lieberman-Integration/nexpose_integration.rb:
--------------------------------------------------------------------------------
1 | class NexposeIntegration
2 |
3 | require 'nexpose'
4 | include Nexpose
5 |
6 | def connect (console, nxuser, nxpass)
7 | @nsc = Connection.new(console, nxuser, nxpass)
8 | @nsc.login
9 | end
10 |
11 | def get_all_sites
12 | all_sites = @nsc.list_sites
13 | end
14 |
15 | def get_hostnames(site)
16 | hostnames = []
17 | site = Site.load(@nsc, site.id)
18 | assets = site.assets
19 | assets.each do |asset|
20 | if asset.is_a?(HostName)
21 | # Lieberman doesn't like FQDNs
22 | hostname = asset.host.slice(/^[^.]*/)
23 | hostnames.push(hostname)
24 | end
25 | end
26 | end
27 |
28 | def save_credential(asset_info)
29 | site = Site.load(@nsc, asset_info[:site_id])
30 | newcred = Credential.for_service(asset_info[:service], asset_info[:user], asset_info[:password],asset_info[:realm], asset_info[:hostname], asset_info[:port])
31 | newcreds = [newcred]
32 | site.credentials = newcreds
33 | site.save(@nsc)
34 | end
35 |
36 | def save_all_credentials_for_site(assets_info, site)
37 | site = Site.load(@nsc, site)
38 | newcreds = []
39 | assets_info.each do |asset_info|
40 | newcred = Credential.for_service(asset_info[:service], asset_info[:user], asset_info[:password],asset_info[:realm], asset_info[:hostname], asset_info[:port])
41 | newcreds.push(newcred)
42 | end
43 | site.credentials = newcreds
44 | site.save(@nsc)
45 | end
46 |
47 | def start_scan_site(site)
48 | site = Site.load(@nsc, site)
49 | site.scan(@nsc)
50 | end
51 | end
--------------------------------------------------------------------------------
/dhcp_heal.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Date Created: 11.16.2017
4 | # Written by: BrianWGray
5 |
6 | # Written for
7 | # DCHP dynamic connectors are unstable.
8 | # Currently running in an hourly cronjob
9 |
10 | ## Script performs the following tasks
11 | ## 1.) List DHCP dynamic connections
12 | ## 2.) Check connection status
13 | ## 3.) Heal failed connections
14 | ## 4.) TODO: Re-Evaluate connection status for confirmation
15 |
16 | require 'yaml'
17 | require 'nexpose'
18 | require 'pp'
19 | include Nexpose
20 |
21 | # Default Values from yaml file
22 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
23 | config = YAML.load_file(config_path)
24 |
25 | host = config["hostname"]
26 | userid = config["username"]
27 | password = config["passwordkey"]
28 | port = config["port"]
29 |
30 | nsc = Nexpose::Connection.new(host, userid, password, port)
31 |
32 | begin
33 | nsc.login
34 | rescue ::Nexpose::APIError => err
35 | $stderr.puts("Connection failed: #{err.reason}")
36 | exit(1)
37 | end
38 | at_exit { nsc.logout }
39 |
40 | connectionList = nsc.list_discovery_connections
41 |
42 | connectionList.each do | dynCon |
43 | puts("#{dynCon.id} : #{dynCon.name} Status: #{dynCon.status}")
44 |
45 | # Hardcode name of the dhcp connection to check until I determine a cleaner way
46 | if((!dynCon.name.include?("Sonar")) && (dynCon.name.include?("DHCP")) && (dynCon.status.include?("Not Connected")))
47 | dynCon.type = 'DHCP_SERVICE'
48 | dynCon.collection_method = 'SYSLOG'
49 | dynCon.event_source = 'INFOBLOX_TRINZIC'
50 | dynCon.engine_id = 14
51 |
52 | #pp(dynCon)
53 | puts "Correcting dynamic connection issue on ID #{dynCon.id} : #{dynCon.name}"
54 | end
55 | end
56 |
57 | exit
--------------------------------------------------------------------------------
/scanAssetGroup.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Date Created: 04.30.2014
4 | # Written by: BrianWGray
5 |
6 |
7 | ## Script performs the following tasks
8 | ## 1.) Initiates scans for assets located within a specified asset Group ID
9 |
10 |
11 | require 'yaml'
12 | require 'nexpose'
13 | require 'optparse'
14 | require 'highline/import'
15 | require 'csv'
16 |
17 | include Nexpose
18 |
19 | # Default Values
20 |
21 | config = YAML.load_file("conf/nexpose.yaml") # From file
22 |
23 | @host = config["hostname"]
24 | @userid = config["username"]
25 | @password = config["passwordkey"]
26 | @port = config["port"]
27 |
28 |
29 | OptionParser.new do |opts|
30 | opts.banner = "Usage: #{File::basename($0)} [Asset Group ID number] [options]"
31 | opts.separator ''
32 | opts.separator 'This script will re-launch scans against a provided asset group id number.'
33 | opts.separator ''
34 | opts.separator 'Note that this script will always prompt for a connection password.'
35 | opts.separator ''
36 | opts.separator 'Options:'
37 | opts.on_tail('--help', 'Print this help message.') { puts opts; exit }
38 | end.parse!
39 |
40 | unless ARGV[0]
41 | $stderr.puts 'Asset Group ID Required.'
42 | exit(1)
43 | end
44 |
45 | @agid = ARGV[0]
46 |
47 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
48 | puts 'logging into Nexpose'
49 |
50 | begin
51 | nsc.login
52 | rescue ::Nexpose::APIError => err
53 | $stderr.puts("Connection failed: #{err.reason}")
54 | exit(1)
55 | end
56 |
57 | puts 'logged into Nexpose'
58 | at_exit { nsc.logout }
59 |
60 |
61 | puts "Initializing scans for Site ID #{@agid}"
62 | group = AssetGroup.load(nsc, @agid)
63 | scans = group.rescan_assets(nsc)
64 |
65 | puts 'Scan jobs submitted'
66 | puts 'Logging out'
67 | exit
68 |
--------------------------------------------------------------------------------
/scan_asset_group.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Date Created: 04.30.2014
4 | # Written by: BrianWGray
5 |
6 | require 'nexpose'
7 | require 'optparse'
8 | require 'highline/import'
9 |
10 | include Nexpose
11 |
12 | # Default Values
13 |
14 | @host = 'localhost'
15 | @port = '3780'
16 | @user = 'nxadmin'
17 |
18 | OptionParser.new do |opts|
19 | opts.banner = "Usage: #{File::basename($0)} [Asset Group ID number] [options]"
20 | opts.separator ''
21 | opts.separator 'This script will re-launch scans against a provided asset group id number.'
22 | opts.separator ''
23 | opts.separator 'Note that this script will always prompt for a connection password.'
24 | opts.separator ''
25 | opts.separator 'Options:'
26 | opts.on('-h', '--host [HOST]', 'IP or hostname of Nexpose console. Default: localhost') { |host| @host = host }
27 | opts.on('-p', '--port [PORT]', Integer, 'Port of Nexpose console. Default: 3780') { |port| @port = port }
28 | opts.on('-u', '--user [USER]', 'Username to connect to Nexpose with. Default: nxadmin') { |user| @user = user }
29 | opts.on_tail('--help', 'Print this help message.') { puts opts; exit }
30 | end.parse!
31 |
32 | unless ARGV[0]
33 | $stderr.puts 'Asset Group ID Required.'
34 | exit(1)
35 | end
36 |
37 | @agid = ARGV[0]
38 |
39 | def get_password(prompt = 'Password: ')
40 | ask(prompt) { |query| query.echo = false }
41 | end
42 |
43 | puts "logging into #{@host} as #{@user} on port #{@port}"
44 | @password = get_password
45 |
46 |
47 | nsc = Nexpose::Connection.new(@host, @user, @password, @port)
48 | puts 'Nexpose login initiated'
49 | nsc.login
50 |
51 | puts 'Nexpose login successful'
52 |
53 | puts "Initializing scans for Asset Group ID #{@agid}"
54 | group = AssetGroup.load(nsc, @agid)
55 | scans = group.rescan_assets(nsc)
56 |
57 | puts 'Scan jobs submitted'
58 |
59 | at_exit { nsc.logout }
60 | puts 'Logging out'
61 | exit
62 |
--------------------------------------------------------------------------------
/export_running_log.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'yaml'
4 | require 'nexpose'
5 | include Nexpose
6 |
7 | # Default Values from yaml file
8 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
9 | config = YAML.load_file(config_path)
10 |
11 | @host = config["hostname"]
12 | @userid = config["username"]
13 | @password = config["passwordkey"]
14 | @port = config["port"]
15 |
16 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
17 |
18 | begin
19 | nsc.login
20 | rescue ::Nexpose::APIError => err
21 | $stderr.puts("Connection failed: #{err.reason}")
22 | exit(1)
23 | raise
24 | end
25 | at_exit { nsc.logout }
26 |
27 | # Allow the user to pass in the Scan ID to the script.
28 | scan_id = ARGV[0].to_i
29 |
30 |
31 |
32 | # Export the data associated with a single scan, and optionally store it in
33 | # a zip-compressed file under the provided name.
34 | #
35 | # @param [Fixnum] scan_id Scan ID to remove data for.
36 | # @param [String] zip_file Filename to export scan data to.
37 | # @return [Fixnum] On success, returned the number of bytes written to
38 | # zip_file, if provided. Otherwise, returns raw ZIP binary data.
39 | #
40 | def nsc.scan_log(scan_id, zip_file = nil)
41 | http = AJAX.https(self)
42 | headers = { 'Cookie' => "nexposeCCSessionID=#{@session_id}",
43 | 'Accept-Encoding' => 'identity' }
44 | resp = http.get("/data/scan/log?scan-id=#{scan_id}", headers)
45 |
46 | case resp
47 | when Net::HTTPSuccess
48 | if zip_file
49 | ::File.open(zip_file, 'wb') { |file| file.write(resp.body) }
50 | else
51 | resp.body
52 | end
53 | when Net::HTTPForbidden
54 | raise Nexpose::PermissionError.new(resp)
55 | else
56 | raise Nexpose::APIError.new(resp, "#{resp.class}: Unrecognized response.")
57 | end
58 | end
59 |
60 |
61 |
62 | nsc.scan_log(scan_id, "scan-#{scan_id}.zip")
63 |
--------------------------------------------------------------------------------
/stopScansForEngine.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 01.26.2015
4 |
5 | ## Script performs the following tasks
6 | ## 1.) Retrieve a list of active scans from a console.
7 | ## 2.) Iteratively stop all scans for a specific scan engine id.
8 | ## 3.) TODO: Massive code cleanup + efficiency improvements.
9 |
10 | require 'yaml'
11 | require 'nexpose'
12 |
13 | include Nexpose
14 |
15 |
16 | engineID = 6 # engine id to stop
17 |
18 | # Default Values from yaml file
19 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
20 | config = YAML.load_file(config_path)
21 |
22 | @host = config["hostname"]
23 | @userid = config["username"]
24 | @password = config["passwordkey"]
25 | @port = config["port"]
26 | @nexposeAjaxTimeout = config["nexposeajaxtimeout"]
27 |
28 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
29 | begin
30 | nsc.login
31 | rescue ::Nexpose::APIError => err
32 | $stderr.puts("Connection failed: #{err.reason}")
33 | exit(1)
34 | end
35 | at_exit { nsc.logout }
36 |
37 | ## Pull data for active scans
38 | activeScans = nsc.scan_activity()
39 | # Collect Site info to provide additional information for screen output.
40 | siteInfo = nsc.sites
41 |
42 | ## Iterate through active scans and stop scans matching the specified engine id.
43 | activeScans.each do |status|
44 | siteInfoID = status.site_id
45 | begin
46 | if status.engine_id == engineID # This should probably just be an include? engine_id = engineID for the array.
47 | puts "Stopping scanid: #{status.scan_id} on EngineID: #{status.engine_id} for SiteID #{status.site_id} : #{siteInfo[siteInfoID].name}"
48 | nsc.stop_scan(status.scan_id)
49 | end
50 | rescue
51 | puts "Error stopping scanid #{status.scan_id} on EngineID: #{status.engine_id} for SiteID #{status.site_id} : #{siteInfo[siteInfoID].name} to the stop queue"
52 | end
53 | end
54 |
55 | exit
--------------------------------------------------------------------------------
/moveEngineToPool.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 04.09.2015
4 |
5 | ## Script performs the following tasks
6 | ## 1.) Stop all scans assigned sites assigned to a specified scan engine.
7 | ## 2.) Stop all scans running on the engine to be removed.
8 | ## 3.) Assign the listed sites from one scan engine to a new scan engine.
9 | ## 4.) TODO: efficiency improvements.
10 |
11 | ## This script was primarily meant to be used for moving sites from engines to scan pools.
12 |
13 |
14 | require 'yaml'
15 | require 'nexpose'
16 |
17 | include Nexpose
18 |
19 |
20 | engineID = 3 # engine id to move from
21 | newEngineID = 6 # engine id to move to
22 |
23 | # Default Values from yaml file
24 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
25 | config = YAML.load_file(config_path)
26 |
27 | @host = config["hostname"]
28 | @userid = config["username"]
29 | @password = config["passwordkey"]
30 | @port = config["port"]
31 | @nexposeAjaxTimeout = config["nexposeajaxtimeout"]
32 |
33 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
34 | begin
35 | nsc.login
36 | rescue ::Nexpose::APIError => err
37 | $stderr.puts("Connection failed: #{err.reason}")
38 | exit(1)
39 | end
40 | at_exit { nsc.logout }
41 |
42 | engineInfo = Nexpose::Engine.load(nsc, engineID)
43 |
44 | engineInfo.sites.each do |engineSites|
45 | begin
46 | puts "Moving Site ID: #{engineSites.id}, Site Name: #{engineSites.name} from EngineID: #{engineID} to EngineID #{newEngineID}"
47 |
48 | begin
49 | siteModify = Nexpose::Site.load(nsc,engineSites.id)
50 | siteModify.engine_id = newEngineID
51 | siteModify.save(nsc)
52 |
53 | rescue ::Nexpose::APIError => err
54 | puts "Error during site modify function: #{err.reason}"
55 | end
56 |
57 | rescue ::Nexpose::APIError => err
58 | puts "Error modifying Site ID: #{engineSites.id}, Site Name: #{engineSites.name}'s scan engine: #{err.reason}"
59 | end
60 | end
61 |
62 | exit
63 |
--------------------------------------------------------------------------------
/openPortQuery.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray 08/08/2014
3 |
4 |
5 | # Queries heavily draw from:
6 | # https://community.rapid7.com/message/11358#11358
7 | # https://community.rapid7.com/docs/DOC-2612
8 | #
9 |
10 |
11 |
12 | require 'yaml'
13 | require 'nexpose'
14 | require 'csv'
15 |
16 | # Default Values
17 |
18 | config = YAML.load_file("conf/nexpose.yaml") # From file
19 |
20 | @host = config["hostname"]
21 | @userid = config["username"]
22 | @password = config["passwordkey"]
23 | @port = config["port"]
24 |
25 |
26 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
27 | puts 'logging into Nexpose'
28 |
29 | begin
30 | nsc.login
31 | rescue ::Nexpose::APIError => err
32 | $stderr.puts("Connection failed: #{e.reason}")
33 | exit(1)
34 | end
35 |
36 | puts 'logged into Nexpose'
37 | at_exit { nsc.logout }
38 |
39 |
40 | puts "Example: Enter \"23\" if you want to query for port 23."
41 | prompt = 'Please enter a port number to query for: '
42 | print prompt
43 | #Limiting character count to 32
44 | UserInput = STDIN.gets(32).chomp()
45 |
46 |
47 |
48 | sqlSelect = "SELECT da.ip_address, das.port, dp.name AS protocol, ds.name AS service, dsf.version AS service_version, dsf.name AS service_name, da.host_name, dos.name AS OS, dos.version AS os_version
49 | FROM dim_asset_service das
50 | JOIN dim_service ds USING (service_id)
51 | JOIN dim_protocol dp USING (protocol_id)
52 | JOIN dim_asset da USING (asset_id)
53 | JOIN dim_operating_system dos USING (operating_system_id)
54 | JOIN dim_service_fingerprint dsf USING (service_fingerprint_id) "
55 |
56 | sqlWhere = "Where das.port = #{UserInput}"
57 |
58 | sqlOrderBy = " ORDER BY da.ip_address, das.port;"
59 |
60 | query = sqlSelect + sqlWhere + sqlOrderBy
61 |
62 |
63 | report = Nexpose::AdhocReportConfig.new(nil, 'sql')
64 | report.add_filter('version', '1.2.1')
65 | report.add_filter('query', query)
66 | report_output = report.generate(nsc,18000) # Timeout for report generation is currently set at ~30 minutes
67 | csv_output = CSV.parse(report_output.chomp, { :headers => :first_row })
68 | CSV.open("openPort_#{UserInput}_export.csv", 'w') do |csv_file|
69 | csv_file << csv_output.headers
70 | csv_output.each do |row|
71 | csv_file << row
72 | end
73 | end
74 |
75 | puts "CSV export completed and saved to ./OpenPort_#{UserInput}_export.csv."
76 |
77 | exit
78 |
--------------------------------------------------------------------------------
/add_global_exclusion.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Date Created: 10.18.2018
4 | # Written by: BrianWGray
5 |
6 | # Generated for https://kb.help.rapid7.com/discuss/5bbf13faeb416300039a1efa
7 |
8 | ## Script performs the following tasks
9 | ## 1.) Read addresses from a text file
10 | ## 2.) Add addresses to the Global Exclusion list.
11 |
12 | # TODO: Possibly change the script to allow loading contents from a csv instead of a text file with an asset entry per line.
13 |
14 | ## Currently the script loads a text file with a single entry per line.
15 | # Each asset entry may be:
16 | ## single ip
17 | ## ip range 192.168.0.0/24 | 192.168.0.0-192.168.0.255
18 | ## hostname
19 |
20 | require 'yaml'
21 | require 'nexpose'
22 | include Nexpose
23 |
24 | # Default Values from yaml file
25 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
26 | config = YAML.load_file(config_path)
27 |
28 | @host = config["hostname"]
29 | @userid = config["username"]
30 | @password = config["passwordkey"]
31 | @port = config["port"]
32 | @debug = false
33 |
34 | # Any arguments after flags can be grabbed now."
35 | unless ARGV[0]
36 | $stderr.puts 'Input file is required.'
37 | exit(1)
38 | end
39 | file = ARGV[0]
40 |
41 | def load_file(file)
42 | # This will fail if the file cannot be read.
43 | begin
44 | fileContents = File.read(file).split.uniq
45 | rescue
46 | $stderr.puts "Error reading file: #{file}"
47 | exit(1)
48 | end
49 |
50 | return fileContents
51 | end
52 |
53 | def add_global_exclusion(nsc,assetList)
54 | globalSettings = GlobalSettings.load(nsc)
55 | assetList.each do |asset|
56 | puts "Adding #{asset} to global exclusion list"
57 | globalSettings.add_exclusion(asset)
58 | end
59 |
60 | begin
61 | # Save global exclusion changes
62 | globalSettings.save(nsc)
63 | return true # success
64 | rescue ::Nexpose::APIError => err
65 | $stderr.puts("Saving Global Settings Failed")
66 | $stderr.puts("#{err.reason}")
67 | return false # save failed
68 | end
69 | end
70 |
71 | # Create Nexpose connection and authenticate
72 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
73 | begin
74 | nsc.login
75 | at_exit { nsc.logout }
76 | rescue ::Nexpose::APIError => err
77 | $stderr.puts("Connection failed: #{err.reason}")
78 | exit(1)
79 | end
80 |
81 | # Load asset list from file
82 | assetList = load_file(file)
83 | # Load asset list into global exclusions
84 | add_global_exclusion(nsc,assetList)
85 |
86 | exit()
--------------------------------------------------------------------------------
/discoveryCount.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 12.01.2014
4 |
5 | # Put together to try and determine a good way to answer question https://community.rapid7.com/thread/5394
6 |
7 | # Script performs the following tasks
8 | ## 1.) Retrieve a list of available sites from a console.
9 | ## 2.) Retrieve address entries for each site.
10 | ## 3.) Convert address ranges to ip address counts
11 | ## 4.) Provide a total of addresses per site.
12 | ## 5.) Provide a total count of addresses for all sites combined.
13 |
14 |
15 |
16 | require 'yaml'
17 | require 'nexpose'
18 | require 'ipaddr'
19 | include Nexpose
20 |
21 | # Default Values
22 |
23 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
24 | config = YAML.load_file(config_path)
25 |
26 | @host = config["hostname"]
27 | @userid = config["username"]
28 | @password = config["passwordkey"]
29 | @port = config["port"]
30 |
31 | assetCounter = 0
32 |
33 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
34 | puts 'logging into Nexpose'
35 |
36 | begin
37 | nsc.login
38 | rescue ::Nexpose::APIError => err
39 | $stderr.puts("Connection failed: #{err.reason}")
40 | exit(1)
41 | end
42 |
43 | at_exit { nsc.logout }
44 |
45 | site = nsc.list_sites
46 | case site.length
47 | when 0
48 | puts("There are currently no active sites on this NeXpose instance")
49 | end
50 |
51 |
52 | def convert_ip_range(start_ip, end_ip)
53 | start_ip = IPAddr.new(start_ip)
54 | end_ip = IPAddr.new(end_ip)
55 |
56 | (start_ip..end_ip).map(&:to_s)
57 | end
58 |
59 | begin
60 | site.each do |site|
61 | site = Nexpose::Site.load(nsc, site.id)
62 | puts "Getting defined assets for #{site.name}"
63 | site.included_addresses.each do |asset|
64 | if asset.respond_to? :from
65 |
66 | if asset.to != nil
67 | startRange = "#{asset.from}" if asset.to
68 | endRange = "#{asset.to}"
69 | currentCount = convert_ip_range(startRange.to_s, endRange.to_s).count
70 | else
71 | currentCount = 1
72 | end
73 |
74 | assetCounter += currentCount
75 |
76 | puts ("Current Site Address Count: #{currentCount} Total Address Tally: #{assetCounter}")
77 |
78 | end
79 | end
80 | end
81 | end
82 |
83 | puts "Total tally of discoverable addresses for all sites: #{assetCounter}"
84 |
85 | puts 'Logging out'
86 | exit
87 |
--------------------------------------------------------------------------------
/search_ip.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 08.22.2016
4 |
5 | # Example on querying whether an ip exists in a specified site.
6 |
7 | # Script performs the following tasks
8 | ## 1.) Retrieve address entries for each site.
9 | ## 2.) check the asset array for a specified address.
10 | ## 3.) Print true or false whether the ip provided is within the site provided
11 |
12 | require 'yaml'
13 | require 'nexpose'
14 | require 'ipaddr'
15 | include Nexpose
16 |
17 | # Default Values
18 |
19 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
20 | config = YAML.load_file(config_path)
21 |
22 | @host = config["hostname"]
23 | @userid = config["username"]
24 | @password = config["passwordkey"]
25 | @port = config["port"]
26 |
27 | siteId = 0
28 |
29 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
30 |
31 | begin
32 | nsc.login
33 | rescue ::Nexpose::APIError => err
34 | $stderr.puts("Connection failed: #{err.reason}")
35 | exit(1)
36 | end
37 |
38 | at_exit { nsc.logout }
39 |
40 |
41 | def convert_ip_range(start_ip, end_ip)
42 | start_ip = IPAddr.new(start_ip)
43 | end_ip = IPAddr.new(end_ip)
44 |
45 | (start_ip..end_ip).map(&:to_s)
46 | end
47 |
48 | def search_ip(assetList, ip)
49 | @assetList, @ip = assetList, ip
50 | @assetList.include?(@ip)
51 | end
52 |
53 | def assets (siteAddresses)
54 | @siteAddresses = siteAddresses
55 | @assetList = [] # => list of ipaddresses in a site
56 |
57 | @siteAddresses.each do |asset|
58 | if asset.respond_to? :from
59 | if asset.to != nil
60 | startRange = "#{asset.from}" if asset.to
61 | endRange = "#{asset.to}"
62 | @assetList << convert_ip_range(startRange.to_s, endRange.to_s)
63 | else
64 | @assetList << asset
65 | end
66 | end
67 | end
68 | return @assetList.flatten
69 | end
70 |
71 |
72 | # Accept arguments for the numerical site ID and an address to search for.
73 | if ARGV.length < 2
74 | # If no argument is passed print usage and exit
75 | puts "usage: #{__FILE__} siteId# Address"
76 | exit
77 | else
78 | siteId, findIp = ARGV[0], ARGV[1]
79 | end
80 |
81 | #TODO: Add validation for whether a site exists prior to attempting to access it
82 | site = Nexpose::Site.load(nsc, siteId) # => Load Nexpose Site Data
83 | assetList = assets(site.included_addresses) # => detonate all site assets into an array
84 |
85 | # provide a detonated ip list and search the array for a specified ip address value
86 | # if the address is within the array true is returned else false returned.
87 | if search_ip(assetList, findIp) == true; puts "true"; else puts "false"; end
88 |
89 |
90 | exit
91 |
--------------------------------------------------------------------------------
/powershell/alter_credential.ps1:
--------------------------------------------------------------------------------
1 | # Makes PS ignore self signed certs used by internal servers
2 | # Have someone actually sign this certificate... - BrianWGray
3 | if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type)
4 | {
5 | $certCallback = @"
6 | using System;
7 | using System.Net;
8 | using System.Net.Security;
9 | using System.Security.Cryptography.X509Certificates;
10 | public class ServerCertificateValidationCallback
11 | {
12 | public static void Ignore()
13 | {
14 | if(ServicePointManager.ServerCertificateValidationCallback ==null)
15 | {
16 | ServicePointManager.ServerCertificateValidationCallback +=
17 | delegate
18 | (
19 | Object obj,
20 | X509Certificate certificate,
21 | X509Chain chain,
22 | SslPolicyErrors errors
23 | )
24 | {
25 | return true;
26 | };
27 | }
28 | }
29 | }
30 | "@
31 | Add-Type $certCallback
32 | }
33 | [ServerCertificateValidationCallback]::Ignore()
34 | # https://help.rapid7.com/insightvm/en-us/api/index.html
35 |
36 | # Collects user credentials for login (Functional)
37 | $creds = Get-Credential
38 | $unsecureCreds = $creds.GetNetworkCredential()
39 | $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $unsecureCreds.UserName,$unsecureCreds.Password)))
40 | Remove-Variable unsecureCreds
41 |
42 | # Define data transfer type
43 | $data_type = "application/json"
44 |
45 | # URL Definintions
46 | $hostName = "127.0.0.1"
47 | $port = "3780"
48 | $credentialId = 2
49 | $url = "https://${hostName}:${port}/api/3/shared_credentials/${credentialId}/"
50 |
51 | # Build headers to send to the API. In this case we set the Basic authentication value
52 | $table_headers = @{
53 | Authorization=("Basic {0}" -f $base64AuthInfo)
54 | }
55 |
56 | # Pull existing object for modification
57 | $credential = Invoke-RestMethod -Method 'GET' -Uri $url -Headers $table_headers
58 |
59 | # Modify credential object
60 | $credential.PSObject.Properties.Remove('links') # The links can be left intact but this is an example of removing an unnecessary element
61 | $credential.description = 'This is a modified description'
62 | $credential.account | Add-Member NoteProperty password('altered password')
63 |
64 | # Build PUT Content using the existing credential object
65 | $json = $credential | Convertto-JSON
66 |
67 | # Visual of the json being sent - just for show
68 | $json
69 |
70 | $data = Invoke-RestMethod -Method 'PUT' -Uri $url -ContentType $data_type -Headers $table_headers -Body $json;
71 |
--------------------------------------------------------------------------------
/powershell/example_site_get.ps1:
--------------------------------------------------------------------------------
1 | # Makes PS ignore self signed certs used by internal servers
2 | # Have someone actually sign this certificate... - BrianWGray
3 | if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type)
4 | {
5 | $certCallback = @"
6 | using System;
7 | using System.Net;
8 | using System.Net.Security;
9 | using System.Security.Cryptography.X509Certificates;
10 | public class ServerCertificateValidationCallback
11 | {
12 | public static void Ignore()
13 | {
14 | if(ServicePointManager.ServerCertificateValidationCallback ==null)
15 | {
16 | ServicePointManager.ServerCertificateValidationCallback +=
17 | delegate
18 | (
19 | Object obj,
20 | X509Certificate certificate,
21 | X509Chain chain,
22 | SslPolicyErrors errors
23 | )
24 | {
25 | return true;
26 | };
27 | }
28 | }
29 | }
30 | "@
31 | Add-Type $certCallback
32 | }
33 | [ServerCertificateValidationCallback]::Ignore()
34 |
35 | # Check out the following links to get yourself started: - BrianWGray
36 | # https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod?view=powershell-6
37 | # https://www.gngrninja.com/script-ninja/2016/7/24/powershell-getting-started-utilizing-the-web-part-2-invoke-restmethod
38 | # https://127.0.0.1:3780/api/3/
39 | # https://help.rapid7.com/insightvm/en-us/api/index.html
40 |
41 | # Collects user credentials for login (Functional)
42 | # I don't highly recommend this specific credential collection method it's just to get the script bootstrapped until you are more comfortable - BrianWGray
43 | $creds = Get-Credential
44 | $unsecureCreds = $creds.GetNetworkCredential()
45 | $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $unsecureCreds.UserName,$unsecureCreds.Password)))
46 | Remove-Variable unsecureCreds
47 |
48 | # Set the API URI to call : In this example a list of sites (visit this in your authenticated browser for what you should be requesting) - BrianWGray
49 | # $url = "https://127.0.0.1:3780/api/3/sites"
50 | $url = "https://127.0.0.1/api/3/shared_credentials/"
51 |
52 | # Interact with the API in this case send a GET request to the specified URL and supply a basic auth header with a base64 encoded username:password
53 | $data = Invoke-RestMethod -Method 'Get' -Uri $url -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
54 |
55 | # Now we have a data object full of the API response content that can be manipulated for view however you would like. - BrianWGray
56 | $data | Get-Member
57 |
58 |
--------------------------------------------------------------------------------
/nexposeListBackups.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 09.08.2014
4 |
5 | ## Script lists available application backup.
6 |
7 | require 'yaml'
8 | require 'nexpose'
9 |
10 | include Nexpose
11 |
12 | # Default Values
13 |
14 | # Default Values from yaml file
15 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
16 | config = YAML.load_file(config_path)
17 |
18 | @host = config["hostname"]
19 | @userid = config["username"]
20 | @password = config["passwordkey"]
21 | @port = config["port"]
22 | @serviceTimeout = config["servicetimeout"]
23 |
24 |
25 | def checkService()
26 | tryAgain = 0
27 |
28 | begin
29 | begin
30 | path = '/login.html' # Check to see if we may login or if we are re-directed to the maintenance login page.
31 |
32 | http = Net::HTTP.new(@host,@port)
33 | http.read_timeout = 1
34 | http.use_ssl = true
35 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
36 | response = nil
37 |
38 | http.start{|http|
39 | request = Net::HTTP::Get.new(path)
40 | response = http.request(request)
41 | }
42 |
43 | rescue Exception # should really list all the possible http exceptions
44 | puts "Attempt: #{tryAgain} Service Unavailable"
45 | sleep (30)
46 | retry if (tryAgain += 1) < @serviceTimeout
47 | end
48 |
49 | response.code
50 | if response.code == "200" # Check the status code anything other than 200 indicates the service is not ready.
51 | puts "Attempt: #{tryAgain} #{response.code} The Nexpose Service appears to be up and functional"
52 | tryAgain = @serviceTimeout
53 | else
54 | puts "Attempt: #{tryAgain} #{response.code} #{response.message} The Service is not yet fully initialized"
55 | tryAgain += 1
56 | sleep(30)
57 | end
58 | end while tryAgain < @serviceTimeout
59 |
60 | if (response.code != "200")
61 | puts "The service was never determined to be available. Action Timed Out"
62 | exit
63 | end
64 | end
65 |
66 |
67 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
68 |
69 | begin
70 | nsc.login
71 | rescue ::Nexpose::APIError => err
72 | $stderr.puts("Connection failed: #{err.reason}")
73 | exit(1)
74 | end
75 |
76 | at_exit { nsc.logout }
77 |
78 |
79 | # Check scan activity wait until there are no scans running
80 | listBackups = nsc.list_backups
81 | if listBackups.any?
82 | puts "List of available Backups on #{@host} :\r\n"
83 | listBackups.each do |backupList|
84 | puts "Name: #{backupList.name} Description: #{backupList.description} size: #{backupList.size} Date : #{backupList.date}"
85 | end
86 |
87 | end
88 |
89 | exit
90 |
--------------------------------------------------------------------------------
/create_asset_group.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'nexpose'
3 | require 'optparse'
4 | require 'highline/import'
5 |
6 | # Default values
7 |
8 | @host = "localhost"
9 | @port = "3780"
10 | @user = "nxadmin"
11 | @password = ""
12 |
13 | @name = @desc = nil
14 |
15 | OptionParser.new do |opts|
16 | opts.banner = "Usage: #{File::basename($0)} [options]"
17 | opts.separator ''
18 | opts.separator 'Create an asset group based upon an input file, one IP per line.'
19 | opts.separator ''
20 | opts.separator 'By default, it uses the name of the file as the name of the asset group.'
21 | opts.separator 'As currently written, the script will only add one asset per IP address.'
22 | opts.separator 'If multiple sites have the same IP, it is non-deterministic which asset it will choose.'
23 | opts.separator ''
24 | opts.separator 'Note that this script will always prompt for a connection password.'
25 | opts.separator ''
26 | opts.separator 'Options:'
27 | opts.on('-n', '--name [NAME]', 'Name to use for new asset group. Must not already exist.') { |name| @name = name }
28 | opts.on('-d', '--desc [DESCRIPTION]', 'Description to use for new asset group.') { |desc| @desc = desc }
29 | opts.on('-h', '--host [HOST]', 'IP or hostname of Nexpose console. Default: localhost') { |host| @host = host }
30 | opts.on('-p', '--port [PORT]', Integer, 'Port of Nexpose console. Default: 3780') { |port| @port = port }
31 | opts.on('-u', '--user [USER]', 'Username to connect to Nexpose with. Default: nxadmin') { |user| @user = user }
32 | opts.on('-x', '--debug', 'Report duplicate IP addresses to STDERR.') { |debug| @debug = debug }
33 | opts.on_tail('--help', 'Print this help message.') { puts opts; exit }
34 | end.parse!
35 |
36 | # Any arguments after flags can be grabbed now."
37 | unless ARGV[0]
38 | $stderr.puts 'Input file is required.'
39 | exit(1)
40 | end
41 | file = ARGV[0]
42 | @name = File.basename(file, File.extname(file)) unless @name
43 |
44 | def get_password(prompt = 'Password: ')
45 | ask(prompt) { |query| query.echo = false }
46 | end
47 |
48 | @password = get_password
49 |
50 | # This will fail if the file cannot be read.
51 | ips = File.read(file).split.uniq
52 |
53 | nsc = Nexpose::Connection.new(@host, @user, @password, @port)
54 | nsc.login
55 |
56 | # Create a map of all assets by IP to make them quicker to find.
57 | all_assets = nsc.assets.reduce({}) do |hash, dev|
58 | $stderr.puts("Duplicate asset: #{dev.address}") if @debug and hash.member? dev.address
59 | hash[dev.address] = dev
60 | hash
61 | end
62 |
63 | # Drop the connection, in case group creation takes too long.
64 | nsc.logout
65 |
66 | group = Nexpose::AssetGroup.new(@name, @desc)
67 |
68 | ips.each do |ip|
69 | if all_assets.member? ip
70 | group.devices << all_assets[ip]
71 | elsif @debug
72 | $stderr.puts("No asset with IP #{ip} found.")
73 | end
74 | end
75 |
76 | nsc.login
77 | at_exit { nsc.logout }
78 | group.save(nsc)
79 | puts "Group '#{@name}' saved with #{group.devices.size} assets."
80 |
--------------------------------------------------------------------------------
/addToAssetGroup.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Date Created: 07.21.2017
4 | # Written by: BrianWGray
5 |
6 | # Borrows heavily from https://github.com/rapid7/nexpose-client/blob/master/scripts/create_asset_group.rb
7 | # Generated for https://community.rapid7.com/thread/7584
8 |
9 | ## Script performs the following tasks
10 | ## 1.) Read addresses from text file
11 | ## 2.) De-duplicate addresses
12 | ## 3.) Add addresses to the specified asset group id.
13 |
14 | require 'yaml'
15 | require 'nexpose'
16 | require 'optparse'
17 |
18 | include Nexpose
19 |
20 | # Default Values from yaml file
21 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
22 | config = YAML.load_file(config_path)
23 |
24 | @host = config["hostname"]
25 | @userid = config["username"]
26 | @password = config["passwordkey"]
27 | @port = config["port"]
28 |
29 | @groupid = nil
30 |
31 | OptionParser.new do |opts|
32 | opts.banner = "Usage: #{File::basename($0)} addresses.txt [options]"
33 | opts.separator ''
34 | opts.separator 'Add assets to an existing asset group based upon an input file, one IP per line.'
35 | opts.separator ''
36 | opts.separator 'A group id must be provided.'
37 | opts.separator 'If multiple sites include the same address, it is non-deterministic which asset it will choose.'
38 | opts.separator ''
39 | opts.separator 'Options:'
40 | opts.on('-i', '--groupid [groupid]', 'Group ID you are adding to. Must already exist.') { |groupid| @groupid = groupid }
41 | opts.on('-x', '--debug', 'Report duplicate IP addresses to STDERR.') { |debug| @debug = debug }
42 | opts.on_tail('--help', 'Print this help message.') { puts opts; exit }
43 | end.parse!
44 |
45 | # Any arguments after flags can be grabbed now."
46 | unless ARGV[0]
47 | $stderr.puts 'Input file is required.'
48 | exit(1)
49 | end
50 | file = ARGV[0]
51 |
52 | # This will fail if the file cannot be read.
53 | ips = File.read(file).split.uniq
54 | puts "#{file} loaded."
55 |
56 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
57 | puts 'logging into Nexpose'
58 |
59 | begin
60 | nsc.login
61 | rescue ::Nexpose::APIError => err
62 | $stderr.puts("Connection failed: #{err.reason}")
63 | exit(1)
64 | end
65 |
66 | puts 'logged into Nexpose'
67 | at_exit { nsc.logout }
68 |
69 | # Create a map of all assets by IP to make them quicker to find.
70 | all_assets = nsc.assets.reduce({}) do |hash, dev|
71 | $stderr.puts("Duplicate asset: #{dev.address}") if @debug and hash.member? dev.address
72 | hash[dev.address] = dev
73 | hash
74 | end
75 |
76 | # Drop the connection, in case group creation takes too long.
77 | # nsc.logout
78 |
79 | group = Nexpose::AssetGroup.load(nsc, @groupid)
80 |
81 | ips.each do |ip|
82 | puts "Adding #{ip}"
83 | if all_assets.member? ip
84 | group.assets << all_assets[ip]
85 | elsif @debug
86 | $stderr.puts("No asset with IP #{ip} found.")
87 | end
88 | end
89 |
90 | # nsc.login
91 | group.save(nsc)
92 | puts "Group '#{group.id}:#{group.name}' saved with #{group.devices.size} assets."
93 |
94 | exit
--------------------------------------------------------------------------------
/createAssetGroup.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Date Created: 04.30.2014
4 | # Written by: BrianWGray
5 |
6 | # Original script sourced from [mdaines-r7] https://github.com/rapid7/nexpose-client/blob/master/scripts/create_asset_group.rb
7 |
8 | ## Script performs the following tasks
9 | ## 1.) Read addresses from text file
10 | ## 2.) De-duplicate addresses
11 | ## 3.) Create new asset group
12 | ## 4.) Add addresses to the created asset group
13 |
14 | require 'yaml'
15 | require 'nexpose'
16 | require 'optparse'
17 | require 'highline/import'
18 |
19 | # Default Values
20 |
21 | config = YAML.load_file("conf/nexpose.yaml") # From file
22 |
23 | @host = config["hostname"]
24 | @userid = config["username"]
25 | @password = config["passwordkey"]
26 | @port = config["port"]
27 |
28 | @name = @desc = nil
29 |
30 | OptionParser.new do |opts|
31 | opts.banner = "Usage: #{File::basename($0)} [options]"
32 | opts.separator ''
33 | opts.separator 'Create an asset group based upon an input file, one IP per line.'
34 | opts.separator ''
35 | opts.separator 'By default, it uses the name of the file as the name of the asset group and does not check if name exists.'
36 | opts.separator 'If multiple sites include the same address, it is non-deterministic which asset it will choose.'
37 | opts.separator ''
38 | opts.separator 'Options:'
39 | opts.on('-n', '--name [NAME]', 'Name to use for new asset group. Must not already exist.') { |name| @name = name }
40 | opts.on('-d', '--desc [DESCRIPTION]', 'Description to use for new asset group.') { |desc| @desc = desc }
41 | opts.on('-x', '--debug', 'Report duplicate IP addresses to STDERR.') { |debug| @debug = debug }
42 | opts.on_tail('--help', 'Print this help message.') { puts opts; exit }
43 | end.parse!
44 |
45 | # Any arguments after flags can be grabbed now."
46 | unless ARGV[0]
47 | $stderr.puts 'Input file is required.'
48 | exit(1)
49 | end
50 | file = ARGV[0]
51 | @name = File.basename(file, File.extname(file)) unless @name
52 |
53 | # This will fail if the file cannot be read.
54 | ips = File.read(file).split.uniq
55 |
56 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
57 | puts 'logging into Nexpose'
58 |
59 | begin
60 | nsc.login
61 | rescue ::Nexpose::APIError => err
62 | $stderr.puts("Connection failed: #{e.reason}")
63 | exit(1)
64 | end
65 |
66 | puts 'logged into Nexpose'
67 | at_exit { nsc.logout }
68 |
69 | # Create a map of all assets by IP to make them quicker to find.
70 | all_assets = nsc.assets.reduce({}) do |hash, dev|
71 | $stderr.puts("Duplicate asset: #{dev.address}") if @debug and hash.member? dev.address
72 | hash[dev.address] = dev
73 | hash
74 | end
75 |
76 | # Drop the connection, in case group creation takes too long.
77 | nsc.logout
78 |
79 | group = Nexpose::AssetGroup.new(@name, @desc)
80 |
81 | ips.each do |ip|
82 | if all_assets.member? ip
83 | group.devices << all_assets[ip]
84 | elsif @debug
85 | $stderr.puts("No asset with IP #{ip} found.")
86 | end
87 | end
88 |
89 | nsc.login
90 | group.save(nsc)
91 | puts "Group '#{@name}' saved with #{group.devices.size} assets."
92 |
93 | exit
--------------------------------------------------------------------------------
/deleteStaleAssets.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # WhyIsThisOpen
3 | # 03.02.2015
4 |
5 | # Fixed yaml relative path issues with running the script from outside of its directory. - BrianWGray 07.20.2015
6 | # Fixed error output typo. - BrianWGray 07.20.2015
7 | # Slapped in some basic output formatting. - BrianWGray 07.20.2015
8 |
9 | ## Script deletes stale assets that are part of a site with a scheduled scan.
10 |
11 | require 'yaml'
12 | require 'nexpose'
13 |
14 | include Nexpose
15 |
16 | # Default Values
17 |
18 | # Default Values from yaml file
19 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
20 | config = YAML.load_file(config_path)
21 |
22 | @host = config["hostname"]
23 | @userid = config["username"]
24 | @password = config["passwordkey"]
25 | @port = config["port"]
26 | @staleDays = config["staledays"]
27 | @cleanupWaitTime = config["cleanupwaittime"]
28 |
29 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
30 | puts 'logging into Nexpose'
31 |
32 | begin
33 | nsc.login
34 | rescue ::Nexpose::APIError => err
35 | $stderr.puts("Connection failed: #{err.reason}")
36 | exit(1)
37 | end
38 |
39 | puts 'logged into Nexpose'
40 | at_exit { nsc.logout }
41 |
42 | # Check scan activity and wait until there are no scans running
43 | begin
44 | active_scans = nsc.scan_activity
45 | if active_scans.any?
46 | puts "Active Scans:"
47 | ## Pull data for active scans
48 | activeScans = nsc.scan_activity()
49 | ## Output a list of active scans in the scan queue.
50 | activeScans.each do |status|
51 | siteInfoID = status.site_id
52 | siteDetail = Site.load(nsc, siteInfoID)
53 | begin
54 | Scan
55 | puts "ScanID: #{status.scan_id}, Assets: #{status.nodes.live}, ScanTemplate: #{siteDetail.scan_template_id}, SiteID: #{status.site_id} - #{siteDetail.name}, Status:#{status.status}, EngineID:#{status.engine_id}, StartTime:#{status.start_time}"
56 | rescue
57 | raise
58 | end
59 |
60 | end
61 | puts "Checking for scans again in #{@cleanupWaitTime} seconds."
62 | sleep(@cleanupWaitTime)
63 | end
64 | end while active_scans.any?
65 |
66 | # Determine which sites are being scanned on a schedule
67 | scheduledSites = Array.new
68 | sites = nsc.list_sites
69 | sites.each do |site|
70 | site = Nexpose::Site.load(nsc, site.id)
71 | if site.schedules.any?
72 | scheduledSites << site.id
73 | else
74 | puts "No scheduled scans for SiteID: #{site.id} SiteName: #{site.name}"
75 | end
76 | end
77 |
78 | # Find assets that have not been scanned in the last @staleDays.
79 | old_assets = nsc.filter(Search::Field::SCAN_DATE, Search::Operator::EARLIER_THAN, @staleDays)
80 |
81 | # Iterate through the assets and delete those in sites with schedules.
82 | old_assets.each do |device|
83 | if scheduledSites.include?(device.site_id)
84 | puts "Deleting #{device.ip} [ID: #{device.id}] Site: #{device.site_id} Last Scanned: #{device.last_scan}"
85 | nsc.delete_device(device.id)
86 | end
87 | end
88 |
89 | puts 'Logging out'
90 | exit
91 |
92 |
--------------------------------------------------------------------------------
/powershell/alter_credential_build.ps1:
--------------------------------------------------------------------------------
1 | # Makes PS ignore self signed certs used by internal servers
2 | # Have someone actually sign this certificate... - BrianWGray
3 | if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type)
4 | {
5 | $certCallback = @"
6 | using System;
7 | using System.Net;
8 | using System.Net.Security;
9 | using System.Security.Cryptography.X509Certificates;
10 | public class ServerCertificateValidationCallback
11 | {
12 | public static void Ignore()
13 | {
14 | if(ServicePointManager.ServerCertificateValidationCallback ==null)
15 | {
16 | ServicePointManager.ServerCertificateValidationCallback +=
17 | delegate
18 | (
19 | Object obj,
20 | X509Certificate certificate,
21 | X509Chain chain,
22 | SslPolicyErrors errors
23 | )
24 | {
25 | return true;
26 | };
27 | }
28 | }
29 | }
30 | "@
31 | Add-Type $certCallback
32 | }
33 | [ServerCertificateValidationCallback]::Ignore()
34 | # https://help.rapid7.com/insightvm/en-us/api/index.html
35 |
36 | # Collects user credentials for login (Functional)
37 | $creds = Get-Credential
38 | $unsecureCreds = $creds.GetNetworkCredential()
39 | $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $unsecureCreds.UserName,$unsecureCreds.Password)))
40 | Remove-Variable unsecureCreds
41 |
42 | # Define data transfer type
43 | $data_type = "application/json"
44 |
45 | # URL Definintions
46 | $hostName = "127.0.0.1"
47 | $port = "3780"
48 | $credentialId = 2
49 | $url = "https://${hostName}:${port}/api/3/shared_credentials/${credentialId}/"
50 |
51 | # Build headers to send to the API. In this case we set the Basic authentication value
52 | $table_headers = @{
53 | Authorization=("Basic {0}" -f $base64AuthInfo)
54 | }
55 |
56 | # Pull existing object for modification
57 | $credential = Invoke-RestMethod -Method 'GET' -Uri $url -Headers $table_headers
58 |
59 | $credential.account
60 |
61 | # Credential information use for building a json submission
62 | $credName = $credential.account.username
63 | $service = $credential.account.service
64 | $domain = $credential.account.domain
65 | $userName = $credential.account.username
66 | $userPass = "N3wSVCCr3d3nt1al"
67 | $description = "An altered description"
68 | $siteAssignment = $credential.siteAssignment
69 |
70 | # If you wanted to build the content for a credential object you could build it like:
71 | $body = @{
72 | account = @{
73 | domain = $domain;
74 | service = $service;
75 | username = $userName;
76 | password = $userPass;
77 | };
78 | description = $description;
79 | id = $credentialId;
80 | name = $credName;
81 | siteAssignment = $siteAssignment;
82 | }
83 |
84 |
85 | # Build PUT Content using the existing credential object
86 | $json = $body | Convertto-JSON
87 |
88 | # Visual of the json being sent - just for show
89 | $json
90 |
91 | $data = Invoke-RestMethod -Method 'PUT' -Uri $url -ContentType $data_type -Headers $table_headers -Body $json;
92 |
--------------------------------------------------------------------------------
/powershell/create_asset_group.ps1:
--------------------------------------------------------------------------------
1 | # Attempt to help with https://kb.help.rapid7.com/discuss/5c66e04394dea300577d6d47
2 |
3 | # Makes PS ignore self signed certs used by internal servers
4 | # Have someone actually sign this certificate... - BrianWGray
5 |
6 | if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type)
7 | {
8 | $certCallback = @"
9 | using System;
10 | using System.Net;
11 | using System.Net.Security;
12 | using System.Security.Cryptography.X509Certificates;
13 | public class ServerCertificateValidationCallback
14 | {
15 | public static void Ignore()
16 | {
17 | if(ServicePointManager.ServerCertificateValidationCallback ==null)
18 | {
19 | ServicePointManager.ServerCertificateValidationCallback +=
20 | delegate
21 | (
22 | Object obj,
23 | X509Certificate certificate,
24 | X509Chain chain,
25 | SslPolicyErrors errors
26 | )
27 | {
28 | return true;
29 | };
30 | }
31 | }
32 | }
33 | "@
34 | Add-Type $certCallback
35 | }
36 | [ServerCertificateValidationCallback]::Ignore()
37 | # https://help.rapid7.com/insightvm/en-us/api/index.html
38 | # https://help.rapid7.com/insightvm/en-us/api/index.html#operation/getAssetGroups
39 |
40 | # Collects user credentials for login (Functional)
41 | $creds = Get-Credential
42 | $unsecureCreds = $creds.GetNetworkCredential()
43 | $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $unsecureCreds.UserName,$unsecureCreds.Password)))
44 | Remove-Variable unsecureCreds
45 |
46 | # Define data transfer type
47 | $data_type = "application/json"
48 |
49 | # URL Definintions
50 | $hostName = "nexpose-test.example.com"
51 | $port = "3780"
52 | $url = "https://${hostName}:${port}/api/3/asset_groups/"
53 |
54 | # Build headers to send to the API. In this case we set the Basic authentication value
55 | $table_headers = @{
56 | "Content-Type" = $data_type
57 | Authorization=("Basic {0}" -f $base64AuthInfo)
58 | }
59 |
60 | # Search filter criteria
61 |
62 | $filter = @{ field = "operating-system"; operator = "contains"; value = "linux"}
63 |
64 | $filters = @($filter)
65 |
66 | # Here we're using straight JSON to prove API / documentation issues
67 | $body = @"
68 | {
69 | "description": "A Static Asset Group with Assets that are Linux Assets running Containers (With Low Access Complexity Vulnerabilities) for remediation purposes.",
70 | "name": "Container Hosts - Linux",
71 | "searchCriteria": {
72 | "filters": [
73 | { "field": "operating-system", "operator": "contains", "value": "linux" },
74 | { "field": "containers", "operator": "are", "value": 0 },
75 | { "field": "cvss-access-complexity", "operator": "is", "value": "L" }
76 | ],
77 | "match": "all"
78 | },
79 | "type": "static"
80 | }
81 |
82 | "@
83 |
84 | # Build Post Content using the existing credential object
85 | # We don't need to use the convert to JSON in this example but left it for additional testing.
86 | $json = $body # | Convertto-JSON
87 |
88 | # Visual of the json being sent - just for show
89 | $json
90 |
91 | $data = Invoke-RestMethod -Method 'Post' -Uri $url -Headers $table_headers -Body $json
--------------------------------------------------------------------------------
/stopPausedScans.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 11.10.2014
4 |
5 | ## Script performs the following tasks
6 | ## 1.) Retrieve a list of paused scans from a console.
7 | ## 2.) Iteratively stop scans that have paused without completing.
8 | ## 3.) TODO: Massive code cleanup + efficiency improvements.
9 |
10 | require 'yaml'
11 | require 'nexpose'
12 |
13 | include Nexpose
14 |
15 | # Default Values from yaml file
16 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
17 | config = YAML.load_file(config_path)
18 |
19 | @host = config["hostname"]
20 | @userid = config["username"]
21 | @password = config["passwordkey"]
22 | @port = config["port"]
23 | @consecutiveCleanupScans = config["cleanupqueue"]
24 | @cleanupWaitTime = config["cleanupwaittime"]
25 | @nexposeAjaxTimeout = config["nexposeajaxtimeout"]
26 |
27 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
28 | puts 'logging into Nexpose'
29 |
30 | begin
31 | nsc.login
32 | rescue ::Nexpose::APIError => err
33 | $stderr.puts("Connection failed: #{e.reason}")
34 | exit(1)
35 | end
36 |
37 | puts 'logged into Nexpose'
38 | at_exit { nsc.logout }
39 |
40 |
41 | ## Initialize connection timeout values.
42 | ## Timeout example provided by JGreen in https://community.rapid7.com/thread/5075
43 |
44 | module Nexpose
45 | class APIRequest
46 | include XMLUtils
47 | # Execute an API request
48 | def self.execute(url, req, api_version='2.0', options = {})
49 | options = {timeout: @nexposeAjaxTimeout}
50 | obj = self.new(req.to_s, url, api_version)
51 | obj.execute(options)
52 | return obj
53 | end
54 | end
55 |
56 |
57 | module AJAX
58 | def self._https(nsc)
59 | http = Net::HTTP.new(nsc.host, nsc.port)
60 | http.use_ssl = true
61 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
62 | http.read_timeout = @nexposeAjaxTimeout
63 | http
64 | end
65 | end
66 | end
67 |
68 |
69 | ## Start loop that will continue until there are no longer any scans that are paused.
70 | begin
71 | begin
72 | puts "\r\nRequesting scan status updates from #{@host}\r\n"
73 | ## Pull data for paused scans - Method suggested by JGreen https://community.rapid7.com/thread/5075 (THANKS!!!)
74 | pausedScans = DataTable._get_dyn_table(nsc, '/data/site/scans/dyntable.xml?printDocType=0&tableID=siteScansTable&activeOnly=true').select { |scanHistory| (scanHistory['Status'].include? 'Paused')}
75 | rescue Exception # should really list all the possible http exceptions
76 | puts "Connection issue detected - Retrying in 30 seconds"
77 | sleep (120)
78 | retry
79 | end
80 |
81 | ## Loop through paused scans for cleanup.
82 | pausedScans.each do |scanHistory|
83 |
84 | scanIDReport = scanHistory['Scan ID']
85 | statusReport = scanHistory['Status']
86 | discoveredReport = scanHistory['Devices Discovered']
87 | puts "Stopping ScanID: #{scanIDReport}, Discovered: #{discoveredReport} - #{statusReport}"
88 | ## Stop the provided scanid.
89 | nsc.stop_scan(scanIDReport)
90 | end
91 |
92 |
93 | ## If there are no more paused scans, we can exit.
94 | end while ((pausedScans.count) > 0)
95 |
96 | puts "No Paused scans were returned in the request. Exiting"
97 |
98 | puts 'Logging out'
99 | exit
--------------------------------------------------------------------------------
/powershell/example_start_scan_post.ps1:
--------------------------------------------------------------------------------
1 | # Makes PS ignore self signed certs used by internal servers
2 | # Have someone actually sign this certificate... - BrianWGray
3 | if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type)
4 | {
5 | $certCallback = @"
6 | using System;
7 | using System.Net;
8 | using System.Net.Security;
9 | using System.Security.Cryptography.X509Certificates;
10 | public class ServerCertificateValidationCallback
11 | {
12 | public static void Ignore()
13 | {
14 | if(ServicePointManager.ServerCertificateValidationCallback ==null)
15 | {
16 | ServicePointManager.ServerCertificateValidationCallback +=
17 | delegate
18 | (
19 | Object obj,
20 | X509Certificate certificate,
21 | X509Chain chain,
22 | SslPolicyErrors errors
23 | )
24 | {
25 | return true;
26 | };
27 | }
28 | }
29 | }
30 | "@
31 | Add-Type $certCallback
32 | }
33 | [ServerCertificateValidationCallback]::Ignore()
34 |
35 | # Check out the following links to get yourself started: - BrianWGray
36 | # https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod?view=powershell-6
37 | # https://www.gngrninja.com/script-ninja/2016/7/24/powershell-getting-started-utilizing-the-web-part-2-invoke-restmethod
38 | # https://127.0.0.1:3780/api/3/
39 | # https://help.rapid7.com/insightvm/en-us/api/index.html
40 |
41 | # Collects user credentials for login (Functional)
42 | # I don't highly recommend this specific credential collection method it's just to get the script bootstrapped until you are more comfortable - BrianWGray
43 | $creds = Get-Credential
44 | $unsecureCreds = $creds.GetNetworkCredential()
45 | $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $unsecureCreds.UserName,$unsecureCreds.Password)))
46 | Remove-Variable unsecureCreds
47 |
48 | # Set the API URI to call : In this example we specify the site ID integer that we want to start a scan on - BrianWGray
49 | # $url = "https://127.0.0.1:3780/api/3/sites/1/scans"
50 |
51 | $data_type = "application/json"
52 |
53 | # Build headers to send to the API. In this case we set the data type of the body content and the Basic authentication value
54 | $table_headers = @{
55 | "Content-Type" = $data_type
56 | Authorization=("Basic {0}" -f $base64AuthInfo)
57 | }
58 |
59 | # Build a POST Body to send to the API
60 | # There are other options but we are just providing a name for the scan for an example
61 | # https://help.rapid7.com/insightvm/en-us/api/index.html#operation/startScan
62 | $body = @{
63 | name="API Scan Start"
64 | }
65 | $json = $body | Convertto-JSON
66 |
67 | # Interact with the API in this case send a POST request to the specified URL and supply a basic auth header with a base64 encoded username:password
68 | # The $body variable holds the data that we want to provide to the API then we convert the $body content to $json format for the API to parse.
69 | $data = Invoke-RestMethod -Method 'Post' -Uri $url -Headers $table_headers -Body $json
70 |
71 | # Now we have a data object full of the API response content that can be manipulated for view however you would like. - BrianWGray
72 | # $data | Get-Member # Show returned object attributes
73 | $data.id # display the id of the scan that was started
74 |
75 |
76 |
--------------------------------------------------------------------------------
/dbMaint.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 07.14.2014
4 |
5 | ## Script runs the following database maintenance tasks
6 | ## 1.) Clean up database - Removes any unnecessary data from the database
7 | ## 2.) Compress database tables - Compresses the database tables and reclaims unused, allocated space.
8 | ## 3.) Re-index database - Drops and recreates the database indexes for improved performance.
9 |
10 | require 'yaml' # Add support for external configurations via yaml file.
11 | require 'net/http' # Used to check whether the nexpose service is available.
12 | require 'nexpose' # Add nexpose-client gem to interact with Nexpose.
13 |
14 | include Nexpose
15 |
16 | # Default Values
17 |
18 | config = YAML.load_file("conf/nexpose.yaml") # From file
19 |
20 | @host = config["hostname"]
21 | @userid = config["username"]
22 | @password = config["passwordkey"]
23 | @port = config["port"]
24 | @serviceTimeout = config["servicetimeout"]
25 |
26 |
27 | def checkService()
28 | tryAgain = 0
29 |
30 | begin
31 | begin
32 | path = '/login.html' # Check to see if we may login or if we are re-directed to the maintenance login page.
33 |
34 | http = Net::HTTP.new(@host,@port)
35 | http.read_timeout = 1
36 | http.use_ssl = true
37 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
38 | response = nil
39 |
40 | http.start{|http|
41 | request = Net::HTTP::Get.new(path)
42 | response = http.request(request)
43 | }
44 |
45 | rescue Exception # should really list all the possible http exceptions
46 | puts "Attempt: #{tryAgain} Service Unavailable"
47 | sleep (30)
48 | retry if (tryAgain += 1) < @serviceTimeout
49 | end
50 |
51 | response.code
52 | if response.code == "200" # Check the status code anything other than 200 indicates the service is not ready.
53 | puts "Attempt: #{tryAgain} #{response.code} The Nexpose Service appears to be up and functional"
54 | tryAgain = @serviceTimeout
55 | else
56 | puts "Attempt: #{tryAgain} #{response.code} #{response.message} The Service is not yet fully initialized"
57 | tryAgain += 1
58 | sleep(30)
59 | end
60 | end while tryAgain < @serviceTimeout
61 |
62 | if (response.code != "200")
63 | puts "The service was never determined to be available. Action Timed Out"
64 | exit
65 | end
66 | end
67 |
68 |
69 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
70 |
71 |
72 | begin
73 | checkService()
74 | puts 'logging into Nexpose'
75 | nsc.login
76 | rescue ::Nexpose::APIError => err
77 | $stderr.puts("Connection failed: #{e.reason}")
78 | exit(1)
79 | end
80 |
81 | puts 'logged into Nexpose'
82 | at_exit { nsc.logout }
83 |
84 | begin
85 | # Check scan activity wait until there are no scans running
86 | active_scans = nsc.scan_activity
87 | if active_scans.any?
88 | puts "Current scan status: #{active_scans.to_s}"
89 | sleep(15)
90 | end
91 | end while active_scans.any?
92 |
93 | # Start database maintenance
94 | if active_scans.empty?
95 | platform_independent = true
96 | puts "Initiating Database Maintenance tasks"
97 | nsc.db_maintenance(1,1,1)
98 | else
99 |
100 | end
101 |
102 |
103 | puts 'Logging out'
104 | exit
105 |
--------------------------------------------------------------------------------
/discoveryCountCSV.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 04.07.2016
4 |
5 | # Put together to try and determine a good way to answer question https://community.rapid7.com/thread/5394
6 |
7 | # Script performs the following tasks
8 | ## 1.) Retrieve a list of available sites from a console.
9 | ## 2.) Retrieve address entries for each site.
10 | ## 3.) Convert address ranges to ip address counts
11 | ## 4.) Provide a total of scanned addresses per site.
12 | ## 5.) Provide a count of live nodes recorded in the last scan for each site
13 | ## 6.) Provide a total count of discoverable addresses for all sites combined.
14 | ## 7.) Provide a total count of active nodes for all sites combined.
15 |
16 |
17 | require 'yaml'
18 | require 'nexpose'
19 | require 'ipaddr'
20 | require 'pp'
21 | include Nexpose
22 |
23 | # Default Values
24 |
25 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
26 | config = YAML.load_file(config_path)
27 |
28 | @host = config["hostname"]
29 | @userid = config["username"]
30 | @password = config["passwordkey"]
31 | @port = config["port"]
32 |
33 | assetCounter = 0
34 | @liveAssetCounter = 0
35 | @liveNodes = 0
36 |
37 | defaultFile = 'AssetUsage_' + DateTime.now.strftime('%Y-%m-%d--%H%M') + '.csv'
38 |
39 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
40 | #puts 'logging into Nexpose'
41 |
42 |
43 | begin
44 | nsc.login
45 | rescue ::Nexpose::APIError => err
46 | $stderr.puts("Connection failed: #{err.reason}")
47 | exit(1)
48 | end
49 |
50 | at_exit { nsc.logout }
51 |
52 | site = nsc.list_sites
53 | case site.length
54 | when 0
55 | puts("There are currently no active sites on this NeXpose instance")
56 | end
57 |
58 |
59 | def convert_ip_range(start_ip, end_ip)
60 | start_ip = IPAddr.new(start_ip)
61 | end_ip = IPAddr.new(end_ip)
62 |
63 | (start_ip..end_ip).map(&:to_s)
64 | end
65 |
66 | File.open(defaultFile, 'w') do |file|
67 |
68 | file.puts "\"Site\",Discovery Count,Nodes detected in last scan"
69 | puts "Site, Discovery Count, Nodes detected in last scan"
70 |
71 | begin
72 | site.each do |site|
73 | site = Nexpose::Site.load(nsc, site.id)
74 | # puts "Getting defined assets for #{site.name}"
75 |
76 | # pp site ## DEBUG
77 |
78 | site.included_addresses.each do |asset|
79 | @siteCount = 0
80 | @liveNodes = 0
81 | currentCount = 0
82 |
83 | if asset.respond_to? :from
84 |
85 | if asset.to != nil
86 | startRange = "#{asset.from}" if asset.to
87 | endRange = "#{asset.to}"
88 | currentCount = convert_ip_range(startRange.to_s, endRange.to_s).count
89 | else
90 | currentCount = 1
91 | end
92 | end
93 | assetCounter += currentCount
94 | @siteCount += currentCount
95 | end
96 |
97 | latest = nsc.last_scan(site.id)
98 |
99 | if latest
100 | @liveNodes += latest.nodes.live
101 | # @liveNodes += latest.nodes.dead
102 | # @liveNodes += latest.nodes.filtered
103 | # @liveNodes += latest.nodes.unresolved
104 | # @liveNodes += latest.nodes.other
105 | end
106 |
107 | @liveAssetCounter += @liveNodes
108 | file.puts "\"#{site.name}\",#{@siteCount},#{@liveNodes}"
109 | puts "\"#{site.name}\", #{@siteCount}, #{@liveNodes}"
110 | end
111 | end
112 |
113 | file.puts "\"Total tally of discoverable addresses for all sites:\",#{assetCounter}"
114 | puts "\"Total tally of discoverable addresses for all sites:\",#{assetCounter}"
115 | file.puts "\"Total tally of live nodes for all sites:\",#{@liveAssetCounter}"
116 | puts "\"Total tally of live nodes for all sites:\",#{@liveAssetCounter}"
117 | end
118 |
119 | puts 'Logging out'
120 | exit
121 |
--------------------------------------------------------------------------------
/updateEmailAlerts.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # Original creation date 07.25.2014
4 | #
5 | # The bulk of email modification code is adopted from gschneider https://gist.github.com/gschneider-r7/cccbef2293c007122f58
6 | #
7 |
8 |
9 | ## Script performs the following
10 | ## 1.) Parses all sites for SMTP alerts
11 | ## 2.) Finds SMTP alerts that contain a provided email address
12 | ## 3.) Replaces found email address with new email address
13 |
14 | # require gems
15 | require 'yaml'
16 | require 'nexpose'
17 | require 'optparse'
18 |
19 |
20 | # Default Values from yaml file
21 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
22 | config = YAML.load_file(config_path)
23 |
24 | @host = config["hostname"]
25 | @userid = config["username"]
26 | @password = config["passwordkey"]
27 | @port = config["port"]
28 |
29 |
30 | OptionParser.new do |opts|
31 | opts.banner = "Usage: #{File::basename($0)} [options] "
32 | opts.separator ''
33 | opts.separator 'Update an alert email address for each site.'
34 | opts.separator ''
35 | opts.separator 'Options:'
36 | opts.on_tail('--help', 'Print this help message.') { puts opts; exit }
37 | end.parse!
38 |
39 | # Input for old and new email addresses.
40 | unless ARGV[0] and ARGV[1]
41 | $stderr.puts 'The old and new email addresses are required. Use --help for instructions.'
42 | exit(1)
43 | end
44 |
45 | # Assigning the arguments to variables with some assurance that they are proper strings.
46 | oldEmail = ARGV[0].to_s.chomp
47 | newEmail = ARGV[1].to_s.chomp
48 |
49 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
50 | puts 'logging into Nexpose'
51 |
52 | begin
53 | nsc.login
54 | rescue ::Nexpose::APIError => err
55 | $stderr.puts("Connection failed: #{e.reason}")
56 | exit(1)
57 | end
58 |
59 | puts 'logged into Nexpose'
60 | at_exit { nsc.logout }
61 |
62 | # Query the list of sites to work with
63 | sites = nsc.list_sites
64 |
65 | # User notification of changes to be made.
66 | puts "Alerts will be modified to remove #{oldEmail} and add #{newEmail}."
67 |
68 | begin
69 | # Step through each site in the site listing.
70 | sites.each do |site|
71 | begin
72 | # Load the site configuration to make changes
73 | site = Nexpose::Site.load(nsc, site.id)
74 | puts "Evaluating site #{site.name} (id: #{site.id})."
75 |
76 | # Check for configured alerts; skip sites without alerts.
77 | if site.alerts.length > 0
78 | puts "Found #{site.alerts.length} alerts for #{site.name} (id: #{site.id})."
79 | site.alerts.each do |alert|
80 |
81 | # Confirm that the alert is type: SMTPAlert.
82 | # Create a new object from the SMTP alert to make changes
83 | if alert.alert_type.instance_of? Nexpose::SMTPAlert
84 | smtpAlert = alert.alert_type
85 |
86 | # Only edit alerts where the old email address is present.
87 | if smtpAlert.recipients.include?(oldEmail)
88 | smtpAlert.recipients.delete_if { |r| r == oldEmail }
89 | puts "Deleted #{oldEmail} from alert for site #{site.name} (id: #{site.id})."
90 |
91 | # Only update the alert with the email address if it isn't already present
92 | unless smtpAlert.recipients.include?(newEmail)
93 | smtpAlert.add_recipient(newEmail)
94 | puts "Added #{newEmail} to alert for site #{site.name} (id: #{site.id})."
95 | end
96 |
97 | # Commit changes from the new alert object to the existing alert.
98 | # Save the site configuration
99 | alert.alert_type = smtpAlert
100 | site.save(nsc)
101 | puts "Saved changes to site #{site.name} (id:#{site.id})."
102 | else
103 | puts "No changes made to #{site.name} (id: #{site.id})."
104 | end
105 | end
106 | end
107 | end
108 |
109 | # Site level error, continue to the next site.
110 | rescue Exception => e
111 | puts e.message
112 | end
113 | end
114 |
115 | # Global error, this usually exits the loop and terminates.
116 | rescue Exception => e
117 | puts e.message
118 | end
119 |
120 | puts "Updates completed."
121 | exit
122 |
--------------------------------------------------------------------------------
/vulnIDQuery.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray 05/13/2014
3 |
4 | require 'yaml'
5 | require 'nexpose'
6 | require 'csv'
7 |
8 | # Default Values
9 |
10 | config = YAML.load_file("conf/nexpose.yaml") # From file
11 |
12 | @host = config["hostname"]
13 | @userid = config["username"]
14 | @password = config["passwordkey"]
15 | @port = config["port"]
16 |
17 |
18 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
19 | puts 'logging into Nexpose'
20 |
21 | begin
22 | nsc.login
23 | rescue ::Nexpose::APIError => err
24 | $stderr.puts("Connection failed: #{e.reason}")
25 | exit(1)
26 | end
27 |
28 | puts 'logged into Nexpose'
29 | at_exit { nsc.logout }
30 |
31 |
32 | puts "Example: Enter \"http-openssl-cve-2014-0160\" if you want to query for HeartBleed."
33 | prompt = 'Please enter Vuln-ID to search: '
34 | print prompt
35 | #Limiting character count to 32
36 | UserInput = STDIN.gets(32).chomp()
37 |
38 |
39 |
40 | sqlSelect = "
41 | WITH
42 | asset_ips AS (
43 | SELECT asset_id, ip_address, type
44 | FROM dim_asset_ip_address dips
45 | ),
46 | asset_addresses AS (
47 | SELECT da.asset_id,
48 | (SELECT array_to_string(array_agg(ip_address), ',') FROM asset_ips WHERE asset_id = da.asset_id AND type = 'IPv4') AS ipv4s,
49 | (SELECT array_to_string(array_agg(ip_address), ',') FROM asset_ips WHERE asset_id = da.asset_id AND type = 'IPv6') AS ipv6s,
50 | (SELECT array_to_string(array_agg(mac_address), ',') FROM dim_asset_mac_address WHERE asset_id = da.asset_id) AS macs
51 | FROM dim_asset da
52 | JOIN asset_ips USING (asset_id)
53 | ),
54 | asset_names AS (
55 | SELECT asset_id, array_to_string(array_agg(host_name), ',') AS names
56 | FROM dim_asset_host_name
57 | GROUP BY asset_id
58 | ),
59 | asset_facts AS (
60 | SELECT asset_id, riskscore, exploits, malware_kits
61 | FROM fact_asset
62 | ),
63 | vulnerability_metadata AS (
64 | SELECT *
65 | FROM dim_vulnerability dv
66 | ),
67 | vuln_cves_ids AS (
68 | SELECT vulnerability_id, array_to_string(array_agg(reference), ',') AS cves
69 | FROM dim_vulnerability_reference
70 | WHERE source = 'CVE'
71 | GROUP BY vulnerability_id
72 | )
73 |
74 |
75 | SELECT
76 | da.ip_address AS \"Asset IP Address\",
77 | favi.port AS \"Service Port\",
78 | dp.name AS \"Service Protocol\",
79 | dsvc.name AS \"Service Name\",
80 | an.names AS \"Asset Names\",
81 | favi.date AS \"Vulnerability Test Date\",
82 | dsc.started AS \"Last Scan Time\",
83 | favi.scan_id AS \"Scan ID\",
84 | ds.name AS \"Site Name\",
85 | ds.importance AS \"Site Importance\",
86 | vm.date_published AS \"Vulnerability Published Date\",
87 | ROUND((EXTRACT(epoch FROM age(now(), date_published)) / (60 * 60 * 24))::numeric, 0) AS \"Vulnerability Age\",
88 | cves.cves AS \"Vulnerability CVE IDs\",
89 | vm.title AS \"Vulnerability Title\",
90 | vm.cvss_score AS \"Vulnerability CVSS Score\",
91 | proofAsText(vm.description) AS \"Vulnerability Description\",
92 | vm.nexpose_id AS \"Vulnerability ID\",
93 | vm.severity AS \"Vulnerability Severity Level\",
94 | dvs.description AS \"Vulnerability Test Result Description\"
95 |
96 |
97 | FROM fact_asset_vulnerability_instance favi
98 | JOIN dim_asset da USING (asset_id)
99 | LEFT OUTER JOIN asset_addresses aa USING (asset_id)
100 | LEFT OUTER JOIN asset_names an USING (asset_id)
101 | JOIN asset_facts af USING (asset_id)
102 | JOIN dim_service dsvc USING (service_id)
103 | JOIN dim_protocol dp USING (protocol_id)
104 | JOIN dim_site_asset dsa USING (asset_id)
105 | JOIN dim_site ds USING (site_id)
106 | JOIN vulnerability_metadata vm USING (vulnerability_id)
107 | JOIN dim_vulnerability_status dvs USING (status_id)
108 | JOIN dim_operating_system dos USING (operating_system_id)
109 | LEFT OUTER JOIN dim_scan dsc USING (scan_id)
110 | LEFT OUTER JOIN vuln_cves_ids cves USING (vulnerability_id) "
111 |
112 | sqlWhere = "WHERE vm.nexpose_id LIKE '%#{UserInput}%';"
113 |
114 | query = sqlSelect + sqlWhere
115 |
116 |
117 | report = Nexpose::AdhocReportConfig.new(nil, 'sql')
118 | report.add_filter('version', '1.2.1')
119 | report.add_filter('query', query)
120 | report.add_filter('group', 1)
121 | report_output = report.generate(nsc)
122 | csv_output = CSV.parse(report_output.chomp, { :headers => :first_row })
123 | CSV.open('nexpose-export.csv', 'w') do |csv_file|
124 | csv_file << csv_output.headers
125 | csv_output.each do |row|
126 | csv_file << row
127 | end
128 | end
129 | # else
130 | # puts "Failure generating report"
131 | # nsc.logout
132 | # exit 1
133 |
134 |
135 | puts 'Report completed and saved to ./nexpose-export.csv.'
136 |
137 | exit
138 |
--------------------------------------------------------------------------------
/assetGroupQuery.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray 08/08/2014
3 |
4 |
5 | # Queries heavily draw from:
6 | # https://community.rapid7.com/message/11358#11358
7 | # https://community.rapid7.com/docs/DOC-2612
8 | #
9 |
10 |
11 |
12 | require 'yaml'
13 | require 'nexpose'
14 | require 'csv'
15 |
16 | # Default Values
17 |
18 | config = YAML.load_file("conf/nexpose.yaml") # From file
19 |
20 | @host = config["hostname"]
21 | @userid = config["username"]
22 | @password = config["passwordkey"]
23 | @port = config["port"]
24 |
25 |
26 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
27 | puts 'logging into Nexpose'
28 |
29 | begin
30 | nsc.login
31 | rescue ::Nexpose::APIError => err
32 | $stderr.puts("Connection failed: #{e.reason}")
33 | exit(1)
34 | end
35 |
36 | puts 'logged into Nexpose'
37 | at_exit { nsc.logout }
38 |
39 |
40 | puts "Example: Enter \"1\" if you want to query for asset groupid 1."
41 | prompt = 'Please enter an Asset groupid to export a vulnerability list for: '
42 | print prompt
43 | #Limiting character count to 32
44 | UserInput = STDIN.gets(32).chomp()
45 |
46 |
47 |
48 | sqlSelect = "
49 | WITH
50 | asset_ips AS (
51 | SELECT asset_id, ip_address, type
52 | FROM dim_asset_ip_address dips
53 | ),
54 | asset_addresses AS (
55 | SELECT da.asset_id,
56 | (SELECT array_to_string(array_agg(ip_address), ',') FROM asset_ips WHERE asset_id = da.asset_id AND type = 'IPv4') AS ipv4s,
57 | (SELECT array_to_string(array_agg(ip_address), ',') FROM asset_ips WHERE asset_id = da.asset_id AND type = 'IPv6') AS ipv6s,
58 | (SELECT array_to_string(array_agg(mac_address), ',') FROM dim_asset_mac_address WHERE asset_id = da.asset_id) AS macs
59 | FROM dim_asset da
60 | JOIN asset_ips USING (asset_id)
61 | ),
62 | asset_names AS (
63 | SELECT asset_id, array_to_string(array_agg(host_name), ',') AS names
64 | FROM dim_asset_host_name
65 | GROUP BY asset_id
66 | ),
67 | asset_facts AS (
68 | SELECT asset_id, riskscore, exploits, malware_kits
69 | FROM fact_asset
70 | ),
71 | vulnerability_metadata AS (
72 | SELECT *
73 | FROM dim_vulnerability dv
74 | ),
75 | vuln_cves_ids AS (
76 | SELECT vulnerability_id, array_to_string(array_agg(reference), ',') AS cves
77 | FROM dim_vulnerability_reference
78 | GROUP BY vulnerability_id
79 | )
80 |
81 |
82 | SELECT
83 | da.ip_address AS \"Asset IP Address\",
84 | favi.port AS \"Service Port\",
85 | dp.name AS \"Service Protocol\",
86 | dsvc.name AS \"Service Name\",
87 | an.names AS \"Asset Names\",
88 | favi.date AS \"Vulnerability Test Date\",
89 | dsc.started AS \"Last Scan Time\",
90 | favi.scan_id AS \"Scan ID\",
91 | ds.name AS \"Site Name\",
92 | ds.importance AS \"Site Importance\",
93 | vm.date_published AS \"Vulnerability Published Date\",
94 | ROUND((EXTRACT(epoch FROM age(now(), date_published)) / (60 * 60 * 24))::numeric, 0) AS \"Vulnerability Age\",
95 | cves.cves AS \"Vulnerability CVE IDs\",
96 | vm.title AS \"Vulnerability Title\",
97 | vm.cvss_score AS \"Vulnerability CVSS Score\",
98 | proofAsText(vm.description) AS \"Vulnerability Description\",
99 | vm.nexpose_id AS \"Vulnerability ID\",
100 | vm.severity AS \"Vulnerability Severity Level\",
101 | dvs.description AS \"Vulnerability Test Result Description\",
102 | dag.name AS \"Asset Group\"
103 |
104 | FROM fact_asset_vulnerability_instance favi
105 | JOIN dim_asset da USING (asset_id)
106 | LEFT OUTER JOIN asset_addresses aa USING (asset_id)
107 | LEFT OUTER JOIN asset_names an USING (asset_id)
108 | JOIN asset_facts af USING (asset_id)
109 | JOIN dim_service dsvc USING (service_id)
110 | JOIN dim_protocol dp USING (protocol_id)
111 | JOIN dim_site_asset dsa USING (asset_id)
112 | JOIN dim_asset_group_asset USING (asset_id)
113 | JOIN dim_asset_group dag USING (asset_group_id)
114 | JOIN dim_site ds USING (site_id)
115 | JOIN vulnerability_metadata vm USING (vulnerability_id)
116 | JOIN dim_vulnerability_status dvs USING (status_id)
117 | JOIN dim_operating_system dos USING (operating_system_id)
118 | LEFT OUTER JOIN dim_scan dsc USING (scan_id)
119 | LEFT OUTER JOIN vuln_cves_ids cves USING (vulnerability_id) "
120 |
121 | sqlWhere = "WHERE asset_group_id = '#{UserInput}';"
122 |
123 | query = sqlSelect + sqlWhere
124 |
125 |
126 | report = Nexpose::AdhocReportConfig.new(nil, 'sql')
127 | report.add_filter('version', '1.2.1')
128 | report.add_filter('query', query)
129 | report_output = report.generate(nsc,18000) # Timeout for report generation is currently set at ~30 minutes
130 | csv_output = CSV.parse(report_output.chomp, { :headers => :first_row })
131 | CSV.open("AssetGroup_#{UserInput}_export.csv", 'w') do |csv_file|
132 | csv_file << csv_output.headers
133 | csv_output.each do |row|
134 | csv_file << row
135 | end
136 | end
137 |
138 | puts "CSV export completed and saved to ./AssetGroup_#{UserInput}_export.csv."
139 |
140 | exit
141 |
--------------------------------------------------------------------------------
/massMaxDurationMod.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # Original creation date 08.26.2015
4 |
5 | ## Script performs the following
6 | ## 1.) Parses all sites
7 | ## 2.) Itterates all available scan schedules for each site
8 | ## 3.) Modifies existing max scan duration times to a new default time
9 |
10 | ## ToDo:)
11 | # 1.) Output a csv log of changes.
12 | # 2.) Accept a csv of sites to change and the values to be used for each specified siteID.
13 |
14 | # require gems
15 | require 'yaml'
16 | require 'nexpose'
17 | require 'pp'
18 |
19 | include Nexpose
20 |
21 | # Default Values from yaml file
22 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
23 | config = YAML.load_file(config_path)
24 |
25 | # Console login configurations
26 | @host = config["hostname"]
27 | @userid = config["username"]
28 | @password = config["passwordkey"]
29 | @port = config["port"]
30 |
31 |
32 | # Configure Options for alert template
33 |
34 | ## Define a new Max Scan Duration time to use for all scans as a default
35 | defaultMaxDuration = 1440
36 |
37 | ## Initialize connection timeout values.
38 | ## Timeout example provided by JGreen in https://community.rapid7.com/thread/5075
39 |
40 | module Nexpose
41 | class APIRequest
42 | include XMLUtils
43 | # Execute an API request
44 | def self.execute(url, req, api_version='2.0', options = {})
45 | options = {timeout: @nexposeAjaxTimeout}
46 | obj = self.new(req.to_s, url, api_version)
47 | obj.execute(options)
48 | return obj
49 | end
50 | end
51 |
52 |
53 | module AJAX
54 | def self._https(nsc)
55 | http = Net::HTTP.new(nsc.host, nsc.port)
56 | http.use_ssl = true
57 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
58 | http.read_timeout = @nexposeAjaxTimeout
59 | http
60 | end
61 | end
62 | end
63 |
64 |
65 |
66 |
67 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
68 | begin
69 | nsc.login
70 | rescue ::Nexpose::APIError => err
71 | $stderr.puts("Connection failed: #{err.reason}")
72 | exit(1)
73 | end
74 |
75 | puts 'logged into Nexpose'
76 | at_exit { nsc.logout }
77 |
78 | puts "Changes may not be made while there are active or paused scans in the console"
79 |
80 | begin
81 | begin
82 | puts "Requesting scan status updates from #{@host}\r\n"
83 | ## Pull data for paused scans - Method suggested by JGreen https://community.rapid7.com/thread/5075 (THANKS!!!)
84 | pausedScans = DataTable._get_dyn_table(nsc, '/data/site/scans/dyntable.xml?printDocType=0&tableID=siteScansTable&activeOnly=true').select { |scanHistory| (scanHistory['Status'].include? 'Paused')}
85 |
86 | # Check scan activity wait until there are no scans running or paused
87 | activeScans = nsc.scan_activity()
88 |
89 | puts "Active Scans: #{activeScans.count}"
90 | puts "Paused Scans: #{pausedScans.count}"
91 |
92 | if (activeScans.any? or pausedScans.any?)
93 | puts " Trying again in 60 seconds"
94 | sleep (60)
95 | end
96 | rescue Exception => err
97 | puts err.message # should really list all the possible http exceptions
98 | exit
99 | end
100 | end while (activeScans.any? or pausedScans.any?)
101 |
102 |
103 | if activeScans.empty?
104 |
105 | # Query the list of sites to work with
106 | sites = nsc.list_sites
107 |
108 | # User notification of changes to be made.
109 | puts "Maximum scan duration times will be added or modified for every site located on this console."
110 |
111 | begin
112 | # Step through each site in the site listing.
113 | sites.each do |eachSite|
114 | begin
115 | # Load the site configuration to make changes
116 | site = Nexpose::Site.load(nsc, eachSite.id)
117 | puts "Evaluating site #{site.name} (id: #{site.id})."
118 |
119 |
120 | begin
121 | # Check for existing scheduled scans within the site
122 | if site.schedules.length > 0
123 | puts "Number of scheduled scans for #{site.name} (id: #{site.id}): #{site.schedules.length} "
124 |
125 | site.schedules.each do |scheduledScan|
126 |
127 | puts "Current max_duration: #{scheduledScan.max_duration}"
128 | scheduledScan.max_duration = defaultMaxDuration
129 | puts "Modified max_duration: #{scheduledScan.max_duration}"
130 |
131 | begin
132 |
133 | # Finalize changes
134 |
135 | # Save the site configuration with the modified scan schedule value
136 | puts " Saving the new duration value for the scheduled scan."
137 | site.save(nsc)
138 | puts "Changes saved to site #{site.name} (id:#{site.id})."
139 |
140 | # Site schedule level error, continue to the next schedule.
141 | rescue Exception => err
142 | puts err.message
143 | end
144 |
145 |
146 | end
147 | end
148 | end
149 | # Site level error, continue to the next site.
150 | rescue Exception => err
151 | puts err.message
152 | end
153 |
154 | end
155 |
156 | # Global error, this usually exits the loop and terminates.
157 | rescue Exception => err
158 | puts err.message
159 | exit
160 | end
161 |
162 | else
163 |
164 | end
165 |
166 | puts "Updates completed."
167 | exit
168 |
--------------------------------------------------------------------------------
/createSyslogAlerts.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # Original creation date 04.28.2015
4 |
5 | ## Script performs the following
6 | ## 1.) Parses all sites and adds a syslog alert
7 | ## 2.) Looks for existing alerts in each site with the same name as the new alert and removes them prior to adding the new alert.
8 |
9 |
10 | # require gems
11 | require 'yaml'
12 | require 'nexpose'
13 | require 'pp'
14 |
15 |
16 | # Default Values from yaml file
17 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
18 | config = YAML.load_file(config_path)
19 |
20 | # Console login configurations
21 | @host = config["hostname"]
22 | @userid = config["username"]
23 | @password = config["passwordkey"]
24 | @port = config["port"]
25 |
26 |
27 | # Configure Options for alert template
28 |
29 | ## Alert Name Prefix
30 | alertPrefix = "SysLog_SiteID_"
31 |
32 | ## Log server / port to use in alerts
33 | logServer = config["logserver"]
34 | logPort = config["logport"]
35 |
36 | ## Scan alert filters
37 | alertFail = config["alertFail"]
38 | alertPause = config["alertPause"]
39 | alertResume = config["alertResume"]
40 | alertStart = config["alertStart"]
41 | alertStop = config["alertStop"]
42 |
43 | ## Vuln alert filters
44 | alertConfirmed = config["alertConfirmed"]
45 | alertPotential = config["alertPotential"]
46 | alertSeverity = config["alertSeverity"]
47 | alertUnconfirmed = config["alertUnconfirmed"]
48 |
49 |
50 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
51 | begin
52 | nsc.login
53 | rescue ::Nexpose::APIError => err
54 | $stderr.puts("Connection failed: #{err.reason}")
55 | exit(1)
56 | end
57 |
58 | puts 'logged into Nexpose'
59 | at_exit { nsc.logout }
60 |
61 | # Query the list of sites to work with
62 | sites = nsc.list_sites
63 |
64 | # User notification of changes to be made.
65 | puts "Syslog alerts will be added to every site located on this console."
66 |
67 | begin
68 | # Step through each site in the site listing.
69 | sites.each do |eachSite|
70 | begin
71 | # Load the site configuration to make changes
72 | site = Nexpose::Site.load(nsc, eachSite.id)
73 | puts "Evaluating site #{site.name} (id: #{site.id})."
74 | # Check for an existing alert within the site with the configured pre-fix #{alertPrefix}
75 | if site.alerts.length > 0
76 | puts "Found #{site.alerts.length} alerts for #{site.name} (id: #{site.id})."
77 |
78 | site.alerts.each do |alert|
79 |
80 | # Remove old syslog alerts with the same alert name.
81 | if alert.name.include?("#{alertPrefix}#{site.id}")
82 | # Create a new object from the alerts to make changes
83 | if alert.alert_type.include?("Syslog")
84 | if alert.name.include?("#{alertPrefix}#{site.id}")
85 | site.alerts.delete_if{ |obj| obj.name.include?("#{alertPrefix}#{site.id}")}
86 | puts "Deleted #{alertPrefix}#{site.id} from alerts for site #{site.name} (id: #{site.id})."
87 |
88 | # Save the site configuration
89 | site.save(nsc)
90 | puts "Saved changes to site #{site.name} (id:#{site.id})."
91 | end
92 | end
93 | end
94 | end
95 | end
96 | rescue Exception => err
97 | puts err.message
98 | end
99 |
100 | begin
101 | # Initialize a syslog alert for the site.
102 | syslogAlert = Nexpose::SyslogAlert.new("#{alertPrefix}#{site.id}", nsc, 1, -1)
103 | puts "Initiated adding #{logServer} to alert for site #{site.name} (id: #{site.id})."
104 |
105 | # Set the syslog server to use.
106 | puts " Setting #{logServer} as the log receiver"
107 | syslogAlert.server = logServer # Defined in ./conf/nexpose.yaml
108 |
109 | # Set the syslog port to use
110 | puts " Setting logging port #{logPort} for the log receiver"
111 | syslogAlert.server_port = logPort # Defined in ./conf/nexpose.yaml
112 |
113 | puts " Setting the scan filters"
114 | alertScanFilter = Nexpose::ScanFilter.new # setup scan filter?
115 |
116 | ## Scan alert filters
117 | alertScanFilter.fail = alertFail
118 | puts " Fail = #{alertFail}"
119 | alertScanFilter.pause = alertPause
120 | puts " Pause = #{alertPause}"
121 | alertScanFilter.resume = alertResume
122 | puts " Resume = #{alertResume}"
123 | alertScanFilter.start = alertStart
124 | puts " Start = #{alertStart}"
125 | alertScanFilter.stop = alertStop
126 | puts " Stop = #{alertStop}"
127 |
128 | # Assign filters to scan_filter
129 | syslogAlert.scan_filter = alertScanFilter
130 |
131 | puts " Setting the vuln filter"
132 | alertVulnFilter = Nexpose::VulnFilter.new # setup vuln filter?
133 |
134 | ## Vuln alert filters
135 | alertVulnFilter.confirmed = alertConfirmed
136 | puts " Confirmed = #{alertConfirmed}"
137 | alertVulnFilter.potential = alertPotential
138 | puts " Potential = #{alertPotential}"
139 | alertVulnFilter.severity = alertSeverity
140 | puts " Severity = #{alertSeverity}"
141 | alertVulnFilter.unconfirmed = alertUnconfirmed
142 | puts " Unconfirmed = #{alertUnconfirmed}"
143 |
144 | # Assign filters to vuln_filter
145 | syslogAlert.vuln_filter = alertVulnFilter
146 |
147 | # Apply alert to site.
148 | site.alerts << syslogAlert
149 |
150 | # Save the site configuration
151 | puts " Saving the alert"
152 | site.save(nsc)
153 | puts "Changes saved to site #{site.name} (id:#{site.id})."
154 |
155 | # Site level error, continue to the next site.
156 | rescue Exception => err
157 | puts err.message
158 | end
159 | end
160 |
161 | # Global error, this usually exits the loop and terminates.
162 | rescue Exception => err
163 | puts err.message
164 | end
165 |
166 | puts "Updates completed."
167 | exit
168 |
--------------------------------------------------------------------------------
/createEmailAlerts.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # https://github.com/BrianWGray
4 | # Original creation date 08.22.2017
5 |
6 | ## Script performs the following
7 | ## 1.) Parses all sites and adds an smtp alert
8 | ## 2.) Looks for existing alerts in each site with the same name as the new alert and removes them prior to adding the new alert.
9 |
10 | # require gems
11 | require 'yaml'
12 | require 'nexpose'
13 | require 'pp'
14 |
15 | # Default Values from yaml file
16 | # The values may be configured manually, this is for the script creators benefit.
17 | # An example configuration file is available in the source git repository
18 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
19 | config = YAML.load_file(config_path)
20 |
21 | # Console login configurations
22 | @host = config["hostname"]
23 | @userid = config["username"]
24 | @password = config["passwordkey"]
25 | @port = config["port"]
26 |
27 | # Configure Options for alert template
28 |
29 | ## Alert Name Prefix
30 | alertPrefix = "SMTPAlert_SiteID_"
31 |
32 | ## SMTP server / port to use in alerts
33 | smtpServer = "localhost" #config["smtpserver"]
34 | smtpPort = 25 #config["smtpport"] # Not currently used
35 |
36 | # Mail Info
37 | sender = "vulnsender@example.com" #config["smtpsender"]
38 | recipients = ["recipients@example.com"] #config["smtprecipients"] # must be in array form.
39 |
40 | ## Scan alert filters
41 | alertFail = 1 #config["alertFail"]
42 | alertPause = 1 #config["alertPause"]
43 | alertResume = 1 #config["alertResume"]
44 | alertStart = 1 #config["alertStart"]
45 | alertStop = 1 #config["alertStop"]
46 |
47 | ## Vuln alert filters
48 | alertConfirmed = 1 #config["alertConfirmed"]
49 | alertPotential = 1 #config["alertPotential"]
50 | alertSeverity = 1 #config["alertSeverity"]
51 | alertUnconfirmed = 1 #config["alertUnconfirmed"]
52 |
53 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
54 | begin
55 | nsc.login
56 | rescue ::Nexpose::APIError => err
57 | $stderr.puts("Connection failed: #{err.reason}")
58 | exit(1)
59 | end
60 | at_exit { nsc.logout }
61 |
62 | # Query the list of sites to work with
63 | sites = nsc.list_sites
64 |
65 | # User notification of changes to be made.
66 | puts "SMTP alerts will be added to every site located on this console."
67 |
68 | begin
69 | # Step through each site in the site listing.
70 | sites.each do |eachSite|
71 | begin
72 | # Load the site configuration to make changes
73 | site = Nexpose::Site.load(nsc, eachSite.id)
74 | puts "\nEvaluating site #{site.name} (id: #{site.id})."
75 | # Check for an existing alert within the site with the configured pre-fix #{alertPrefix}
76 | if site.alerts.length > 0
77 | puts "Found #{site.alerts.length} alerts for #{site.name} (id: #{site.id})."
78 |
79 | site.alerts.each do |alert|
80 | # Remove old alerts with the same alert name.
81 | if alert.alert_type.include?("SMTP")
82 | # Create a new object from the alerts to make changes
83 | if alert.alert_type.include?("SMTP")
84 | if alert.name.include?("#{alertPrefix}#{site.id}")
85 | site.alerts.delete_if{ |obj| obj.alert_type.include?("SMTP")}
86 | puts "Deleted #{alert.name} from alerts for site #{site.name} (id: #{site.id})."
87 |
88 | # Save the site configuration
89 | site.save(nsc)
90 | puts "Saved changes to site #{site.name} (id:#{site.id})."
91 | end
92 | end
93 | end
94 | end
95 | end
96 | rescue Exception => err
97 | puts err.message
98 | end
99 |
100 | begin
101 | # Initialize an alert for the site.
102 | smtpAlert = Nexpose::SMTPAlert.new("#{alertPrefix}#{site.id}", sender, smtpServer, recipients, 1, -1, 1)
103 |
104 | puts "Initiated adding #{alertPrefix}#{site.id} alert for site #{site.name} (id: #{site.id})."
105 | puts " Set alert mail recipients #{recipients} for alerts"
106 | puts " Set sender address #{sender} for alerts"
107 | puts " Set SMTP server #{smtpServer} for alerts"
108 | puts " Setting the scan filters"
109 | alertScanFilter = Nexpose::ScanFilter.new # setup scan filter?
110 |
111 | ## Scan alert filters
112 | alertScanFilter.fail = alertFail
113 | puts " Fail = #{alertFail}"
114 | alertScanFilter.pause = alertPause
115 | puts " Pause = #{alertPause}"
116 | alertScanFilter.resume = alertResume
117 | puts " Resume = #{alertResume}"
118 | alertScanFilter.start = alertStart
119 | puts " Start = #{alertStart}"
120 | alertScanFilter.stop = alertStop
121 | puts " Stop = #{alertStop}"
122 |
123 | # Assign filters to scan_filter
124 | smtpAlert.scan_filter = alertScanFilter
125 |
126 | puts " Setting the vuln filter"
127 | alertVulnFilter = Nexpose::VulnFilter.new # setup vuln filter?
128 |
129 | ## Vuln alert filters
130 | alertVulnFilter.confirmed = alertConfirmed
131 | puts " Confirmed = #{alertConfirmed}"
132 | alertVulnFilter.potential = alertPotential
133 | puts " Potential = #{alertPotential}"
134 | alertVulnFilter.severity = alertSeverity
135 | puts " Severity = #{alertSeverity}"
136 | alertVulnFilter.unconfirmed = alertUnconfirmed
137 | puts " Unconfirmed = #{alertUnconfirmed}"
138 |
139 | # Assign filters to vuln_filter
140 | smtpAlert.vuln_filter = alertVulnFilter
141 |
142 | # Save the alert configuration
143 | puts " Saving the alert"
144 | smtpAlert.save(nsc, site.id)
145 |
146 | puts "Alert changes saved to site #{site.name} (id:#{site.id})."
147 |
148 | # Site level error, continue to the next site.
149 | rescue Exception => err
150 | puts err.message
151 | end
152 | end
153 |
154 | # Global error, this usually exits the loop and terminates.
155 | rescue Exception => err
156 | puts err.message
157 | end
158 |
159 | puts "Updates completed."
160 | exit
161 |
162 |
163 |
--------------------------------------------------------------------------------
/calGen.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 12.23.2014
4 |
5 |
6 | # This script generates an importable .ics file for scan schedules.
7 | #
8 |
9 | # Dependencies required to be installed:
10 | # sudo gem install icalendar
11 | # sudo gem install nexpose
12 | # sudo gem install yaml
13 | # for an example ./conf/nexpose.yaml see https://github.com/BrianWGray/nexpose/blob/master/conf/nexpose.yaml
14 |
15 | # misterpaul's scanPlanReporter.rb script was the catalyst for this script.
16 | # https://github.com/misterpaul/NexposeRubyScripts/tree/master/ScanPlanReporter
17 |
18 |
19 | require 'yaml'
20 | require 'icalendar'
21 | require 'nexpose'
22 | require 'time'
23 | include Nexpose
24 |
25 | # Default Values from yaml file
26 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
27 | config = YAML.load_file(config_path)
28 |
29 | @host = config["hostname"]
30 | @userid = config["username"]
31 | @password = config["passwordkey"]
32 | @port = config["port"]
33 | @icsfilename = config["icsfilename"]
34 | @icsiterations = config["icsitterations"]
35 |
36 |
37 | # Output filename
38 | output_fn = @icsfilename.to_s
39 |
40 | # Number of scan recurrences for the .ics file to include.
41 | numIterations = @icsiterations.to_i
42 |
43 | begin
44 | @nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
45 | puts 'logging into Nexpose'
46 |
47 | begin
48 | @nsc.login
49 | rescue ::Nexpose::APIError => err
50 | $stderr.puts("Connection failed: #{err.reason}")
51 | exit(1)
52 | end
53 |
54 | puts 'logged into Nexpose'
55 | at_exit { @nsc.logout }
56 |
57 | sites = @nsc.sites
58 |
59 | # Create calendar
60 | cal = Icalendar::Calendar.new
61 |
62 | # Check engine status.
63 | @nsc.console_command('version engines')
64 | engine_list = {}
65 | @nsc.engines.each do |engine|
66 | engine_list[engine.id] = "#{engine.name} (#{engine.status})"
67 | end
68 |
69 | # 'Site Name,Last Scan Start,Last Scan Live Nodes,Scan Template,Scan Engine,Next Scan Start,Schedule'
70 |
71 | sites.each do |s|
72 |
73 | latest_start_time = status = active = duration = engine_name = 'na'
74 |
75 | site = Site.load(@nsc, s.id)
76 | puts "Pulling Scan data for site: #{site.id}\tname: #{site.name}"
77 | template = site.scan_template
78 |
79 | latest = @nsc.last_scan(site.id)
80 | if latest
81 | latest_start_time = latest.start_time
82 | latest_end_time = latest.end_time # We initially set the end_time to the amount of time the last scan took.
83 | end_time = latest_end_time
84 | active = latest.nodes.live
85 | engine_name = engine_list[site.engine]
86 | if sched = site.schedules.first
87 | schedule = "#{sched.type}:#{sched.interval}"
88 | if sched.max_duration
89 | # If we find a max time defined we use it as the end_time to specify the available scan window
90 | # this replaces the guess created from the last scan duration.
91 | maxDuration = sched.max_duration
92 | else
93 | maxDuration = 0
94 | end
95 | end
96 |
97 | if latest.end_time
98 | duration_sec = latest.end_time - latest_start_time
99 | hours = (duration_sec / 3600).to_i
100 | minutes = (duration_sec / 60 - hours * 60).to_i
101 | seconds = (duration_sec - (minutes * 60 + hours * 3600))
102 | duration = sprintf('%dh %02dm %02ds', hours, minutes, seconds)
103 | else
104 | duration = 'na'
105 | end
106 |
107 | if defined? sched.enabled # Check to see if the site has a schedule enabled.
108 |
109 |
110 | start_time = Time.parse(sched.start)
111 | end_time = start_time + maxDuration*60
112 |
113 | puts "Site:#{site.name} starts #{start_time} Max scan time: #{maxDuration} #{end_time} using #{template} from #{engine_name} on a #{sched.type.upcase} schedule Interval: #{sched.interval}"
114 |
115 | event = cal.event
116 |
117 | event.summary = "Nexpose Scan for site: #{site.name}"
118 | event.dtstart = DateTime.parse("#{start_time}")
119 | event.dtend = DateTime.parse("#{end_time}")
120 | event.location = "#{engine_name} scanning #{site.name} with template #{template}"
121 | event.description = "Site:#{site.name} is scheduled to be scanned by #{engine_name} starting at #{start_time} with an expected end time of #{end_time} and a Max scan time of #{maxDuration} minutes. The scan will be completed using scan template: #{template}. Scans for this site have taken ~ #{duration} in the past. This scan is part of a schedule to scan this site with a #{sched.type} schedule type. https://#{@host}:#{@port}/site.jsp?siteid=#{site.id}"
122 |
123 | # This is intended to generate Recurrence Rules for the icalendar entries based on
124 | # http://www.ietf.org/rfc/rfc2445.txt
125 | # There is still a good bit of work that needs to be done here.
126 |
127 | case
128 | when sched.type == "daily"
129 | event.rrule = ["FREQ=DAILY;INTERVAL=#{sched.interval.to_i};COUNT=numIterations*7"] # Iterations are assumed to be in weeks here.
130 | when sched.type == "weekly"
131 | event.rrule = ["FREQ=WEEKLY;INTERVAL=#{sched.interval.to_i};COUNT=numIterations"]
132 | when sched.type == "monthly-date"
133 | event.rrule = ["FREQ=MONTHLY;INTERVAL=#{sched.interval.to_i};COUNT=numIterations"]
134 | when sched.type == "monthly-day"
135 | # I need to take more time to hash out the best way to implement this. Should be fairly straight forward?
136 | # event.rrule = ["FREQ=MONTHLY;BYMONTHDAY=#{sched.interval.to_i};COUNT=numIterations"]
137 | end
138 |
139 | cal.add_event(event)
140 |
141 | else
142 | puts "No schedule is enabled for this site"
143 |
144 | end
145 |
146 | else
147 | # No scans found.
148 | end
149 | end
150 |
151 | output = File.new(output_fn, 'w')
152 | output.write(cal.to_ical)
153 | puts "iCalendar file #{output_fn} saved"
154 |
155 | end
156 |
157 |
--------------------------------------------------------------------------------
/Nexpose-Lieberman-Integration/lieberman_integration.rb:
--------------------------------------------------------------------------------
1 | # Nexpose <-> Lieberman integration script.
2 | # The following gems need to be installed: nexpose
3 | # This script will perform the following steps:
4 | # 1.- Query nexpose for a list of sites.
5 | # 2.- Go site by site and retrieve the assets.
6 | # 3.- For all assets that are hostnames (not ips) it'll query lieberman for passwords.
7 | # 4.- For those that could checkout passwords it'll save back those credentials back into nexpose.
8 | # 5.- Kick a scan of the site if the setting for it below was set to yes.
9 | # For support, please email integrations_support@rapid7.com with the issue and a copy of the log.
10 |
11 |
12 | # SCRIPT CONFIGURATION:
13 | # Nexpose console information.
14 | # Nexpose IP / Hostname.
15 | @console = '192.168.99.190'
16 |
17 | # Nexpose username.
18 | @nxuser = 'nxadmin'
19 |
20 | # Nexpose Password.
21 | @nxpass = 'nxadmin'
22 |
23 | # Start scan after site is updated?
24 | @scan = 'N'
25 |
26 | # LOGGING.
27 | # This script includes a logger, all output will be sent to the file service_now.log in the directory
28 | # where this script is run.
29 | require 'Logger'
30 | $LOG = Logger.new('lieberman.log', 'monthly')
31 |
32 | # Valid log levels: Logger::DEBUG Logger::INFO Logger::WARN Logger::ERROR Logger:FATAL
33 | $LOG.level = Logger::INFO
34 |
35 | class LiebermanIntegration
36 | require "net/http"
37 | require "uri"
38 | require_relative "nexpose_integration"
39 |
40 | # Lieberman Web SDK URL.
41 | @@lieberman_instance = "https://lscerpm-2012/PWCWEB/ClientAgentRequests.asp"
42 | # Lieberman Authenticator domain.
43 | @@lieberman_authenticator = "demo"
44 | # Lieberman username.
45 | @@lieberman_user = "rapid7"
46 | # Lieberman password
47 | @@lieberman_password = "R@,o!D7p@ssw0rd"
48 |
49 | def lieberman_login
50 | $LOG.info "Login to Lieberman."
51 | uri = URI.parse("#{@@lieberman_instance}?Command=Login&Authenticator=#{@@lieberman_authenticator}&LoginUsername=#{@@lieberman_user}&LoginPassword=#{@@lieberman_password}")
52 | http = Net::HTTP.new(uri.host, uri.port)
53 | #http.set_debug_output $stdout
54 | request = Net::HTTP::Get.new(uri.request_uri)
55 | http.use_ssl = true
56 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
57 | response = http.request(request)
58 | token = response.body
59 | raise "Could not connect." if token.nil?
60 | raise "Could not authenticate. Check your credentials." unless token.start_with?('Success')
61 | token_array = token.split(';')
62 | @login_token = token_array[1]
63 | $LOG.info "Obtained Lieberman credentials."
64 | end
65 |
66 | def get_account_info(hostname)
67 | begin
68 | $LOG.info "Searching for account information for #{hostname}."
69 | uri = URI.parse("#{@@lieberman_instance}?Command=ListStoredAccountsForSystem&AuthenticationToken=#{@login_token}&SystemName=#{hostname}")
70 | http = Net::HTTP.new(uri.host, uri.port)
71 | http.use_ssl = true
72 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
73 | #http.set_debug_output $stdout
74 | request = Net::HTTP::Get.new(uri.request_uri)
75 |
76 | response = http.request(request)
77 | token = response.body
78 | begin
79 | if token.start_with?('Success')
80 | token_array = token.split(';')
81 | account_info = token_array[1]
82 | if account_info.include? "Linux" then service = "ssh"
83 | else service = "cifs"
84 | end
85 | namespace = account_info.slice(/^[^\\]*\\/).gsub(/\\$/, '').gsub(/\(.*\)/, '')
86 | account_login = account_info.slice(/\\([^\$]*)\$/).gsub(/\\/, '').gsub(/\$/, '')
87 | $LOG.info "Found account information for #{hostname}."
88 | account = {:hostname => hostname, :realm => namespace, :user => account_login, :service => service}
89 | else
90 | $LOG.info "Could not find account information for #{hostname} in Lieberman."
91 | account
92 | end
93 | rescue Exception
94 | $LOG.info "Could not find account information about #{hostname} continuing with the next account."
95 | account
96 | end
97 | rescue
98 | $LOG.info "Lieberman Login token not set."
99 | end
100 | end
101 |
102 | def get_password_for_system(account)
103 | raise "Account information cannot be null or empty." if account.nil? or account.empty?
104 | begin
105 | realm = account[:realm]
106 | user = account[:user]
107 | hostname = account[:hostname]
108 | service = account[:service]
109 |
110 | uri = URI.parse("#{@@lieberman_instance}?Command=GetPasswordFromStore&AuthenticationToken=#{@login_token}&SystemName=#{hostname}&Namespace=#{realm}&AccountName=#{user}&Comment=Nexpose")
111 | http = Net::HTTP.new(uri.host, uri.port)
112 | http.use_ssl = true
113 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
114 | #http.set_debug_output $stdout
115 | request = Net::HTTP::Get.new(uri.request_uri)
116 |
117 | response = http.request(request)
118 | token = response.body
119 |
120 | if token.start_with?('Success')
121 | $LOG.info "Found password for #{hostname}"
122 | token_array = token.split(';')
123 | pre_password = token_array[1].split('=')
124 | full_account = {:service => service, :realm => realm, :user => user, :hostname => hostname, :password => pre_password[1]}
125 | else
126 | $LOG.info "Could not retrieve password for #{hostname} in Lieberman. Check your permissions."
127 | full_account
128 | end
129 | rescue Exception
130 | $LOG.info "Could not retrieve password for #{hostname}."
131 | full_account
132 | end
133 | end
134 |
135 | end
136 |
137 |
138 | # Connects and login to Lieberman
139 | lieberman = LiebermanIntegration.new
140 | lieberman.lieberman_login
141 |
142 | # Connects to Nexpose.
143 | nexpose = NexposeIntegration.new()
144 | nexpose.connect(@console, @nxuser, @nxpass)
145 |
146 | # Get all the sites in Nexpose.
147 | all_sites = nexpose.get_all_sites
148 | raise "No sites found." if all_sites.empty? or all_sites.nil?
149 |
150 | # Resaves every site with the new credentials.
151 | all_sites.each do |site|
152 |
153 | # Get all the hostnames for the site.
154 | hostnames = nexpose.get_hostnames(site)
155 | next if hostnames.empty?
156 |
157 | all_creds = []
158 |
159 | # Queries Lieberman for all the credentials for all the hostnames.
160 | hostnames.each do |hostname|
161 | account_info = lieberman.get_account_info(hostname.host.slice(/^[^.]*/))
162 | next if account_info.nil? or account_info.empty?
163 | fullcred = lieberman.get_password_for_system(account_info)
164 | next if fullcred.nil? or fullcred.empty?
165 | all_creds.push(fullcred)
166 | end
167 |
168 | # Saves the site with the credentials.
169 | $LOG.info "Saving credentials for site-id: #{site.id}"
170 | nexpose.save_all_credentials_for_site(all_creds, site.id)
171 | if @scan == 'Y'
172 | $LOG.info "Starting scan for #{site.id}"
173 | nexpose.start_scan_site site.id
174 | end
175 | end
--------------------------------------------------------------------------------
/scan_mailer.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # Initial Creation Date: 9.30.2016
4 |
5 |
6 | # Purpose of this script.
7 | # Written as a POC for:
8 | # https://community.rapid7.com/thread/8990
9 |
10 | # This script queries all scheduled scans on a console and the contact email out of each site
11 | # then sends an email notification about when the scan will occur with a 24 hour advanced notice..
12 | #
13 |
14 | # Dependencies required to be installed:
15 | # sudo gem install nexpose
16 | # sudo gem install yaml
17 | # for an example ./conf/nexpose.yaml see https://github.com/BrianWGray/nexpose/blob/master/conf/nexpose.yaml
18 |
19 |
20 | require 'yaml'
21 | require 'nexpose'
22 | require 'time'
23 | require 'pp'
24 | require 'net/smtp'
25 | include Nexpose
26 |
27 | # Default Values from yaml file
28 | config_path = File.expand_path("../conf/pgh-nvs-01.yaml", __FILE__)
29 | config = YAML.load_file(config_path)
30 |
31 | @host = config["hostname"]
32 | @userid = config["username"]
33 | @password = config["passwordkey"]
34 | @port = config["port"]
35 |
36 | # If you need to add auth: https://www.tutorialspoint.com/ruby/ruby_sending_email.htm
37 | mailFrom = config["mailFrom"]
38 | mailServer = config["mailServer"]
39 | mailPort = config["mailPort"]
40 | mailDomain = config["mailDomain"]
41 |
42 | # If an organizations contact email is not configured for a site send the notice to the defaultEmail address.
43 | defaultEmail = config["defaultEmail"]
44 | mailTo = defaultEmail
45 |
46 |
47 | # The intent for the mailer times is as follows:
48 | # a mailerRange of 24 hours checks for scheduled scans 24 hours from the time of the script running.
49 | # If the script is run at 11am then the script is looking for scans the next day at 11am.
50 | # The scans won't always be exactly 24 hours from now so we provide a mailerWindow of 60 minutes
51 | # This means that any scheduled scan the next day between 11am and 12 will be in the notify window.
52 |
53 | # Values are in seconds and ultimately handled via epoch values
54 | mailerRange = ((60 * 60) * 24)
55 | mailerWindow = ((60 * 60)) # * 24)# => window of time for scheduled scans to be alerted for
56 |
57 |
58 | # Replace Time.parse(time.to_s).to_s assignments with a time normalizing method - BWG
59 | def normalize_time(time)
60 | begin
61 | time = time if(time.is_a?(Time))
62 | time = Time.parse("#{time.to_s}") if(!time.is_a?(Time))
63 | rescue
64 | time = Time.now # Upon failure use the current time value
65 | end
66 |
67 | return time
68 | end
69 |
70 |
71 | def send_notification(mailFrom, mailTo, mailDomain, mailServer, noticeContent)
72 |
73 | # These values don't really need to be re-assigned to local variables but I did it.
74 | @summary = noticeContent[:summary]
75 | @location = noticeContent[:location]
76 | @dtstart = noticeContent[:dtstart]
77 | @template = noticeContent[:template]
78 | @description = noticeContent[:description]
79 |
80 |
81 | # Example Email Notification Template. Modify as needed. Sending HTML email by default because I like it.
82 | message = <Vulnerability Scan Notice
90 |
91 |
The listed email address #{mailTo} is registered as the primary notification list for this notice.
93 |
94 | #{@summary}
95 |
96 | A vulnerability scan is scheduled to run against #{@location} starting #{@dtstart}
97 | The scheduled scan is assigned the following template: #{@template}
98 |
99 |
100 | #{@description}
101 |
102 | If you believe you have received this notice in error please contact help@example.com.
103 |
104 |
105 |
106 | MESSAGE_END
107 |
108 | begin
109 | Net::SMTP.start(mailServer) do |smtp|
110 | smtp.send_message message, mailFrom, mailTo
111 | end
112 |
113 | rescue => err
114 | $stderr.puts("Fail: #{err}")
115 | exit(1)
116 | end
117 |
118 | end
119 |
120 | begin
121 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
122 |
123 | begin
124 | nsc.login
125 | rescue ::Nexpose::APIError => err
126 | $stderr.puts("Connection failed: #{err.reason}")
127 | exit(1)
128 | end
129 | at_exit { nsc.logout }
130 |
131 | sites = nsc.sites
132 |
133 | begin
134 | sites.each do |s|
135 |
136 | latest_start_time = status = active = 'na'
137 |
138 | site = Site.load(nsc, s.id)
139 | # puts "Pulling Scan data for site: #{site.id}\tname: #{site.name}"
140 |
141 | site.schedules.each do |sched|
142 | begin
143 |
144 | schedule = "#{sched.type}:#{sched.interval}"
145 |
146 | if defined? sched.enabled # Check to see if the site has a schedule enabled.
147 |
148 | start_time = normalize_time(sched.next_run_time) # => Time of the Next scheduled scan
149 | currentTime = Time.now.to_i # => Current Time to calculate a time range for which notifications to send.
150 | rangeTime = currentTime + mailerRange
151 | timeRange = (rangeTime)..(rangeTime + mailerWindow) # => Range of time from the script run time to notify for.
152 |
153 | if timeRange === start_time.to_i
154 |
155 | # puts "Site:#{site.name} starts #{start_time} on a #{sched.type.upcase} schedule Interval: #{sched.interval}"
156 | if !site.organization.email.nil?
157 | orgMail = "#{site.organization.email}"
158 | else
159 | orgMail = "#{defaultEmail}"
160 | end
161 |
162 | noticeContent = {
163 | contact: orgMail,
164 | summary: "Vulnerability Scan for site: #{site.name}",
165 | dtstart: "#{start_time}",
166 | location: "#{site.name}",
167 | template: "#{sched.scan_template_id}",
168 | description: "Site: #{site.name} is scheduled to be scanned starting at #{start_time}. This scan is part of a schedule to scan this site with a #{sched.type} schedule type. https://#{@host}:#{@port}/site.jsp?siteid=#{site.id}"
169 | # Additional hash values may be added here to provide more information to the notification template.
170 | }
171 |
172 | # Call the mail function to email this schedule.
173 | send_notification(mailFrom, mailTo, mailDomain, mailServer, noticeContent)
174 | end
175 | end
176 | rescue => err
177 | $stderr.puts("Fail: #{err}")
178 | exit(1)
179 | end
180 | end
181 | end
182 |
183 | end
184 | end
185 |
--------------------------------------------------------------------------------
/scanCleanup.rb:
--------------------------------------------------------------------------------
1 | "#!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 09.04.2014
4 |
5 | ## Script performs the following tasks
6 | ## 1.) Retrieve a list of paused scans from a console.
7 | ## 2.) Retrieve a list of active scans from a console.
8 | ## 3.) Sort paused scans from least number of discovered assets to most
9 | ## 4.) Iteratively resume scans in batches for scans that have paused without completing.
10 | ## 5.) TODO: Massive code cleanup + efficiency improvements.
11 |
12 | ## Major code changes as of 11.21.2014 - BWG
13 | # Rearranged output information to a more logical order.
14 | # Screen output now includes additional information about the scan in the screen output.
15 | # Worked on Bug reduction.
16 | # - Fixed connection error information.
17 | # - Fixed issue with sessions being invalidating and never being recreated.
18 | # - Fixed yaml relative path issues with running the script from outside of its directory.
19 | # - Some code clean up.
20 |
21 |
22 | require 'yaml'
23 | require 'nexpose'
24 |
25 | include Nexpose
26 |
27 | # Default Values from yaml file
28 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
29 | config = YAML.load_file(config_path)
30 |
31 | @host = config["hostname"]
32 | @userid = config["username"]
33 | @password = config["passwordkey"]
34 | @port = config["port"]
35 | @consecutiveCleanupScans = config["cleanupqueue"]
36 | @cleanupWaitTime = config["cleanupwaittime"]
37 | @nexposeAjaxTimeout = config["nexposeajaxtimeout"]
38 |
39 | fillQueue = 0
40 |
41 |
42 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
43 | puts 'logging into Nexpose'
44 |
45 | begin
46 | nsc.login
47 | rescue ::Nexpose::APIError => err
48 | $stderr.puts("Connection failed: #{err.reason}")
49 | exit(1)
50 | end
51 |
52 | puts 'logged into Nexpose'
53 | at_exit { nsc.logout }
54 |
55 |
56 | ## Initialize connection timeout values.
57 | ## Timeout example provided by JGreen in https://community.rapid7.com/thread/5075
58 |
59 | module Nexpose
60 | class APIRequest
61 | include XMLUtils
62 | # Execute an API request
63 | def self.execute(url, req, api_version='2.0', options = {})
64 | options = {timeout: @nexposeAjaxTimeout}
65 | obj = self.new(req.to_s, url, api_version)
66 | obj.execute(options)
67 | return obj
68 | end
69 | end
70 |
71 |
72 | module AJAX
73 | def self._https(nsc)
74 | http = Net::HTTP.new(nsc.host, nsc.port)
75 | http.use_ssl = true
76 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
77 | http.read_timeout = @nexposeAjaxTimeout
78 | http
79 | end
80 | end
81 | end
82 |
83 |
84 | ## Start loop that will continue until there are no longer any scans paused or actively running.
85 | begin
86 | begin
87 | puts "\r\nRequesting scan status updates from #{@host}\r\n"
88 | ## Pull data for paused scans - Method suggested by JGreen https://community.rapid7.com/thread/5075 (THANKS!!!)
89 | pausedScans = DataTable._get_dyn_table(nsc, '/data/site/scans/dyntable.xml?printDocType=0&tableID=siteScansTable&activeOnly=true').select { |scanHistory| (scanHistory['Status'].include? 'Paused')}
90 | ## Pull data for active scans
91 | activeScans = nsc.scan_activity()
92 |
93 | rescue Exception # should really list all the possible http exceptions
94 | puts "Connection issue detected - Retrying in #{@cleanupWaitTime} seconds)"
95 | sleep (@cleanupWaitTime)
96 | begin # This is a less than ideal bandaid to make sure there is a valid session.
97 | nsc.login
98 | rescue ::Nexpose::APIError => err
99 | $stderr.puts("Connection failed: #{err.reason}")
100 | exit(1)
101 | end
102 | retry
103 | end
104 |
105 | # Collect Site info to provide additional information for screen output.
106 | siteInfo = nsc.sites
107 |
108 | ## Attempting some basic prioritization to complete lower asset count scans first.
109 | ## Perform a destructive sort of the pausedScans array based on the number of discovered assets.
110 | pausedScans.sort! { |a,b| a['Devices Discovered'].to_i <=> b['Devices Discovered'].to_i }
111 |
112 | ## List all of the paused scans to stdout.
113 | puts "\r\n-- Paused Scans Detected : #{pausedScans.count} --\r\n"
114 | pausedScans.each do |scanHistory|
115 | siteInfoID = scanHistory['Site ID'].to_i
116 | begin
117 | puts "ScanID: #{scanHistory['Scan ID']}, Assets: #{scanHistory['Devices Discovered']}, SiteID: #{siteInfoID} - #{siteInfo[siteInfoID].name}, #{scanHistory['Status']}"
118 | rescue
119 | puts "ScanID: #{scanHistory['Scan ID']}, Assets: #{scanHistory['Devices Discovered']}, SiteID: #{siteInfoID}, #{scanHistory['Status']}"
120 | end
121 | end
122 | puts "-- Paused Scans Detected : #{pausedScans.count} --\r\n"
123 |
124 |
125 | puts "\r\n -- Queue Size: #{@consecutiveCleanupScans} -- \r\n"
126 |
127 | ## Output a list of active scans in the scan queue.
128 | activeScans.each do |status|
129 | siteInfoID = status.site_id
130 | begin
131 | puts "ScanID: #{status.scan_id}, Assets: #{status.nodes.live}, SiteID: #{status.site_id} - #{siteInfo[siteInfoID].name}, Status:#{status.status}, EngineID:#{status.engine_id}, StartTime:#{status.start_time}"
132 | rescue
133 | puts "ScanID: #{status.scan_id}, Assets: #{status.nodes.live}, SiteID: #{status.site_id}, Status:#{status.status}, EngineID:#{status.engine_id}, StartTime:#{status.start_time}"
134 | end
135 | end
136 |
137 | ## Check to see if there are any slots open in the cleanup queue and that there are still scans to resume.
138 | if ((activeScans.count < @consecutiveCleanupScans.to_i) and (pausedScans.count > 0))
139 |
140 | ## Determine how many slots are left in the cleanup queue to use.
141 | fillQueue = ((@consecutiveCleanupScans - activeScans.count)-1)
142 | ## Loop through just enough paused scans to fill the open slots in the cleanup queue.
143 | pausedScans[0..fillQueue.to_i].each do |scanHistory|
144 | siteInfoID = scanHistory['Site ID'].to_i
145 | begin
146 | puts "Resuming ScanID: #{scanHistory['Scan ID']}, Assets: #{scanHistory['Devices Discovered']}, SiteID: #{siteInfoID} - #{siteInfo[siteInfoID].name}, Status: #{scanHistory['Status']}"
147 | rescue
148 | puts "Resuming ScanID: #{scanHistory['Scan ID']}, Assets: #{scanHistory['Devices Discovered']}, SiteID: #{siteInfoID}, Status: #{scanHistory['Status']}"
149 | end
150 | ## Resume the provided scanid.
151 | nsc.resume_scan(scanHistory['Scan ID'])
152 | end
153 |
154 | end
155 |
156 | if ((pausedScans.count + activeScans.count) > 0)
157 | ## Wait between checks so that the scans have time to run.
158 | sleep(@cleanupWaitTime)
159 | end
160 |
161 | ## If there are no more paused scans and the active scans have all completed without failing we can exit.
162 | end while ((pausedScans.count + activeScans.count) > 0)
163 |
164 | puts 'Logging out'
165 | exit
--------------------------------------------------------------------------------
/scan_by_iplist.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 010.07.2016
4 |
5 |
6 | ## Script Heavily borrows from Steve Tempest : https://community.rapid7.com/docs/DOC-2733 , https://community.rapid7.com/docs/DOC-2732
7 | ## Written as PoC for https://community.rapid7.com/thread/9132
8 |
9 | ## Script performs the following tasks
10 | ## 1.) Read addresses from text file
11 | ## 2.) De-duplicate addresses
12 | ## 3.) Scan addresses
13 | ## 4.) Generate a report of vulnerabilities detected during the scan.
14 |
15 |
16 | require 'yaml'
17 | require 'nexpose'
18 | require 'csv'
19 | include Nexpose
20 |
21 | # Default Values
22 |
23 | config = YAML.load_file("conf/nexpose.yaml") # From file
24 |
25 | @host = config["hostname"]
26 | @userid = config["username"]
27 | @password = config["passwordkey"]
28 | @port = config["port"]
29 | @nexposeAjaxTimeout = config["nexposeajaxtimeout"]
30 |
31 | @name = nil
32 |
33 | # Any arguments after flags can be grabbed now."
34 | unless ARGV[0]
35 | $stderr.puts 'Input file and site id is required.'
36 | exit(1)
37 | end
38 | file = ARGV[0]
39 | @name = File.basename(file, File.extname(file)) unless @name
40 |
41 | siteID = 0
42 | siteID = ARGV[1]
43 |
44 | # This will fail if the file cannot be read.
45 | ips = File.read(file).split.uniq
46 |
47 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
48 |
49 | begin
50 | nsc.login
51 | rescue ::Nexpose::APIError => err
52 | $stderr.puts("Connection failed: #{err.reason}")
53 | exit(1)
54 | end
55 |
56 | at_exit { nsc.logout }
57 |
58 | ## Initialize connection timeout values.
59 | ## Timeout example provided by JGreen in https://community.rapid7.com/thread/5075
60 |
61 | module Nexpose
62 | class APIRequest
63 | include XMLUtils
64 | # Execute an API request
65 | def self.execute(url, req, api_version='2.0', options = {})
66 | options = {timeout: @nexposeAjaxTimeout}
67 | obj = self.new(req.to_s, url, api_version)
68 | obj.execute(options)
69 | return obj
70 | end
71 | end
72 |
73 |
74 | module AJAX
75 | def self._https(nsc)
76 | http = Net::HTTP.new(nsc.host, nsc.port)
77 | http.use_ssl = true
78 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
79 | http.read_timeout = @nexposeAjaxTimeout
80 | http
81 | end
82 | end
83 | end
84 |
85 |
86 | # Create a map of all assets by IP to make them quicker to find.
87 | all_assets = nsc.assets.reduce({}) do |hash, dev|
88 | $stderr.puts("Duplicate asset: #{dev.address}") if @debug and hash.member? dev.address
89 | hash[dev.address] = dev
90 | hash
91 | end
92 |
93 | # Collect Site info to provide additional information for screen output.
94 | siteInfo = nsc.sites
95 | siteDetail = Site.load(nsc, siteID)
96 | @name = siteDetail.name
97 |
98 | puts "Starting partial scan of siteID #{siteID} "
99 | #scan = site.scan(nsc)
100 |
101 | scan = nsc.scan_ips(siteID, ips)
102 |
103 |
104 | begin
105 | sleep(15)
106 | status = nsc.scan_status(scan.id)
107 | puts "ScanID: #{scan.id} ScanTemplate: #{siteDetail.scan_template_id}, SiteID: #{siteID} - #{siteDetail.name}, Status:#{status}, EngineID:#{scan.engine}"
108 | end while status == Nexpose::Scan::Status::RUNNING
109 |
110 |
111 | sqlSelect = "
112 | WITH
113 | asset_ips AS (
114 | SELECT asset_id, ip_address, type
115 | FROM dim_asset_ip_address dips
116 | ),
117 | asset_addresses AS (
118 | SELECT da.asset_id,
119 | (SELECT array_to_string(array_agg(ip_address), ',') FROM asset_ips WHERE asset_id = da.asset_id AND type = 'IPv4') AS ipv4s,
120 | (SELECT array_to_string(array_agg(ip_address), ',') FROM asset_ips WHERE asset_id = da.asset_id AND type = 'IPv6') AS ipv6s,
121 | (SELECT array_to_string(array_agg(mac_address), ',') FROM dim_asset_mac_address WHERE asset_id = da.asset_id) AS macs
122 | FROM dim_asset da
123 | JOIN asset_ips USING (asset_id)
124 | ),
125 | asset_names AS (
126 | SELECT asset_id, array_to_string(array_agg(host_name), ',') AS names
127 | FROM dim_asset_host_name
128 | GROUP BY asset_id
129 | ),
130 | asset_facts AS (
131 | SELECT asset_id, riskscore, exploits, malware_kits
132 | FROM fact_asset
133 | ),
134 | vulnerability_metadata AS (
135 | SELECT *
136 | FROM dim_vulnerability dv
137 | ),
138 | vuln_cves_ids AS (
139 | SELECT vulnerability_id, array_to_string(array_agg(reference), ',') AS cves
140 | FROM dim_vulnerability_reference
141 | WHERE source = 'CVE'
142 | GROUP BY vulnerability_id
143 | )
144 |
145 |
146 | SELECT
147 | da.ip_address AS \"Asset IP Address\",
148 | favi.port AS \"Service Port\",
149 | dp.name AS \"Service Protocol\",
150 | dsvc.name AS \"Service Name\",
151 | an.names AS \"Asset Names\",
152 | favi.date AS \"Vulnerability Test Date\",
153 | dsc.started AS \"Last Scan Time\",
154 | favi.scan_id AS \"Scan ID\",
155 | ds.name AS \"Site Name\",
156 | ds.importance AS \"Site Importance\",
157 | vm.date_published AS \"Vulnerability Published Date\",
158 | ROUND((EXTRACT(epoch FROM age(now(), date_published)) / (60 * 60 * 24))::numeric, 0) AS \"Vulnerability Age\",
159 | cves.cves AS \"Vulnerability CVE IDs\",
160 | vm.title AS \"Vulnerability Title\",
161 | vm.cvss_score AS \"Vulnerability CVSS Score\",
162 | proofAsText(vm.description) AS \"Vulnerability Description\",
163 | vm.nexpose_id AS \"Vulnerability ID\",
164 | vm.severity AS \"Vulnerability Severity Level\",
165 | dvs.description AS \"Vulnerability Test Result Description\"
166 |
167 |
168 | FROM fact_asset_vulnerability_instance favi
169 | JOIN dim_asset da USING (asset_id)
170 | LEFT OUTER JOIN asset_addresses aa USING (asset_id)
171 | LEFT OUTER JOIN asset_names an USING (asset_id)
172 | JOIN asset_facts af USING (asset_id)
173 | JOIN dim_service dsvc USING (service_id)
174 | JOIN dim_protocol dp USING (protocol_id)
175 | JOIN dim_site_asset dsa USING (asset_id)
176 | JOIN dim_site ds USING (site_id)
177 | JOIN vulnerability_metadata vm USING (vulnerability_id)
178 | JOIN dim_vulnerability_status dvs USING (status_id)
179 | JOIN dim_operating_system dos USING (operating_system_id)
180 | LEFT OUTER JOIN dim_scan dsc USING (scan_id)
181 | LEFT OUTER JOIN vuln_cves_ids cves USING (vulnerability_id) "
182 |
183 | sqlWhere = ""
184 | orderBy = " ORDER BY da.ip_address;"
185 |
186 | query = sqlSelect + sqlWhere + orderBy
187 |
188 | puts "Initiating Report"
189 |
190 | report = Nexpose::AdhocReportConfig.new(nil, 'sql')
191 | report.add_filter('version', '1.2.1')
192 | report.add_filter('query', query)
193 | report.add_filter('scan', scan.id) # filter the report scope based on the scanID that was just run.
194 | report_output = report.generate(nsc)
195 | csv_output = CSV.parse(report_output.chomp, { :headers => :first_row })
196 | CSV.open("adhoc_SiteID_#{siteID}_Report.csv", 'w') do |csv_file|
197 | csv_file << csv_output.headers
198 | csv_output.each do |row|
199 | csv_file << row
200 | end
201 | end
202 |
203 | puts 'Report completed and saved'
204 |
205 | exit
206 |
--------------------------------------------------------------------------------
/adhocScanGen.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 07.25.2014
4 |
5 | ## Script Heavily borrows from Steve Tempest : https://community.rapid7.com/docs/DOC-2733 , https://community.rapid7.com/docs/DOC-2732
6 |
7 | ## Script performs the following tasks
8 | ## 1.) Read addresses from text file
9 | ## 2.) De-duplicate addresses
10 | ## 3.) Create new temporary site
11 | ## 4.) Add addresses to the created site
12 | ## 5.) Specify scan engine and template to use from nexpose.yaml
13 | ## 6.) Perform scan of the temporary site.
14 | ## 7.) Generate a report of vulnerabilities detected
15 | ## 8.) Delete temporary site.
16 |
17 | require 'yaml'
18 | require 'nexpose'
19 | require 'optparse'
20 | require 'highline/import'
21 | require 'csv'
22 |
23 |
24 | # Default Values
25 |
26 | config = YAML.load_file("conf/nexpose.yaml") # From file
27 |
28 | @host = config["hostname"]
29 | @userid = config["username"]
30 | @password = config["passwordkey"]
31 | @port = config["port"]
32 | @scanTemplate = config["adhocscantemplate"]
33 | @scanEngine = config["adhocscanengine"]
34 |
35 |
36 | @name = @desc = nil
37 |
38 | OptionParser.new do |opts|
39 | opts.banner = "Usage: #{File::basename($0)} [options]"
40 | opts.separator ''
41 | opts.separator 'Create a temporary site based upon an input file, one address per line, scans, reports findings, then deletes the site.'
42 | opts.separator ''
43 | opts.separator 'By default, the filename is used as the name of the adhoc site, if --name is not provided.'
44 | opts.separator ''
45 | opts.separator 'Options:'
46 | opts.on('-n', '--name [NAME]', 'Name to use for the adhoc site. Must not already exist.') { |name| @name = name }
47 | opts.on('-d', '--desc [DESCRIPTION]', 'Description to use for new asset group.') { |desc| @desc = desc }
48 | opts.on('-x', '--debug', 'Report duplicate IP addresses to STDERR.') { |debug| @debug = debug }
49 | opts.on_tail('--help', 'Print this help message.') { puts opts; exit }
50 | end.parse!
51 |
52 |
53 | # Any arguments after flags can be grabbed now."
54 | unless ARGV[0]
55 | $stderr.puts 'Input file is required.'
56 | exit(1)
57 | end
58 | file = ARGV[0]
59 | @name = File.basename(file, File.extname(file)) unless @name
60 |
61 | # This will fail if the file cannot be read.
62 | ips = File.read(file).split.uniq
63 |
64 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
65 | puts 'logging into Nexpose'
66 |
67 | begin
68 | nsc.login
69 | rescue ::Nexpose::APIError => err
70 | $stderr.puts("Connection failed: #{e.reason}")
71 | exit(1)
72 | end
73 |
74 | puts 'logged into Nexpose'
75 | at_exit { nsc.logout }
76 |
77 | # Create a map of all assets by IP to make them quicker to find.
78 | all_assets = nsc.assets.reduce({}) do |hash, dev|
79 | $stderr.puts("Duplicate asset: #{dev.address}") if @debug and hash.member? dev.address
80 | hash[dev.address] = dev
81 | hash
82 | end
83 |
84 | puts "Creating site #{@name}"
85 |
86 | site = Nexpose::Site.new(@name, @scanTemplate)
87 | site.description = @desc
88 | site.engine = @scanEngine
89 |
90 | ips.each do |ip|
91 | site.add_asset(ip)
92 | puts "Adding #{ip} to site #{@name}"
93 | end
94 |
95 | site.save(nsc)
96 |
97 | puts 'Create site successfully'
98 |
99 | puts "Starting scan of adhoc site #{@name} "
100 | scan = site.scan(nsc)
101 |
102 |
103 | begin
104 | sleep(15)
105 | status = nsc.scan_status(scan.id)
106 | puts "Current scan status: #{status.to_s}"
107 | end while status == Nexpose::Scan::Status::RUNNING
108 |
109 |
110 | sqlSelect = "
111 | WITH
112 | asset_ips AS (
113 | SELECT asset_id, ip_address, type
114 | FROM dim_asset_ip_address dips
115 | ),
116 | asset_addresses AS (
117 | SELECT da.asset_id,
118 | (SELECT array_to_string(array_agg(ip_address), ',') FROM asset_ips WHERE asset_id = da.asset_id AND type = 'IPv4') AS ipv4s,
119 | (SELECT array_to_string(array_agg(ip_address), ',') FROM asset_ips WHERE asset_id = da.asset_id AND type = 'IPv6') AS ipv6s,
120 | (SELECT array_to_string(array_agg(mac_address), ',') FROM dim_asset_mac_address WHERE asset_id = da.asset_id) AS macs
121 | FROM dim_asset da
122 | JOIN asset_ips USING (asset_id)
123 | ),
124 | asset_names AS (
125 | SELECT asset_id, array_to_string(array_agg(host_name), ',') AS names
126 | FROM dim_asset_host_name
127 | GROUP BY asset_id
128 | ),
129 | asset_facts AS (
130 | SELECT asset_id, riskscore, exploits, malware_kits
131 | FROM fact_asset
132 | ),
133 | vulnerability_metadata AS (
134 | SELECT *
135 | FROM dim_vulnerability dv
136 | ),
137 | vuln_cves_ids AS (
138 | SELECT vulnerability_id, array_to_string(array_agg(reference), ',') AS cves
139 | FROM dim_vulnerability_reference
140 | WHERE source = 'CVE'
141 | GROUP BY vulnerability_id
142 | )
143 |
144 |
145 | SELECT
146 | da.ip_address AS \"Asset IP Address\",
147 | favi.port AS \"Service Port\",
148 | dp.name AS \"Service Protocol\",
149 | dsvc.name AS \"Service Name\",
150 | an.names AS \"Asset Names\",
151 | favi.date AS \"Vulnerability Test Date\",
152 | dsc.started AS \"Last Scan Time\",
153 | favi.scan_id AS \"Scan ID\",
154 | ds.name AS \"Site Name\",
155 | ds.importance AS \"Site Importance\",
156 | vm.date_published AS \"Vulnerability Published Date\",
157 | ROUND((EXTRACT(epoch FROM age(now(), date_published)) / (60 * 60 * 24))::numeric, 0) AS \"Vulnerability Age\",
158 | cves.cves AS \"Vulnerability CVE IDs\",
159 | vm.title AS \"Vulnerability Title\",
160 | vm.cvss_score AS \"Vulnerability CVSS Score\",
161 | proofAsText(vm.description) AS \"Vulnerability Description\",
162 | vm.nexpose_id AS \"Vulnerability ID\",
163 | vm.severity AS \"Vulnerability Severity Level\",
164 | dvs.description AS \"Vulnerability Test Result Description\"
165 |
166 |
167 | FROM fact_asset_vulnerability_instance favi
168 | JOIN dim_asset da USING (asset_id)
169 | LEFT OUTER JOIN asset_addresses aa USING (asset_id)
170 | LEFT OUTER JOIN asset_names an USING (asset_id)
171 | JOIN asset_facts af USING (asset_id)
172 | JOIN dim_service dsvc USING (service_id)
173 | JOIN dim_protocol dp USING (protocol_id)
174 | JOIN dim_site_asset dsa USING (asset_id)
175 | JOIN dim_site ds USING (site_id)
176 | JOIN vulnerability_metadata vm USING (vulnerability_id)
177 | JOIN dim_vulnerability_status dvs USING (status_id)
178 | JOIN dim_operating_system dos USING (operating_system_id)
179 | LEFT OUTER JOIN dim_scan dsc USING (scan_id)
180 | LEFT OUTER JOIN vuln_cves_ids cves USING (vulnerability_id) "
181 |
182 | sqlWhere = "WHERE ds.name LIKE '#{@name}';"
183 |
184 | query = sqlSelect + sqlWhere
185 |
186 |
187 | report = Nexpose::AdhocReportConfig.new(nil, 'sql')
188 | report.add_filter('version', '1.2.1')
189 | report.add_filter('query', query)
190 | report.add_filter('group', 1)
191 | report_output = report.generate(nsc)
192 | csv_output = CSV.parse(report_output.chomp, { :headers => :first_row })
193 | CSV.open("adhoc#{@name}Report.csv", 'w') do |csv_file|
194 | csv_file << csv_output.headers
195 | csv_output.each do |row|
196 | csv_file << row
197 | end
198 | end
199 |
200 | puts 'Report completed and saved, deleting site'
201 | site.delete(nsc)
202 |
203 | puts 'Site deleted, logging out'
204 | exit
205 |
--------------------------------------------------------------------------------
/smartCleanup.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # 01.22.2015
4 |
5 | ## Script performs the following tasks
6 | ## 1.) Retrieve a list of paused scans from a console.
7 | ## 2.) Retrieve a list of active scans from a console.
8 | ## 3.) Sort paused scans from least number of discovered assets to most
9 | ## 4.) Iteratively resume scans in batches for scans that have paused without completing.
10 | ## 5.)
11 | ## 6.) TODO: Massive code cleanup + efficiency improvements.
12 |
13 | ## Major code changes as of 11.21.2014 - BWG
14 | # Rearranged output information to a more logical order.
15 | # Screen output now includes additional information about the scan in the screen output.
16 | # Worked on Bug reduction.
17 | # - Fixed connection error information.
18 | # - Fixed issue with sessions being invalidating and never being recreated.
19 | # - Fixed yaml relative path issues with running the script from outside of its directory.
20 | # - Some code clean up.
21 |
22 | ## Update - 02.23.2016 - BWG
23 | # Changed how paused scans are pulled basd on https://community.rapid7.com/thread/7904
24 | ## Update - 04.25.2017 - BWG
25 | # Code refactor
26 |
27 | #require 'pp'
28 | require 'yaml'
29 | require 'nexpose'
30 | require 'time'
31 | include Nexpose
32 |
33 | # Default Values from yaml file
34 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
35 | config = YAML.load_file(config_path)
36 |
37 | @host = config["hostname"]
38 | @userid = config["username"]
39 | @password = config["passwordkey"]
40 | @port = config["port"]
41 | @nexposeAjaxTimeout = config["nexposeajaxtimeout"]
42 |
43 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
44 |
45 | begin
46 | nsc.login
47 | rescue ::Nexpose::APIError => err
48 | $stderr.puts("Connection failed: #{err.reason}")
49 | exit(1)
50 | raise
51 | end
52 | at_exit { nsc.logout }
53 |
54 | =begin
55 | ## Initialize connection timeout values.
56 | module Nexpose
57 | class APIRequest
58 | include XMLUtils
59 | # Execute an API request (5th param used for gem version 5.3.0+)
60 | def self.execute(url, req, api_version='2.0', options = {}, trust_store = nil)
61 | options = {timeout: 6000000}
62 | obj = self.new(req.to_s, url, api_version, trust_store)
63 | obj.execute(options)
64 | return obj
65 | end
66 | end
67 |
68 | module AJAX
69 | def self.https(nsc, timeout = nil)
70 | http = Net::HTTP.new(nsc.host, nsc.port)
71 | http.use_ssl = true
72 | # changes for gem version 5.3.0+
73 | if nsc.trust_store.nil?
74 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
75 | else
76 | http.cert_store = nsc.trust_store
77 | end
78 | http.read_timeout = @nexposeAjaxTimeout
79 | http.open_timeout = @nexposeAjaxTimeout
80 | http.continue_timeout = @nexposeAjaxTimeout
81 | http
82 | end
83 | end
84 | end
85 | =end
86 |
87 | def scan_status(nsc, config)
88 | begin
89 | puts "\r\nRequesting scan status updates from #{@host}\r\n"
90 | ## Pull data for paused scans
91 | pausedScans = nsc.paused_scans
92 | ## Pull data for active scans
93 | activeScans = nsc.scan_activity()
94 | rescue Exception # should really list all the possible http exceptions
95 | puts "Connection issue detected - Retrying in #{config["cleanupwaittime"]} seconds)"
96 | sleep (config["cleanupwaittime"])
97 | begin # This is a less than ideal bandaid to make sure there is a valid session.
98 | nsc.login
99 | rescue ::Nexpose::APIError => err
100 | $stderr.puts("Connection failed: #{err.reason}")
101 | exit(1)
102 | raise
103 | end
104 | retry
105 | end
106 | scans = {:activeScans => activeScans, :pausedScans => pausedScans}
107 | return scans
108 | end
109 |
110 | def list_paused_scans(nsc, config, scans)
111 | ## Attempting some basic prioritization to complete lower asset count scans first.
112 | ## Perform a destructive sort of the pausedScans array based on the number of discovered assets.
113 | scans[:pausedScans].sort! { |a,b| a.assets.to_i <=> b.assets.to_i }
114 | # Collect Site info to provide additional information for screen output.
115 | siteInfo = nsc.sites
116 | ## List all of the paused scans to stdout.
117 | puts "\r\n-- Paused Scans Detected : #{scans[:pausedScans].count} --\r\n"
118 | scans[:pausedScans].each do |scanHistory|
119 | siteInfoID = scanHistory.site_id.to_i
120 | siteDetail = Site.load(nsc, siteInfoID)
121 | ## scanDetail = ScanSummary.select{ |scanInfo| (scanInfo['scan_id'].include? scanHistory.id.to_i)}
122 | begin
123 | puts "ScanID: #{scanHistory.id}, Assets: #{scanHistory.assets}, ScanTemplate: #{siteDetail.scan_template_id}, SiteID: #{siteInfoID} - #{siteDetail.name}, #{scanHistory.status}"
124 | rescue
125 | #raise
126 | end
127 | end
128 | puts "\r\n\r\n"
129 | end
130 |
131 | def list_active_scans(nsc, config, scans)
132 | hostCount = 0
133 | puts "-- Active Scans Detected : #{scans[:activeScans].count} | Queue Size: #{config["cleanupqueue"]}. --\r\n"
134 | ## Output a list of active scans in the scan queue.
135 | scans[:activeScans].each do |status|
136 | siteInfoID = status.site_id
137 | siteDetail = Site.load(nsc, siteInfoID)
138 | ## scanDetail = ScanSummary.select{ |scanInfo| (scanInfo['scan_id'].include? scanHistory['Scan ID'].to_i)}
139 | begin
140 | Scan
141 | puts "ScanID: #{status.scan_id}, Assets: #{status.nodes.live}, ScanTemplate: #{siteDetail.scan_template_id}, SiteID: #{status.site_id} - #{siteDetail.name}, Status:#{status.status}, EngineID:#{status.engine_id}, StartTime:#{status.start_time}"
142 | hostCount += status.nodes.live
143 | rescue
144 | #raise
145 | end
146 | end
147 | return hostCount
148 | end
149 |
150 | def resume_scans(nsc, config, scans)
151 | fillQueue = hostCount = 0
152 | ## Check to see if there are any slots open in the cleanup queue and that there are still scans to resume.
153 | if ((scans[:activeScans].count < config["cleanupqueue"]) and (scans[:pausedScans].count > 0))
154 | ## Determine how many slots are left in the cleanup queue to use.
155 | fillQueue = ((config["cleanupqueue"] - scans[:activeScans].count) -1)
156 | ## Loop through just enough paused scans to fill the open slots in the cleanup queue.
157 | scans[:pausedScans][0..fillQueue.to_i].each do |scanHistory|
158 | siteInfoID = scanHistory.site_id.to_i
159 | siteDetail = Site.load(nsc, siteInfoID)
160 | begin
161 | hostCount += scanHistory.assets.to_i # Count the number of hosts being scanned.
162 | puts "Resuming ScanID: #{scanHistory.id}, Assets: #{scanHistory.assets}, ScanTemplate: #{siteDetail.scan_template_id}, SiteID: #{siteInfoID} - #{siteDetail.name}, Status: #{scanHistory.status}"
163 | rescue
164 | raise
165 | end
166 |
167 | begin
168 | # Resume the provided scanid.
169 | nsc.resume_scan(scanHistory.id)
170 | rescue
171 | end
172 | end
173 | end
174 | return hostCount
175 | end
176 |
177 | ## Start loop that will continue until there are no longer any scans paused or actively running.
178 | begin
179 | scans = scan_status(nsc, config)
180 | list_paused_scans(nsc, config, scans)
181 | hostCount = 0 # Initialize hostCount.
182 | hostCount += list_active_scans(nsc, config, scans)
183 | hostCount += resume_scans(nsc, config, scans)
184 | puts "Total expected Active hosts being scanned: #{hostCount} - #{Time.now}\r\n\r\nNext Run time: #{Time.now + config["cleanupwaittime"]}\r\n\r\n"
185 | if ((scans[:pausedScans].count + scans[:activeScans].count) > 0)
186 | ## Wait between checks so that the scans have time to run.
187 | sleep(config["cleanupwaittime"])
188 | end
189 | ## If there are no more paused scans and the active scans have all completed without failing we can exit.
190 | end while ((scans[:pausedScans].count + scans[:activeScans].count) > 0)
191 |
192 | puts 'Logging out'
193 | exit
194 |
--------------------------------------------------------------------------------
/systemCheck.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Brian W. Gray
4 | # 06.08.2015
5 | #
6 | # Purpose: Perform the health checks and perform corrective actions when able.
7 | # 1.) Pull system info
8 | # 2.) Pull List of available Backups
9 | # 3.) Check Console Name
10 | # 4.) Check OS
11 | # 5.) Check Console Version
12 | # 6.) Check up time
13 | # 7.) Check console memory utilization
14 | # 8.) Check console CPU information
15 | # 9.) Check DB Version
16 | # 10.) Check Java Information
17 | # 11.) List available scan engines
18 | # 12.) List available scan pools
19 | # 13.) List scan pool member engines
20 | # 14.) Update status
21 | # 15.) Console performance monitor and limited issue resolution.
22 | #
23 | # Original idea and the start of much of the code sourced from https://github.com/dc401/NexposeRubyScripts/blob/master/prescan_healthcheck.rb
24 |
25 | require 'yaml'
26 | require 'rubygems'
27 | require 'nexpose'
28 | # require 'Time'
29 | require 'io/console'
30 | require 'pp'
31 |
32 | include Nexpose
33 |
34 | # Default Values from yaml file
35 | config_path = File.expand_path("../conf/nexpose.yaml", __FILE__)
36 | config = YAML.load_file(config_path)
37 |
38 | @host = config["hostname"]
39 | @userid = config["username"]
40 | @password = config["passwordkey"]
41 | @port = config["port"]
42 | @serviceTimeout = config["servicetimeout"]
43 |
44 | # Acceptable uptime value. (uptime at least this value)
45 | nscUpTimeThreshold = 600
46 |
47 |
48 | #One blink for yes, two blinks for no!
49 | class Beep
50 | #The use of "self" init a class method rather than instance method
51 | def self.pass
52 | print "\a"
53 | end
54 |
55 | def self.fail
56 | print "\a \a"
57 | end
58 | end
59 |
60 | def checkService()
61 | tryAgain = 0
62 |
63 | begin
64 | begin
65 | path = '/login.html' # Check to see if we may login or if we are re-directed to the maintenance login page.
66 |
67 | http = Net::HTTP.new(@host,@port)
68 | http.read_timeout = 1
69 | http.use_ssl = true
70 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
71 | response = nil
72 |
73 | http.start{|http|
74 | request = Net::HTTP::Get.new(path)
75 | response = http.request(request)
76 | }
77 |
78 | rescue Exception # should really list all the possible http exceptions
79 | puts "Attempt: #{tryAgain} Service Unavailable"
80 | sleep (30)
81 | retry if (tryAgain += 1) < @serviceTimeout
82 | end
83 |
84 | response.code
85 | if response.code == "200" # Check the status code anything other than 200 indicates the service is not ready.
86 | puts "Attempt: #{tryAgain} #{response.code} The Nexpose Service appears to be up and functional"
87 | tryAgain = @serviceTimeout
88 | else
89 | puts "Attempt: #{tryAgain} #{response.code} #{response.message} The Service is not yet fully initialized"
90 | tryAgain += 1
91 | sleep(30)
92 | end
93 | end while tryAgain < @serviceTimeout
94 |
95 | if (response.code != "200")
96 | puts "The service was never determined to be available. Action Timed Out"
97 | exit
98 | end
99 | end
100 |
101 |
102 |
103 | #
104 | # Connect and authenticate
105 | #
106 | nsc = Nexpose::Connection.new(@host, @userid, @password, @port)
107 |
108 | begin
109 | checkService()
110 | nsc.login
111 | rescue ::Nexpose::APIError => err
112 | $stderr.puts("Connection failed: #{err.reason}")
113 | exit(1)
114 | end
115 |
116 | at_exit { nsc.logout }
117 |
118 | #Pull system info
119 | sysInfo = nsc.system_information
120 |
121 | #Pull List of available Backups
122 | listBackups = nsc.list_backups
123 |
124 |
125 | # temp read all available info
126 | # pp sysInfo
127 |
128 |
129 | ## Gather console information ##
130 |
131 | #Check Console Name
132 | nscName = sysInfo["nsc-name"]
133 |
134 | #Check OS
135 | nscOs = sysInfo["os"]
136 |
137 | #Check Console Version
138 | nscConsoleVersion = sysInfo["nsc-version"]
139 | nscConsoleUpdateId = sysInfo["last-update-id"]
140 | nscLastUpdate = sysInfo["last-update-date"]
141 | nscVersion = sysInfo["nsc-version"]
142 |
143 | #Check up time
144 | nscUpTime = sysInfo["up-time"]
145 |
146 | #Check memory utilization
147 | nscFreeMem = sysInfo["ram-free"]
148 | nscTotalMem = sysInfo["ram-total"]
149 |
150 | #Check CPU information
151 | nscCpuCount = sysInfo["cpu-count"]
152 | nscCpuSpeed = sysInfo["cpu-speed"]
153 |
154 | #Check DB Version
155 | nscDbProduct = sysInfo["db-product"]
156 | nscDbVersion = sysInfo["db-version"]
157 |
158 | #Check Java Information
159 | nscJavaName = sysInfo["java-name"]
160 | nscJavaHeapMax = sysInfo["java-heap-max"]
161 | nscJavaHeapCommitted = sysInfo["java-heap-committed"]
162 | nscJavaHeapFree = sysInfo["java-heap-free"]
163 | nscJavaHeapUsed = sysInfo["java-heap-used"]
164 | nscJreVersion = sysInfo["jre-version"]
165 | nscJavaDaemonThreadCount = sysInfo["java-daemon-thread-count"]
166 | nscJavaTotalThreadCount = sysInfo["java-total-thread-count"]
167 | nscJavaThreadPeakCount = sysInfo["java-thread-peak-count"]
168 | nscJavaStartedThreadCount = sysInfo["java-started-thread-count"]
169 |
170 |
171 |
172 | ## Output Collected Data ##
173 | puts #Blank line
174 | puts "---- Console Information ----"
175 | puts #Blank line
176 |
177 | #Check Console Name
178 | puts "Console Name: #{nscName}"
179 |
180 | #Check OS
181 | puts "Console Operating System: #{nscOs}"
182 |
183 | #Check Console Version
184 | puts "Console Version: #{nscConsoleVersion}"
185 | puts "Console Update ID: #{nscConsoleUpdateId}"
186 | puts "Console Last Update: #{nscLastUpdate}"
187 | puts "Console Version: #{nscVersion}"
188 |
189 | #Check up time
190 | puts "Console Uptime: #{nscUpTime}"
191 |
192 | #Check memory utilization
193 | puts "Console Free Memory: #{nscFreeMem}"
194 | puts "Console Total Memory: #{nscTotalMem}"
195 |
196 | #Check CPU information
197 | puts "Number of Console CPUs: #{nscCpuCount}"
198 | puts "Speed of Console Processors: #{nscCpuSpeed}"
199 |
200 | #Check DB Version
201 | puts "Console Database Type: #{nscDbProduct}"
202 | puts "Console Database Version: #{nscDbVersion}"
203 |
204 | #Check Java Information
205 | puts "Console Java Information"
206 | puts "Name: #{nscJavaName}"
207 | puts "Heap Max: #{nscJavaHeapMax}"
208 | puts "Heap Committed: #{nscJavaHeapCommitted}"
209 | puts "Heap Free: #{nscJavaHeapFree}"
210 | puts "Heap Used: #{nscJavaHeapUsed}"
211 | puts "JRE Version: #{nscJreVersion}"
212 | puts "Daemon Thread Count: #{nscJavaDaemonThreadCount}"
213 | puts "Total Thread Count: #{nscJavaTotalThreadCount}"
214 | puts "Peak Thread Count: #{nscJavaThreadPeakCount}"
215 | puts "Started Thread Count: #{nscJavaStartedThreadCount}"
216 |
217 |
218 | puts #Blank line
219 | puts "---- List all configured console users ----"
220 | puts #Blank line
221 |
222 | nsc.list_users.each do |listUsers|
223 | puts "User Name: #{listUsers.name}"
224 | puts "Full Name: #{listUsers.full_name}"
225 | puts "Email Address: #{listUsers.email}"
226 | puts "Admin User? #{listUsers.is_admin}"
227 | puts "Disabled? #{listUsers.is_disabled}"
228 | puts "Locked? #{listUsers.is_locked}"
229 | puts "Auth Source: #{listUsers.auth_source}"
230 | # puts "#{listUsers.}"
231 | puts # Blank line
232 | end
233 |
234 |
235 | #Pull the list of available backups
236 | if listBackups.any?
237 |
238 | puts #Blank line
239 | puts "---- List of available Backups on #{@host} ----"
240 | puts #Blank line
241 |
242 | listBackups.each do |backupList|
243 | puts "Name: #{backupList.name} Description: #{backupList.description} size: #{backupList.size} Date : #{backupList.date}"
244 | end
245 |
246 | end
247 |
248 |
249 | puts #Blank line
250 | puts "---- List of Available Scan Engines ----"
251 | puts #Blank line
252 |
253 | # Pull scan engine status / ensure engine status is current.
254 | ## versionEngines = nsc.console_command('version engines')
255 |
256 | ## I don't use a rapid7 hosted scan engine so I've excluded it due to timeouts etc. attempting to refresh it.
257 |
258 | # Pull Engine version information to for reference below
259 | engineVer = nsc.engine_versions
260 |
261 | engine_ids = Array.new
262 | nsc.engines.each do |engine|
263 | unless engine.name.include?("Rapid7 Hosted Scan Engine")
264 | engine_ids << engine.id
265 | end
266 | end
267 |
268 | # Disabled until I track down the new api request location
269 | # Nexpose::AJAX.post(nsc, "/ajax/engine-refreshAll.txml", "engineIds=#{engine_ids * ','}", Nexpose::AJAX::CONTENT_TYPE::FORM)
270 |
271 | engine_list = {}
272 | nsc.engines.each do |engine|
273 | engine_list[engine.id] = "#{engine.name} (#{engine.status})"
274 | puts "Engine Name: #{engine.name}"
275 | puts " Engine ID: #{engine.id}"
276 | puts " Scope: #{engine.scope}"
277 | puts " Address: #{engine.address}"
278 | puts " Port: #{engine.port}"
279 | puts " Status: #{engine.status}"
280 | puts #Blank line
281 |
282 | # puts versionEngines
283 | engineVer.each do |enVerInfo|
284 | if enVerInfo["Name"].include?(engine.name)
285 | puts " DN: #{enVerInfo["DN"]}"
286 | puts " Version: #{enVerInfo["Version"]}"
287 | puts " Address (FQDN): #{enVerInfo["Address (FQDN)"]}"
288 | puts " Platform: #{enVerInfo["Platform"]}"
289 | puts " Serial No: #{enVerInfo["Serial No"]}"
290 | puts " Product Name: #{enVerInfo["Product Name"]}"
291 | puts " Last Content Update ID: #{enVerInfo["Last Content Update ID"]}"
292 | puts " Last Auto Content Update ID: #{enVerInfo["Last Auto Content Update ID"]}"
293 | puts " Last Product Update ID: #{enVerInfo["Last Product Update ID"]}"
294 | puts " Software Revision: #{enVerInfo["Software Revision"]}"
295 | puts " Product ID: #{enVerInfo["Product ID"]}"
296 | puts " Version ID: #{enVerInfo["Version ID"]}"
297 | puts " VM Version: #{enVerInfo["VM Version"]}"
298 | puts
299 | end
300 | end
301 | end
302 |
303 |
304 | engineVer = nsc.engine_versions
305 |
306 | puts #Blank line
307 | puts "---- List of Available Engine Pools ----"
308 | puts #Blank line
309 |
310 |
311 |
312 | nsc.engine_pools.each do |enginePool|
313 | puts "Pool Name: #{enginePool.name}"
314 | puts " Pool ID: #{enginePool.id}"
315 | puts " Pool Scope: #{enginePool.scope}"
316 | puts #Blank line
317 | puts " --- Engines ---"
318 |
319 | if !enginePool.name.include?('Default Engine Pool')
320 | poolConf = Nexpose::EnginePool::load(nsc, enginePool.name, 'silo')
321 |
322 | poolConf.engines.each do |poolEng|
323 |
324 | puts " Engine ID: #{poolEng.id}"
325 | puts " Engine Name: #{poolEng.name}"
326 | puts " Engine Address: #{poolEng.address}"
327 | puts " Engine Port: #{poolEng.port}"
328 | puts " Engine Scope: #{poolEng.scope}"
329 | puts " Engine Status: #{poolEng.status}"
330 | puts
331 |
332 | end
333 | end
334 | end
335 |
336 |
337 |
338 | puts #Blank line
339 | puts "---- Diagnostics ----"
340 | puts #Blank line
341 |
342 | puts "== Update status =="
343 | puts # blank line
344 |
345 | begin
346 | #Check for the last update
347 | CTime = Time.now.to_i
348 | #Check to see if the update was within last 7 days
349 | if (CTime + 604800) < nscLastUpdate.to_i
350 | puts "Last Update: OK"
351 | elsif (CTime + 604800) >= nscLastUpdate.to_i
352 | puts "Last Update: Not Updated within 7 days"
353 | puts Time.at(CTime)
354 | Beep.fail
355 | puts "Starting update. Please wait."
356 | nscUpdate = nsc.console_command("updatenow")
357 | puts nscUpdate
358 | puts "Pushing update to scan engines. Please wait."
359 | engineUpdate = nsc.console_command("update engines") # throws error after api changes made to the application
360 | end
361 |
362 | rescue StandardError => err
363 | print err
364 |
365 | end
366 |
367 | puts # blank line
368 | puts "== Console Performance =="
369 | puts # blank line
370 |
371 | begin
372 |
373 | #Check to see if up time is greater than 5 minutes
374 | if nscUpTime.to_i >= nscUpTimeThreshold
375 | puts "Uptime: #{nscUpTime} - OK "
376 | elsif nscUpTime.to_i <= nscUpTimeThreshold
377 | puts "Uptime: #{nscUpTime} - Recent service restart. Potential Issue."
378 | Beep.fail
379 | end
380 |
381 | rescue StandardError => err
382 | print err
383 |
384 | end
385 |
386 |
387 | begin
388 | nscMemUse = (nscFreeMem.to_i / nscTotalMem.to_i)
389 | #Check to see if we are at 75% or greater usage
390 | if nscMemUse < (0.75)
391 | puts "Memory Usage: OK #{(nscMemUse)}%"
392 | elsif nscMemUse >= (0.75)
393 | puts "Memory Usage: Above 75%"
394 | puts "Utilization: #{(nscMemUse.to_i * 10)}%"
395 | Beep.fail
396 | puts "Attempting to free up Java resources. Please wait."
397 | GarbageCollect = nsc.console_command("garbagecollect")
398 | puts GarbageCollect
399 | end
400 |
401 | rescue StandardError => err
402 | print err
403 |
404 | end
405 |
406 |
407 |
408 | puts #Blank line
409 | puts "---- End Diagnostics ----"
410 | puts #Blank line
411 |
412 | exit
413 |
--------------------------------------------------------------------------------
/vulnReporter.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Brian W. Gray
3 | # Initial code generated: 10.11.2016
4 |
5 | # Purpose of this script.
6 | # Query for notifiable vulnerabilities and initiate configured actions.
7 |
8 | # This script queries all scans that occured in a specified time range from a console and then takes action
9 | # on systems that have been found to be vulnerable.
10 |
11 | # Dependencies required to be installed:
12 | # sudo gem install nexpose
13 | # sudo gem install yaml
14 | # for an example ./conf/nexpose.yaml see https://github.com/BrianWGray/nexpose/blob/master/conf/nexpose.yaml
15 |
16 | require 'yaml' # used to parse configuration files
17 | require 'nexpose' # makes the world turn
18 | require 'time' # supports timestamping and time manipulation for queries
19 | require 'active_support/all' # used for time rounding methods
20 | require 'htmlentities' # used for html entity filters
21 | require 'json' # used for json support
22 | require 'csv' # enables csv parsing of reports to hashes
23 | require 'net/smtp' # used to support proof of concept email notices
24 | require 'pp' # lazy trouble shooting
25 |
26 | # Default Values from yaml file
27 | configPath = File.expand_path("../conf/pgh-nvs-01.yaml", __FILE__)
28 | config = YAML.load_file(configPath)
29 | vulNotifyPath = File.expand_path("../conf/vulnotify.yaml", __FILE__)
30 | vulNotify = YAML.load_file(vulNotifyPath)
31 |
32 | # debug sets verbose output to stdout.
33 | debug = config["vrDebug"]
34 |
35 | host = config["hostname"]
36 | userid = config["username"]
37 | password = config["passwordkey"]
38 | port = config["port"]
39 | @nexposeAjaxTimeout = config["nexposeajaxtimeout"]
40 |
41 | ageInterval = config["ageInterval"] # => Integer In Hours defines the number of hours to subtract from the specified query time to create a query time window.
42 |
43 | # If you need to add auth: https://www.tutorialspoint.com/ruby/ruby_sending_email.htm
44 | mailFrom = config["mailFrom"]
45 | mailServer = config["mailServer"]
46 | mailPort = config["mailPort"]
47 | mailDomain = config["mailDomain"]
48 |
49 | # Default Email address for notifications.
50 | defaultEmail = config["defaultEmail"]
51 | mailTo = defaultEmail
52 |
53 | # Number of threads alotted for consecutive nexpose_id's queried
54 | threadLimit = config["vulnReporterThreads"]
55 |
56 | ## Initialize connection timeout values.
57 | ## Timeout example provided by JGreen in https://community.rapid7.com/thread/5075
58 | # Here we extend the default web request timeouts for the script
59 | module Nexpose
60 | class APIRequest
61 | include XMLUtils
62 | # Execute an API request
63 | def self.execute(url, req, api_version='1.2', options = {})
64 | options = {timeout: @nexposeAjaxTimeout}
65 | obj = self.new(req.to_s, url, api_version)
66 | obj.execute(options)
67 | return obj
68 | end
69 | end
70 |
71 | module AJAX
72 | def self._https(nsc)
73 | http = Net::HTTP.new(nsc.host, nsc.port)
74 | http.use_ssl = true
75 | # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
76 | http.read_timeout = @nexposeAjaxTimeout
77 | http
78 | end
79 | end
80 | end
81 |
82 | # Support for changing blank csv fields to nil during csv parsing
83 | CSV::Converters[:blank_to_nil] = lambda do |field|
84 | field && field.empty? ? nil : field
85 | end
86 |
87 | # Display generic debug info to stdout
88 | def debug_print(returnedData, debug="false")
89 | if debug == "true" then
90 | puts "\r\n[DEBUG]\r\n"
91 | pp(returnedData)
92 | end
93 | end
94 |
95 | def checkService(config)
96 | tryAgain = 0
97 |
98 | host = config["hostname"]
99 | userid = config["username"]
100 | password = config["passwordkey"]
101 | port = config["port"]
102 | @nexposeAjaxTimeout = config["nexposeajaxtimeout"]
103 | @serviceTimeout = config["servicetimeout"]
104 |
105 | begin
106 | begin
107 | path = '/login.html' # Check to see if we may login or if we are re-directed to the maintenance login page.
108 |
109 | http = Net::HTTP.new(host,port)
110 | http.read_timeout = 1
111 | http.use_ssl = true
112 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE
113 | response = nil
114 |
115 | http.start{|http|
116 | request = Net::HTTP::Get.new(path)
117 | response = http.request(request)
118 | }
119 |
120 | rescue Exception # should really list all the possible http exceptions
121 | puts "Attempt: #{tryAgain} Service Unavailable"
122 | sleep (30)
123 | retry if (tryAgain += 1) < @serviceTimeout
124 | end
125 |
126 | # response.code
127 | if response.code == "200" # Check the status code anything other than 200 indicates the service is not ready.
128 | puts "Attempt: #{tryAgain} #{response.code} The Nexpose Service appears to be up and functional"
129 | tryAgain = @serviceTimeout
130 | else
131 | puts "Attempt: #{tryAgain} #{response.code} #{response.message} The Service is not yet fully initialized"
132 | tryAgain += 1
133 | sleep(30)
134 | end
135 | end while tryAgain < @serviceTimeout
136 |
137 | if (response.code != "200")
138 | puts "The service was never determined to be available. Action Timed Out"
139 | exit
140 | end
141 | end
142 |
143 | # Take time in various formats and normalize it to a time object
144 | def normalize_time(time, debug)
145 | begin
146 | time = time if(time.is_a?(Time))
147 | time = Time.parse("#{time.to_s}") if(!time.is_a?(Time))
148 | rescue
149 | time = Time.now # Upon failure use the current time value
150 | end
151 |
152 | return time
153 | end
154 |
155 | # Record the current time when the query client is run
156 | def query_time(lastRunFile, time=nil, debug)
157 |
158 | # TODO:
159 | # write the current run time to the lastRunFile location
160 | currentRunTime = normalize_time(time, debug)
161 |
162 | lastRunTime = normalize_time(time, debug)
163 |
164 | # Running the query hourly we start the query at the beginning of the hour... (subject to change)
165 | return lastRunTime.beginning_of_hour()
166 | end
167 |
168 | # Determine the last time the reporting client ran a query
169 | def last_query_time(lastRunFile, ageInterval, time=nil, debug)
170 |
171 | # TODO:
172 | # Was the run successful?
173 | loggedRunTime = nil
174 | # Read last run file and pull the last date entry in the file to determine how far back to query for new vulnerabilities
175 |
176 |
177 | # If there is not previous logged run time assume the default time scope
178 | loggedRunTime ? lastRunTime = normalize_time(loggedRunTime, debug) : lastRunTime = (normalize_time(time, debug) - ageInterval.hours).to_datetime
179 |
180 | return lastRunTime
181 | end
182 |
183 |
184 | # Query a defined nexpose console for all vulnerabilities matching the listed vulnId value
185 | def query_vulns(nexposeId, nsc, debug)
186 |
187 | @sqlSelect = "SELECT * FROM dim_vulnerability "
188 | @sqlWhere = "WHERE nexpose_id ILIKE '#{nexposeId}';"
189 |
190 | @query = @sqlSelect + @sqlWhere
191 | debug_print(@query, debug)
192 |
193 | # Query all nexpose_id's matching the provided vulnerabilities within the vulnotify.yaml configuration file.
194 | @pullVulns = Nexpose::AdhocReportConfig.new(nil, 'sql')
195 | @pullVulns.add_filter('version', '2.0.2')
196 | @pullVulns.add_filter('query', @query)
197 |
198 | # Generate report to be parsed
199 | @pulledVulns = @pullVulns.generate(nsc,18000)
200 |
201 | # http://stackoverflow.com/questions/14199784/convert-csv-file-into-array-of-hashes
202 | # http://technicalpickles.com/posts/parsing-csv-with-ruby/
203 | # Convert the CSV report information provided by the API back into a hashed format. *Should submit an Idea to Rapid7 for JSON report output type from reports?
204 | @returnedVulns = CSV.parse(@pulledVulns, { :headers => true, :header_converters => :symbol, :converters => [:all, :blank_to_nil] }).map(&:to_hash) if @pulledVulns
205 |
206 | return @returnedVulns
207 | end
208 |
209 | # Using the information from "query_vulns()" query all systems that have the Vulnerability ID associated to it within the specified time range of when it was detected.
210 | def vuln_assets(vulnId, queryTime, lastQueryTime, nsc, debug)
211 |
212 | # Create a base query containing information about assets associated with the provided Vulnerability ID.
213 | @sqlSelect = "
214 | WITH
215 |
216 | asset_names AS (
217 | SELECT asset_id, array_to_string(array_agg(host_name), ',') AS names
218 | FROM dim_asset_host_name
219 | GROUP BY asset_id
220 | )
221 |
222 | SELECT DISTINCT ON (asset_id,port)
223 |
224 | asset_id,
225 | ip_address,
226 | port,
227 | dp.name,
228 | mac_address,
229 | host_name,
230 | an.names,
231 | favi.date,
232 | dvs.description,
233 | proofAsText(favi.proof) as proof,
234 | nexpose_id
235 |
236 | FROM fact_asset_vulnerability_instance favi
237 | JOIN dim_asset da USING (asset_id)
238 | JOIN dim_service dsvc USING (service_id)
239 | JOIN dim_protocol dp USING (protocol_id)
240 | JOIN dim_vulnerability_status dvs USING (status_id)
241 | JOIN dim_vulnerability USING (vulnerability_id)
242 | LEFT OUTER JOIN asset_names an USING (asset_id)
243 | LEFT OUTER JOIN dim_scan dsc USING (scan_id)
244 | "
245 | # Provide the Vulnerability ID and time window for the query
246 | @sqlWhere = "WHERE (favi.vulnerability_id = '#{vulnId}') AND (favi.date BETWEEN ('#{lastQueryTime}'::timestamp) and ('#{queryTime}'::timestamp))"
247 | @sqlOrderBy = " ORDER BY asset_id, port;"
248 | @query = @sqlSelect + @sqlWhere + @sqlOrderBy
249 |
250 | @pullVulns = Nexpose::AdhocReportConfig.new(nil, 'sql')
251 | @pullVulns.add_filter('version', '2.0.2')
252 | @pullVulns.add_filter('query', @query)
253 |
254 | # Generate report to be parsed
255 | @pulledVulns = @pullVulns.generate(nsc,18000)
256 |
257 | # http://stackoverflow.com/questions/14199784/convert-csv-file-into-array-of-hashes
258 | # http://technicalpickles.com/posts/parsing-csv-with-ruby/
259 | # Convert the CSV report information provided by the API back into a hashed format. *Should submit an Idea to Rapid7 for JSON report output type from reports?
260 | @returnedVulns = CSV.parse(@pulledVulns, { :headers => true, :header_converters => :symbol, :converters => [:all, :blank_to_nil] }).map(&:to_hash) if @pulledVulns
261 |
262 | return @returnedVulns
263 |
264 | end
265 |
266 | def vuln_solutions(vulnId, nexposeId, assetId, queryTime, nsc, debug)
267 |
268 | # Create a base query containing information about assets associated with the provided Vulnerability ID.
269 | @sqlSelect = "
270 | SELECT DISTINCT
271 |
272 | ds.summary,
273 | ds.url,
274 | ds.solution_type,
275 | ds.fix,
276 | ds.estimate,
277 | ds.additional_data,
278 | ds.applies_to,
279 | ds.nexpose_id
280 |
281 | FROM fact_asset_vulnerability_instance favi
282 | JOIN dim_asset da USING (asset_id)
283 | JOIN dim_asset_vulnerability_solution davs USING (asset_id, vulnerability_id)
284 | JOIN dim_solution_highest_supercedence dshs USING (solution_id)
285 | JOIN dim_vulnerability dv USING (vulnerability_id)
286 | JOIN dim_solution ds ON ds.solution_id = dshs.superceding_solution_id
287 |
288 | "
289 | # Provide the Vulnerability ID and time window for the query
290 | @sqlWhere = "WHERE (asset_id = #{assetId.to_i} AND vulnerability_id = #{vulnId.to_i})"
291 | @sqlOrderBy = ";"
292 | @query = @sqlSelect + @sqlWhere + @sqlOrderBy
293 |
294 | @pullSols = Nexpose::AdhocReportConfig.new(nil, 'sql')
295 | @pullSols.add_filter('version', '2.0.2')
296 | @pullSols.add_filter('query', @query)
297 |
298 | # Generate report to be parsed
299 | @pulledSols = @pullSols.generate(nsc,18000)
300 |
301 | # http://stackoverflow.com/questions/14199784/convert-csv-file-into-array-of-hashes
302 | # http://technicalpickles.com/posts/parsing-csv-with-ruby/
303 | # Convert the CSV report information provided by the API back into a hashed format. *Should submit an Idea to Rapid7 for JSON report output type from reports?
304 | @returnedSols = CSV.parse(@pulledSols, { :headers => true, :header_converters => :symbol, :converters => [:all, :blank_to_nil] }).map(&:to_hash) if @pulledSols
305 |
306 | return @returnedSols
307 |
308 | end
309 |
310 |
311 | # For this proof of concept this is one example action that can be taken for an asset that is found to be vulnerable.
312 | def send_notification(mailFrom, mailTo, mailDomain, mailServer, noticeContent, debug)
313 |
314 | # Example Email Notification Template. Modify as needed. Sending HTML email by default because I like it.
315 | # Example Email Notification Template. Modify as needed. Sending HTML email by default because I like it.
316 | message = <#{noticeContent[:date]} - ISO IR Resolve - #{noticeContent[:vulnTitle]} (#{noticeContent[:ipAddress]})
324 | Link to IDS or other system showing the vulnerability or compromise
325 | https://#{noticeContent[:console]}:#{noticeContent[:conPort]}/vulnerability/vuln-summary.jsp?vulnid=#{noticeContent[:vulnId]}&devid=#{noticeContent[:devId]}
326 |
327 |
328 |
Issue Summary:
329 | A recent scan of #{noticeContent[:ipAddress]} indicates a vulnerability on the system.
330 | The following issue was detected: #{noticeContent[:vulnTitle]}
331 |