@@ -96,91 +96,160 @@ async function loadScores(refresh = false) {
96
96
97
97
// Check CVSS Score in NVD database for the CVE
98
98
async function checkCVSS ( cveID ) {
99
- const response = await fetch (
100
- `https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=${ cveID } `
101
- ) ;
102
- const data = await response . json ( ) ;
103
- if ( data . totalResults == 0 ) {
104
- return ;
105
- }
99
+ try {
100
+ const response = await fetch (
101
+ `https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=${ cveID } `
102
+ ) ;
103
+
104
+ const data = await response . json ( ) ;
105
+ if ( data . totalResults == 0 ) {
106
+ return ;
107
+ }
106
108
107
- const cvssScoreV31 = data . vulnerabilities [ 0 ] . cve . metrics . cvssMetricV31 ;
108
- return cvssScoreV31 ? cvssScoreV31 [ 0 ] : null ;
109
+ const cvssScoreV31 = data . vulnerabilities [ 0 ] . cve . metrics . cvssMetricV31 ;
110
+ return cvssScoreV31 ? cvssScoreV31 [ 0 ] : null ;
111
+ } catch ( err ) {
112
+ console . log ( `Error fetching CVSS score for ${ cveID } ` ) ;
113
+ console . error ( err ) ;
114
+ return null ;
115
+ }
109
116
}
110
117
111
118
async function audit (
112
- cveID ,
119
+ cveIDStr ,
113
120
verbose = false ,
114
121
threshold = 0.0 ,
115
122
failOnPastDue = false ,
116
- score = 0.0
123
+ score = 0.0 ,
124
+ delay = 0.0
117
125
) {
118
126
let tabularData = [ ] ;
127
+ let cveIDs = [ ] ;
128
+ let counter = 0 ;
119
129
120
- const cveRegex = new RegExp ( `^CVE-\\d{4}-\\d{4,}$` ) ;
121
- if ( ! cveRegex . test ( cveID ) ) {
122
- console . warn (
123
- `\n Invalid CVE number ${ cveID } . Expecting number as: CVE-YYYY-XXXX.\n`
124
- ) ;
125
- process . exit ( 1 ) ;
130
+ let cveAuditFail = false ;
131
+ let epssAuditFail = false ;
132
+ let kevAuditFail = false ;
133
+
134
+ // If multiple CVEs are provided, audit each one
135
+ if ( cveIDStr . includes ( "," ) ) {
136
+ cveIDs = cveIDStr . split ( "," ) . map ( ( cve ) => cve . trim ( ) ) ;
137
+ } else {
138
+ cveIDs . push ( cveIDStr . trim ( ) ) ;
126
139
}
127
140
128
- const today = new Date ( ) ;
141
+ for ( const cveID of cveIDs ) {
142
+ const cveRegex = new RegExp ( `^CVE-\\d{4}-\\d{4,}$` ) ;
129
143
130
- console . log ( `\n Auditing ${ cveID } at ${ new Date ( ) . toLocaleString ( ) } \n` ) ;
144
+ let epssScore = null ;
145
+ let kevDueDate = null ;
146
+ let cvssScore = null ;
147
+
148
+ if ( ! cveRegex . test ( cveID ) ) {
149
+ console . warn (
150
+ `\n Invalid CVE number ${ cveID } . Expecting number as: CVE-YYYY-XXXX. Skipping.\n`
151
+ ) ;
152
+ // continue the loop
153
+ continue ;
154
+ }
155
+
156
+ const today = new Date ( ) ;
157
+
158
+ counter ++ ;
131
159
132
- if ( ! epssScores [ cveID ] ) {
133
- console . log ( ` No EPSS score found for CVE ${ cveID } \n` ) ;
134
- } else {
135
- const { epss, percentile } = epssScores [ cveID ] ;
136
160
console . log (
137
- `\n EPSS score (probability of exploitation) : ${ + Number (
138
- epss * 100.0
139
- ) . toFixed ( 3 ) } % \n`
161
+ `\n Auditing ${ counter } of ${
162
+ cveIDs . length
163
+ } ${ cveID } at ${ new Date ( ) . toLocaleString ( ) } \n`
140
164
) ;
141
165
142
- // If EPSS score is above threshold, fail the audit
143
- if ( Number ( threshold ) > 0.0 && parseFloat ( epss ) > threshold ) {
144
- console . warn (
145
- ` EPSS score is above threshold of ${ threshold } . Failing the audit.\n`
166
+ if ( ! epssScores [ cveID ] ) {
167
+ console . log ( ` No EPSS score found for CVE ${ cveID } \n` ) ;
168
+ } else {
169
+ const { epss, percentile } = epssScores [ cveID ] ;
170
+
171
+ epssScore = + Number ( epss * 100.0 ) . toFixed ( 3 ) ;
172
+
173
+ console . log (
174
+ `\n EPSS score (probability of exploitation) : ${ + Number (
175
+ epss * 100.0
176
+ ) . toFixed ( 3 ) } % \n`
146
177
) ;
147
- process . exit ( 2 ) ;
178
+
179
+ // If EPSS score is above threshold, fail the audit
180
+ if ( Number ( threshold ) > 0.0 && parseFloat ( epss ) > threshold ) {
181
+ console . warn (
182
+ ` EPSS score is above threshold of ${ threshold } . Failing the audit.\n`
183
+ ) ;
184
+ epssAuditFail = true ;
185
+ }
148
186
}
149
- }
150
187
151
- if ( ! kevData [ cveID ] ) {
152
- console . log ( ` No CISA KEV data found for CVE ${ cveID } \n` ) ;
153
- } else {
154
- const { dateAdded, dueDate } = kevData [ cveID ] ;
155
- console . log ( ` CISA KEV Date Added: ${ dateAdded } , Due Date: ${ dueDate } \n` ) ;
188
+ if ( ! kevData [ cveID ] ) {
189
+ console . log ( ` No CISA KEV data found for CVE ${ cveID } \n` ) ;
190
+ } else {
191
+ const { dateAdded, dueDate } = kevData [ cveID ] ;
192
+ kevDueDate = dueDate ;
156
193
157
- // If CVE is past due date, fail the audit
158
- if ( failOnPastDue && new Date ( dueDate ) < today ) {
159
- console . warn (
160
- `\n CVE is past due date of ${ dueDate } . Failing the audit.\n`
161
- ) ;
162
- process . exit ( 2 ) ;
194
+ console . log ( ` CISA KEV Date Added: ${ dateAdded } , Due Date: ${ dueDate } \n` ) ;
195
+
196
+ // If CVE is past due date, fail the audit
197
+ if ( failOnPastDue && new Date ( dueDate ) < today ) {
198
+ console . warn (
199
+ `\n CVE is past due date of ${ dueDate } . Failing the audit.\n`
200
+ ) ;
201
+ kevAuditFail = true ;
202
+ }
163
203
}
164
- }
165
204
166
- const cvssScoreV31 = await checkCVSS ( cveID ) ;
167
- if ( ! cvssScoreV31 ) {
168
- console . log ( ` No CVSS score found for CVE ${ cveID } \n` ) ;
169
- } else {
170
- const { exploitabilityScore, impactScore } = cvssScoreV31 ;
171
- const { baseScore, baseSeverity } = cvssScoreV31 . cvssData ;
172
- console . log ( ` CVSS v3.1 Base Score: ${ baseScore } (${ baseSeverity } )
205
+ const cvssScoreV31 = await checkCVSS ( cveID ) ;
206
+ if ( ! cvssScoreV31 ) {
207
+ console . log ( ` No CVSS score found for CVE ${ cveID } \n` ) ;
208
+ } else {
209
+ const { exploitabilityScore, impactScore } = cvssScoreV31 ;
210
+ const { baseScore, baseSeverity } = cvssScoreV31 . cvssData ;
211
+ console . log ( ` CVSS v3.1 Base Score: ${ baseScore } (${ baseSeverity } )
173
212
\n\t Exploitability Score: ${ exploitabilityScore } Impact Score : ${ impactScore } \n` ) ;
174
213
175
- // If CVSS score is above threshold, fail the audiat
176
- if ( Number ( score ) > 0.0 && parseFloat ( baseScore ) > score ) {
177
- console . warn (
178
- ` CVSS v3.1 Base Score is above threshold of ${ score } . Failing the audit.\n`
179
- ) ;
180
- process . exit ( 2 ) ;
214
+ cvssScore = baseScore ;
215
+
216
+ // If CVSS score is above threshold, fail the audiat
217
+ if ( Number ( score ) > 0.0 && parseFloat ( baseScore ) > score ) {
218
+ console . warn (
219
+ ` CVSS v3.1 Base Score is above threshold of ${ score } . Failing the audit.\n`
220
+ ) ;
221
+ cveAuditFail = true ;
222
+ }
223
+ }
224
+
225
+ tabularData . push ( {
226
+ "CVE ID" : cveID ,
227
+ "EPSS Score (%)" : epssScore ? epssScore : "N/A" ,
228
+ "CVSS Base Score" : cvssScore ? cvssScore : "N/A" ,
229
+ "CISA KEV Due Date" : kevDueDate ? kevDueDate : "N/A" ,
230
+ } ) ;
231
+
232
+ // print a line separator
233
+ console . log ( "-" . repeat ( 40 ) ) ;
234
+
235
+ // Try adding a delay to avoid rate limiting
236
+ if ( Number ( delay ) > 0.0 && cveIDs . length > 2 && counter < cveIDs . length ) {
237
+ await new Promise ( ( resolve ) => setTimeout ( resolve , Number ( delay ) * 1000 ) ) ;
181
238
}
182
239
}
183
240
241
+ if ( tabularData . length > 0 ) {
242
+ console . log ( "\n Audit Summary \n" ) ;
243
+ console . table ( tabularData ) ;
244
+ }
245
+
246
+ if ( cveAuditFail || epssAuditFail || kevAuditFail ) {
247
+ console . warn (
248
+ `\n Audit failed. Please review the CVEs above and take appropriate action.\n`
249
+ ) ;
250
+ process . exit ( 2 ) ;
251
+ }
252
+
184
253
process . exit ( 0 ) ;
185
254
}
186
255
@@ -212,6 +281,12 @@ async function audit(
212
281
type : "number" ,
213
282
default : 0.0 ,
214
283
} )
284
+ . option ( "d" , {
285
+ alias : "delay" ,
286
+ describe : "Delay between each CVE audit, in seconds" ,
287
+ type : "number" ,
288
+ default : 0.0 ,
289
+ } )
215
290
. help ( true ) . argv ;
216
291
217
292
// If no CVE number is provided, print help
@@ -231,7 +306,8 @@ async function audit(
231
306
options . verbose ,
232
307
options . threshold ,
233
308
options [ "fail-on-past-duedate" ] ,
234
- options . score
309
+ options . score ,
310
+ options [ "delay" ]
235
311
) ;
236
312
} catch ( err ) {
237
313
console . error ( err ) ;
0 commit comments