Commit 8042a13f authored by Stan Hu's avatar Stan Hu

Merge branch 'use-dast-common-report-format-fields' into 'master'

Parse DAST reports using the common report format

Closes #33913

See merge request gitlab-org/gitlab!22413
parents 17517bc8 ec339d87
...@@ -5,52 +5,17 @@ module Gitlab ...@@ -5,52 +5,17 @@ module Gitlab
module Parsers module Parsers
module Security module Security
class Dast < Common class Dast < Common
FORMAT_VERSION = '2.0'.freeze
protected protected
def parse_report(json_data) def parse_report(json_data)
report = super report = super
format_report(report) return Formatters::Dast.new(report).format if Formatters::Dast.satisfies?(report)
end
private
def format_report(data)
{
'vulnerabilities' => extract_vulnerabilities_from(Array.wrap(data['site'])),
'version' => FORMAT_VERSION
}
end
# Log messages to be added here to track usage of legacy reports,
# parsing failures and any other scenarios: https://gitlab.com/gitlab-org/gitlab/issues/34668
def extract_vulnerabilities_from(sites = [])
return [] if sites.empty?
vulnerabilities = []
sites.each do |site| report
site_report = Hash(site)
next if site_report.blank?
# If host is blank for legacy reports
host = site_report['@name']
site_report['alerts'].each do |vulnerability|
vulnerabilities += flatten_vulnerabilities(vulnerability, host)
end
end
vulnerabilities
end end
def flatten_vulnerabilities(vulnerability, host) private
vulnerability['instances'].map do |instance|
Formatters::Dast.new(vulnerability).format(instance, host)
end
end
def create_location(location_data) def create_location(location_data)
::Gitlab::Ci::Reports::Security::Locations::Dast.new( ::Gitlab::Ci::Reports::Security::Locations::Dast.new(
......
...@@ -6,11 +6,53 @@ module Gitlab ...@@ -6,11 +6,53 @@ module Gitlab
module Security module Security
module Formatters module Formatters
class Dast class Dast
def initialize(vulnerability) FORMAT_VERSION = '2.0'.freeze
@vulnerability = vulnerability
def initialize(report)
@report = report
end
def self.satisfies?(report)
report.key?('site') && !report.key?('vulnerabilities')
end
def format
{
'vulnerabilities' => extract_vulnerabilities_from(Array.wrap(@report['site'])),
'version' => FORMAT_VERSION
}
end
private
# Log messages to be added here to track usage of legacy reports,
# parsing failures and any other scenarios: https://gitlab.com/gitlab-org/gitlab/issues/34668
def extract_vulnerabilities_from(sites = [])
return [] if sites.empty?
vulnerabilities = []
sites.each do |site|
site_report = Hash(site)
next if site_report.blank?
# If host is blank for legacy reports
host = site_report['@name']
site_report['alerts'].each do |vulnerability|
vulnerabilities += flatten_vulnerabilities(vulnerability, host)
end
end
vulnerabilities
end
def flatten_vulnerabilities(vulnerability, host)
vulnerability['instances'].map { |instance| format_vulnerability(vulnerability, instance, host) }
end end
def format(instance, hostname) def format_vulnerability(vulnerability, instance, hostname)
{ {
'category' => 'dast', 'category' => 'dast',
'message' => vulnerability['name'], 'message' => vulnerability['name'],
...@@ -50,10 +92,6 @@ module Gitlab ...@@ -50,10 +92,6 @@ module Gitlab
} }
end end
private
attr_reader :vulnerability
SEVERITY_MAPPING = %w{info low medium high}.freeze SEVERITY_MAPPING = %w{info low medium high}.freeze
CONFIDENCE_MAPPING = %w{ignore low medium high confirmed}.freeze CONFIDENCE_MAPPING = %w{ignore low medium high confirmed}.freeze
......
...@@ -42,13 +42,25 @@ FactoryBot.define do ...@@ -42,13 +42,25 @@ FactoryBot.define do
end end
end end
trait :dast_deprecated do trait :dast_deprecated_no_spider do
file_format { :raw } file_format { :raw }
file_type { :dast } file_type { :dast }
after(:build) do |artifact, _| after(:build) do |artifact, _|
artifact.file = fixture_file_upload( artifact.file = fixture_file_upload(
Rails.root.join('ee/spec/fixtures/security_reports/deprecated/gl-dast-report.json'), 'application/json') Rails.root.join('ee/spec/fixtures/security_reports/deprecated/gl-dast-report-no-spider.json'), 'application/json')
end
end
trait :dast_deprecated_no_common_fields do
file_format { :raw }
file_type { :dast }
after(:build) do |artifact, _|
artifact.file = fixture_file_upload(
Rails.root.join('ee/spec/fixtures/security_reports/deprecated/gl-dast-report-no-common-fields.json'),
'application/json'
)
end end
end end
......
...@@ -22,8 +22,9 @@ describe Gitlab::Ci::Parsers::Security::Dast do ...@@ -22,8 +22,9 @@ describe Gitlab::Ci::Parsers::Security::Dast do
:last_occurrence_severity, :last_occurrence_severity,
:last_occurrence_confidence) do :last_occurrence_confidence) do
:dast | 24 | 15 | 1 | 'http://goat:8080' | 'GET' | '/WebGoat/plugins/bootstrap/css/bootstrap.min.css' | 'info' | 'low' :dast | 24 | 15 | 1 | 'http://goat:8080' | 'GET' | '/WebGoat/plugins/bootstrap/css/bootstrap.min.css' | 'info' | 'low'
:dast_multiple_sites | 25 | 15 | 1 | 'https://goat:8080' | 'GET' | '/WebGoat/registration' | 'high' | 'medium' :dast_multiple_sites | 25 | 15 | 1 | 'http://goat:8080' | 'GET' | '/WebGoat/plugins/bootstrap/css/bootstrap.min.css' | 'info' | 'low'
:dast_deprecated | 2 | 3 | 1 | 'http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io' | 'GET' | '/' | 'low' | 'medium' :dast_deprecated_no_spider | 2 | 3 | 1 | 'http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io' | 'GET' | '/' | 'low' | 'medium'
:dast_deprecated_no_common_fields | 24 | 15 | 1 | 'http://goat:8080' | 'GET' | '/WebGoat/plugins/bootstrap/css/bootstrap.min.css' | 'info' | 'low'
end end
with_them do with_them do
......
...@@ -3,56 +3,58 @@ ...@@ -3,56 +3,58 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Parsers::Security::Formatters::Dast do describe Gitlab::Ci::Parsers::Security::Formatters::Dast do
let(:formatter) { described_class.new(file_vulnerability) } let(:formatter) { described_class.new(parsed_report) }
let(:file_vulnerability) { parsed_report['site'].first['alerts'][0] }
let(:parsed_report) do let(:parsed_report) do
JSON.parse!( JSON.parse!(
File.read( File.read(
Rails.root.join('ee/spec/fixtures/security_reports/master/gl-dast-report.json') Rails.root.join('ee/spec/fixtures/security_reports/deprecated/gl-dast-report-no-common-fields.json')
) )
) )
end end
describe '#format_vulnerability' do describe '#format_vulnerability' do
let(:instance) { file_vulnerability['instances'][0] }
let(:hostname) { 'http://goat:8080' } let(:hostname) { 'http://goat:8080' }
let(:file_vulnerability) { parsed_report['site'].first['alerts'][0] }
let(:sanitized_desc) { file_vulnerability['desc'].gsub('<p>', '').gsub('</p>', '') } let(:sanitized_desc) { file_vulnerability['desc'].gsub('<p>', '').gsub('</p>', '') }
let(:sanitized_solution) { file_vulnerability['solution'].gsub('<p>', '').gsub('</p>', '') } let(:sanitized_solution) { file_vulnerability['solution'].gsub('<p>', '').gsub('</p>', '') }
let(:version) { parsed_report['@version'] } let(:version) { parsed_report['@version'] }
it 'format ZAProxy vulnerability into common format' do it 'format ZAProxy vulnerability into common format' do
data = formatter.format(instance, hostname) data = formatter.format
expect(data['vulnerabilities'].size).to eq(24)
vulnerability = data['vulnerabilities'][0]
expect(data['category']).to eq('dast') expect(vulnerability['category']).to eq('dast')
expect(data['message']).to eq('Anti CSRF Tokens Scanner') expect(vulnerability['message']).to eq('Anti CSRF Tokens Scanner')
expect(data['description']).to eq(sanitized_desc) expect(vulnerability['description']).to eq(sanitized_desc)
expect(data['cve']).to eq('20012') expect(vulnerability['cve']).to eq('20012')
expect(data['severity']).to eq('high') expect(vulnerability['severity']).to eq('high')
expect(data['confidence']).to eq('medium') expect(vulnerability['confidence']).to eq('medium')
expect(data['solution']).to eq(sanitized_solution) expect(vulnerability['solution']).to eq(sanitized_solution)
expect(data['scanner']).to eq({ 'id' => 'zaproxy', 'name' => 'ZAProxy' }) expect(vulnerability['scanner']).to eq({ 'id' => 'zaproxy', 'name' => 'ZAProxy' })
expect(data['links']).to eq([{ 'url' => 'http://projects.webappsec.org/Cross-Site-Request-Forgery' }, expect(vulnerability['links']).to eq([{ 'url' => 'http://projects.webappsec.org/Cross-Site-Request-Forgery' },
{ 'url' => 'http://cwe.mitre.org/data/definitions/352.html' }]) { 'url' => 'http://cwe.mitre.org/data/definitions/352.html' }])
expect(data['identifiers'][0]).to eq({ expect(vulnerability['identifiers'][0]).to eq({
'type' => 'ZAProxy_PluginId', 'type' => 'ZAProxy_PluginId',
'name' => 'Anti CSRF Tokens Scanner', 'name' => 'Anti CSRF Tokens Scanner',
'value' => '20012', 'value' => '20012',
'url' => "https://github.com/zaproxy/zaproxy/blob/w2019-01-14/docs/scanners.md" 'url' => "https://github.com/zaproxy/zaproxy/blob/w2019-01-14/docs/scanners.md"
}) })
expect(data['identifiers'][1]).to eq({ expect(vulnerability['identifiers'][1]).to eq({
'type' => 'CWE', 'type' => 'CWE',
'name' => "CWE-352", 'name' => "CWE-352",
'value' => '352', 'value' => '352',
'url' => "https://cwe.mitre.org/data/definitions/352.html" 'url' => "https://cwe.mitre.org/data/definitions/352.html"
}) })
expect(data['identifiers'][2]).to eq({ expect(vulnerability['identifiers'][2]).to eq({
'type' => 'WASC', 'type' => 'WASC',
'name' => "WASC-9", 'name' => "WASC-9",
'value' => '9', 'value' => '9',
'url' => "http://projects.webappsec.org/w/page/13246974/Threat%20Classification%20Reference%20Grid" 'url' => "http://projects.webappsec.org/w/page/13246974/Threat%20Classification%20Reference%20Grid"
}) })
expect(data['location']).to eq({ expect(vulnerability['location']).to eq({
'param' => '', 'param' => '',
'method' => 'GET', 'method' => 'GET',
'hostname' => hostname, 'hostname' => hostname,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment