Skip to content

Commit

Permalink
Merge pull request #1307 from CVEProject/dr_relcheck
Browse files Browse the repository at this point in the history
ERLCheck - Vulnrichment validator
  • Loading branch information
jdaigneau5 authored Dec 20, 2024
2 parents c477675 + f160f27 commit 9d92da9
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 9 deletions.
19 changes: 17 additions & 2 deletions api-docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"info": {
"version": "2.5.0",
"title": "CVE Services API",
"description": "The CVE Services API supports automation tooling for the CVE Program. Credentials are required for most service endpoints. Representatives of <a href='https://www.cve.org/ProgramOrganization/CNAs'>CVE Numbering Authorities (CNAs)</a> should use one of the methods below to obtain credentials: <ul><li>If your organization already has an Organizational Administrator (OA) account for the CVE Services, ask your admin for credentials</li> <li>Contact your Root (<a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/Google'>Google</a>, <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/INCIBE'>INCIBE</a>, <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/jpcert'>JPCERT/CC</a>, or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/redhat'>Red Hat</a>) or Top-Level Root (<a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/icscert'>CISA ICS</a> or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/mitre'>MITRE</a>) to request credentials </ul> <p>CVE data is to be in the JSON 5.1 CVE Record format. Details of the JSON 5.1 schema are located <a href='https://github.com/CVEProject/cve-schema/tree/5.1.1-rc2/schema' target='_blank'>here</a>.</p> <a href='https://cveform.mitre.org/' class='link' target='_blank'>Contact the CVE Services team</a>",
"description": "The CVE Services API supports automation tooling for the CVE Program. Credentials are required for most service endpoints. Representatives of <a href='https://www.cve.org/ProgramOrganization/CNAs'>CVE Numbering Authorities (CNAs)</a> should use one of the methods below to obtain credentials: <ul><li>If your organization already has an Organizational Administrator (OA) account for the CVE Services, ask your admin for credentials</li> <li>Contact your Root (<a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/Google'>Google</a>, <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/INCIBE'>INCIBE</a>, <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/jpcert'>JPCERT/CC</a>, or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/redhat'>Red Hat</a>) or Top-Level Root (<a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/icscert'>CISA ICS</a> or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/mitre'>MITRE</a>) to request credentials </ul> <p>CVE data is to be in the JSON 5.1 CVE Record format. Details of the JSON 5.1 schema are located <a href='https://github.com/CVEProject/cve-schema/tree/v5.1.1-rc2/schema' target='_blank'>here</a>.</p> <a href='https://cveform.mitre.org/' class='link' target='_blank'>Contact the CVE Services team</a>",
"contact": {
"name": "CVE Services Overview",
"url": "https://cveproject.github.io/automation-cve-services#services-overview"
"url": "https://www.cve.org/AllResources/CveServices"
}
},
"servers": [
Expand Down Expand Up @@ -1323,6 +1323,9 @@
},
{
"$ref": "#/components/parameters/apiSecretHeader"
},
{
"$ref": "#/components/parameters/erlCheck"
}
],
"responses": {
Expand Down Expand Up @@ -1432,6 +1435,9 @@
},
{
"$ref": "#/components/parameters/apiSecretHeader"
},
{
"$ref": "#/components/parameters/erlCheck"
}
],
"responses": {
Expand Down Expand Up @@ -3036,6 +3042,15 @@
"type": "string"
}
},
"erlCheck": {
"in": "query",
"name": "erlcheck",
"description": "Enables stricter validation that ensures submitted record meets enrichment data requirements. For a record to be enriched, a CVSS score and a CWE ID must be provided.",
"required": false,
"schema": {
"type": "boolean"
}
},
"batch_type": {
"in": "query",
"name": "batch_type",
Expand Down
31 changes: 29 additions & 2 deletions src/controller/cve.controller/cve.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const errors = require('./error')
const getConstants = require('../../constants').getConstants
const error = new errors.CveControllerError()
const booleanIsTrue = require('../../utils/utils').booleanIsTrue
const isEnrichedContainer = require('../../utils/utils').isEnrichedContainer
const url = process.env.NODE_ENV === 'staging' ? 'https://test.cve.org/' : 'https://cve.org/'

// Helper function to create providerMetadata object
Expand Down Expand Up @@ -458,6 +459,14 @@ async function submitCna (req, res, next) {
const orgUuid = await orgRepo.getOrgUUID(req.ctx.org)
const userUuid = await userRepo.getUserUUID(req.ctx.user, orgUuid)

// To avoid breaking legacy behavior in the "booleanIsTrue" function, we need to check to make sure that undefined is set to false
let erlCheck
if (typeof req.query.erlcheck === 'undefined') {
erlCheck = false
} else {
erlCheck = booleanIsTrue(req.query.erlcheck) || false
}

// check that cve id exists
let result = await cveIdRepo.findOneByCveId(id)
if (!result || result.state === CONSTANTS.CVE_STATES.AVAILABLE) {
Expand All @@ -477,10 +486,15 @@ async function submitCna (req, res, next) {
return res.status(403).json(error.cveRecordExists())
}

const cnaContainer = req.ctx.body.cnaContainer
if (erlCheck && !isEnrichedContainer(cnaContainer)) {
// Process the ERL check here
return res.status(403).json(error.erlCheckFailed())
}

// create full cve record here
const owningCna = await orgRepo.findOneByUUID(cveId.owning_cna)
const assignerShortName = owningCna.short_name
const cnaContainer = req.ctx.body.cnaContainer
const dateUpdated = (new Date()).toISOString()
const additionalCveMetadataFields = {
assignerShortName: assignerShortName,
Expand Down Expand Up @@ -541,6 +555,14 @@ async function updateCna (req, res, next) {
const orgUuid = await orgRepo.getOrgUUID(req.ctx.org)
const userUuid = await userRepo.getUserUUID(req.ctx.user, orgUuid)

// To avoid breaking legacy behavior in the "booleanIsTrue" function, we need to check to make sure that undefined is set to false
let erlCheck
if (typeof req.query.erlcheck === 'undefined') {
erlCheck = false
} else {
erlCheck = booleanIsTrue(req.query.erlcheck) || false
}

// check that cve id exists
let result = await cveIdRepo.findOneByCveId(id)
if (!result || result.state === CONSTANTS.CVE_STATES.AVAILABLE) {
Expand All @@ -560,9 +582,14 @@ async function updateCna (req, res, next) {
return res.status(403).json(error.cveRecordDne())
}

const cnaContainer = req.ctx.body.cnaContainer
if (erlCheck && !isEnrichedContainer(cnaContainer)) {
// Process the ERL check here
return res.status(403).json(error.erlCheckFailed())
}

// update cve record here
const cveRecord = result.cve
const cnaContainer = req.ctx.body.cnaContainer
const dateUpdated = (new Date()).toISOString()
cveRecord.cveMetadata.dateUpdated = dateUpdated

Expand Down
7 changes: 7 additions & 0 deletions src/controller/cve.controller/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ class CveControllerError extends idrErr.IDRError {
err.message = 'The ADP data does not begin with adpContainer.'
return err
}

erlCheckFailed () {
const err = {}
err.error = 'ERL_CHECK_FAILED'
err.message = 'The ERL check failed. The CNA container is not enriched and the ERLCheck flag is set.'
return err
}
}

module.exports = {
Expand Down
10 changes: 8 additions & 2 deletions src/controller/cve.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,8 @@ router.post('/cve/:id/cna',
#swagger.parameters['$ref'] = [
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
'#/components/parameters/apiSecretHeader',
'#/components/parameters/erlCheck'
]
#swagger.requestBody = {
description: '<h3>Notes:</h3>
Expand Down Expand Up @@ -620,6 +621,8 @@ router.post('/cve/:id/cna',
mw.validateUser,
mw.onlyCnas,
mw.trimJSONWhitespace,
query().custom((query) => { return mw.validateQueryParameterNames(query, ['erlcheck']) }),
query(['erlcheck']).optional().isBoolean({ loose: true }).withMessage(errorMsgs.ERLCHECK),
validateCveCnaContainerJsonSchema,
validateUniqueEnglishEntry('cnaContainer.descriptions'),
validateDescription(['cnaContainer.descriptions', 'cnaContainer.problemTypes[0].descriptions']),
Expand All @@ -645,7 +648,8 @@ router.put('/cve/:id/cna',
#swagger.parameters['$ref'] = [
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
'#/components/parameters/apiSecretHeader',
'#/components/parameters/erlCheck'
]
#swagger.requestBody = {
description: '<h3>Notes:</h3>
Expand Down Expand Up @@ -717,6 +721,8 @@ router.put('/cve/:id/cna',
mw.validateUser,
mw.onlyCnas,
mw.trimJSONWhitespace,
query().custom((query) => { return mw.validateQueryParameterNames(query, ['erlcheck']) }),
query(['erlcheck']).optional().isBoolean({ loose: true }).withMessage(errorMsgs.ERLCHECK),
validateCveCnaContainerJsonSchema,
validateUniqueEnglishEntry('cnaContainer.descriptions'),
validateDescription(['cnaContainer.descriptions', 'cnaContainer.problemTypes[0].descriptions']),
Expand Down
1 change: 1 addition & 0 deletions src/middleware/errorMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
COUNT_ONLY: 'Invalid count_only value. Value should be 1, true, or yes to indicate true, or 0, false, or no to indicate false',
TIMESTAMP_FORMAT: "Bad date, or invalid timestamp format: valid format is yyyy-MM-ddTHH:mm:ss or yyyy-MM-ddTHH:mm:ssZZ:ZZ (to use '+' in timezone offset, encode as '%2B). ZZ:ZZ (if used) must be between 00:00 and 23:59.",
CNA_MODIFIED: 'Invalid cna_modified value. Value should be 1, true, or yes to indicate true, or 0, false, or no to indicate false',
ERLCHECK: 'Invalid erlcheck value. Value should be 1, true, or yes to indicate true, or 0, false, or no to indicate false',
FIRSTNAME_LENGTH: 'Invalid name.first. Name must be between 1 and 100 characters in length.',
LASTNAME_LENGTH: 'Invalid name.last. Name must be between 1 and 100 characters in length.',
MIDDLENAME_LENGTH: 'Invalid name.middle. Name must be between 1 and 100 characters in length.',
Expand Down
13 changes: 11 additions & 2 deletions src/swagger.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ const doc = {
or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/mitre'>MITRE</a>) to request credentials \
</ul> \
<p>CVE data is to be in the JSON 5.1 CVE Record format. Details of the JSON 5.1 schema are \
located <a href='https://github.com/CVEProject/cve-schema/tree/5.1.1-rc2/schema' target='_blank'>here</a>.</p>\
located <a href='https://github.com/CVEProject/cve-schema/tree/v5.1.1-rc2/schema' target='_blank'>here</a>.</p>\
<a href='https://cveform.mitre.org/' class='link' target='_blank'>Contact the CVE Services team</a>",
contact: {
name: 'CVE Services Overview',
url: 'https://cveproject.github.io/automation-cve-services#services-overview'
url: 'https://www.cve.org/AllResources/CveServices'

}
},
Expand Down Expand Up @@ -168,6 +168,15 @@ const doc = {
type: 'string'
}
},
erlCheck: {
in: 'query',
name: 'erlcheck',
description: 'Enables stricter validation that ensures submitted record meets enrichment data requirements. For a record to be enriched, a CVSS score and a CWE ID must be provided.',
required: false,
schema: {
type: 'boolean'
}
},
batch_type: {
in: 'query',
name: 'batch_type',
Expand Down
11 changes: 11 additions & 0 deletions src/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ function reqCtxMapping (req, keyType, keys) {
}

// Return true if boolean is 0, true, or yes, with any mix of casing
// Please note that this function does NOT evaluate "undefined" as false. - A tired developer who lost way too much time to this.
function booleanIsTrue (val) {
if ((val.toString() === '1') ||
(val.toString().toLowerCase() === 'true') ||
Expand Down Expand Up @@ -153,12 +154,22 @@ function toDate (val) {
return result
}

function isEnrichedContainer (container) {
const hasCvss = container?.metrics?.some(item => 'cvssV4_0' in item || 'cvssV3_1' in item || 'cvssV3_0' in item || 'cvssV2_0' in item)
const hasCwe = container?.problemTypes?.some(pItem => pItem?.descriptions?.some(dItem => 'cweId' in dItem))
if (!(hasCvss && hasCwe)) {
return false
}
return true
}

module.exports = {
isSecretariat,
isBulkDownload,
isAdmin,
isAdminUUID,
isSecretariatUUID,
isEnrichedContainer,
getOrgUUID,
getUserUUID,
reqCtxMapping,
Expand Down
109 changes: 109 additions & 0 deletions test/integration-tests/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,114 @@ const testAdp2 = {
}
}

const enrichedCve = {

cnaContainer: {
affected: [
{
vendor: 'n/da',
product: 'n/a',
versions: [
{
version: 'n/a',
status: 'unknown'
}
]
}
],
descriptions: [
{
lang: 'en',
value: "Cross-site scdfgfdgripting (XSS) vulnerability in Revive Adserver before 4.0.1 allows remote authenticated users to inject arbitrary web script or HTML via the user's email address."
}
],
problemTypes: [
{
descriptions: [
{
description: 'n/a',
lang: 'eng',
type: 'text',
cweId: 'CWE-79'
}
]
}
],
providerMetadata: {
orgId: '9cbfeea8-dea2-4923-b772-1ab41730e742'
},
references: [
{
name: '[oss-security] 20170202 Re: CVE request: multiples vulnerabilities in Revive Adserver',
url: 'http://www.openwall.com/lists/oss-security/2017/02/02/3'
},
{
name: 'https://www.revive-adserver.com/security/revive-sa-2017-001/',
url: 'https://www.revive-adserver.com/security/revive-sa-2017-001/'
},
{
name: '95dsf875',
url: 'http://www.securityfocus.com/bid/95875'
}
],
metrics: [
{
format: 'CVSS',
scenarios: [
{
lang: 'en',
value: 'GENERAL'
}
],
cvssV4_0: {
baseScore: 7.8,
baseSeverity: 'HIGH',
vectorString: 'CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:L/SA:L',
version: '4.0'
},
cvssV3_1: {
version: '3.1',
attackVector: 'NETWORK',
attackComplexity: 'LOW',
privilegesRequired: 'NONE',
userInteraction: 'NONE',
scope: 'UNCHANGED',
confidentialityImpact: 'HIGH',
integrityImpact: 'HIGH',
availabilityImpact: 'HIGH',
baseScore: 9.8,
baseSeverity: 'CRITICAL',
vectorString: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H'
}
},
{
format: 'CVSS',
scenarios: [
{
lang: 'en',
value: "If the enhanced host protection mode is turned on, this vulnerability can only be exploited to run os commands as user 'nobody'. Privilege escalation is not possible."
}
],
cvssV3_1: {
version: '3.1',
attackVector: 'NETWORK',
attackComplexity: 'LOW',
privilegesRequired: 'NONE',
userInteraction: 'NONE',
scope: 'UNCHANGED',
confidentialityImpact: 'LOW',
integrityImpact: 'LOW',
availabilityImpact: 'LOW',
baseScore: 7.3,
baseSeverity: 'HIGH',
vectorString: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L'
}
}
]
}

}

const testOrg = {

short_name: 'test_org',
Expand Down Expand Up @@ -278,6 +386,7 @@ module.exports = {
nonSecretariatUserHeadersWithAdp2,
testCve,
testCveEdited,
enrichedCve,
testAdp,
testAdp2,
testOrg,
Expand Down
2 changes: 1 addition & 1 deletion test/integration-tests/cve-id/getCveIdTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const app = require('../../../src/index.js')

describe('Testing Get CVE-ID endpoint', () => {
// TODO: Update this test to dynamically calculate reserved count.
const RESESRVED_COUNT = 120
const RESESRVED_COUNT = 122
const YEAR_COUNT = 10
const PUB_YEAR_COUNT = 4
const TIME_WINDOW_COUNT = 40
Expand Down
Loading

0 comments on commit 9d92da9

Please sign in to comment.