Help on creating a report to map all CVEs on the environment

Hello,

I am just starting my journey on Web Reports, and I came up with many ideas, the first and most important one is that I was wondering if there are any possibility to create a custom report where I can grab the following information.

  • Number of all (relevant) unique CVEs identified on the environment.
  • Total number of CVEs identified in the whole environment (relevant).
  • Number of all (relevant) unique CVEs fixed in the environment within specified date.
  • Total number of CVEs fixed in the whole environment (relevant).

I don’t know if we already have anything like this already, but it would be really nice to have something where we can see the vulnerabilities (CVEs) going down or up as we patch the environment. If it could have graphs, that would be even better.

This sounds a lot like what is already available in BigFix Compliance (especially in Compliance Analytics and Vulnerability Compliance reporting) as well as CyberFOCUS Analytics which can really help with vulnerability prioritization.

Yeah, it is kinda of available. But why not a dashboard to show how many computers affected by CVE. CyberFocus is pretty cool, but I wanted a more customizable report. In fact, I find building custom reports extremely difficult, I still haven’t figure it out. I can build fixlets, tasks, construct relevance queries pretty easily, but I struggle a lot with custom reports logic.

I know it’s probably a strategy from HCL to not let this public, but I know it is possible to build.

I think it’s just not as easy to correlate CVEs as one might think.

Our content is focused around patch management & software updates. A Fixlet is generally a single patch…but might have tens (or hundreds) of CVEs associated with the same update.

Once a patch is superseded by a new version, the original Fixlet is generally marked as superseded and switched to False. A new Fixlet is published that may have tens or hundreds of new CVEs. And the relevance of the older CVEs is lost.

A machine that is relevant to the second patch, might not suffer from all of the CVEs addressed by the patch. The computer might have some services disabled, some software not installed…just because a machine is relevant to a March 2024 Security Rollup from Microsoft, does not mean it’s vulnerable to every CVE corrected in the Rollup. And from a Fixlet/Relevance perspective…we can’t make that distinction.

And when the machine marks itself not-relevant to last month’s rollup, that doesn’t mean the older CVEs are resolved, either. We just can’t tell anymore.

Where the CyberFOCUS Analytics comes in to play is in taking data from a vulnerability-focused scanner (like Nessus or QRadar) and determining that, based on some list of CVEs, here are the Fixlets to resolve those. It follows the supersedence chain to determine a machine that needs a patch for last month’s (or last year’s) CVE should apply this month’s Rollup Package.

I understand, and I really liked the CyberFocus view. We are planning on integrating with our Qualys scan to have a better approach and address better our demands.

Now, once you showed me a dataTables - Action Detail Report (BigFix/Test Content/Web Reports/dataTables - Action Details Report by Action with Filters.beswrpt at master ¡ Jwalker107/BigFix (github.com)), which comes in handy to make reports about the action results. I added a CVE Column, but since then I am failing to retrieve CVE data.

I bet the CVE data would have to be get from the action name, which would them check for that fixlet and grab its relevant CVEs. Right?

I think one could get the CVE from the source fixlet, not necessarily the action name or fixlet name. You’d probable also need to error-trap for fixlets/actions that don’t have CVEs associated. Something like

Q: (id of it, name of it, cve id list of source fixlet of it | "no cve") of bes actions
A: 2279, Lab Application Deployment, 
A: 2301, Schedule VM Manager Tool Scan Results Upload (10.0.11.0), 
A: 3100, Catalog Download  (Version: 2638755.1), no cve
A: 3113, Configure Windows Firewall: Allow Inbound ICMP Ping, no cve
A: 3114, Configure Windows Firewall: Allow inbound UDP on BigFix Port, no cve
A: 3115, Configure Windows Firewall: Allow Remote Desktop Services, no cve
A: 3238, MS20-OCT: Security Update for Adobe Flash Player for Windows 10 Version 2004 - Windows 10 Version 2004 - Adobe Flash Player - KB4580325 (x64), CVE-2020-9746
A: 3239, MS21-FEB: Cumulative Update for .NET Framework 3.5 and 4.7.2 for Windows Server 2019 - Windows Server 2019 - .NET Framework 3.5/4.7.2 - KB4601060 (x64), CVE-2021-24111
A: 3240, MS21-MAY: Cumulative Update for Windows 10 Version 2004 - Windows 10 Version 2004 - KB5003173 (x64), CVE-2020-24587; CVE-2020-24588; CVE-2020-26144; CVE-2021-26419; CVE-2021-28455; CVE-2021-28476; CVE-2021-28479; CVE-2021-31165; CVE-2021-31166; CVE-2021-31167; CVE-2021-31168; CVE-2021-31169; CVE-2021-31170; CVE-2021-31182; CVE-2021-31184; CVE-2021-31185; CVE-2021-31186; CVE-2021-31187; CVE-2021-31188; CVE-2021-31191; CVE-2021-31192; CVE-2021-31193; CVE-2021-31194; CVE-2021-31205; CVE-2021-31208
2 Likes

I keep getting the same error when trying to insert this relevance into the source code of the dataTables you made:

Action History Details
The tuple index 6 is out of range.

No data appears when I edit the relevance of the CVE column

I’m probably missing something silly, but I am not the best coder out there.

Do you have a hint of exacly how I would put these together?

If you’re still basing that off the original report I posted at that link, you could change the relevance to

concatenation of trs of
(
 concatenation "%0a"of (td of item 0 of it; td of  item 1 of it; td of item 2 of it; td of item 3 of it;td of  item 4 of it; td of item 5 of it; td of item 6 of it;td of  item 7 of it;td of  item 8 of it; td of item 9 of it; td of item 10 of it; td of item 11 of it)
) of
 (
  (
   id of computer of it as string
   , link of computer of it | "No Name" as html
   , (if group member flag of action of it then link of parent group  of action of it else "no parent" as html) 
   , id of action of it as string
   , link of action of it
   , name of issuer of action of it
   , time issued of action of it as string
   , state of action of it as string
   , status of it as string
   , exit code of it as string | "no exit code"
   , end time of it as string | "no end time"
   , cve id list of source fixlet of action of it as string| "no CVE"
  ) of results of it
   ;
  (
   if (exists targeted computer set of it) then /* retrieve properties for non-reported computer results */
    (
    id of item 0 of it as string /* non-reported computer id */
    , link of item 0 of it | "<computer deleted>" as html
    , (if group member flag of item 1 of it then link of parent group  of item 1 of it else "" as html) | "" as html
    , id of item 1 of it as string
    , link of item 1 of it | "" as html
    , name of issuer of item 1 of it | "no issuer"
    , time issued of item 1 of it as string
    , state of item 1 of it as string
    , "Not Reported"
    , "no exit code" /* exit code */
    , "no end time" /* end time */
    , cve id list of source fixlet of item 1 of it | "no CVE"
   ) of (elements of (targeted computer set of it - reported computer set of it), it ) else nothing
  ) of it
 )
of bes actions

You’d also need to change the header row.

I just did it, I added the relevance you made into the code and added the collumn "CVEs, modified the header row, but I am getting now the error This expression could not be parsed.

Here’s what I did, if you throw it on webreports, it adds the column succesfully, I tried to debug the error, but no success.

$(document).ready(function() {
   buildResults();
});
function buildResults() {
 var table = $('#table1').DataTable( {
        orderCellsTop: true,
        fixedHeader: true,
        autowidth: true,
        paging: true,
        //scrollX: true,
        //scrollY: true,
        "lengthMenu": [ [10, 25, 50, -1], [10, 25, 50, "All"] ],
        dom: 'lBfrtlip',
        buttons: [
            {extend: 'copy', title: ''},
            'csv', 
           { extend: 'excel', title: ''},
           'pdf', 'print'
        ],
     
    } );
   // Clone the header row so we can add search filters on each column
    $('#table1 thead tr').clone(true).appendTo( '#table1 thead' );
    $('#table1 thead tr:eq(1) th').each( function (i) {
        var title = $(this).text();
        $(this).html( '<input type="text" placeholder=" '+title+'" />' );
 
        $( 'input', this ).on( 'keyup change', function () {
            if ( table.column(i).search() !== this.value ) {
                table
                    .column(i)
                    .search( this.value )
                    .draw();
            }
       } );
  } );
table.draw();
document.getElementById("table1").style.visibility = "visible";
document.getElementById("table1").style.display= "inherit";
} 
</script>
<style> 				
.header {
	font-size: 14px;
	background-color: #990000;
	color: white;
	padding: 6px 40px 6px 10px;
	white-space: wrap;
}
.row {
	font-size: 14px;
	padding: 6px 40px 6px 10px;
	border: 1px solid black;
	border-top: none;
}
.nowrap {
	white-space: nowrap;
} 			
</style> 			
<div id="seed_2" style="border: 5px solid #1976d2; border-top: none;">
 	<div style="background-color: #1976d2; color: white; padding: 5px 5px 5px 5px;">
	</div>
	<div style="padding: 20px; font-size: 18px; font-family: sans-serif; font-weight: 300;">
	<div style="">
Action History Details
	</div>
<div id="tableDiv" style="overflow:auto">
<table name='table1' id='table1' class='display' border='all' width="100%" style="visibility:hidden; display:none">
<thead>
<tr>
<th>Computer ID</th>
<th>Computer Name</th>
<th>Computer Groups</th>
<th>Parent Action ID</th>
<th>Parent Action Name</th>
<th>Action ID</th>
<th>Action Name</th>
<th>Issuer</th>
<th>Time Issued</th>
<th>Result</th>
<th>Exit Code</th>
<th>End Time</th>
<th>CVEs</th>
</tr>
</thead>
<tbody>
<?relevance
concatenation of trs of
(
 concatenation "%0a"of (td of item 0 of it; td of  item 1 of it; td of item 2 of it; td of item 3 of it;td of  item 4 of it; td of item 5 of it; td of item 6 of it;td of  item 7 of it;td of  item 8 of it; td of item 9 of it; td of item 10 of it; td of item 11 of it;)
) of
 (
  (
   id of computer of it as string
   , link of computer of it | "No Name" as html
   , (if group member flag of action of it then link of parent group  of action of it else "no parent" as html) 
   , id of action of it as string
   , link of action of it
   , name of issuer of action of it
   , time issued of action of it as string
   , state of action of it as string
   , status of it as string
   , exit code of it as string | "no exit code"
   , end time of it as string | "no end time"
   , cve id list of source fixlet of action of it as string| "no CVE"
  ) of results of it
   ;
  (
   if (exists targeted computer set of it) then /* retrieve properties for non-reported computer results */
    (
    id of item 0 of it as string /* non-reported computer id */
    , link of item 0 of it | "&lt;computer deleted&gt;" as html
    , (if    group member flag of item 1 of it then link of parent group  of item 1 of it else "" as html) | "" as html
    , id of item 1 of it as string
    , link of item 1 of it | "" as html
    , name of issuer of item 1 of it | "no issuer"
    , time issued of item 1 of it as string
    , state of item 1 of it as string
    , "Not Reported"
    , "no exit code" /* exit code */
    , "no end time" /* end time */
    , cve id list of source fixlet of item 1 of it | "no CVE"
   ) of (elements of (targeted computer set of it - reported computer set of it), it ) else nothing
  ) of it
 )
of bes actions
?>
</tbody>
</table>
</div>
</div>
</div>

I’m away from computer at the moment but I think it’s the trailing semicolon after td of item 11 of it;

(Just to add, this query can get quite expensive at larger scales. I’d take caution if you have more than 15k or so endpoints. Once you get it working there might be a bit more performance tuning we could do, if it’s needed.)

It appears that it worked, it’s not returning me any error, BUT I have no way of knowing because, as you said:

The thing is… Our environment has about 2200 endpoints:

And it never loads any results. Maybe it is even more expensive than we expected :sweat_smile:

Hey @JasonWalker, would you be able to help on this? I don’t know if you’ve already took a look at it, but I am having no “log” errors, just shows me an empty action history detail, even after minutes waiting for it to load.

Is this query even possible? How can I trace how big it is and why it doesn’t show me any results. Any idea?

Thanks in advance.

Hmm I’m not sure the query is even going to be possible, if it’s not loading for you. It loaded on mine but I only have a few dozen machines on my lab. The scaling on this is probably based more around number of actions than it is on number of machines though.

How many ‘bes actions’ do you have? How many ‘bes action results’ ? If you can filter the ‘bes actions’ that may help significantly

Would you be able to provide the code that worked for you? I just want to check if I did it wrong or if I missed something.

Now, I don’t know how I can check how many actions I have. On the console I can see 149 actions, but there are some baselines. Since the environment is kind of new, I guess we have about 500 with excluded actions and baseline, but I’m not sure.

Try the Web Reports QNA page Web Reports QnA or the Console Presentation Debugger Open the BigFix Console Presentation Debugger

queries:

q: number of bes actions

q: number of bes action results

That should give us an idea of how this needs to scale.

To test the query itself, we can use the Console Debugger - with the caveat that there is no way to stop the query running. If you trigger a query that takes too long / uses too much memory, it could grow in memory until it crashes the Console, with no way to abort it outside of killing the Console process. Make sure any editing you do in the background is saved before using the Console Debugger to run these queries, and if you are making complex queries be sure to copy them off to a notepad.

Here’s what I was running to check the correctness of the query -

table of concatenation of trs of
(
 concatenation "%0a" of (td of item 0 of it; td of  item 1 of it; td of item 2 of it; td of item 3 of it;td of  item 4 of it; td of item 5 of it; td of item 6 of it;td of  item 7 of it;td of  item 8 of it; td of item 9 of it; td of item 10 of it; td of item 11 of it)
) of
 (
  (
   id of computer of it as string
   , link of computer of it | "No Name" as html
   , (if group member flag of action of it then link of parent group  of action of it else "no parent" as html) 
   , id of action of it as string
   , link of action of it
   , name of issuer of action of it
   , time issued of action of it as string
   , state of action of it as string
   , status of it as string
   , exit code of it as string | "no exit code"
   , end time of it as string | "no end time"
   , cve id list of source fixlet of action of it as string| "no CVE"
  ) of results of it
   ;
  (
   if (exists targeted computer set of it) then /* retrieve properties for non-reported computer results */
    (
    id of item 0 of it as string /* non-reported computer id */
    , link of item 0 of it | "&lt;computer deleted&gt;" as html
    , (if    group member flag of item 1 of it then link of parent group  of item 1 of it else "" as html) | "" as html
    , id of item 1 of it as string
    , link of item 1 of it | "" as html
    , name of issuer of item 1 of it | "no issuer"
    , time issued of item 1 of it as string
    , state of item 1 of it as string
    , "Not Reported"
    , "no exit code" /* exit code */
    , "no end time" /* end time */
    , cve id list of source fixlet of item 1 of it | "no CVE"
   ) of (elements of (targeted computer set of it - reported computer set of it), it ) else nothing
  ) of it
 )
of bes actions

Thank you for the clear instructions.

I was able to evaluate the number of bes actions, this is the result I’ve got:

image

And this is the result of the Presentation Debbuger:

I guess it worked, but no CVEs identified in this particular KB which has a lot of CVEs.
I got some CVE results from fixlets I made in the past though.

Here, for example the task we created to remove WinRAR and Anydesk which we have some CVE numbers:

Apologies, that’s what I get for working from memoery. The ‘result’ count should come from

q: number of results of bes actions

It’s interesting that this is getting results in the Console, maybe there is an issue with how you’re merging it in to your custom Web Report?

That’s the result I got from the queries mentioned above:

q: number of bes actions
A: 1718

q: number of results of bes actions
A: 52695

Now about this… I mean, I probably need to merge it better and do the proper modifications, I merged in the following way:

<!-- import jQuery -->
<script type="text/javascript" charset="utf8" src='https://code.jquery.com/jquery-3.4.1.js'></script>
<!-- import styles for dataTables and dataTables Buttons extension -->
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.20/css/jquery.dataTables.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/1.6.1/css/buttons.dataTables.min.css">
<!-- import javascript for dataTables and dataTables Buttons extension -->
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.6.1/js/dataTables.buttons.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.6.1/js/buttons.flash.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.53/pdfmake.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.53/vfs_fonts.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.6.1/js/buttons.html5.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.6.1/js/buttons.print.min.js"></script>
<script>
$(document).ready(function() {
   buildResults();
});
function buildResults() {
 var table = $('#table1').DataTable( {
        orderCellsTop: true,
        fixedHeader: true,
        autowidth: true,
        paging: true,
        //scrollX: true,
        //scrollY: true,
        "lengthMenu": [ [10, 25, 50, -1], [10, 25, 50, "All"] ],
        dom: 'lBfrtlip',
        buttons: [
            {extend: 'copy', title: ''},
            'csv', 
           { extend: 'excel', title: ''},
           'pdf', 'print'
        ],
     
    } );
   // Clone the header row so we can add search filters on each column
    $('#table1 thead tr').clone(true).appendTo( '#table1 thead' );
    $('#table1 thead tr:eq(1) th').each( function (i) {
        var title = $(this).text();
        $(this).html( '<input type="text" placeholder=" '+title+'" />' );
 
        $( 'input', this ).on( 'keyup change', function () {
            if ( table.column(i).search() !== this.value ) {
                table
                    .column(i)
                    .search( this.value )
                    .draw();
            }
       } );
  } );
table.draw();
document.getElementById("table1").style.visibility = "visible";
document.getElementById("table1").style.display= "inherit";
} 
</script>
<style> 				
.header {
	font-size: 14px;
	background-color: #990000;
	color: white;
	padding: 6px 40px 6px 10px;
	white-space: wrap;
}
.row {
	font-size: 14px;
	padding: 6px 40px 6px 10px;
	border: 1px solid black;
	border-top: none;
}
.nowrap {
	white-space: nowrap;
} 			
</style> 			
<div id="seed_2" style="border: 5px solid #1976d2; border-top: none;">
 	<div style="background-color: #1976d2; color: white; padding: 5px 5px 5px 5px;">
	</div>
	<div style="padding: 20px; font-size: 18px; font-family: sans-serif; font-weight: 300;">
	<div style="">
Action History Details
	</div>
<div id="tableDiv" style="overflow:auto">
<table name='table1' id='table1' class='display' border='all' width="100%" style="visibility:hidden; display:none">
<thead>
<tr>
<th>Computer ID</th>
<th>Computer Name</th>
<th>Computer Groups</th>
<th>Parent Action ID</th>
<th>Parent Action Name</th>
<th>Action ID</th>
<th>Action Name</th>
<th>Issuer</th>
<th>Time Issued</th>
<th>Result</th>
<th>Exit Code</th>
<th>End Time</th>
<th>CVEs</th>
</tr>
</thead>
<tbody>
<?relevance
concatenation of trs of
(
 concatenation "%0a"of (td of item 0 of it; td of  item 1 of it; td of item 2 of it; td of item 3 of it;td of  item 4 of it; td of item 5 of it; td of item 6 of it;td of  item 7 of it;td of  item 8 of it; td of item 9 of it; td of item 10 of it; td of item 11 of it)
) of
 (
  (
   id of computer of it as string
   , link of computer of it | "No Name" as html
   , (if group member flag of action of it then link of parent group  of action of it else "no parent" as html) 
   , id of action of it as string
   , link of action of it
   , name of issuer of action of it
   , time issued of action of it as string
   , state of action of it as string
   , status of it as string
   , exit code of it as string | "no exit code"
   , end time of it as string | "no end time"
   , cve id list of source fixlet of action of it as string| "no CVE"
  ) of results of it
   ;
  (
   if (exists targeted computer set of it) then /* retrieve properties for non-reported computer results */
    (
    id of item 0 of it as string /* non-reported computer id */
    , link of item 0 of it | "&lt;computer deleted&gt;" as html
    , (if group member flag of item 1 of it then link of parent group  of item 1 of it else "" as html) | "" as html
    , id of item 1 of it as string
    , link of item 1 of it | "" as html
    , name of issuer of item 1 of it | "no issuer"
    , time issued of item 1 of it as string
    , state of item 1 of it as string
    , "Not Reported"
    , "no exit code" /* exit code */
    , "no end time" /* end time */
    , cve id list of source fixlet of item 1 of it | "no CVE"
   ) of (elements of (targeted computer set of it - reported computer set of it), it ) else nothing
  ) of it
 )
of bes actions
?>
</tbody>
</table>
</div>
</div>
</div>

Either it is getting too expensive, or it is not being able to display the results because of a mistake in the code.

**please if you need to redact anything, feel free.

Ok, somewhere along the line we lost the ‘computer groups’ in the Relevance but you still had a column heading for it, and having a different number of columns in the header row vs in the table itself must have broken something in dataTables.js
Try just removing the column name for ‘Computer Groups’ in the table header.