Skip to content

Commit 7ff0509

Browse files
committed
Add: EPSS scoring info in CVEs
Exploit Prediction Scoring System (EPSS) info is added to CVE info if it is available. This provides information on the probabily of exploitation activity for vulnerabilities.
1 parent 1a336ad commit 7ff0509

File tree

7 files changed

+277
-4
lines changed

7 files changed

+277
-4
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ include (CPack)
103103

104104
set (GVMD_DATABASE_VERSION 255)
105105

106-
set (GVMD_SCAP_DATABASE_VERSION 20)
106+
set (GVMD_SCAP_DATABASE_VERSION 21)
107107

108108
set (GVMD_CERT_DATABASE_VERSION 8)
109109

src/gmp.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13403,6 +13403,18 @@ handle_get_info (gmp_parser_t *gmp_parser, GError **error)
1340313403
cve_info_iterator_vector (&info),
1340413404
cve_info_iterator_description (&info),
1340513405
cve_info_iterator_products (&info));
13406+
13407+
if (cve_info_iterator_epss_score (&info) > 0.0)
13408+
{
13409+
xml_string_append (result,
13410+
"<epss>"
13411+
"<score>%0.5f</score>"
13412+
"<percentile>%0.5f</percentile>"
13413+
"</epss>",
13414+
cve_info_iterator_epss_score (&info),
13415+
cve_info_iterator_epss_percentile (&info));
13416+
}
13417+
1340613418
if (get_info_data->details == 1)
1340713419
{
1340813420
iterator_t nvts;

src/manage.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3331,6 +3331,12 @@ cve_info_iterator_description (iterator_t*);
33313331
const char*
33323332
cve_info_iterator_products (iterator_t*);
33333333

3334+
double
3335+
cve_info_iterator_epss_score (iterator_t*);
3336+
3337+
double
3338+
cve_info_iterator_epss_percentile (iterator_t*);
3339+
33343340
int
33353341
init_cve_info_iterator (iterator_t*, get_data_t*, const char*);
33363342

src/manage_pg.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3422,6 +3422,12 @@ manage_db_init (const gchar *name)
34223422
" (cve INTEGER,"
34233423
" cpe INTEGER);");
34243424

3425+
sql ("CREATE TABLE scap2.epss_scores"
3426+
" (cve TEXT,"
3427+
" epss DOUBLE PRECISION,"
3428+
" percentile DOUBLE PRECISION);");
3429+
3430+
34253431
/* Init tables. */
34263432

34273433
sql ("INSERT INTO scap2.meta (name, value)"
@@ -3466,6 +3472,10 @@ manage_db_add_constraints (const gchar *name)
34663472
" ADD UNIQUE (cve, cpe),"
34673473
" ADD FOREIGN KEY(cve) REFERENCES cves(id),"
34683474
" ADD FOREIGN KEY(cpe) REFERENCES cpes(id);");
3475+
3476+
sql ("ALTER TABLE scap2.epss_scores"
3477+
" ALTER cve SET NOT NULL,"
3478+
" ADD UNIQUE (cve);");
34693479
}
34703480
else
34713481
{
@@ -3512,6 +3522,9 @@ manage_db_init_indexes (const gchar *name)
35123522
" ON scap2.affected_products (cpe);");
35133523
sql ("CREATE INDEX afp_cve_idx"
35143524
" ON scap2.affected_products (cve);");
3525+
3526+
sql ("CREATE INDEX epss_scores_by_cve"
3527+
" ON scap2.epss_scores (cve);");
35153528
}
35163529
else
35173530
{

src/manage_sql_secinfo.c

Lines changed: 218 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include <sys/types.h>
4949
#include <unistd.h>
5050

51+
#include <cjson/cJSON.h>
5152
#include <gvm/base/gvm_sentry.h>
5253
#include <bsd/unistd.h>
5354
#include <gvm/util/fileutils.h>
@@ -72,6 +73,11 @@
7273
*/
7374
static int secinfo_commit_size = SECINFO_COMMIT_SIZE_DEFAULT;
7475

76+
/**
77+
* @brief Maximum number of rows in a EPSS INSERT.
78+
*/
79+
#define EPSS_MAX_CHUNK_SIZE 10000
80+
7581

7682
/* Headers. */
7783

@@ -650,7 +656,9 @@ cve_info_count (const get_data_t *get)
650656
{
651657
static const char *filter_columns[] = CVE_INFO_ITERATOR_FILTER_COLUMNS;
652658
static column_t columns[] = CVE_INFO_ITERATOR_COLUMNS;
653-
return count ("cve", get, columns, NULL, filter_columns, 0, 0, 0, FALSE);
659+
return count ("cve", get, columns, NULL, filter_columns, 0,
660+
" LEFT JOIN epss_scores ON cve = cves.uuid",
661+
0, FALSE);
654662
}
655663

656664
/**
@@ -696,7 +704,7 @@ init_cve_info_iterator (iterator_t* iterator, get_data_t *get, const char *name)
696704
NULL,
697705
filter_columns,
698706
0,
699-
NULL,
707+
" LEFT JOIN epss_scores ON cve = cves.uuid",
700708
clause,
701709
FALSE);
702710
g_free (clause);
@@ -769,6 +777,38 @@ DEF_ACCESS (cve_info_iterator_severity, GET_ITERATOR_COLUMN_COUNT + 2);
769777
*/
770778
DEF_ACCESS (cve_info_iterator_description, GET_ITERATOR_COLUMN_COUNT + 3);
771779

780+
/**
781+
* @brief Get the EPSS score for this CVE.
782+
*
783+
* @param[in] iterator Iterator.
784+
*
785+
* @return The EPSS score of this CVE, or 0.0 if iteration is
786+
* complete.
787+
*/
788+
double
789+
cve_info_iterator_epss_score (iterator_t *iterator)
790+
{
791+
if (iterator->done)
792+
return 0.0;
793+
return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 5);
794+
}
795+
796+
/**
797+
* @brief Get the EPSS percentile for this CVE.
798+
*
799+
* @param[in] iterator Iterator.
800+
*
801+
* @return The EPSS percentile of this CVE, or 0.0 if iteration is
802+
* complete.
803+
*/
804+
double
805+
cve_info_iterator_epss_percentile (iterator_t *iterator)
806+
{
807+
if (iterator->done)
808+
return 0.0;
809+
return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
810+
}
811+
772812

773813
/* CERT-Bund data. */
774814

@@ -2762,6 +2802,174 @@ update_scap_cves ()
27622802
return 0;
27632803
}
27642804

2805+
/**
2806+
* @brief Adds a EPSS score entry to an SQL inserts buffer.
2807+
*
2808+
* @param[in] inserts The SQL inserts buffer to add to.
2809+
* @param[in] cve The CVE the epss score and percentile apply to.
2810+
* @param[in] epss The EPSS score to add.
2811+
* @param[in] percentile The EPSS percentile to add.
2812+
*/
2813+
static void
2814+
insert_epss_score_entry (inserts_t *inserts, const char *cve,
2815+
double epss, double percentile)
2816+
{
2817+
gchar *quoted_cve;
2818+
int first = inserts_check_size (inserts);
2819+
2820+
quoted_cve = sql_quote (cve);
2821+
g_string_append_printf (inserts->statement,
2822+
"%s ('%s', %lf, %lf)",
2823+
first ? "" : ",",
2824+
quoted_cve,
2825+
epss,
2826+
percentile);
2827+
2828+
inserts->current_chunk_size++;
2829+
}
2830+
2831+
/**
2832+
* @brief Checks a failure condition for validating EPSS JSON.
2833+
*/
2834+
#define EPSS_JSON_FAIL_IF(failure_condition, error_message) \
2835+
if (failure_condition) { \
2836+
g_warning ("%s: %s", __func__, error_message); \
2837+
goto fail_insert; \
2838+
}
2839+
2840+
/**
2841+
* @brief Updates the base EPSS scores table in the SCAP database.
2842+
*
2843+
* @return 0 success, -1 error.
2844+
*/
2845+
static int
2846+
update_epss_scores ()
2847+
{
2848+
GError *error = NULL;
2849+
gchar *latest_json_path;
2850+
gchar *file_contents = NULL;
2851+
cJSON *parsed, *list_item;
2852+
inserts_t inserts;
2853+
2854+
latest_json_path = g_build_filename (GVM_SCAP_DATA_DIR, "epss-latest.json",
2855+
NULL);
2856+
2857+
if (! g_file_get_contents (latest_json_path, &file_contents, NULL, &error))
2858+
{
2859+
int ret;
2860+
if (error->code == G_FILE_ERROR_NOENT)
2861+
{
2862+
g_info ("%s: EPSS scores file '%s' not found",
2863+
__func__, latest_json_path);
2864+
ret = 0;
2865+
}
2866+
else
2867+
{
2868+
g_warning ("%s: Error loading EPSS scores file: %s",
2869+
__func__, error->message);
2870+
ret = -1;
2871+
}
2872+
g_error_free (error);
2873+
g_free (latest_json_path);
2874+
return ret;
2875+
}
2876+
2877+
g_info ("Updating EPSS scores from %s", latest_json_path);
2878+
g_free (latest_json_path);
2879+
2880+
parsed = cJSON_Parse (file_contents);
2881+
g_free (file_contents);
2882+
2883+
if (parsed == NULL)
2884+
{
2885+
g_warning ("%s: EPSS scores file is not valid JSON", __func__);
2886+
return -1;
2887+
}
2888+
2889+
if (! cJSON_IsArray (parsed))
2890+
{
2891+
g_warning ("%s: EPSS scores file is not a JSON array", __func__);
2892+
cJSON_Delete (parsed);
2893+
return -1;
2894+
}
2895+
2896+
sql_begin_immediate ();
2897+
inserts_init (&inserts,
2898+
EPSS_MAX_CHUNK_SIZE,
2899+
setting_secinfo_sql_buffer_threshold_bytes (),
2900+
"INSERT INTO scap2.epss_scores"
2901+
" (cve, epss, percentile)"
2902+
" VALUES ",
2903+
" ON CONFLICT (cve) DO NOTHING");
2904+
2905+
cJSON_ArrayForEach (list_item, parsed)
2906+
{
2907+
cJSON *cve_json, *epss_json, *percentile_json;
2908+
2909+
EPSS_JSON_FAIL_IF (! cJSON_IsObject (list_item),
2910+
"Unexpected non-object item in EPSS scores file")
2911+
2912+
cve_json = cJSON_GetObjectItem (list_item, "cve");
2913+
epss_json = cJSON_GetObjectItem (list_item, "epss");
2914+
percentile_json = cJSON_GetObjectItem (list_item, "percentile");
2915+
2916+
EPSS_JSON_FAIL_IF (cve_json == NULL,
2917+
"Item missing mandatory 'cve' field");
2918+
2919+
EPSS_JSON_FAIL_IF (epss_json == NULL,
2920+
"Item missing mandatory 'epss' field");
2921+
2922+
EPSS_JSON_FAIL_IF (percentile_json == NULL,
2923+
"Item missing mandatory 'percentile' field");
2924+
2925+
EPSS_JSON_FAIL_IF (! cJSON_IsString (cve_json),
2926+
"Field 'cve' in item is not a string");
2927+
2928+
EPSS_JSON_FAIL_IF (! cJSON_IsNumber(epss_json),
2929+
"Field 'epss' in item is not a number");
2930+
2931+
EPSS_JSON_FAIL_IF (! cJSON_IsNumber(percentile_json),
2932+
"Field 'percentile' in item is not a number");
2933+
2934+
insert_epss_score_entry (&inserts,
2935+
cve_json->valuestring,
2936+
epss_json->valuedouble,
2937+
percentile_json->valuedouble);
2938+
}
2939+
2940+
inserts_run (&inserts, TRUE);
2941+
sql_commit ();
2942+
cJSON_Delete (parsed);
2943+
2944+
return 0;
2945+
2946+
fail_insert:
2947+
inserts_free (&inserts);
2948+
sql_rollback ();
2949+
char *printed_item = cJSON_Print (list_item);
2950+
g_message ("%s: invalid item: %s", __func__, printed_item);
2951+
free (printed_item);
2952+
cJSON_Delete (parsed);
2953+
return -1;
2954+
}
2955+
2956+
/**
2957+
* @brief Update EPSS data as supplement to SCAP CVEs.
2958+
*
2959+
* Assume that the databases are attached.
2960+
*
2961+
* @return 0 success, -1 error.
2962+
*/
2963+
static int
2964+
update_scap_epss ()
2965+
{
2966+
if (update_epss_scores ())
2967+
return -1;
2968+
2969+
return 0;
2970+
}
2971+
2972+
27652973

27662974
/* CERT and SCAP update. */
27672975

@@ -3672,6 +3880,14 @@ update_scap (gboolean reset_scap_db)
36723880

36733881
g_debug ("%s: updating user defined data", __func__);
36743882

3883+
g_debug ("%s: update epss", __func__);
3884+
setproctitle ("Syncing SCAP: Updating EPSS scores");
3885+
3886+
if (update_scap_epss () == -1)
3887+
{
3888+
abort_scap_update ();
3889+
return -1;
3890+
}
36753891

36763892
/* Do calculations that need all data. */
36773893

src/manage_sql_secinfo.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@
7676
*/
7777
#define CVE_INFO_ITERATOR_FILTER_COLUMNS \
7878
{ GET_ITERATOR_FILTER_COLUMNS, "cvss_vector", "products", \
79-
"description", "published", "severity", NULL }
79+
"description", "published", "severity", "epss_score", \
80+
"epss_percentile", NULL }
8081

8182
/**
8283
* @brief CVE iterator columns.
@@ -91,6 +92,8 @@
9192
{ "severity", NULL, KEYWORD_TYPE_DOUBLE }, \
9293
{ "description", NULL, KEYWORD_TYPE_STRING }, \
9394
{ "creation_time", "published", KEYWORD_TYPE_INTEGER }, \
95+
{ "coalesce (epss, 0.0)", "epss_score", KEYWORD_TYPE_DOUBLE }, \
96+
{ "coalesce (percentile, 0.0)", "epss_percentile", KEYWORD_TYPE_DOUBLE }, \
9497
{ NULL, NULL, KEYWORD_TYPE_UNKNOWN } \
9598
}
9699

src/schema_formats/XML/GMP.xml.in

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12529,6 +12529,7 @@ END:VCALENDAR
1252912529
<e>cvss_vector</e>
1253012530
<e>description</e>
1253112531
<e>products</e>
12532+
<o><e>epss</e></o>
1253212533
<o><e>nvts</e></o>
1253312534
<o><e>cert</e></o>
1253412535
<o><e>raw_data</e></o>
@@ -12562,6 +12563,28 @@ END:VCALENDAR
1256212563
text
1256312564
</pattern>
1256412565
</ele>
12566+
<ele>
12567+
<name>epss</name>
12568+
<summary>Exploit Prediction Scoring System (EPSS) info if available</summary>
12569+
<pattern>
12570+
<e>score</e>
12571+
<e>percentile</e>
12572+
</pattern>
12573+
<ele>
12574+
<name>score</name>
12575+
<summary>EPSS score of the CVE</summary>
12576+
<pattern>
12577+
<t>decimal</t>
12578+
</pattern>
12579+
</ele>
12580+
<ele>
12581+
<name>percentile</name>
12582+
<summary>EPSS percentile of the CVE</summary>
12583+
<pattern>
12584+
<t>decimal</t>
12585+
</pattern>
12586+
</ele>
12587+
</ele>
1256512588
<ele>
1256612589
<name>nvts</name>
1256712590
<summary>NVTs addressing this CVE. Only when details were requested</summary>

0 commit comments

Comments
 (0)