#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use Mojo::Log;
use Mojo::File;
use Mojo::JSON qw(decode_json encode_json);
use Mojo::UserAgent;
# cisa-kev-mon
# monitor the CISA KEV feed
# * Send notifications to gotify
# * Log them locally so we don't keep reporting on the same thing.
# For further info on CISA Known Exploited Vulnerablities visit:
# https://www.cisa.gov/known-exploited-vulnerabilities
# logging
my $log = Mojo::Log->new( path => 'cve_mon.log', level => 'debug' );
# read list software we're interested in
unless ( -e 'targets.json' ){
$log->fatal( 'no targets.json' );
die 'No targets.json';
}
# get targets
my @targets = @{ decode_json( Mojo::File->new( 'targets.json' )->slurp ) };
# gotify notification server config
unless ( $ENV{GOTIFY_URL} && $ENV{GOTIFY_TOKEN} ){
$log->fatal( 'Check Gotify env vars, GOTIFY_URL & GOTIFY_TOKEN' );
die 'Fail: Check Gotify env vars, GOTIFY_URL & GOTIFY_TOKEN';
}
my $gotify_url = $ENV{GOTIFY_URL};
my $gotify_token = $ENV{GOTIFY_TOKEN};
# cve.org base url
my $cve_url = 'https://www.cve.org/CVERecord?id=';
# cisa recent json feed
my $cisa_url = 'https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json';
# local cache so we don't report on already seen CVEs
my $cve_cache = Mojo::File->new( 'seen_cves.json' );
# Fetch JSON
my $ua = Mojo::UserAgent->new;
my $res = $ua->get( $cisa_url )->result;
# die if we can't get the JSON feed
unless ( $res->is_success ){
$log->error( 'Failed to fetch CISA feed: ' . $res->message );
die 'Failed to fetch CISA feed: ' . $res->message;
}
my $data = decode_json( $res->body );
my @vulns = @{ $data->{vulnerabilities} };
# Load existing CVEs from local cache
my %seen_cves;
if ( -e $cve_cache ){
%seen_cves = %{ decode_json( $cve_cache->slurp ) };
}
# Filter existing & collect new CVEs
my @new_cves;
foreach my $vuln ( @vulns ) {
my $vendor = $vuln->{vendorProject};
my $product = $vuln->{product};
my $cve_id = $vuln->{cveID};
# skip if the CVE has been logged before
next if $seen_cves{$cve_id};
# for each target
for my $target( @targets ){
my $matches = 0;
# Match both vendor and product
if (defined $target->{vendor} && defined $target->{product}) {
$matches = ($vendor =~ /\Q$target->{vendor}\E/i && $product =~ /\Q$target->{product}\E/i);
}
# match vendor
elsif (defined $target->{vendor}) {
$matches = ($vendor =~ /\Q$target->{vendor}\E/i);
}
# match product
elsif (defined $target->{product}) {
$matches = ($product =~ /\Q$target->{product}\E/i);
}
if ($matches) {
# post to gotify
my $res = $ua->post( $gotify_url =>
{ 'X-Gotify-Key' => $gotify_token } =>
form => {
title => 'cisa KEV CVE alert',
message => "New CVE: $vendor - $product $cve_url$cve_id",
priority => 5,
}
)->result;
unless ( $res->is_success ){
$log->fatal( 'Failed to post to gotify: ' . $res->code . ' - ' . $res->message );
die 'Failed to post to gotify: ' . $res->code . ' - ' . $res->message;
}
# add to local cache
push @new_cves, $vuln;
$seen_cves{$cve_id} = 1;
last;
}
}
}
# Output
if ( @new_cves ) {
say 'New vulnerabilities found:';
foreach my $cve ( @new_cves ) {
say "[$cve->{cveID}] $cve->{vendorProject} $cve->{product}: $cve->{vulnerabilityName} (Added: $cve->{dateAdded})";
}
} else {
say 'No new vulnerabilities for your monitored vendors/products.';
}
# Save updated seen CVEs to local file
Mojo::File->new( 'seen_cves.json' )->spew( encode_json( \%seen_cves ) );
####
[
{ "vendor": "Microsoft", "product": "SharePoint" },
{ "vendor": "Microsoft", "product": "Windows 2012" },
{ "vendor": "Microsoft", "product": "Windows 10" },
{ "vendor": "Microsoft", "product": "CoPilot" },
{ "vendor": "Microsoft", "product": "Teams" },
{ "vendor": "Microsoft", "product": "Edge" },
{ "vendor": "Oracle", "product": "Solaris" }
{ "vendor": "Example Vendor"},
{ "product": "Example Product"}
]
##
##
New vulnerabilities found:
[CVE-2025-49704] Microsoft SharePoint: Microsoft SharePoint Code Injection Vulnerability (Added: 2025-07-22)
[CVE-2025-49706] Microsoft SharePoint: Microsoft SharePoint Improper Authentication Vulnerability (Added: 2025-07-22)
[CVE-2025-53770] Microsoft SharePoint: Microsoft SharePoint Deserialization of Untrusted Data Vulnerability (Added: 2025-07-20)