#!/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)