Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading