Scripting Scenario: Ad hoc scanning and reporting

Document created by S Tempest Employee on Apr 4, 2014Last modified by S Tempest Employee on Apr 4, 2014
Version 2Show Document
  • View in full screen mode

(TL;DR: Steve, Bob's smarter brother, is already done. Click here to see the finished script.)

 

Bob is a security engineer. He's "wicked smaht" and has developed a lot of his own toolsets that he utilizes in his own framework. He has Nexpose, but finds manually exporting data from Nexpose and then manually consuming it from his framework to be tedious and time consuming. Instead he wants to create tools do the work for him. In the example below, we will see how Bob created a script to automate data collection and reporting. Since Bob is a loyal Ruby ninja he has already installed and is using version 0.7.0 of the Nexpose Gem (https://github.com/rapid7/nexpose-client).

 

Bob likes to have a lot of visual feedback in his script to ensure it's working, so he uses a lot of "put" commands to log progress to his console.

 

In order to use the Nexpose Gem's functionality, Bob must load it at the beginning of the script along with the Ruby CSV module for reporting:

require 'nexpose'
require 'csv'







 

 

The first thing Bob's script needs to do is log into Nexpose. In order to do this he needs to already have created certain items in Nexpose. These are the items and the values he uses:

 

  •      A user ID = Bob
  •     Password = IamSuperCool
  •     The Nexpose hostname or IP address = localhost

 

Once he has these values defined Bob uses the built-in login method of the gem. To call this method Bob passes the variables for the Nexpose hostname, his username, and password to the Connection object:

 

@host = 'localhost'
@userid = 'bob'
@password = 'IamSuperCool'

nsc = Nexpose::Connection.new(@host, @userid, @password)
puts 'Logging into Nexpose'
nsc.login
puts 'Logged into Nexpose'







 

 

Now that the script is logged in Bob wants it to do a one-off scan of some devices. Since Nexpose requires sites in order to scan, the script creates a site for temporary use. In order to do this Bob must provide the script with the following values:

 

  •      The site name = BobsTemporarySite
  •      The hostnames or IP addresses of the devices = 192.168.1.2, www.foo.com, 10.0.0.1-10.0.0.25

 

In this particular case Bob knows that the gem defaults to the "Full audit without Web Spider" scan template. Because he wants to use this, he doesn't need to specify a different scan template when creating the site. (He also knows that if he wanted more information on how to use a different scan template he could check out the Nexpose Gem wiki.)

 

Now that he's collected all of the necessary information to create a site, he can input it to the script.

 

@name = 'BobsTemporarySite'
@device_hostname = 'www.foo.com'
@device_ip_address = '192.168.1.2'
@device_ip_range_start = '10.0.0.1'
@device_ip_range_end = '10.0.0.25'

puts "Creating site #{@name}"
site = Nexpose::Site.new(@name)
site.add_host(@device_hostname)
site.add_ip_range(@device_ip_range_start, @device_ip_range_end)
site.add_ip(@device_ip_address)
site.save(nsc)
puts 'Created site successfully'







 

 

If Bob were to look at Nexpose at this time he would notice in the web interface that a site is created with the name BobsTemporarySite with the devices he specified as scan targets. Ultimately, though, his goal is to automate it so he doesn't have to check it. Now the script needs to kick off the scan so we can get the vulnerability results for Bob's framework. Since Bob still has the site object referenced from when it was created he thinks "This is so easy!"

 

puts 'Starting scan'
scan = site.scan(nsc)







 

 

Now that the scan is running, the script needs to know when the scan has completed. Bob needs to make sure the script asks Nexpose for status updates on a regular basis, as Nexpose won't provide them without the request. To do so Bob needs:

 

  •      The scan ID

 

Once again Bob has such great luck that the scan object already knows the scan ID from when it started the scan. He makes use of this to poll Nexpose for status updates on the scan every 15 seconds until the status has changed to something other than "Running".

 

begin
  sleep(15)
  status = nsc.scan_status(scan.id)
  puts "Current scan status: #{status.to_s}"
end while status == Nexpose::Scan::Status::RUNNING







 

 

Since Bob likes to save effort and the community is awesome, he goes to Rapid7's community site, Security Street, and searches for "sql". As a result he finds a fascinating discussion between members that contains a SQL Query Export example that seems to work for his needs.


query = "
  SELECT DISTINCT ip_address, mac_address, host_name, title, date_published, severity, riskscore, summary, fix

  FROM fact_asset_scan_vulnerability_finding
   JOIN dim_asset USING (asset_id)
   JOIN dim_vulnerability USING (vulnerability_id)
   JOIN dim_vulnerability_solution USING (vulnerability_id)
   JOIN dim_solution_highest_supercedence USING (solution_id)
   JOIN dim_solution ds ON superceding_solution_id = ds.solution_id"


So now Bob wants to make sure his script runs an ad hoc report once the scan is no longer running. The script will parse the completed report and write the results to a CSV file named nexpose-export.csv in the same directory as the script. If the scan status is not "Running" or "Finished," the script will output an error message and exit with a status code of 1 so that Bob will know something went wrong.

 

 

if status == Nexpose::Scan::Status::FINISHED
  puts 'Scan complete, generating report'
  report = Nexpose::AdhocReportConfig.new(nil, 'sql')
  report.add_filter('version', '1.1.0')
  report.add_filter('query', query)
  report.add_filter('site', site.id)
  report_output = report.generate(nsc)
  csv_output = CSV.parse(report_output.chomp, { :headers => :first_row })
  CSV.open('nexpose-export.csv', 'w') do |csv_file|
    csv_file << csv_output.headers
    csv_output.each do |row|
      csv_file << row
    end
  end
else
  puts "Help me, Bob Kenobi, you're my only hope! It failed!"
  site.delete(nsc)
  nsc.logout
  exit 1
end

 

 

Since Bob decided that the site would only be temporary, he tells the script to delete the site when the report work is all done. The script still has the site object referenced from earlier so it's trivial to implement:

 

puts 'Report completed and saved, deleting site'
site.delete(nsc)







 

 

As Bob knows, it's good hygiene to close any open sessions when you're done with the API. So as a final step the script will log out of Nexpose and exit:

 

puts 'Site deleted, logging out'
nsc.logout
exit







 

 

Bob is SO excited by his new script that he shares with his brother Steve, who rips it off completely and saves it in a text file as his own work. Steve posted it to Security Street here under his name and took all the credit.

 

Stay tuned for the continued story of Bob and his automation adventures.

 

Thanks to Gavin Schneider and ospannero

1 person found this helpful

Attachments

    Outcomes