# frozen_string_literal: true
require 'nokogiri'
require 'wicked_pdf'
require 'fileutils'
require 'uri'
module PWN
module Plugins
# This plugin is used for interacting w/ IBM Appscan Enterprise using
# the 'rest' browser type of PWN::Plugins::TransparentBrowser.
# The IBM Appscan Spec in which this PWN module is based is located here:
# http://www-01.ibm.com/support/knowledgecenter/SSW2NF_9.0.0/com.ibm.ase.help.doc/topics/c_web_services.html?lang=en
module IBMAppscan
@@logger = PWN::Plugins::PWNLogger.create
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.login(
# appscan_ip: 'required host/ip of IBM Appscan Server',
# username: 'required username',
# password: 'optional password (will prompt if nil)'
# )
public_class_method def self.login(opts = {})
appscan_ip = opts[:appscan_ip]
username = opts[:username].to_s.scrub
base_appscan_api_uri = "https://#{appscan_ip}/ase/services".to_s.scrub
password = if opts[:password].nil?
PWN::Plugins::AuthenticationHelper.mask_password
else
opts[:password].to_s.scrub
end
@@logger.info("Logging into IBM Appscan Enterprise Server: #{appscan_ip}")
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
rest_client = browser_obj[:browser]::Request
response = rest_client.execute(
method: :post,
url: "#{base_appscan_api_uri}/login",
payload: "userid=#{username}&password=#{password}",
verify_ssl: false
)
# Return array containing the Appscan Server host/ip & post-authenticated Appscan REST cookie
appscan_ip = URI.parse(response.args[:url]).host
appscan_cookie = "asc_session_id=#{response.cookies['asc_session_id']}; ASP.NET_SessionId=#{response.cookies['ASP.NET_SessionId']}"
appscan_obj = {}
appscan_obj[:appscan_ip] = appscan_ip
appscan_obj[:cookie] = appscan_cookie
appscan_obj[:raw_response] = response
appscan_obj[:xml_response] = Nokogiri::XML(response)
appscan_obj[:build] = appscan_obj[:xml_response].xpath(
'/xmlns:version/xmlns:build'
).text
appscan_obj[:dbversion] = appscan_obj[:xml_response].xpath(
'/xmlns:version/xmlns:dbversion'
).text
appscan_obj[:rules_version] = appscan_obj[:xml_response].xpath(
'/xmlns:version/xmlns:rules-version'
).text
appscan_obj[:username] = appscan_obj[:xml_response].xpath(
'/xmlns:version/xmlns:user-name'
).text
appscan_obj[:password] = Base64.strict_encode64(password)
appscan_obj[:logged_in] = true
appscan_obj
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# appscan_rest_call(
# appscan_obj: 'required appscan_obj returned from login method',
# http_method: 'optional HTTP method (defaults to GET)
# rest_call: 'required rest call to make per the schema',
# http_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST'
# )
private_class_method def self.appscan_rest_call(opts = {})
appscan_obj = opts[:appscan_obj]
http_method = if opts[:http_method].nil?
:get
else
opts[:http_method].to_s.scrub.to_sym
end
rest_call = opts[:rest_call].to_s.scrub
http_body = opts[:http_body].to_s.scrub
appscan_ip = appscan_obj[:appscan_ip].to_s.scrub
appscan_cookie = appscan_obj[:cookie]
base_appscan_api_uri = "https://#{appscan_ip}/ase/services".to_s.scrub
retry_count = 3
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
rest_client = browser_obj[:browser]::Request
case http_method
when :get
response = rest_client.execute(
method: :get,
url: "#{base_appscan_api_uri}/#{rest_call}",
headers: { cookie: appscan_cookie },
verify_ssl: false
)
when :post
response = rest_client.execute(
method: :post,
url: "#{base_appscan_api_uri}/#{rest_call}",
headers: { cookie: appscan_cookie },
payload: http_body,
verify_ssl: false
)
else
return @@logger.error("Unsupported HTTP Method #{http_method} for #{self} Plugin")
end
response
rescue StandardError => e
if (e.message == '401 Unauthorized') && retry_count.positive? && appscan_obj[:logged_in]
# Try logging back in to refresh the connection
@@logger.warn("Got Response: #{e}...Attempting to Re-Authenticate; Retries left #{retry_count}")
n_appscan_obj = login(
appscan_ip: appscan_obj[:appscan_ip],
username: appscan_obj[:username],
password: Base64.decode64(appscan_obj[:password])
)
appscan_cookie = n_appscan_obj[:cookie]
# "copy" the new app obj over the old app obj
appscan_obj.each_key do |k|
appscan_obj[k] = n_appscan_obj[k]
end
retry_count -= 1
retry
end
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.schema(
# appscan_obj: 'required appscan_obj returned from login method'
# )
public_class_method def self.schema(opts = {})
appscan_obj = opts[:appscan_obj]
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: 'schema')
schema = {}
schema[:raw_response] = response
schema[:xml_response] = Nokogiri::XML(response)
schema
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.version(
# appscan_obj: 'required appscan_obj returned from login method'
# )
public_class_method def self.version(opts = {})
appscan_obj = opts[:appscan_obj]
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: 'version')
version = {}
version[:raw_response] = response
version[:xml_response] = Nokogiri::XML(response)
version[:build] = version[:xml_response].xpath(
'/xmlns:version/xmlns:build'
).text
version[:dbversion] = version[:xml_response].xpath(
'/xmlns:version/xmlns:dbversion'
).text
version[:rules_version] = version[:xml_response].xpath(
'/xmlns:version/xmlns:rules-version'
).text
version[:username] = version[:xml_response].xpath(
'/xmlns:version/xmlns:user-name'
).text
version
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_folders(
# appscan_obj: 'required appscan_obj returned from login method'
# )
public_class_method def self.get_folders(opts = {})
appscan_obj = opts[:appscan_obj]
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: 'folders')
folders = {}
folders[:raw_response] = response
folders[:xml_response] = Nokogiri::XML(response)
folders
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_subfolders_of_folder(
# appscan_obj: 'required appscan_obj returned from login method',
# folder_id: 'required folder to retrieve'
# )
public_class_method def self.get_subfolders_of_folder(opts = {})
appscan_obj = opts[:appscan_obj]
folder_id = opts[:folder_id].to_i
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: "folders/#{folder_id}/folders")
subfolders = {}
subfolders[:raw_response] = response
subfolders[:xml_response] = Nokogiri::XML(response)
subfolders
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_folder_by_id(
# appscan_obj: 'required appscan_obj returned from login method',
# folder_id: 'required folder to retrieve'
# )
public_class_method def self.get_folder_by_id(opts = {})
appscan_obj = opts[:appscan_obj]
folder_id = opts[:folder_id].to_i
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: "folders/#{folder_id}")
folder = {}
folder[:raw_response] = response
folder[:xml_response] = Nokogiri::XML(response)
folder
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_folder_items(
# appscan_obj: 'required appscan_obj returned from login method'
# )
public_class_method def self.get_folder_items(opts = {})
appscan_obj = opts[:appscan_obj]
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: 'folderitems')
folder_items = {}
folder_items[:raw_response] = response
folder_items[:xml_response] = Nokogiri::XML(response)
folder_items
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_folder_item_by_id(
# appscan_obj: 'required appscan_obj returned from login method',
# folder_item_id: 'required folder item to retrieve'
# )
public_class_method def self.get_folder_item_by_id(opts = {})
appscan_obj = opts[:appscan_obj]
folder_item_id = opts[:folder_item_id].to_i
retry_count = 3
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: "folderitems/#{folder_item_id}")
folder_item = {}
folder_item[:raw_response] = response
folder_item[:xml_response] = Nokogiri::XML(response)
# Get Current Status of a Scan
# Available states:
# READY = 1;
# STARTING = 2;
# RUNNING = 3;
# RESUMING = 6;
# CANCELING = 7;
# SUSPENDING = 8;
# SUSPENDED = 9;
# POSTPROCESSING = 10;
# ENDING = 12;
folder_item[:state] = folder_item[:xml_response].xpath('//xmlns:state/xmlns:name').text
folder_item
rescue StandardError => e
@@logger.error("Error: #{e} | #{e.class}\nResponse Returned: #{folder_item[:raw_response]}")
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_a_folders_folder_items(
# appscan_obj: 'required appscan_obj returned from login method',
# folder_id: 'required folder to retrieve'
# )
public_class_method def self.get_a_folders_folder_items(opts = {})
appscan_obj = opts[:appscan_obj]
folder_id = opts[:folder_item_id].to_i
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: "folders/#{folder_id}/folderitems")
a_folders_folder_items = {}
a_folders_folder_items[:raw_response] = response
a_folders_folder_items[:xml_response] = Nokogiri::XML(response)
a_folders_folder_items
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_folder_item_options(
# appscan_obj: 'required appscan_obj returned from login method',
# folder_item_id: 'required folder item to retrieve'
# )
public_class_method def self.get_folder_item_options(opts = {})
appscan_obj = opts[:appscan_obj]
folder_item_id = opts[:folder_item_id].to_i
# TODO: Discover why not all options are returned
# (e.g. esCOTAutoFormFillUserNameValue & esCOTAutoFormFillPasswordValue)
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: "folderitems/#{folder_item_id}/options")
folder_item_options = {}
folder_item_options[:raw_response] = response
folder_item_options[:xml_response] = Nokogiri::XML(response)
folder_item_options[:options] = folder_item_options[:xml_response].xpath(
'//xmlns:available-option/@href'
)
folder_item_options
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_scan_templates(
# appscan_obj: 'required appscan_obj returned from login method'
# )
public_class_method def self.get_scan_templates(opts = {})
appscan_obj = opts[:appscan_obj]
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: 'templates')
templates = {}
templates[:raw_response] = response
templates[:xml_response] = Nokogiri::XML(response)
templates
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.create_scan_based_on_template(
# appscan_obj: 'required appscan_obj returned from login method'
# template_id: 'required template id returned from get_scan_templates method'
# scan_name: 'required name of scan'
# scan_desc: 'required description of scan'
# )
public_class_method def self.create_scan_based_on_template(opts = {})
appscan_obj = opts[:appscan_obj]
template_id = opts[:template_id].to_i
scan_name = opts[:scan_name].to_s.scrub
scan_desc = opts[:scan_desc].to_s.scrub
response = appscan_rest_call(
appscan_obj: appscan_obj,
http_method: :post,
rest_call: "folderitems?templateId=#{template_id}",
http_body: "name=#{scan_name}&description=#{scan_desc}"
)
# Return an Easy to Use Data Structure
# Instead of Leaving it to the End User
# To Parse Out the XML on their own.
scan = {}
scan[:raw_response] = response
scan[:xml_response] = Nokogiri::XML(response)
scan[:folder_url] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/@href'
).text
scan[:folder_item_id] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:id'
).text
scan[:scan_name] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:name'
).text
scan[:scan_desc] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:description'
).text
scan[:parent_folder_url] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:parent/@href'
).text
scan[:parent_folder_id] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:parent/xmlns:id'
).text
scan[:contact] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:contact'
).text
scan[:state_id] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:state/xmlns:id'
).text
scan[:state_name] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:state/xmlns:name'
).text
scan[:action_id] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:action/xmlns:id'
).text
scan[:action_name] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:action/xmlns:name'
).text
scan[:options_url] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:content-scan-job/xmlns:options/@href'
).text
scan[:report_pack_url] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:report-pack/@href'
).text
scan[:report_pack_id] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:report-pack/xmlns:id'
).text
scan[:reports_url] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:report-pack/xmlns:reports/@href'
).text
scan[:reports_count] = scan[:xml_response].xpath(
'/xmlns:folder-items/xmlns:report-pack/xmlns:reports/xmlns:count'
).text.to_i
scan
rescue StandardError => e
@@logger.error("Error #{e}:\nREST response returned:\n#{response}")
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.configure_scan_options(
# appscan_obj: 'required appscan_obj returned from login method',
# folder_item_id: 'required folder item id',
# option: 'required option to change within the scan (folder item)',
# value: 'required option value(s)'
# )
public_class_method def self.configure_scan_options(opts = {})
appscan_obj = opts[:appscan_obj]
folder_item_id = opts[:folder_item_id].to_i
option = opts[:option].to_s.scrub
value = opts[:value]
case option.to_sym
when :epcsCOTListOfStartingUrls
post_body = ''
value.to_s.scrub.split(',').each_with_index do |url, index|
post_body << '&' unless index.zero?
post_body << "value=#{URI.encode_www_form(url.strip.chomp)}"
end
when :ebCOTHttpAuthentication
post_body = if value == false
'value=0' # Don't require authentication
else
'value=1' # Require authentication
end
when :esCOTHttpUser, :esCOTHttpPassword, :elCOTScanLimit
post_body = "value=#{value.to_s.scrub}"
when :help
available_options = ''
get_folder_item_options(
appscan_obj: appscan_obj,
folder_item_id: folder_item_id
)[:options].each { |url| available_options << "#{File.basename(url)}\n" }
return @@logger.info("Valid Options are:\n\n#{available_options}")
else
available_options = ''
get_folder_item_options(
appscan_obj: appscan_obj,
folder_item_id: folder_item_id
)[:options].each { |url| available_options << "#{File.basename(url)}\n" }
return @@logger.error("Invalid option '#{option}' parameter passed.\nValid Options are:\n\n#{available_options}")
end
# Always Overwrite Existing Option Values
response = appscan_rest_call(
appscan_obj: appscan_obj,
http_method: :post,
rest_call: "folderitems/#{folder_item_id}/options/#{option}?put=1",
http_body: post_body.to_s
)
scan_config = {}
scan_config[:raw_response] = response
scan_config[:xml_response] = Nokogiri::XML(response)
scan_config[:options] = scan_config[:xml_response].xpath('//xmlns:option/@value')
scan_config
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.folder_item_scan_action(
# appscan_obj: 'required appscan_obj returned from login method',
# folder_item_id: 'required folder item id',
# action: 'required action for scan to follow. Available actions are: :run, :suspend, :cancel, & :end',
# poll_interval: 'optional setting to determine length in seconds to poll for scan state (defaults to 60)'
# )
public_class_method def self.folder_item_scan_action(opts = {})
appscan_obj = opts[:appscan_obj]
folder_item_id = opts[:folder_item_id].to_i
action = opts[:action].to_s.scrub.to_sym
poll_interval = if opts[:poll_interval].nil?
60
else
opts[:poll_interval].to_i
end
case action
when :run
# Make sure scan is in a Ready state
this_folder_item = PWN::Plugins::IBMAppscan.get_folder_item_by_id(
appscan_obj: appscan_obj,
folder_item_id: folder_item_id
)
state = this_folder_item[:state]
return @@logger.error("Scan isn't in a Ready state. Current state: #{state}, abort.") if state != 'Ready'
@@logger.info("Kicking Off Scan for Folder Item: #{folder_item_id}")
response = appscan_rest_call(
appscan_obj: appscan_obj,
http_method: :post,
rest_call: "folderitems/#{folder_item_id}",
http_body: 'action=2'
)
# Obtain Status to Monitor Scan Completion
state = nil
until state == 'Ready'
sleep poll_interval
this_folder_item = PWN::Plugins::IBMAppscan.get_folder_item_by_id(
appscan_obj: appscan_obj,
folder_item_id: folder_item_id
)
state = this_folder_item[:state]
@@logger.info("Current Scan State: #{state}...")
end
@@logger.info("Scan Completed @ #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}")
when :suspend
response = appscan_rest_call(
appscan_obj: appscan_obj,
http_method: :post,
rest_call: "folderitems/#{folder_item_id}",
http_body: 'action=3'
)
when :cancel
response = appscan_rest_call(
appscan_obj: appscan_obj,
http_method: :post,
rest_call: "folderitems/#{folder_item_id}",
http_body: 'action=4'
)
when :end
response = appscan_rest_call(
appscan_obj: appscan_obj,
http_method: :post,
rest_call: "folderitems/#{folder_item_id}",
http_body: 'action=5'
)
else
return @@logger.error("Invalid action. Valid actions are:\n:run\n:suspend\n:cancel\n:end\n")
end
scan_action = {}
scan_action[:raw_response] = response
scan_action[:xml_response] = Nokogiri::XML(response)
scan_action
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_report_collection(
# appscan_obj: 'required appscan_obj returned from login method',
# report_folder_item_id: 'required report folder item id'
# )
public_class_method def self.get_report_collection(opts = {})
appscan_obj = opts[:appscan_obj]
report_folder_item_id = opts[:report_folder_item_id].to_i
@@logger.info("Retrieving Report Collection ID: #{report_folder_item_id} - Available Report Pack Collection:")
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: "folderitems/#{report_folder_item_id}/reports")
report_collection = {}
report_collection[:raw_response] = response
report_collection[:xml_response] = Nokogiri::XML(response)
# Output full report pack collection
report_collection[:xml_response].xpath('//xmlns:report').each do |r|
@@logger.info(" - #{r.xpath('xmlns:name').text}")
end
report_collection
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_single_report(
# appscan_obj: 'required appscan_obj returned from login method',
# report_id: 'required report id'
# )
public_class_method def self.get_single_report(opts = {})
appscan_obj = opts[:appscan_obj]
report_id = opts[:report_id].to_i
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: "reports/#{report_id}")
report = {}
report[:raw_response] = response
report[:xml_response] = Nokogiri::XML(response)
@@logger.info("Retrieved Report ID/Name: #{report_id}/#{report[:xml_response].xpath('//xmlns:report/xmlns:name').text}")
report
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_single_report_data(
# appscan_obj: 'required appscan_obj returned from login method',
# report_id: 'required report id'
# )
public_class_method def self.get_single_report_data(opts = {})
appscan_obj = opts[:appscan_obj]
report_id = opts[:report_id].to_i
response = appscan_rest_call(
appscan_obj: appscan_obj,
rest_call: "reports/#{report_id}/data?mode=all"
)
report_data = {}
report_data[:raw_response] = response
report_data[:xml_response] = Nokogiri::XML(response)
@@logger.info("Retrieved Report Data for Report ID: #{report_id}")
report_data
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_single_report_schema(
# appscan_obj: 'required appscan_obj returned from login method',
# report_id: 'required report id'
# )
public_class_method def self.get_single_report_schema(opts = {})
appscan_obj = opts[:appscan_obj]
report_id = opts[:report_id].to_i
response = appscan_rest_call(
appscan_obj: appscan_obj,
rest_call: "reports/#{report_id}/data?metadata=schema"
)
report_schema = {}
report_schema[:raw_response] = response
report_schema[:xml_response] = Nokogiri::XML(response)
@@logger.info("Retrieved Report Schema for Report ID: #{report_id}")
report_schema
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_issue_collection(
# appscan_obj: 'required appscan_obj returned from login method',
# report_id: 'required report id'
# )
public_class_method def self.get_issue_collection(opts = {})
appscan_obj = opts[:appscan_obj]
report_id = opts[:report_id].to_i
response = appscan_rest_call(
appscan_obj: appscan_obj,
rest_call: "reports/#{report_id}/issues?mode=all"
)
issue_collection = {}
issue_collection[:raw_response] = response
issue_collection[:xml_response] = Nokogiri::XML(response)
@@logger.info("Retrieved Issue Collection for Report ID: #{report_id}")
issue_collection
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.get_report_data
# appscan_obj: 'required appscan_obj returned from login method'
# report_link: 'required report link to start report generation
# output_name: 'required name to save generated report'
private_class_method def self.get_report_data(opts = {})
appscan_obj = opts[:appscan_obj]
report_link = opts[:report_link]
output_name = opts[:output_name]
# First Get request
uri = URI.parse(report_link)
browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
rb = browser_obj[:browser]
res = rb.get(report_link, 'Cookie' => appscan_obj[:cookie], :verify_ssl => OpenSSL::SSL::VERIFY_NONE)
location = "https://#{uri.host}#{res.headers['location']}"
puts "Location: #{location}"
# Generate the report on the server side
res = rb.get(location, 'Cookie' => appscan_obj[:cookie], :verify_ssl => OpenSSL::SSL::VERIFY_NONE)
# Now get the file
f = File.open(output_name, 'wb')
location['Export'] = 'Stream'
begin
rb.get(location, 'Cookie' => appscan_obj[:cookie], :verify_ssl => OpenSSL::SSL::VERIFY_NONE) do |resp|
resp.read_body do |seg|
f.write(seg)
end
end
ensure
f.close
end
rescue StandardError => e
@@logger.error("Could not get report data: #{e}")
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.generate_scan_report
# appscan_obj: 'required appscan_obj returned from login method'
# scan_name: 'required name of scan for which to generate a report'
# output_path: 'required path to save generated report'
public_class_method def self.generate_scan_report(opts = {})
appscan_obj = opts[:appscan_obj]
scan_name = opts[:scan_name]
output_path = opts[:output_path]
appscan_ip = appscan_obj[:appscan_ip].to_s.scrub
login_uri = "https://#{appscan_ip}:9443/ase/pages/Login.jsp"
base_appscan_uri = "https://#{appscan_ip}/ase/FolderExplorer.aspx"
logout_uri = "https://#{appscan_ip}/ase/LogOut.aspx"
# verify the output path actually exists
return @@logger.error("Output directory does not exist: #{output_path}") unless File.directory?(output_path)
browser_obj = PWN::Plugins::TransparentBrowser.open(
browser_type: :headless,
proxy: 'http://127.0.0.1:8080'
)
h_browser = browser_obj[:browser]
# log into the system
h_browser.goto login_uri.to_s.to_s.scrub
h_browser.text_field(name: 'j_username').when_present.set(appscan_obj[:username])
h_browser.text_field(name: 'j_password').when_present.set(Base64.decode64(appscan_obj[:password]))
h_browser.button(name: 'login').when_present.click
# head over to the reports page and click on the report link
h_browser.goto base_appscan_uri.to_s.to_s.scrub
h_browser.link(:text, 'ASE').when_present.click
# Search for the report link with a matching name and click it
clicked = false
h_browser.links.each do |link|
next unless (link.text == scan_name.to_s) && link.href =~ /^https:.+XReports.+/
link.when_present.click
clicked = true
break
end
return @@logger.error("Could not find matching scan name for name #{scan_name}") unless clicked
output_path = "#{output_path}/#{scan_name.gsub(/[^\w.-]/, '_')}/"
FileUtils.rm_rf output_path if File.directory?(output_path)
FileUtils.mkpath output_path
# Download the top level report
report_link = "#{h_browser.url}&exportformat=pdf&exportdelivery=download"
output_name = "#{output_path}Top_Level.pdf"
get_report_data(
appscan_obj: appscan_obj,
report_link: report_link,
output_name: output_name
)
rescue StandardError => e
@@logger.error("Error retrieving report for '#{scan_name}': #{e}")
ensure
# make sure we always logout
h_browser.goto logout_uri.to_s.to_s.scrub
h_browser.close
end
# Supported Method Parameters::
# PWN::Plugins::IBMAppscan.logout(
# appscan_obj: 'required appscan_obj returned from login method'
# )
public_class_method def self.logout(opts = {})
appscan_obj = opts[:appscan_obj]
@@logger.info('Logging out...')
response = appscan_rest_call(appscan_obj: appscan_obj, rest_call: 'logout')
if response == ''
appscan_obj[:logged_in] = false
'logout successful'
else
response
end
rescue StandardError => e
raise e
end
# Author(s):: 0day Inc. <request.pentest@0dayinc.com>
public_class_method def self.authors
"AUTHOR(S):
0day Inc. <request.pentest@0dayinc.com>
"
end
# Display Usage for this Module
public_class_method def self.help
puts "USAGE:
appscan_obj = #{self}.login(
appscan_ip: 'required host/ip of Nexpose Console (server)',
username: 'required username',
password: 'optional password (will prompt if nil)'
)
schema = #{self}.schema(
appscan_obj: 'required appscan_obj returned from login method'
)
version = #{self}.version(
appscan_obj: 'required appscan_obj returned from login method'
)
folders = #{self}.get_folders(
appscan_obj: 'required appscan_obj returned from login method'
)
subfolders = #{self}.get_subfolders_of_folder(
appscan_obj: 'required appscan_obj returned from login method',
folder_id: 'required folder to retrieve'
)
folder = #{self}.get_folder_by_id(
appscan_obj: 'required appscan_obj returned from login method',
folder_id: 'required folder to retrieve'
)
folder_items = #{self}.get_folder_items(
appscan_obj: 'required appscan_obj returned from login method'
)
folder_item = #{self}.get_folder_item_by_id(
appscan_obj: 'required appscan_obj returned from login method',
folder_item_id: 'required folder item to retrieve'
)
a_folders_folder_items = #{self}.get_a_folders_folder_items(
appscan_obj: 'required appscan_obj returned from login method',
folder_id: 'required folder to retrieve'
)
folder_item_options = #{self}.get_folder_item_options(
appscan_obj: 'required appscan_obj returned from login method',
folder_item_id: 'required folder item to retrieve'
)
scan = #{self}.create_scan_based_on_template(
appscan_obj: 'required appscan_obj returned from login method'
template_id: 'required template id returned from get_scan_templates method'
scan_name: 'required name of scan'
scan_desc: 'required description of scan'
)
templates = #{self}.get_scan_templates(
appscan_obj: 'required appscan_obj returned from login method'
)
scan_config = #{self}.configure_scan_options(
appscan_obj: 'required appscan_obj returned from login method',
folder_item_id: 'required folder item id',
option: 'required option to change within the scan (folder item). Pass :help for a list of options.',
value: 'required option value(s)'
)
scan_action = #{self}.folder_item_scan_action(
appscan_obj: 'required appscan_obj returned from login method',
folder_item_id: 'required folder item id',
action: 'required action for scan to follow. Available actions are: :run, :suspend, :cancel, & :end',
poll_interval: 'optional setting to determine length in seconds to poll for scan state (defaults to 60)'
)
report_collection = #{self}.get_report_collection(
appscan_obj: 'required appscan_obj returned from login method',
report_folder_item_id: 'required report folder item id'
)
report = #{self}.get_single_report(
appscan_obj: 'required appscan_obj returned from login method',
report_id: 'required report id'
)
report_data = #{self}.get_single_report_data(
appscan_obj: 'required appscan_obj returned from login method',
report_id: 'required report id'
)
report_schema = #{self}.get_single_report_schema(
appscan_obj: 'required appscan_obj returned from login method',
report_id: 'required report id'
)
issue_collection = #{self}.get_issue_collection(
appscan_obj: 'required appscan_obj returned from login method',
report_id: 'required report id'
)
#{self}.generate_scan_report(
appscan_obj: 'required appscan_obj returned from login',
scan_name: 'required name of scan for which to generate a report',
output_path: 'required path to save generated report'
)
#{self}.logout(
appscan_obj: 'required appscan_obj returned from login method'
)
#{self}.authors
"
end
end
end
end