Commit 69444ab8 authored by Tetiana Chupryna's avatar Tetiana Chupryna Committed by Grzegorz Bizon

Extract DAST formatter

DAST parser is a bit feature envy now,
so it was decided to split it.
parent 90c5ddc6
......@@ -40,115 +40,14 @@ module Gitlab
end
def flatten_vulnerabilities(vulnerability, host)
common_vulnerability = format_vulnerability(vulnerability)
vulnerability['instances'].map do |instance|
common_vulnerability.merge('location' => location(instance, host))
end
Formatters::Dast.new(vulnerability).format(instance, host)
end
def format_vulnerability(vulnerability)
{
'category' => 'dast',
'message' => vulnerability['name'],
'description' => sanitize(vulnerability['desc']),
'cve' => vulnerability['pluginid'],
'severity' => severity(vulnerability['riskcode']),
'solution' => sanitize(vulnerability['solution']),
'confidence' => confidence(vulnerability['confidence']),
'scanner' => { 'id' => 'zaproxy', 'name' => 'ZAProxy' },
'identifiers' => [
{
'type' => 'ZAProxy_PluginId',
'name' => vulnerability['name'],
'value' => vulnerability['pluginid'],
'url' => "https://github.com/zaproxy/zaproxy/blob/w2019-01-14/docs/scanners.md"
},
{
'type' => 'CWE',
'name' => "CWE-#{vulnerability['cweid']}",
'value' => vulnerability['cweid'],
'url' => "https://cwe.mitre.org/data/definitions/#{vulnerability['cweid']}.html"
},
{
'type' => 'WASC',
'name' => "WASC-#{vulnerability['wascid']}",
'value' => vulnerability['wascid'],
'url' => "http://projects.webappsec.org/w/page/13246974/Threat%20Classification%20Reference%20Grid"
}
],
'links' => links(vulnerability['reference'])
}
end
def generate_location_fingerprint(location)
Digest::SHA1.hexdigest("#{location['param']} #{location['method']} #{location['path']}")
end
# https://github.com/zaproxy/zaproxy/blob/cfb44f7e29f490d95b03830d90aadaca51a72a6a/src/scripts/templates/passive/Passive%20default%20template.js#L25
# NOTE: ZAProxy levels: 0: info, 1: low, 2: medium, 3: high
def severity(value)
case Integer(value)
when 0
'info'
when 1
'low'
when 2
'medium'
when 3
'high'
else
'unknown'
end
rescue ArgumentError
'unknown'
end
# NOTE: ZAProxy levels: 0: falsePositive, 1: low, 2: medium, 3: high, 4: confirmed
def confidence(value)
case Integer(value)
when 0
'ignore'
when 1
'low'
when 2
'medium'
when 3
'high'
when 4
'confirmed'
else
'unknown'
end
rescue ArgumentError
'unknown'
end
def links(reference)
urls_from(reference).each_with_object([]) do |url, links|
next if url.blank?
links << { 'url' => url }
end
end
def urls_from(reference)
tags = reference.lines('</p>')
tags.map { |tag| sanitize(tag) }
end
def location(instance, hostname)
{
'param' => instance['param'],
'method' => instance['method'],
'hostname' => hostname,
'path' => instance['uri'].sub(hostname, '')
}
end
def sanitize(html_str)
ActionView::Base.full_sanitizer.sanitize(html_str)
end
end
end
end
......
# frozen_string_literal: true
module Gitlab
module Ci
module Parsers
module Security
module Formatters
class Dast
def initialize(vulnerability)
@vulnerability = vulnerability
end
def format(instance, hostname)
{
'category' => 'dast',
'message' => vulnerability['name'],
'description' => sanitize(vulnerability['desc']),
'cve' => vulnerability['pluginid'],
'severity' => severity(vulnerability['riskcode']),
'solution' => sanitize(vulnerability['solution']),
'confidence' => confidence(vulnerability['confidence']),
'scanner' => { 'id' => 'zaproxy', 'name' => 'ZAProxy' },
'identifiers' => [
{
'type' => 'ZAProxy_PluginId',
'name' => vulnerability['name'],
'value' => vulnerability['pluginid'],
'url' => "https://github.com/zaproxy/zaproxy/blob/w2019-01-14/docs/scanners.md"
},
{
'type' => 'CWE',
'name' => "CWE-#{vulnerability['cweid']}",
'value' => vulnerability['cweid'],
'url' => "https://cwe.mitre.org/data/definitions/#{vulnerability['cweid']}.html"
},
{
'type' => 'WASC',
'name' => "WASC-#{vulnerability['wascid']}",
'value' => vulnerability['wascid'],
'url' => "http://projects.webappsec.org/w/page/13246974/Threat%20Classification%20Reference%20Grid"
}
],
'links' => links(vulnerability['reference']),
'location' => {
'param' => instance['param'],
'method' => instance['method'],
'hostname' => hostname,
'path' => instance['uri'].sub(hostname, '')
}
}
end
private
attr_reader :vulnerability
SEVERITY_MAPPING = %w{info low medium high}.freeze
CONFIDENCE_MAPPING = %w{ignore low medium high confirmed}.freeze
# https://github.com/zaproxy/zaproxy/blob/cfb44f7e29f490d95b03830d90aadaca51a72a6a/src/scripts/templates/passive/Passive%20default%20template.js#L25
# NOTE: ZAProxy levels: 0: info, 1: low, 2: medium, 3: high
def severity(value)
SEVERITY_MAPPING[Integer(value)] || 'unknown'
rescue ArgumentError
'unknown'
end
# NOTE: ZAProxy levels: 0: falsePositive, 1: low, 2: medium, 3: high, 4: confirmed
def confidence(value)
CONFIDENCE_MAPPING[Integer(value)] || 'unknown'
rescue ArgumentError
'unknown'
end
def links(reference)
urls_from(reference).each_with_object([]) do |url, links|
next if url.blank?
links << { 'url' => url }
end
end
def urls_from(reference)
tags = reference.lines('</p>')
tags.map { |tag| sanitize(tag) }
end
def sanitize(html_str)
ActionView::Base.full_sanitizer.sanitize(html_str)
end
end
end
end
end
end
end
......@@ -49,113 +49,4 @@ describe Gitlab::Ci::Parsers::Security::Dast do
end
end
end
describe '#format_vulnerability' do
let(:parsed_report) do
JSON.parse!(
File.read(
Rails.root.join('spec/fixtures/security-reports/master/gl-dast-report.json')
)
)
end
let(:file_vulnerability) { parsed_report['site']['alerts'][0] }
let(:sanitized_desc) { file_vulnerability['desc'].gsub('<p>', '').gsub('</p>', '') }
let(:sanitized_solution) { file_vulnerability['solution'].gsub('<p>', '').gsub('</p>', '') }
let(:version) { parsed_report['@version'] }
it 'format ZAProxy vulnerability into common format' do
data = parser.send(:format_vulnerability, file_vulnerability)
expect(data['category']).to eq('dast')
expect(data['message']).to eq('X-Content-Type-Options Header Missing')
expect(data['description']).to eq(sanitized_desc)
expect(data['cve']).to eq('10021')
expect(data['severity']).to eq('low')
expect(data['confidence']).to eq('medium')
expect(data['solution']).to eq(sanitized_solution)
expect(data['scanner']).to eq({ 'id' => 'zaproxy', 'name' => 'ZAProxy' })
expect(data['links']).to eq([{ 'url' => 'http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx' },
{ 'url' => 'https://www.owasp.org/index.php/List_of_useful_HTTP_headers' }])
expect(data['identifiers'][0]).to eq({
'type' => 'ZAProxy_PluginId',
'name' => 'X-Content-Type-Options Header Missing',
'value' => '10021',
'url' => "https://github.com/zaproxy/zaproxy/blob/w2019-01-14/docs/scanners.md"
})
expect(data['identifiers'][1]).to eq({
'type' => 'CWE',
'name' => "CWE-16",
'value' => '16',
'url' => "https://cwe.mitre.org/data/definitions/16.html"
})
expect(data['identifiers'][2]).to eq({
'type' => 'WASC',
'name' => "WASC-15",
'value' => '15',
'url' => "http://projects.webappsec.org/w/page/13246974/Threat%20Classification%20Reference%20Grid"
})
end
end
describe '#location' do
let(:file_vulnerability) do
JSON.parse!(
File.read(
Rails.root.join('spec/fixtures/security-reports/master/gl-dast-report.json')
)
)['site']['alerts'][0]
end
let(:instance) { file_vulnerability['instances'][1] }
let(:host) { 'http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io' }
it 'format location struct' do
data = parser.send(:location, instance, host)
expect(data['param']).to eq('X-Content-Type-Options')
expect(data['method']).to eq('GET')
expect(data['hostname']).to eq(host)
expect(data['path']).to eq('/')
end
end
describe '#severity' do
using RSpec::Parameterized::TableSyntax
where(:severity, :expected) do
'0' | 'info'
'1' | 'low'
'2' | 'medium'
'3' | 'high'
'42' | 'unknown'
'' | 'unknown'
end
with_them do
it 'substitutes with right values' do
expect(parser.send(:severity, severity)).to eq(expected)
end
end
end
describe '#confidence' do
using RSpec::Parameterized::TableSyntax
where(:confidence, :expected) do
'0' | 'ignore'
'1' | 'low'
'2' | 'medium'
'3' | 'high'
'4' | 'confirmed'
'42' | 'unknown'
'' | 'unknown'
end
with_them do
it 'substitutes with right values' do
expect(parser.send(:confidence, confidence)).to eq(expected)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Parsers::Security::Formatters::Dast do
let(:formatter) { described_class.new(file_vulnerability) }
let(:file_vulnerability) { parsed_report['site']['alerts'][0] }
let(:parsed_report) do
JSON.parse!(
File.read(
Rails.root.join('spec/fixtures/security-reports/master/gl-dast-report.json')
)
)
end
describe '#format_vulnerability' do
let(:instance) { file_vulnerability['instances'][1] }
let(:hostname) { 'http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io' }
let(:sanitized_desc) { file_vulnerability['desc'].gsub('<p>', '').gsub('</p>', '') }
let(:sanitized_solution) { file_vulnerability['solution'].gsub('<p>', '').gsub('</p>', '') }
let(:version) { parsed_report['@version'] }
it 'format ZAProxy vulnerability into common format' do
data = formatter.format(instance, hostname)
expect(data['category']).to eq('dast')
expect(data['message']).to eq('X-Content-Type-Options Header Missing')
expect(data['description']).to eq(sanitized_desc)
expect(data['cve']).to eq('10021')
expect(data['severity']).to eq('low')
expect(data['confidence']).to eq('medium')
expect(data['solution']).to eq(sanitized_solution)
expect(data['scanner']).to eq({ 'id' => 'zaproxy', 'name' => 'ZAProxy' })
expect(data['links']).to eq([{ 'url' => 'http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx' },
{ 'url' => 'https://www.owasp.org/index.php/List_of_useful_HTTP_headers' }])
expect(data['identifiers'][0]).to eq({
'type' => 'ZAProxy_PluginId',
'name' => 'X-Content-Type-Options Header Missing',
'value' => '10021',
'url' => "https://github.com/zaproxy/zaproxy/blob/w2019-01-14/docs/scanners.md"
})
expect(data['identifiers'][1]).to eq({
'type' => 'CWE',
'name' => "CWE-16",
'value' => '16',
'url' => "https://cwe.mitre.org/data/definitions/16.html"
})
expect(data['identifiers'][2]).to eq({
'type' => 'WASC',
'name' => "WASC-15",
'value' => '15',
'url' => "http://projects.webappsec.org/w/page/13246974/Threat%20Classification%20Reference%20Grid"
})
expect(data['location']).to eq({
'param' => 'X-Content-Type-Options',
'method' => 'GET',
'hostname' => hostname,
'path' => '/'
})
end
end
describe '#severity' do
using RSpec::Parameterized::TableSyntax
where(:severity, :expected) do
'0' | 'info'
'1' | 'low'
'2' | 'medium'
'3' | 'high'
'42' | 'unknown'
'' | 'unknown'
end
with_them do
it 'substitutes with right values' do
expect(formatter.send(:severity, severity)).to eq(expected)
end
end
end
describe '#confidence' do
using RSpec::Parameterized::TableSyntax
where(:confidence, :expected) do
'0' | 'ignore'
'1' | 'low'
'2' | 'medium'
'3' | 'high'
'4' | 'confirmed'
'42' | 'unknown'
'' | 'unknown'
end
with_them do
it 'substitutes with right values' do
expect(formatter.send(:confidence, confidence)).to eq(expected)
end
end
end
end
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