Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Netsparker Mapper #80

Merged
merged 7 commits into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ HeimdallTools supplies several methods to convert output from various tools to "
- **jfrog_xray_mapper** - package vulnerability scanner
- **dbprotect_mapper** - database vulnerability scanner
- **aws_config_mapper** - assess, audit, and evaluate AWS resources
- **netsparker_mapper** - web application security scanner

Ruby 2.4 or higher (check using "ruby -v")

Expand Down Expand Up @@ -234,6 +235,23 @@ FLAGS:
example: heimdall_tools aws_config_mapper -o aws_config_results_hdf.json
```

## netsparker_mapper

netsparker_mapper translates an Netsparker XML results file into HDF format JSON to be viewable in Heimdall.

The current iteration only works with Netsparker Enterprise Vulnerabilities Scan.

```
USAGE: heimdall_tools netsparker_mapper [OPTIONS] -x <netsparker_results_xml> -o <hdf-scan-results.json>

FLAGS:
-x <netsparker_results_xml> : path to netsparker results XML file.
-o --output <scan-results> : path to output scan-results json.
-V --verbose : verbose run [optional].

example: heimdall_tools netsparker_mapper -x netsparker_results.xml -o netsparker_hdf.json
```

## version

Prints out the gem version
Expand Down
1 change: 1 addition & 0 deletions lib/heimdall_tools.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ module HeimdallTools
autoload :JfrogXrayMapper, 'heimdall_tools/jfrog_xray_mapper'
autoload :DBProtectMapper, 'heimdall_tools/dbprotect_mapper'
autoload :AwsConfigMapper, 'heimdall_tools/aws_config_mapper'
autoload :NetsparkerMapper, 'heimdall_tools/netsparker_mapper'
end
12 changes: 12 additions & 0 deletions lib/heimdall_tools/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ def aws_config_mapper
puts "\r\HDF Generated:\n"
puts "#{options[:output]}"
end

desc 'netsparker_mapper', 'netsparker_mapper translates netsparker enterprise results xml to HDF format Json be viewed on Heimdall'
long_desc Help.text(:netsparker_mapper)
option :xml, required: true, aliases: '-x'
option :output, required: true, aliases: '-o'
option :verbose, type: :boolean, aliases: '-V'
def netsparker_mapper
hdf = HeimdallTools::NetsparkerMapper.new(File.read(options[:xml])).to_hdf
File.write(options[:output], hdf)
puts "\r\HDF Generated:\n"
puts "#{options[:output]}"
end

desc 'version', 'prints version'
def version
Expand Down
7 changes: 7 additions & 0 deletions lib/heimdall_tools/help/netsparker_mapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
netsparker_mapper translates an Netsparker XML results file into HDF format json to be viewable in Heimdall

The current iteration only works with Netsparker Enterprise Vulnerabilities Scan.

Examples:

heimdall_tools netsparker_mapper -x netsparker_results.xml -o netsparker_hdf.json
167 changes: 167 additions & 0 deletions lib/heimdall_tools/netsparker_mapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
require 'json'
require 'csv'
require 'heimdall_tools/hdf'
require 'utilities/xml_to_hash'

RESOURCE_DIR = Pathname.new(__FILE__).join('../../data')

CWE_NIST_MAPPING_FILE = File.join(RESOURCE_DIR, 'cwe-nist-mapping.csv')
OWASP_NIST_MAPPING_FILE = File.join(RESOURCE_DIR, 'owasp-nist-mapping.csv')

IMPACT_MAPPING = {
Critical: 1.0,
High: 0.7,
Medium: 0.5,
Low: 0.3,
Best_Practice: 0.0,
Information: 0.0
}.freeze

DEFAULT_NIST_TAG = ["SA-11", "RA-5"].freeze

# rubocop:disable Metrics/AbcSize

module HeimdallTools
class NetsparkerMapper
def initialize(xml, name=nil, verbose = false)
@verbose = verbose

begin
@cwe_nist_mapping = parse_mapper(CWE_NIST_MAPPING_FILE)
@owasp_nist_mapping = parse_mapper(OWASP_NIST_MAPPING_FILE)
data = xml_to_hash(xml)

@vulnerabilities = data['netsparker-enterprise']['vulnerabilities']['vulnerability']
@scan_info = data['netsparker-enterprise']['target']

rescue StandardError => e
raise "Invalid Netsparker XML file provided Exception: #{e}"
end
end

def to_hdf
controls = []
@vulnerabilities.each do |vulnerability|
@item = {}
@item['id'] = vulnerability['LookupId'].to_s
@item['title'] = vulnerability['name'].to_s
@item['desc'] = format_control_desc(vulnerability)
@item['impact'] = impact(vulnerability['severity'])
@item['tags'] = {}
@item['descriptions'] = []

@item['descriptions'] << desc_tags(format_check_text(vulnerability), 'check')
@item['descriptions'] << desc_tags(format_fix_text(vulnerability), 'fix')
@item['refs'] = NA_ARRAY
@item['source_location'] = NA_HASH
@item['tags']['nist'] = nist_tag(vulnerability['classification'])
@item['code'] = ''
@item['results'] = finding(vulnerability)

controls << @item
end
controls = collapse_duplicates(controls)
results = HeimdallDataFormat.new(profile_name: 'Netsparker Enterprise Scan',
title: "Netsparker Enterprise Scan ID: #{@scan_info['scan-id']} URL: #{@scan_info['url']}",
summary: "Netsparker Enterprise Scan",
target_id: @scan_info['url'],
controls: controls)
results.to_hdf
end

private

def parse_html(block)
block['#cdata-section'].to_s.strip unless block.nil?
end

def finding(vulnerability)
finding = {}
finding['status'] = 'failed'
finding['code_desc'] = []
finding['code_desc'] << "http-request : #{parse_html(vulnerability['http-request']['content']) }"
finding['code_desc'] << "method : #{vulnerability['http-request']['method']}"
finding['code_desc'] = finding['code_desc'].join("\n")

finding['message'] = []
finding['message'] << "http-response : #{parse_html(vulnerability['http-response']['content']) }"
finding['message'] << "duration : #{vulnerability['http-response']['duration']}"
finding['message'] << "status-code : #{vulnerability['http-response']['status-code']}"
finding['message'] = finding['message'].join("\n")
finding['run_time'] = NA_FLOAT

finding['start_time'] = @scan_info['initiated']
[finding]
end

def format_control_desc(vulnerability)
text = []
text << "#{parse_html(vulnerability['description'])}" unless vulnerability['description'].nil?
text << "Exploitation-skills: #{parse_html(vulnerability['exploitation-skills'])}" unless vulnerability['exploitation-skills'].nil?
text << "Extra-information: #{vulnerability['extra-information']}" unless vulnerability['extra-information'].nil?
text << "Classification: #{vulnerability['classification']}" unless vulnerability['classification'].nil?
text << "Impact: #{parse_html(vulnerability['impact'])}" unless vulnerability['impact'].nil?
text << "FirstSeenDate: #{vulnerability['FirstSeenDate']}" unless vulnerability['FirstSeenDate'].nil?
text << "LastSeenDate: #{vulnerability['LastSeenDate']}" unless vulnerability['LastSeenDate'].nil?
text << "Certainty: #{vulnerability['certainty']}" unless vulnerability['certainty'].nil?
text << "Type: #{vulnerability['type']}" unless vulnerability['type'].nil?
text << "Confirmed: #{vulnerability['confirmed']}" unless vulnerability['confirmed'].nil?
text.join("<br>")
end

def format_check_text(vulnerability)
text = []
text << "Exploitation-skills: #{parse_html(vulnerability['exploitation-skills'])}" unless vulnerability['exploitation-skills'].nil?
text << "Proof-of-concept: #{parse_html(vulnerability['proof-of-concept'])}" unless vulnerability['proof-of-concept'].nil?
text.join("<br>")
end

def format_fix_text(vulnerability)
text = []
text << "Remedial-actions: #{parse_html(vulnerability['remedial-actions'])}" unless vulnerability['remedial-actions'].nil?
text << "Remedial-procedure: #{parse_html(vulnerability['remedial-procedure'])}" unless vulnerability['remedial-procedure'].nil?
text << "Remedy-references: #{parse_html(vulnerability['remedy-references'])}" unless vulnerability['remedy-references'].nil?
text.join("<br>")
end

def nist_tag(classification)
tags = []
entries = @cwe_nist_mapping.select { |x| classification['cwe'].include?(x[:cweid].to_s) && !x[:nistid].nil? }
tags << entries.map { |x| x[:nistid] }
entries = @owasp_nist_mapping.select { |x| classification['owasp'].include?(x[:owaspid].to_s) && !x[:nistid].nil? }
tags << entries.map { |x| x[:nistid] }
tags.flatten.empty? ? DEFAULT_NIST_TAG : tags.flatten.uniq
end

def impact(severity)
IMPACT_MAPPING[severity.to_sym]
end

def parse_mapper(mapping_file)
csv_data = CSV.read(mapping_file, { encoding: 'UTF-8',
headers: true,
header_converters: :symbol,
converters: :all })
csv_data.map(&:to_hash)
end

def desc_tags(data, label)
{ "data": data || NA_STRING, "label": label || NA_STRING }
end

# Netsparker report could have multiple issue entries for multiple findings of same issue type.
# The meta data is identical across entries
# method collapse_duplicates return unique controls with applicable findings collapsed into it.
def collapse_duplicates(controls)
unique_controls = []

controls.map { |x| x['id'] }.uniq.each do |id|
collapsed_results = controls.select { |x| x['id'].eql?(id) }.map {|x| x['results']}
unique_control = controls.find { |x| x['id'].eql?(id) }
unique_control['results'] = collapsed_results.flatten
unique_controls << unique_control
end
unique_controls
end
end
end