diff --git a/api/src/organization/loaders/load-all-organization-domain-statuses.js b/api/src/organization/loaders/load-all-organization-domain-statuses.js index e1f561fad3..83909a5c7f 100644 --- a/api/src/organization/loaders/load-all-organization-domain-statuses.js +++ b/api/src/organization/loaders/load-all-organization-domain-statuses.js @@ -74,6 +74,8 @@ export const loadAllOrganizationDomainStatuses = ${domainFilters} FILTER d.wildcardSibling ${comparison} true ` + } else if (filterValue === 'scan-pending') { + domainFilters = aql`${domainFilters}` } else if (filterValue === 'has-entrust-certificate') { domainFilters = aql` ${domainFilters} @@ -102,6 +104,17 @@ export const loadAllOrganizationDomainStatuses = FOR webScan, webScanE IN 1 OUTBOUND web._id webToWebScans RETURN webScan.ipAddress ) + LET vulnerabilities = ( + FOR finding IN additionalFindings + FILTER finding.domain == d._id + LIMIT 1 + RETURN UNIQUE( + FOR wc IN finding.webComponents + FILTER LENGTH(wc.WebComponentCves) > 0 + FOR vuln IN wc.WebComponentCves + RETURN vuln.Cve + ) + )[0] RETURN { "domain": d.domain, "ipAddresses": ipAddresses, @@ -118,7 +131,7 @@ export const loadAllOrganizationDomainStatuses = "blocked": d.blocked, "wildcardSibling": d.wildcardSibling, "hasEntrustCertificate": d.hasEntrustCertificate, - "hasTop25Vulnerability": d.cveDetected + "top25Vulnerabilities": vulnerabilities } ` ).all() diff --git a/api/src/organization/loaders/load-organization-domain-statuses.js b/api/src/organization/loaders/load-organization-domain-statuses.js index 5a246a13e9..fd6de01d29 100644 --- a/api/src/organization/loaders/load-organization-domain-statuses.js +++ b/api/src/organization/loaders/load-organization-domain-statuses.js @@ -84,6 +84,8 @@ export const loadOrganizationDomainStatuses = ${domainFilters} FILTER v.cveDetected ${comparison} true ` + } else if (filterValue === 'scan-pending') { + domainFilters = aql`${domainFilters}` } else { domainFilters = aql` ${domainFilters} @@ -119,6 +121,17 @@ export const loadOrganizationDomainStatuses = FOR webScan, webScanE IN 1 OUTBOUND web._id webToWebScans RETURN webScan.ipAddress ) + LET vulnerabilities = ( + FOR finding IN additionalFindings + FILTER finding.domain == v._id + LIMIT 1 + RETURN UNIQUE( + FOR wc IN finding.webComponents + FILTER LENGTH(wc.WebComponentCves) > 0 + FOR vuln IN wc.WebComponentCves + RETURN vuln.Cve + ) + )[0] RETURN { domain: v.domain, ipAddresses: ipAddresses, @@ -129,7 +142,7 @@ export const loadOrganizationDomainStatuses = blocked: v.blocked, wildcardSibling: v.wildcardSibling, hasEntrustCertificate: v.hasEntrustCertificate, - cveDetected: v.cveDetected + top25Vulnerabilities: vulnerabilities } ` ).all() diff --git a/api/src/organization/objects/organization.js b/api/src/organization/objects/organization.js index c1dcc50a6e..1e6840ba29 100644 --- a/api/src/organization/objects/organization.js +++ b/api/src/organization/objects/organization.js @@ -175,7 +175,7 @@ export const organizationType = new GraphQLObjectType({ 'blocked', 'wildcardSibling', 'hasEntrustCertificate', - 'top25VulnerabilityDetected', + 'top25Vulnerabilities', ] let csvOutput = headers.join(',') domains.forEach( @@ -189,8 +189,9 @@ export const organizationType = new GraphQLObjectType({ blocked, wildcardSibling, hasEntrustCertificate, - cveDetected, + ...rest }) => { + const vulns = rest?.top25Vulnerabilities || [] let csvLine = `${domain}` csvLine += `,${ipAddresses.join('|')}` csvLine += headers.slice(2, 11).reduce((previousValue, currentHeader) => { @@ -198,7 +199,7 @@ export const organizationType = new GraphQLObjectType({ }, '') csvLine += `,${tags.join( '|', - )},${assetState},${rcode},${blocked},${wildcardSibling},${hasEntrustCertificate},${cveDetected}` + )},${assetState},${rcode},${blocked},${wildcardSibling},${hasEntrustCertificate},${vulns.join('|')}` csvOutput += `\n${csvLine}` }, ) diff --git a/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js b/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js index 90fea9bd67..a19807a6c6 100644 --- a/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js +++ b/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js @@ -247,9 +247,9 @@ describe('given getAllOrganizationDomainStatuses', () => { const expectedResponse = { data: { - getAllOrganizationDomainStatuses: `domain,ipAddresses,https,hsts,certificates,ciphers,curves,protocols,spf,dkim,dmarc,rcode,blocked,wildcardSibling,hasEntrustCertificate,hasTop25Vulnerability -"domain.one",,"fail","pass","pass","pass","pass","pass","pass","pass","pass","NOERROR","false","false","false","false" -"domain.two",,"pass","fail","pass","fail","pass","fail","pass","pass","fail","NOERROR","false","false","false","false"`, + getAllOrganizationDomainStatuses: `domain,ipAddresses,https,hsts,certificates,ciphers,curves,protocols,spf,dkim,dmarc,rcode,blocked,wildcardSibling,hasEntrustCertificate,top25Vulnerabilities +"domain.one",,"fail","pass","pass","pass","pass","pass","pass","pass","pass","NOERROR","false","false","false", +"domain.two",,"pass","fail","pass","fail","pass","fail","pass","pass","fail","NOERROR","false","false","false",`, }, } @@ -364,9 +364,9 @@ describe('given getAllOrganizationDomainStatuses', () => { }) const expectedResponse = { data: { - getAllOrganizationDomainStatuses: `domain,ipAddresses,https,hsts,certificates,ciphers,curves,protocols,spf,dkim,dmarc,rcode,blocked,wildcardSibling,hasEntrustCertificate,hasTop25Vulnerability -"domain.one",,"fail","pass","pass","pass","pass","pass","pass","pass","pass","NOERROR","false","false","false","false" -"domain.two",,"pass","fail","pass","fail","pass","fail","pass","pass","fail","NOERROR","false","false","false","false"`, + getAllOrganizationDomainStatuses: `domain,ipAddresses,https,hsts,certificates,ciphers,curves,protocols,spf,dkim,dmarc,rcode,blocked,wildcardSibling,hasEntrustCertificate,top25Vulnerabilities +"domain.one",,"fail","pass","pass","pass","pass","pass","pass","pass","pass","NOERROR","false","false","false", +"domain.two",,"pass","fail","pass","fail","pass","fail","pass","pass","fail","NOERROR","false","false","false",`, }, } expect(response).toEqual(expectedResponse) diff --git a/api/src/organization/queries/get-all-organization-domain-statuses.js b/api/src/organization/queries/get-all-organization-domain-statuses.js index 75bb6f7424..eeda5b01db 100644 --- a/api/src/organization/queries/get-all-organization-domain-statuses.js +++ b/api/src/organization/queries/get-all-organization-domain-statuses.js @@ -47,14 +47,14 @@ export const getAllOrganizationDomainStatuses = { 'blocked', 'wildcardSibling', 'hasEntrustCertificate', - 'hasTop25Vulnerability', + 'top25Vulnerabilities', ] let csvOutput = headers.join(',') domainStatuses.forEach((domainStatus) => { const csvLine = headers .map((header) => { - if (header === 'ipAddresses') { - return domainStatus[header].join('|') + if (['ipAddresses', 'top25Vulnerabilities'].includes(header)) { + return domainStatus[header]?.join('|') || [] } return `"${domainStatus[header]}"` }) diff --git a/azure-defender-easm/import-easm-additional-findings/clients/kusto_client.py b/azure-defender-easm/import-easm-additional-findings/clients/kusto_client.py index 6343eaaa68..aca924d481 100644 --- a/azure-defender-easm/import-easm-additional-findings/clients/kusto_client.py +++ b/azure-defender-easm/import-easm-additional-findings/clients/kusto_client.py @@ -1,6 +1,6 @@ from azure.kusto.data import KustoClient, KustoConnectionStringBuilder from azure.kusto.data.helpers import dataframe_from_result_table - +from datetime import datetime, date, timedelta import logging import os from dotenv import load_dotenv @@ -25,12 +25,22 @@ KUSTO_CLIENT = KustoClient(KCSB_DATA) +def filter_recent_data(data_list, last_seen_key, start_date): + return [ + x + for x in data_list + if datetime.strptime(x[last_seen_key].split("T")[0], "%Y-%m-%d").date() + >= start_date + ] + + def get_web_components_by_asset(asset): query = f""" declare query_parameters(asset_name:string = '{asset}'); EasmAssetWebComponent | where AssetName == asset_name | where TimeGeneratedValue > ago(24h) + | where WebComponentLastSeen > ago(30d) | summarize arg_max(TimeGeneratedValue, WebComponentCves, WebComponentPorts) by WebComponentName, WebComponentCategory, WebComponentVersion, WebComponentFirstSeen, WebComponentLastSeen | project WebComponentName, WebComponentCategory, WebComponentVersion, WebComponentFirstSeen, WebComponentLastSeen, WebComponentCves, WebComponentPorts """ @@ -54,6 +64,7 @@ def get_web_components_by_asset(asset): def get_additional_findings_by_asset(asset): + thirty_days_ago = date.today() - timedelta(days=30) query = f""" declare query_parameters(asset_name:string = '{asset}'); EasmHostAsset @@ -67,4 +78,12 @@ def get_additional_findings_by_asset(asset): data = dataframe_from_result_table(response.primary_results[0]).to_dict( orient="records" )[0] + + data["Ports"] = filter_recent_data( + data["Ports"], "PortStateLastSeen", thirty_days_ago + ) + data["Locations"] = filter_recent_data( + data["Locations"], "LastSeen", thirty_days_ago + ) + return data diff --git a/frontend/src/guidance/AdditionalFindings.js b/frontend/src/guidance/AdditionalFindings.js index 9cbfe14cfa..34ae5f59d1 100644 --- a/frontend/src/guidance/AdditionalFindings.js +++ b/frontend/src/guidance/AdditionalFindings.js @@ -363,7 +363,7 @@ export function AdditionalFindings({ domain }) { > External Attack Surface Management {' '} - tool. Updates to these findings occur weekly. + tool. Updates to these findings occur daily. diff --git a/frontend/src/locales/en.po b/frontend/src/locales/en.po index 24cdf4b94c..8285ea0412 100644 --- a/frontend/src/locales/en.po +++ b/frontend/src/locales/en.po @@ -4263,8 +4263,12 @@ msgid "The user's role has been successfully updated" msgstr "The user's role has been successfully updated" #: src/guidance/AdditionalFindings.js:357 -msgid "These findings are imported from Microsoft's <0>External Attack Surface Management tool. Updates to these findings occur weekly." -msgstr "These findings are imported from Microsoft's <0>External Attack Surface Management tool. Updates to these findings occur weekly." +msgid "These findings are imported from Microsoft's <0>External Attack Surface Management tool. Updates to these findings occur daily." +msgstr "These findings are imported from Microsoft's <0>External Attack Surface Management tool. Updates to these findings occur daily." + +#: src/guidance/AdditionalFindings.js:357 +#~ msgid "These findings are imported from Microsoft's <0>External Attack Surface Management tool. Updates to these findings occur weekly." +#~ msgstr "These findings are imported from Microsoft's <0>External Attack Surface Management tool. Updates to these findings occur weekly." #: src/app/ReadGuidancePage.js:154 msgid "These metrics are an important first step in securing your services and should be treated as minimum requirements. Further metrics are available in your organization's domain list." diff --git a/frontend/src/locales/fr.po b/frontend/src/locales/fr.po index a82830d963..664261088a 100644 --- a/frontend/src/locales/fr.po +++ b/frontend/src/locales/fr.po @@ -4205,8 +4205,8 @@ msgid "The user's role has been successfully updated" msgstr "Le rôle de l'utilisateur a été mis à jour avec succès" #: src/guidance/AdditionalFindings.js:357 -msgid "These findings are imported from Microsoft's <0>External Attack Surface Management tool. Updates to these findings occur weekly." -msgstr "Ces résultats sont importés de l'outil <0>External Attack Surface Management de Microsoft. Ces résultats sont mis à jour chaque semaine." +msgid "These findings are imported from Microsoft's <0>External Attack Surface Management tool. Updates to these findings occur daily." +msgstr "Ces résultats sont importés de l'outil <0>External Attack Surface Management de Microsoft. Ces résultats sont mis à jour quotidiennement." #: src/app/ReadGuidancePage.js:154 msgid "These metrics are an important first step in securing your services and should be treated as minimum requirements. Further metrics are available in your organization's domain list."