1+ const fs = require ( 'fs' ) ;
2+ const path = require ( 'path' ) ;
3+ const validateCve = require ( '../Node_Validator/dist/cve5validator.js' )
4+ process . env [ "NODE_TLS_REJECT_UNAUTHORIZED" ] = 0 ;
5+
6+ const ignore = {
7+ '' : 1 ,
8+ '/cveMetadata/state' : 1 ,
9+ '/cveMetadata' : 1 ,
10+ '/dataVersion' : 1 ,
11+ '/containers/cna/references/url' : 0
12+ }
13+ var cnas = { } ;
14+ var cnaIndex = { } ;
15+ var errorStat = { } ;
16+ var warnStat = { } ;
17+ var errorCount = { } ;
18+ var yStat = { } ;
19+ var invalid = 0 ;
20+ var warns = 0 ;
21+ var total = 0 ;
22+
23+ const start = `
24+ <html><head><title>CVE Quality Report</title><style>
25+ body { font-family:Roboto Mono,sans-serif; margin:3em; }
26+ summary { cursor: pointer; }
27+ a { text-decoration: none; color: #356cac; }
28+ .grid { display:grid; gap: 5px; grid-template-columns: repeat(auto-fill, minmax(8em,1fr)); }
29+ </style></head>
30+ <body>`
31+
32+ async function loadCNAs ( data ) {
33+ for ( c of data ) {
34+ try {
35+ var em = c . contact [ 0 ] . email [ 0 ] . emailAddr ;
36+ var host = em . substr ( em . indexOf ( '@' ) + 1 ) ;
37+ u = new URL ( 'https://www.' + host ) ;
38+ c . i = u . href ;
39+ c . n = c . organizationName ;
40+ } catch ( e ) {
41+ }
42+ cnas [ c . shortName ] = c ;
43+ }
44+
45+ }
46+ async function getCNAs ( ) {
47+ //var data = require('./CNAsList.json');
48+ //loadCNAs(data); return;
49+ const cnaList = 'https://raw.githubusercontent.com/CVEProject/cve-website/dev/src/assets/data/CNAsList.json'
50+ const res = await fetch ( cnaList ) ;
51+ if ( res . ok ) {
52+ const data = await res . json ( ) ;
53+ loadCNAs ( data ) ;
54+ }
55+ }
56+
57+ function cveLink ( id ) {
58+ return 'https://github.com/CVEProject/cvelistV5/tree/main/cves/' + cveRepoPath ( id ) ;
59+ }
60+
61+ function cveRepoPath ( value ) {
62+ var realId = value . match ( / ( C V E - ( \d { 4 } ) - ( \d { 1 , 12 } ) ( \d { 3 } ) ) / ) ;
63+ if ( realId ) {
64+ var id = realId [ 1 ] ;
65+ var year = realId [ 2 ] ;
66+ var bucket = realId [ 3 ] ;
67+ return ( year + '/' + bucket + 'xxx/' + id + '.json' )
68+ }
69+ }
70+
71+ async function getReport ( dir ) {
72+ const files = fs . readdirSync ( dir , { withFileTypes : true } ) ;
73+ for ( const file of files ) {
74+ if ( file . isDirectory ( ) ) {
75+ getReport ( path . join ( dir , file . name ) ) ;
76+ } else {
77+ if ( file . name . match ( ( / C V E - ( \d { 4 } ) - ( \d { 1 , 12 } ) ( \d { 3 } ) / ) ) ) {
78+ var cveFile = fs . readFileSync ( path . join ( dir , file . name ) ) ;
79+ var cve = JSON . parse ( cveFile ) ;
80+ var v = valididate ( cve ) ;
81+ var q = qualityCheck ( cve ) ;
82+ total ++ ;
83+ if ( ! v ) {
84+ invalid ++ ;
85+ }
86+ if ( ! q ) {
87+ warns ++ ;
88+ }
89+ }
90+ }
91+ }
92+ }
93+
94+ /* Example error
95+ {
96+ instancePath: '/cveMetadata/state',
97+ schemaPath: '#/properties/state/enum',
98+ keyword: 'enum',
99+ params: { allowedValues: [Array] },
100+ message: 'must be equal to one of the allowed values'
101+ },
102+ */
103+
104+ function addError ( cve , err ) {
105+ var id = cve . cveMetadata . cveId ;
106+
107+ var shortName = cve . cveMetadata ?. assignerShortName || 'default' ;
108+ if ( ! cnaIndex [ shortName ] ) {
109+ cnaIndex [ shortName ] = [ ] ;
110+ }
111+
112+ //remove oneOf numbers
113+ var path = err ?. instancePath ?. replace ( / \/ \d + \/ ? / g, "/" ) ;
114+ if ( ! ignore [ path ] ) {
115+ var prop = err . params ?. additionalProperty || '' ;
116+ var e = `Problem: <b>${ err . keyword } ${ prop } !</b> - ${ err . message } ` ;
117+ if ( ! errorStat [ shortName ] ) {
118+ errorStat [ shortName ] = { }
119+ errorCount [ shortName ] = 0
120+ }
121+ if ( ! errorStat [ shortName ] [ path ] ) {
122+ errorStat [ shortName ] [ path ] = { }
123+ }
124+ if ( ! errorStat [ shortName ] [ path ] [ e ] ) {
125+ errorStat [ shortName ] [ path ] [ e ] = [ ]
126+ }
127+ errorStat [ shortName ] [ path ] [ e ] . push ( id ) ;
128+ errseen = true ;
129+ }
130+ }
131+
132+ function valididate ( cve ) {
133+ var valid = validateCve ( cve ) ;
134+ errseen = false ;
135+ if ( ! valid ) {
136+ validateCve . errors . forEach ( err => { addError ( cve , err ) } ) ;
137+ }
138+ return ! errseen ;
139+ }
140+
141+ function qualityCheck ( cve ) {
142+ var warned = false ;
143+ var c = checkCVSS ( cve ) ;
144+ if ( c ) {
145+ addError ( cve , c ) ;
146+ warned = true ;
147+ }
148+ /* c = checkLinkRot(cve);
149+ if(c) {
150+ addError(cve, c);
151+ warned = true;
152+ }*/
153+ if ( warned ) {
154+ return false ;
155+ } else {
156+ return true ;
157+ }
158+ }
159+
160+ const four04List = [
161+ 'www.securityfocus.com' ,
162+ 'osvdb.org' ,
163+ 'online.securityfocus.com' ,
164+ 'patches.sgi.com' ,
165+ 'docs.info.apple.com' ,
166+ 'h20000.www2.hp.com' ,
167+ 'labs.idefense.com' ,
168+ 'wiki.rpath.com' ,
169+ 'source.codeaurora.org' ,
170+ 'code.wireshark.org' ,
171+ 'h20564.www2.hp.com' ,
172+ 'www.linux-mandrake.com' ,
173+ 'erpscan.io' ,
174+ 'downloads.securityfocus.com' ,
175+ 'www.atstake.com' ,
176+ 'hermes.opensuse.org' ,
177+ 'itrc.hp.com' ,
178+ 'ftp.caldera.com' ,
179+ 'packetstorm.linuxsecurity.com' ,
180+ 'www1.itrc.hp.com'
181+ ]
182+
183+ const four04 = { } ;
184+ for ( const key of four04List ) {
185+ four04 [ key ] = 1 ;
186+ }
187+
188+ function checkLinkRot ( cve ) {
189+ if ( cve . containers ?. cna ?. references ) {
190+ for ( r of cve . containers ?. cna ?. references ) {
191+ try {
192+ var u = new URL ( r . url ) ;
193+ if ( four04 [ u . host ] && ! ( r . tags && r . tags . includes ( 'broken-link' ) ) ) {
194+ return {
195+ instancePath : '/containers/cna/references' ,
196+ schemaPath : '#/properties/url' ,
197+ keyword : 'Broken link to ' + u . host ,
198+ params : { } ,
199+ message : 'Reference points to defunct site. Replace or add a broken-link tag.'
200+ }
201+ }
202+ } catch ( e ) {
203+ console . log ( 'Error parsing URL' + r . url )
204+ }
205+ }
206+ }
207+ return false ;
208+ }
209+
210+ function checkCVSS ( cve ) {
211+ if ( cve . containers . cna ?. metrics ) {
212+ for ( m of cve . containers . cna ?. metrics ) {
213+ var cvss = m . cvssV3_1 || m . cvssV3_0 ;
214+ if ( cvss ) {
215+ if ( ( cvss . baseSeverity == 'CRITICAL' && cvss . baseScore >= 9 && cvss . baseScore <= 10 )
216+ || ( cvss . baseSeverity == 'HIGH' && cvss . baseScore >= 7 && cvss . baseScore < 9 )
217+ || ( cvss . baseSeverity == 'MEDIUM' && cvss . baseScore >= 4 && cvss . baseScore < 7 )
218+ || ( cvss . baseSeverity == 'LOW' && cvss . baseScore >= 0.1 && cvss . baseScore < 4 )
219+ || ( cvss . baseSeverity == 'NONE' && cvss . baseScore == 0 ) ) {
220+ //console.log('valid CVSS ');
221+ } else {
222+ return {
223+ instancePath : '/containers/cna/metrics' ,
224+ schemaPath : '#/properties' , // TODO?
225+ keyword : 'Bad CVSS' ,
226+ params : { } ,
227+ message : 'Mismatched CVSS score and level'
228+ }
229+ }
230+ }
231+ }
232+ }
233+ return false ;
234+ }
235+
236+ async function checkAffected ( cve ) {
237+
238+ }
239+
240+
241+ run ( process . argv [ 2 ] ) ;
242+
243+ async function run ( dir ) {
244+ await getCNAs ( ) ;
245+ await getReport ( dir ) ;
246+ await printReport ( ) ;
247+ }
248+
249+
250+ const docs = {
251+ '/containers/cna/affected/product:maxLength' : "Product name is too long! If you are listing multiple products, please use separate product objects." ,
252+ '/containers/cna/affected/product:minLength' : "A product name is required." ,
253+ '/containers/cna/affected/versions/version:maxLength' : "Version name is too long! If you are listing multiple versions, please encode as an array of version objects." ,
254+ '/containers/cna/metrics/cvssV3_0:required' : "CVSS objects are incomplete. Please provide a valid vectorString at the minimum in your CVE-JSON v4 submission."
255+ }
256+
257+
258+ function printReport ( ) {
259+ console . log ( start +
260+ `<h2>CVE Quality Workgroup Report</h2><h3> ${ total } CVE analyzed: Found ${ invalid } schema errors, ${ warns } quality issues</h2>` )
261+ /*for (const y in yStat) {
262+ console.log(`<li>year ${y} - ${yStat[y]}</li>`)
263+ }*/
264+
265+ Object . keys ( errorStat ) . sort ( ) . forEach ( shortName => {
266+ var i = cnas [ shortName ] ?. i ;
267+ var name = cnas [ shortName ] ?. n ? cnas [ shortName ] ?. n : shortName ;
268+ console . log ( `<h3 id=${ shortName } ><img style="vertical-align:middle" width=32 height=32 src="https://www.google.com/s2/favicons?sz=64&domain_url=${ encodeURIComponent ( i ) } /"> ${ name } <a href="#${ shortName } ">[link]</a></h3>` )
269+ for ( const k in errorStat [ shortName ] ) {
270+ var alist = errorStat [ shortName ] [ k ] ;
271+ for ( const a in alist ) {
272+ var ids = [ ...new Set ( alist [ a ] ) ] ;
273+ console . log ( `<blockquote><details id="${ shortName } -${ k } -${ a } "><summary>[${ ids . length } CVEs] ${ a } - <i>field ${ k } </i> <a href="#${ shortName } -${ k } -${ a } ">[link]</a>:</summary>` )
274+ if ( docs [ shortName + ':' + k ] ) {
275+ console . log ( `<p>` + docs [ shortName + ':' + k ] + '</p>' )
276+ }
277+ console . log ( '<blockquote class="grid">' )
278+ for ( const c of ids . sort ( ) ) {
279+ console . log ( ` <a href="${ cveLink ( c ) } ">${ c } </a>` )
280+ }
281+ console . log ( '</blockquote></details></blockquote>' )
282+ }
283+ }
284+ } ) ;
285+
286+ console . log ( '</body></html>' ) ;
287+ }
288+ /* var index = start + '<h2>CVE Quality Workgroup Report: CVE Records Indexed by CNAs</h2><div class="grid">';
289+ for(x of Object.keys(cnaIndex).sort(new Intl.Collator('en',{numeric:true}).compare)) {
290+ var i = cnas[x]?.i;
291+ var name = cnas[x]?.n ? cnas[x]?.n : x;
292+ index = index + `<img style="vertical-align:middle" width=32 height=32 src="https://www.google.com/s2/favicons?sz=32&domain_url=${encodeURIComponent(i)}/"><br>${name}<br>${cnaIndex[x].length} records<br><br>`;
293+ //var report = start + `<h2>CVE Quality Workgroup Report: CVEs records belonging to ${name}</h2><blockquote class="grid">`;
294+ for (c in cnaIndex[x].sort(new Intl.Collator('en',{numeric:true}).compare)) {
295+ index += ` <a href="${cveLink(cnaIndex[x][c])}">${cnaIndex[x][c]}</a>`
296+ }
297+ //report = report + '</body</html>';
298+ //fs.writeFileSync('./reports/'+x+'.html',report);
299+ }
300+ fs.writeFileSync('./reports/index.html',index + '</div></body></html>');
301+ }
302+
303+ rl.on('line', validate)
304+ rl.on('close', report)
305+
306+ */
0 commit comments