Skip to content

Commit 9d92da9

Browse files
authored
Merge pull request #1307 from CVEProject/dr_relcheck
ERLCheck - Vulnrichment validator
2 parents c477675 + f160f27 commit 9d92da9

File tree

12 files changed

+360
-9
lines changed

12 files changed

+360
-9
lines changed

api-docs/openapi.json

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
"info": {
44
"version": "2.5.0",
55
"title": "CVE Services API",
6-
"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>",
6+
"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>",
77
"contact": {
88
"name": "CVE Services Overview",
9-
"url": "https://cveproject.github.io/automation-cve-services#services-overview"
9+
"url": "https://www.cve.org/AllResources/CveServices"
1010
}
1111
},
1212
"servers": [
@@ -1323,6 +1323,9 @@
13231323
},
13241324
{
13251325
"$ref": "#/components/parameters/apiSecretHeader"
1326+
},
1327+
{
1328+
"$ref": "#/components/parameters/erlCheck"
13261329
}
13271330
],
13281331
"responses": {
@@ -1432,6 +1435,9 @@
14321435
},
14331436
{
14341437
"$ref": "#/components/parameters/apiSecretHeader"
1438+
},
1439+
{
1440+
"$ref": "#/components/parameters/erlCheck"
14351441
}
14361442
],
14371443
"responses": {
@@ -3036,6 +3042,15 @@
30363042
"type": "string"
30373043
}
30383044
},
3045+
"erlCheck": {
3046+
"in": "query",
3047+
"name": "erlcheck",
3048+
"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.",
3049+
"required": false,
3050+
"schema": {
3051+
"type": "boolean"
3052+
}
3053+
},
30393054
"batch_type": {
30403055
"in": "query",
30413056
"name": "batch_type",

src/controller/cve.controller/cve.controller.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const errors = require('./error')
44
const getConstants = require('../../constants').getConstants
55
const error = new errors.CveControllerError()
66
const booleanIsTrue = require('../../utils/utils').booleanIsTrue
7+
const isEnrichedContainer = require('../../utils/utils').isEnrichedContainer
78
const url = process.env.NODE_ENV === 'staging' ? 'https://test.cve.org/' : 'https://cve.org/'
89

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

462+
// To avoid breaking legacy behavior in the "booleanIsTrue" function, we need to check to make sure that undefined is set to false
463+
let erlCheck
464+
if (typeof req.query.erlcheck === 'undefined') {
465+
erlCheck = false
466+
} else {
467+
erlCheck = booleanIsTrue(req.query.erlcheck) || false
468+
}
469+
461470
// check that cve id exists
462471
let result = await cveIdRepo.findOneByCveId(id)
463472
if (!result || result.state === CONSTANTS.CVE_STATES.AVAILABLE) {
@@ -477,10 +486,15 @@ async function submitCna (req, res, next) {
477486
return res.status(403).json(error.cveRecordExists())
478487
}
479488

489+
const cnaContainer = req.ctx.body.cnaContainer
490+
if (erlCheck && !isEnrichedContainer(cnaContainer)) {
491+
// Process the ERL check here
492+
return res.status(403).json(error.erlCheckFailed())
493+
}
494+
480495
// create full cve record here
481496
const owningCna = await orgRepo.findOneByUUID(cveId.owning_cna)
482497
const assignerShortName = owningCna.short_name
483-
const cnaContainer = req.ctx.body.cnaContainer
484498
const dateUpdated = (new Date()).toISOString()
485499
const additionalCveMetadataFields = {
486500
assignerShortName: assignerShortName,
@@ -541,6 +555,14 @@ async function updateCna (req, res, next) {
541555
const orgUuid = await orgRepo.getOrgUUID(req.ctx.org)
542556
const userUuid = await userRepo.getUserUUID(req.ctx.user, orgUuid)
543557

558+
// To avoid breaking legacy behavior in the "booleanIsTrue" function, we need to check to make sure that undefined is set to false
559+
let erlCheck
560+
if (typeof req.query.erlcheck === 'undefined') {
561+
erlCheck = false
562+
} else {
563+
erlCheck = booleanIsTrue(req.query.erlcheck) || false
564+
}
565+
544566
// check that cve id exists
545567
let result = await cveIdRepo.findOneByCveId(id)
546568
if (!result || result.state === CONSTANTS.CVE_STATES.AVAILABLE) {
@@ -560,9 +582,14 @@ async function updateCna (req, res, next) {
560582
return res.status(403).json(error.cveRecordDne())
561583
}
562584

585+
const cnaContainer = req.ctx.body.cnaContainer
586+
if (erlCheck && !isEnrichedContainer(cnaContainer)) {
587+
// Process the ERL check here
588+
return res.status(403).json(error.erlCheckFailed())
589+
}
590+
563591
// update cve record here
564592
const cveRecord = result.cve
565-
const cnaContainer = req.ctx.body.cnaContainer
566593
const dateUpdated = (new Date()).toISOString()
567594
cveRecord.cveMetadata.dateUpdated = dateUpdated
568595

src/controller/cve.controller/error.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ class CveControllerError extends idrErr.IDRError {
111111
err.message = 'The ADP data does not begin with adpContainer.'
112112
return err
113113
}
114+
115+
erlCheckFailed () {
116+
const err = {}
117+
err.error = 'ERL_CHECK_FAILED'
118+
err.message = 'The ERL check failed. The CNA container is not enriched and the ERLCheck flag is set.'
119+
return err
120+
}
114121
}
115122

116123
module.exports = {

src/controller/cve.controller/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,8 @@ router.post('/cve/:id/cna',
549549
#swagger.parameters['$ref'] = [
550550
'#/components/parameters/apiEntityHeader',
551551
'#/components/parameters/apiUserHeader',
552-
'#/components/parameters/apiSecretHeader'
552+
'#/components/parameters/apiSecretHeader',
553+
'#/components/parameters/erlCheck'
553554
]
554555
#swagger.requestBody = {
555556
description: '<h3>Notes:</h3>
@@ -620,6 +621,8 @@ router.post('/cve/:id/cna',
620621
mw.validateUser,
621622
mw.onlyCnas,
622623
mw.trimJSONWhitespace,
624+
query().custom((query) => { return mw.validateQueryParameterNames(query, ['erlcheck']) }),
625+
query(['erlcheck']).optional().isBoolean({ loose: true }).withMessage(errorMsgs.ERLCHECK),
623626
validateCveCnaContainerJsonSchema,
624627
validateUniqueEnglishEntry('cnaContainer.descriptions'),
625628
validateDescription(['cnaContainer.descriptions', 'cnaContainer.problemTypes[0].descriptions']),
@@ -645,7 +648,8 @@ router.put('/cve/:id/cna',
645648
#swagger.parameters['$ref'] = [
646649
'#/components/parameters/apiEntityHeader',
647650
'#/components/parameters/apiUserHeader',
648-
'#/components/parameters/apiSecretHeader'
651+
'#/components/parameters/apiSecretHeader',
652+
'#/components/parameters/erlCheck'
649653
]
650654
#swagger.requestBody = {
651655
description: '<h3>Notes:</h3>
@@ -717,6 +721,8 @@ router.put('/cve/:id/cna',
717721
mw.validateUser,
718722
mw.onlyCnas,
719723
mw.trimJSONWhitespace,
724+
query().custom((query) => { return mw.validateQueryParameterNames(query, ['erlcheck']) }),
725+
query(['erlcheck']).optional().isBoolean({ loose: true }).withMessage(errorMsgs.ERLCHECK),
720726
validateCveCnaContainerJsonSchema,
721727
validateUniqueEnglishEntry('cnaContainer.descriptions'),
722728
validateDescription(['cnaContainer.descriptions', 'cnaContainer.problemTypes[0].descriptions']),

src/middleware/errorMessages.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module.exports = {
1010
COUNT_ONLY: 'Invalid count_only value. Value should be 1, true, or yes to indicate true, or 0, false, or no to indicate false',
1111
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.",
1212
CNA_MODIFIED: 'Invalid cna_modified value. Value should be 1, true, or yes to indicate true, or 0, false, or no to indicate false',
13+
ERLCHECK: 'Invalid erlcheck value. Value should be 1, true, or yes to indicate true, or 0, false, or no to indicate false',
1314
FIRSTNAME_LENGTH: 'Invalid name.first. Name must be between 1 and 100 characters in length.',
1415
LASTNAME_LENGTH: 'Invalid name.last. Name must be between 1 and 100 characters in length.',
1516
MIDDLENAME_LENGTH: 'Invalid name.middle. Name must be between 1 and 100 characters in length.',

src/swagger.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ const doc = {
3434
or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/mitre'>MITRE</a>) to request credentials \
3535
</ul> \
3636
<p>CVE data is to be in the JSON 5.1 CVE Record format. Details of the JSON 5.1 schema are \
37-
located <a href='https://github.com/CVEProject/cve-schema/tree/5.1.1-rc2/schema' target='_blank'>here</a>.</p>\
37+
located <a href='https://github.com/CVEProject/cve-schema/tree/v5.1.1-rc2/schema' target='_blank'>here</a>.</p>\
3838
<a href='https://cveform.mitre.org/' class='link' target='_blank'>Contact the CVE Services team</a>",
3939
contact: {
4040
name: 'CVE Services Overview',
41-
url: 'https://cveproject.github.io/automation-cve-services#services-overview'
41+
url: 'https://www.cve.org/AllResources/CveServices'
4242

4343
}
4444
},
@@ -168,6 +168,15 @@ const doc = {
168168
type: 'string'
169169
}
170170
},
171+
erlCheck: {
172+
in: 'query',
173+
name: 'erlcheck',
174+
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.',
175+
required: false,
176+
schema: {
177+
type: 'boolean'
178+
}
179+
},
171180
batch_type: {
172181
in: 'query',
173182
name: 'batch_type',

src/utils/utils.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ function reqCtxMapping (req, keyType, keys) {
123123
}
124124

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

157+
function isEnrichedContainer (container) {
158+
const hasCvss = container?.metrics?.some(item => 'cvssV4_0' in item || 'cvssV3_1' in item || 'cvssV3_0' in item || 'cvssV2_0' in item)
159+
const hasCwe = container?.problemTypes?.some(pItem => pItem?.descriptions?.some(dItem => 'cweId' in dItem))
160+
if (!(hasCvss && hasCwe)) {
161+
return false
162+
}
163+
return true
164+
}
165+
156166
module.exports = {
157167
isSecretariat,
158168
isBulkDownload,
159169
isAdmin,
160170
isAdminUUID,
161171
isSecretariatUUID,
172+
isEnrichedContainer,
162173
getOrgUUID,
163174
getUserUUID,
164175
reqCtxMapping,

test/integration-tests/constants.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,114 @@ const testAdp2 = {
241241
}
242242
}
243243

244+
const enrichedCve = {
245+
246+
cnaContainer: {
247+
affected: [
248+
{
249+
vendor: 'n/da',
250+
product: 'n/a',
251+
versions: [
252+
{
253+
version: 'n/a',
254+
status: 'unknown'
255+
}
256+
]
257+
}
258+
],
259+
descriptions: [
260+
{
261+
lang: 'en',
262+
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."
263+
}
264+
],
265+
problemTypes: [
266+
{
267+
descriptions: [
268+
{
269+
description: 'n/a',
270+
lang: 'eng',
271+
type: 'text',
272+
cweId: 'CWE-79'
273+
}
274+
]
275+
}
276+
],
277+
providerMetadata: {
278+
orgId: '9cbfeea8-dea2-4923-b772-1ab41730e742'
279+
},
280+
references: [
281+
{
282+
name: '[oss-security] 20170202 Re: CVE request: multiples vulnerabilities in Revive Adserver',
283+
url: 'http://www.openwall.com/lists/oss-security/2017/02/02/3'
284+
},
285+
{
286+
name: 'https://www.revive-adserver.com/security/revive-sa-2017-001/',
287+
url: 'https://www.revive-adserver.com/security/revive-sa-2017-001/'
288+
},
289+
{
290+
name: '95dsf875',
291+
url: 'http://www.securityfocus.com/bid/95875'
292+
}
293+
],
294+
metrics: [
295+
{
296+
format: 'CVSS',
297+
scenarios: [
298+
{
299+
lang: 'en',
300+
value: 'GENERAL'
301+
}
302+
],
303+
cvssV4_0: {
304+
baseScore: 7.8,
305+
baseSeverity: 'HIGH',
306+
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',
307+
version: '4.0'
308+
},
309+
cvssV3_1: {
310+
version: '3.1',
311+
attackVector: 'NETWORK',
312+
attackComplexity: 'LOW',
313+
privilegesRequired: 'NONE',
314+
userInteraction: 'NONE',
315+
scope: 'UNCHANGED',
316+
confidentialityImpact: 'HIGH',
317+
integrityImpact: 'HIGH',
318+
availabilityImpact: 'HIGH',
319+
baseScore: 9.8,
320+
baseSeverity: 'CRITICAL',
321+
vectorString: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H'
322+
}
323+
},
324+
{
325+
format: 'CVSS',
326+
scenarios: [
327+
{
328+
lang: 'en',
329+
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."
330+
}
331+
],
332+
cvssV3_1: {
333+
version: '3.1',
334+
attackVector: 'NETWORK',
335+
attackComplexity: 'LOW',
336+
privilegesRequired: 'NONE',
337+
userInteraction: 'NONE',
338+
scope: 'UNCHANGED',
339+
confidentialityImpact: 'LOW',
340+
integrityImpact: 'LOW',
341+
availabilityImpact: 'LOW',
342+
baseScore: 7.3,
343+
baseSeverity: 'HIGH',
344+
vectorString: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L'
345+
}
346+
}
347+
]
348+
}
349+
350+
}
351+
244352
const testOrg = {
245353

246354
short_name: 'test_org',
@@ -278,6 +386,7 @@ module.exports = {
278386
nonSecretariatUserHeadersWithAdp2,
279387
testCve,
280388
testCveEdited,
389+
enrichedCve,
281390
testAdp,
282391
testAdp2,
283392
testOrg,

test/integration-tests/cve-id/getCveIdTest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const app = require('../../../src/index.js')
1212

1313
describe('Testing Get CVE-ID endpoint', () => {
1414
// TODO: Update this test to dynamically calculate reserved count.
15-
const RESESRVED_COUNT = 120
15+
const RESESRVED_COUNT = 122
1616
const YEAR_COUNT = 10
1717
const PUB_YEAR_COUNT = 4
1818
const TIME_WINDOW_COUNT = 40

0 commit comments

Comments
 (0)