Commit 3be73a7e authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'make-dast-request-and-response-available-to-frontend' into 'master'

Expose DAST request and response evidence

Closes #215679

See merge request gitlab-org/gitlab!31562
parents efa1eed7 9c93f8ad
......@@ -255,7 +255,19 @@ module Vulnerabilities
end
def evidence
metadata.dig('evidence', 'summary')
{
summary: metadata.dig('evidence', 'summary'),
request: {
headers: metadata.dig('evidence', 'request', 'headers') || [],
method: metadata.dig('evidence', 'request', 'method'),
url: metadata.dig('evidence', 'request', 'url')
},
response: {
headers: metadata.dig('evidence', 'response', 'headers') || [],
status_code: metadata.dig('evidence', 'response', 'status_code'),
reason_phrase: metadata.dig('evidence', 'response', 'reason_phrase')
}
}
end
def message
......
......@@ -28,7 +28,9 @@ class Vulnerabilities::FindingEntity < Grape::Entity
expose :location
expose :remediations
expose :solution
expose :evidence
expose(:evidence) { |model, _| model.evidence[:summary] }
expose(:request, using: Vulnerabilities::RequestEntity) { |model, _| model.evidence[:request] }
expose(:response, using: Vulnerabilities::ResponseEntity) { |model, _| model.evidence[:response] }
end
expose :state
......
# frozen_string_literal: true
class Vulnerabilities::RequestEntity < Grape::Entity
expose :headers
expose :method
expose :url
end
# frozen_string_literal: true
class Vulnerabilities::ResponseEntity < Grape::Entity
expose :headers
expose :reason_phrase
expose :status_code
end
......@@ -13,8 +13,8 @@ FactoryBot.define do
after(:build) do |finding, evaluator|
if evaluator.summary
raw_metadata = Gitlab::Json.parse(finding.raw_metadata)
raw_metadata.delete("solution")
raw_metadata["remediations"] = [
raw_metadata.delete('solution')
raw_metadata['remediations'] = [
{
summary: evaluator.summary
}
......@@ -38,25 +38,35 @@ FactoryBot.define do
metadata_version { 'sast:1.0' }
raw_metadata do
{
description: "The cipher does not provide data integrity update 1",
message: "The cipher does not provide data integrity",
cve: "818bf5dacb291e15d9e6dc3c5ac32178:CIPHER",
solution: "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
description: 'The cipher does not provide data integrity update 1',
message: 'The cipher does not provide data integrity',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:CIPHER',
solution: 'GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.',
location: {
file: "maven/src/main/java/com/gitlab/security_products/tests/App.java",
file: 'maven/src/main/java/com/gitlab/security_products/tests/App.java',
start_line: 29,
end_line: 29,
class: "com.gitlab.security_products.tests.App",
method: "insecureCypher"
class: 'com.gitlab.security_products.tests.App',
method: 'insecureCypher'
},
links: [
{
name: "Cipher does not check for integrity first?",
url: "https://crypto.stackexchange.com/questions/31428/pbewithmd5anddes-cipher-does-not-check-for-integrity-first"
name: 'Cipher does not check for integrity first?',
url: 'https://crypto.stackexchange.com/questions/31428/pbewithmd5anddes-cipher-does-not-check-for-integrity-first'
}
],
evidence: {
summary: 'Credit card detected'
summary: 'Credit card detected',
request: {
headers: [{ name: 'Accept', value: '*/*' }],
method: 'GET',
url: 'http://goat:8080/WebGoat/logout'
},
response: {
headers: [{ name: 'Content-Length', value: '0' }],
reason_phrase: 'OK',
status_code: 200
}
}
}.to_json
end
......@@ -97,7 +107,7 @@ FactoryBot.define do
raw_metadata.delete(:solution)
raw_metadata[:remediations] = [
{
summary: "Use GCM mode which includes HMAC in the resulting encrypted data, providing integrity of the result."
summary: 'Use GCM mode which includes HMAC in the resulting encrypted data, providing integrity of the result.'
}
]
finding.raw_metadata = raw_metadata.to_json
......
......@@ -1016,7 +1016,94 @@
"category": "dast",
"confidence": "medium",
"cve": "10010",
"description": "A cookie has been set without the HttpOnly flag, which means that the cookie can be accessed by JavaScript. If a malicious script can be run on this page then the cookie will be accessible and can be transmitted to another site. If this is a session cookie then session hijacking may be possible.",
"description": "A cookie has been set without the HttpOnly flag, which means that the cookie can be accessed by JavaScript. If a malicious script can be run on this page then the cookie will be accessible and can be transmitted to another site. If this is a session cookie then session hijacking may be possible.",
"evidence": {
"request": {
"headers": [
{
"name": "Accept",
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
},
{
"name": "Accept-Language",
"value": "en-US,en;q=0.5"
},
{
"name": "Connection",
"value": "keep-alive"
},
{
"name": "Cookie",
"value": "JSESSIONID=BB38BE6D77E83FA7522D1429AA5F2EAF"
},
{
"name": "Host",
"value": "goat:8080"
},
{
"name": "Referer",
"value": "http://goat:8080/WebGoat/logout"
},
{
"name": "Upgrade-Insecure-Requests",
"value": "1"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (X11; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0"
}
],
"method": "GET",
"url": "http://goat:8080/WebGoat/logout"
},
"response": {
"headers": [
{
"name": "Connection",
"value": "keep-alive"
},
{
"name": "Content-Length",
"value": "0"
},
{
"name": "Date",
"value": "Thu, 07 May 2020 07:18:06 GMT"
},
{
"name": "Gitlab-DAST-Permission",
"value": "allow"
},
{
"name": "Location",
"value": "http://goat:8080/WebGoat/login"
},
{
"name": "Server",
"value": "nginx/1.17.6"
},
{
"name": "Set-Cookie",
"value": "JSESSIONID=1624E8274D30BB5A029F5A18BB7E4400; Path=/WebGoat"
},
{
"name": "X-Content-Type-Options",
"value": "nosniff"
},
{
"name": "X-Frame-Options",
"value": "DENY"
},
{
"name": "X-XSS-Protection",
"value": "1; mode=block"
}
],
"reason_phrase": "Found",
"status_code": 302
},
"summary": "Set-Cookie: JSESSIONID"
},
"identifiers": [
{
"name": "Cookie No HttpOnly Flag",
......
......@@ -596,22 +596,45 @@ describe Vulnerabilities::Occurrence do
end
describe '#evidence' do
it 'has an evidence summary when present' do
occurrence = create(:vulnerabilities_occurrence)
expect(occurrence.evidence).to eq(occurrence.metadata['evidence']['summary'])
end
it 'has no evidence summary when evidence is present, summary is not' do
occurrence = create(:vulnerabilities_occurrence, raw_metadata: { evidence: {} })
expect(occurrence.evidence).to be_nil
subject { occurrence.evidence }
context 'has an evidence fields' do
let(:occurrence) { create(:vulnerabilities_occurrence) }
let(:evidence) { occurrence.metadata['evidence'] }
it do
is_expected.to match a_hash_including(
summary: evidence['summary'],
request: {
headers: evidence['request']['headers'],
url: evidence['request']['url'],
method: evidence['request']['method']
},
response: {
headers: evidence['response']['headers'],
reason_phrase: evidence['response']['reason_phrase'],
status_code: evidence['response']['status_code']
})
end
end
it 'has no evidence summary when evidence is not present' do
occurrence = create(:vulnerabilities_occurrence, raw_metadata: {})
expect(occurrence.evidence).to be_nil
context 'has no evidence summary when evidence is present, summary is not' do
let(:occurrence) { create(:vulnerabilities_occurrence, raw_metadata: { evidence: {} }) }
it do
is_expected.to match a_hash_including(
summary: nil,
request: {
headers: [],
url: nil,
method: nil
},
response: {
headers: [],
reason_phrase: nil,
status_code: nil
})
end
end
end
......
......@@ -55,7 +55,7 @@ describe Vulnerabilities::FindingEntity do
expect(subject).to include(:scanner, :project, :identifiers)
expect(subject).to include(:dismissal_feedback, :issue_feedback)
expect(subject).to include(:description, :links, :location, :remediations, :solution, :evidence)
expect(subject).to include(:blob_path)
expect(subject).to include(:blob_path, :request, :response)
end
context 'when not allowed to admin vulnerability feedback' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Vulnerabilities::RequestEntity do
let(:request) { create(:vulnerabilities_occurrence).evidence[:request] }
let(:entity) do
described_class.represent(request)
end
describe '#as_json' do
subject { entity.as_json }
it 'contains required fields' do
expect(subject).to include(:headers, :method, :url)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Vulnerabilities::ResponseEntity do
let(:response) { create(:vulnerabilities_occurrence).evidence[:response] }
let(:entity) do
described_class.represent(response)
end
describe '#as_json' do
subject { entity.as_json }
it 'contains required fields' do
expect(subject).to include(:headers, :reason_phrase, :status_code)
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