Last updated
// Validate HMAC
const h1Secret = secrets.h1_webhook_secret;
const h1Signature = String.replace(data.headers['X-H1-Signature'], "SHA256=", '');
const payloadHMAC = String.toLowerCase(String.hmac(data.body, h1Secret, "SHA256", "base16"));
if (h1Signature !== payloadHMAC) {
Logger.info('h1Signature: ' + h1Signature);
Logger.info('hmac: ' + payloadHMAC);
return {
decision: {
status: 'abort',
message: 'Invalid HMAC',
}
};
}
if (data.jsonBody?.data?.report?.id) {
return {
decision: {
status: 'continue',
message: 'Fetching HackerOne Report',
},
request: {
url: 'https://api.hackerone.com/v1/reports/' + data.jsonBody.data.report.id
}
};
}
else {
return {
decision: {
status: 'abort',
message: 'Missing "data.report.id"',
}
};
}
let body;
if (response.headers['Content-Type'] === 'application/json; charset=utf-8') {
body = JSON.parse(response.body);
}
else {
return {
decision: {
status: 'abort',
message: 'Content-Type is expected to be application/json; charset=utf-8'
}
};
}
if (response.statusCode === 200 && body?.data) {
return {
decision: {
status: 'continue',
message: 'Retrieved HackerOne Report'
},
data: {
report: body.data
}
};
}
else {
Logger.error(JSON.stringify(response));
return {
decision: {
status: 'abort',
message: 'Failed retrieving HackerOne Report'
},
};
}let cweId = undefined;
if (data.report?.relationships?.weakness?.data?.attributes?.external_id) {
const cweField = data.report.relationships.weakness.data.attributes.external_id;
if (String.startsWith(cweField, 'cwe-')) {
cweId = String.replace(cweField, "cwe-", "");
}
}
if (cweId) {
return {
decision: {
status: 'continue',
message: 'Fetching CWE [ ' + cweId + ' ] details',
},
request: {
url: 'https://cwe-api.mitre.org/api/v1/cwe/weakness/' + cweId
},
data: {
report: data.report
}
};
}
else {
return {
decision: {
status: 'next',
message: 'No CWE found. Skip fetching CWE details.',
}
};
}
let body;
if (response.headers['Content-Type'] === 'application/json; charset=UTF-8') {
body = JSON.parse(response.body);
}
else {
return {
decision: {
status: 'abort',
message: 'Content-Type is expected to be application/json; charset=UTF-8'
}
};
}
if (response.statusCode === 200 && body?.Weaknesses?[0]) {
return {
decision: {
status: 'continue',
message: 'Retrieved CWE details'
},
data: {
report: data.report,
cwe: body?.Weaknesses[0]
}
};
}
else {
Logger.error(JSON.stringify(response));
return {
decision: {
status: 'abort',
message: 'Failed retrieving CWE details'
},
};
}if (data.report) {
const report = data.report;
const cwe = data.cwe;
return {
decision: {
status: 'continue',
message: 'Creating Vulnerability',
},
request: {
body: buildRequestBody(report, cwe)
},
data: {}
};
}
else {
return {
decision: {
status: 'abort',
message: 'Missing HackerOne Report',
}
};
}
function buildRequestBody (report, cwe) {
const vuln = {
projectId: "685500711d6a44e61f90db4e",
title: "New Submission",
affected_asset_name: "AttackForge-TEST H1B",
priority: "Info",
likelihood_of_exploitation: 1,
description: "TBD",
attack_scenario: "TBD",
remediation_recommendation: "TBD",
steps_to_reproduce: "TBD",
tags: [
'HackerOne',
'H1'
],
is_visible: true,
custom_fields: []
};
if (cwe) {
if (cwe.ID) {
const name = cwe.ID;
Array.push(vuln.tags, 'CWE-' + cwe.ID);
}
if (cwe.Name) {
const name = cwe.Name;
vuln.title = name;
}
if (cwe.Description) {
let description = cwe.Description;
vuln.description = formatVulnInfo(description);
}
if (cwe.ExtendedDescription) {
let extendedDescription = cwe.ExtendedDescription;
vuln.description = vuln.description + ' ' + formatVulnInfo(extendedDescription);
}
if (cwe.BackgroundDetails) {
const backgroundDetails = cwe.BackgroundDetails;
vuln.attack_scenario = "";
if (Array.isArray(backgroundDetails)) {
for (let x = 0; x < backgroundDetails.length; x++) {
const attackScenario = backgroundDetails[x];
vuln.attack_scenario = vuln.attack_scenario + formatVulnInfo(attackScenario);
}
}
else {
vuln.attack_scenario = vuln.attack_scenario + formatVulnInfo(backgroundDetails);
}
}
if (cwe.PotentialMitigations) {
const potentialMitigations = cwe.PotentialMitigations;
vuln.remediation_recommendation = "";
if (Array.isArray(potentialMitigations)) {
for (let x = 0; x < potentialMitigations.length; x++) {
const mitigation = potentialMitigations[x];
if (mitigation.Description) {
vuln.remediation_recommendation = vuln.remediation_recommendation
+ formatVulnInfo(mitigation.Description);
}
}
}
else {
if (potentialMitigations.Description) {
vuln.remediation_recommendation = vuln.remediation_recommendation
+ formatVulnInfo(potentialMitigations.Description);
}
}
}
if (report.attributes?.title && report.attributes.vulnerability_information) {
vuln.steps_to_reproduce = '<p>'
+ report.attributes.title + '</p><p>'
+ formatVulnInfo(report.attributes.vulnerability_information)
+ '</p>';
}
}
else {
if (report.attributes?.title) {
vuln.title = report.attributes.title;
}
if (report.attributes?.vulnerability_information) {
vuln.description = formatVulnInfo(report.attributes.vulnerability_information);
}
}
if (report.id) {
Array.push(vuln.custom_fields, {
key: "hackerone_report_id",
value: report.id
});
if (report.relationships?.program?.data?.attributes?.handle) {
const handle = report.relationships.program.data.attributes.handle;
Array.push(vuln.custom_fields, {
key: "hackerone_report_url",
value: 'https://hackerone.com/bugs?report_id=' + report.id + '&subject=' + handle
});
}
}
if (report.relationships?.severity?.data?.attributes) {
const severity = report.relationships.severity.data.attributes;
if (severity.rating === 'critical') {
vuln.priority = 'Critical';
}
else if (severity.rating === 'high') {
vuln.priority = 'High';
}
else if (severity.rating === 'medium') {
vuln.priority = 'Medium';
}
else if (severity.rating === 'low') {
vuln.priority = 'Low';
}
if (severity.score && severity.cvss_vector_string) {
vuln.likelihood_of_exploitation = Number.parseInt(Math.ceil(severity.score));
Array.push(vuln.tags, 'CVSSv3.1 Base Score: ' + severity.score);
Array.push(vuln.tags, severity.cvss_vector_string);
}
}
return vuln;
};
function formatVulnInfo (value) {
if (String.startsWith(value, "\n\n")) {
value = String.substring(value, 2);
}
else if (String.startsWith(value, "\n")) {
value = String.substring(value, 1);
}
value = String.replaceAll(value, "\n\n", "<br/>");
value = String.replaceAll(value, "\n", "<br/>");
return value;
};let body;
if (response.headers['Content-Type'] === 'application/json') {
body = JSON.parse(response.body);
}
else {
return {
decision: {
status: 'abort',
message: 'Content-Type is expected to be application/json'
}
};
}
if (response.statusCode === 200 && body?.vulnerability?.vulnerability_id) {
return {
decision: {
status: 'finish',
message: 'Created Vulnerability'
}
};
}
else {
Logger.error(JSON.stringify(response));
return {
decision: {
status: 'abort',
message: 'Failed creating Vulnerability'
},
};
}