Skip to content

Commit cd7342c

Browse files
authored
Top 25: List CVEs in exported CSV (#5614)
* replace cveDetected bool with list of top 25 cves in csv export * replace cveDetected bool in all domains export * filter imported web components to last seen within 30 days * reword modal to fit new import schedule * fix api tests * filter ports and locations to last 30 days
1 parent c555e8f commit cd7342c

9 files changed

Lines changed: 70 additions & 20 deletions

File tree

api/src/organization/loaders/load-all-organization-domain-statuses.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ export const loadAllOrganizationDomainStatuses =
7474
${domainFilters}
7575
FILTER d.wildcardSibling ${comparison} true
7676
`
77+
} else if (filterValue === 'scan-pending') {
78+
domainFilters = aql`${domainFilters}`
7779
} else if (filterValue === 'has-entrust-certificate') {
7880
domainFilters = aql`
7981
${domainFilters}
@@ -102,6 +104,17 @@ export const loadAllOrganizationDomainStatuses =
102104
FOR webScan, webScanE IN 1 OUTBOUND web._id webToWebScans
103105
RETURN webScan.ipAddress
104106
)
107+
LET vulnerabilities = (
108+
FOR finding IN additionalFindings
109+
FILTER finding.domain == d._id
110+
LIMIT 1
111+
RETURN UNIQUE(
112+
FOR wc IN finding.webComponents
113+
FILTER LENGTH(wc.WebComponentCves) > 0
114+
FOR vuln IN wc.WebComponentCves
115+
RETURN vuln.Cve
116+
)
117+
)[0]
105118
RETURN {
106119
"domain": d.domain,
107120
"ipAddresses": ipAddresses,
@@ -118,7 +131,7 @@ export const loadAllOrganizationDomainStatuses =
118131
"blocked": d.blocked,
119132
"wildcardSibling": d.wildcardSibling,
120133
"hasEntrustCertificate": d.hasEntrustCertificate,
121-
"hasTop25Vulnerability": d.cveDetected
134+
"top25Vulnerabilities": vulnerabilities
122135
}
123136
`
124137
).all()

api/src/organization/loaders/load-organization-domain-statuses.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ export const loadOrganizationDomainStatuses =
8484
${domainFilters}
8585
FILTER v.cveDetected ${comparison} true
8686
`
87+
} else if (filterValue === 'scan-pending') {
88+
domainFilters = aql`${domainFilters}`
8789
} else {
8890
domainFilters = aql`
8991
${domainFilters}
@@ -119,6 +121,17 @@ export const loadOrganizationDomainStatuses =
119121
FOR webScan, webScanE IN 1 OUTBOUND web._id webToWebScans
120122
RETURN webScan.ipAddress
121123
)
124+
LET vulnerabilities = (
125+
FOR finding IN additionalFindings
126+
FILTER finding.domain == v._id
127+
LIMIT 1
128+
RETURN UNIQUE(
129+
FOR wc IN finding.webComponents
130+
FILTER LENGTH(wc.WebComponentCves) > 0
131+
FOR vuln IN wc.WebComponentCves
132+
RETURN vuln.Cve
133+
)
134+
)[0]
122135
RETURN {
123136
domain: v.domain,
124137
ipAddresses: ipAddresses,
@@ -129,7 +142,7 @@ export const loadOrganizationDomainStatuses =
129142
blocked: v.blocked,
130143
wildcardSibling: v.wildcardSibling,
131144
hasEntrustCertificate: v.hasEntrustCertificate,
132-
cveDetected: v.cveDetected
145+
top25Vulnerabilities: vulnerabilities
133146
}
134147
`
135148
).all()

api/src/organization/objects/organization.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export const organizationType = new GraphQLObjectType({
175175
'blocked',
176176
'wildcardSibling',
177177
'hasEntrustCertificate',
178-
'top25VulnerabilityDetected',
178+
'top25Vulnerabilities',
179179
]
180180
let csvOutput = headers.join(',')
181181
domains.forEach(
@@ -189,16 +189,17 @@ export const organizationType = new GraphQLObjectType({
189189
blocked,
190190
wildcardSibling,
191191
hasEntrustCertificate,
192-
cveDetected,
192+
...rest
193193
}) => {
194+
const vulns = rest?.top25Vulnerabilities || []
194195
let csvLine = `${domain}`
195196
csvLine += `,${ipAddresses.join('|')}`
196197
csvLine += headers.slice(2, 11).reduce((previousValue, currentHeader) => {
197198
return `${previousValue},${status[currentHeader]}`
198199
}, '')
199200
csvLine += `,${tags.join(
200201
'|',
201-
)},${assetState},${rcode},${blocked},${wildcardSibling},${hasEntrustCertificate},${cveDetected}`
202+
)},${assetState},${rcode},${blocked},${wildcardSibling},${hasEntrustCertificate},${vulns.join('|')}`
202203
csvOutput += `\n${csvLine}`
203204
},
204205
)

api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,9 @@ describe('given getAllOrganizationDomainStatuses', () => {
247247

248248
const expectedResponse = {
249249
data: {
250-
getAllOrganizationDomainStatuses: `domain,ipAddresses,https,hsts,certificates,ciphers,curves,protocols,spf,dkim,dmarc,rcode,blocked,wildcardSibling,hasEntrustCertificate,hasTop25Vulnerability
251-
"domain.one",,"fail","pass","pass","pass","pass","pass","pass","pass","pass","NOERROR","false","false","false","false"
252-
"domain.two",,"pass","fail","pass","fail","pass","fail","pass","pass","fail","NOERROR","false","false","false","false"`,
250+
getAllOrganizationDomainStatuses: `domain,ipAddresses,https,hsts,certificates,ciphers,curves,protocols,spf,dkim,dmarc,rcode,blocked,wildcardSibling,hasEntrustCertificate,top25Vulnerabilities
251+
"domain.one",,"fail","pass","pass","pass","pass","pass","pass","pass","pass","NOERROR","false","false","false",
252+
"domain.two",,"pass","fail","pass","fail","pass","fail","pass","pass","fail","NOERROR","false","false","false",`,
253253
},
254254
}
255255

@@ -364,9 +364,9 @@ describe('given getAllOrganizationDomainStatuses', () => {
364364
})
365365
const expectedResponse = {
366366
data: {
367-
getAllOrganizationDomainStatuses: `domain,ipAddresses,https,hsts,certificates,ciphers,curves,protocols,spf,dkim,dmarc,rcode,blocked,wildcardSibling,hasEntrustCertificate,hasTop25Vulnerability
368-
"domain.one",,"fail","pass","pass","pass","pass","pass","pass","pass","pass","NOERROR","false","false","false","false"
369-
"domain.two",,"pass","fail","pass","fail","pass","fail","pass","pass","fail","NOERROR","false","false","false","false"`,
367+
getAllOrganizationDomainStatuses: `domain,ipAddresses,https,hsts,certificates,ciphers,curves,protocols,spf,dkim,dmarc,rcode,blocked,wildcardSibling,hasEntrustCertificate,top25Vulnerabilities
368+
"domain.one",,"fail","pass","pass","pass","pass","pass","pass","pass","pass","NOERROR","false","false","false",
369+
"domain.two",,"pass","fail","pass","fail","pass","fail","pass","pass","fail","NOERROR","false","false","false",`,
370370
},
371371
}
372372
expect(response).toEqual(expectedResponse)

api/src/organization/queries/get-all-organization-domain-statuses.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@ export const getAllOrganizationDomainStatuses = {
4747
'blocked',
4848
'wildcardSibling',
4949
'hasEntrustCertificate',
50-
'hasTop25Vulnerability',
50+
'top25Vulnerabilities',
5151
]
5252
let csvOutput = headers.join(',')
5353
domainStatuses.forEach((domainStatus) => {
5454
const csvLine = headers
5555
.map((header) => {
56-
if (header === 'ipAddresses') {
57-
return domainStatus[header].join('|')
56+
if (['ipAddresses', 'top25Vulnerabilities'].includes(header)) {
57+
return domainStatus[header]?.join('|') || []
5858
}
5959
return `"${domainStatus[header]}"`
6060
})

azure-defender-easm/import-easm-additional-findings/clients/kusto_client.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from azure.kusto.data import KustoClient, KustoConnectionStringBuilder
22
from azure.kusto.data.helpers import dataframe_from_result_table
3-
3+
from datetime import datetime, date, timedelta
44
import logging
55
import os
66
from dotenv import load_dotenv
@@ -25,12 +25,22 @@
2525
KUSTO_CLIENT = KustoClient(KCSB_DATA)
2626

2727

28+
def filter_recent_data(data_list, last_seen_key, start_date):
29+
return [
30+
x
31+
for x in data_list
32+
if datetime.strptime(x[last_seen_key].split("T")[0], "%Y-%m-%d").date()
33+
>= start_date
34+
]
35+
36+
2837
def get_web_components_by_asset(asset):
2938
query = f"""
3039
declare query_parameters(asset_name:string = '{asset}');
3140
EasmAssetWebComponent
3241
| where AssetName == asset_name
3342
| where TimeGeneratedValue > ago(24h)
43+
| where WebComponentLastSeen > ago(30d)
3444
| summarize arg_max(TimeGeneratedValue, WebComponentCves, WebComponentPorts) by WebComponentName, WebComponentCategory, WebComponentVersion, WebComponentFirstSeen, WebComponentLastSeen
3545
| project WebComponentName, WebComponentCategory, WebComponentVersion, WebComponentFirstSeen, WebComponentLastSeen, WebComponentCves, WebComponentPorts
3646
"""
@@ -54,6 +64,7 @@ def get_web_components_by_asset(asset):
5464

5565

5666
def get_additional_findings_by_asset(asset):
67+
thirty_days_ago = date.today() - timedelta(days=30)
5768
query = f"""
5869
declare query_parameters(asset_name:string = '{asset}');
5970
EasmHostAsset
@@ -67,4 +78,12 @@ def get_additional_findings_by_asset(asset):
6778
data = dataframe_from_result_table(response.primary_results[0]).to_dict(
6879
orient="records"
6980
)[0]
81+
82+
data["Ports"] = filter_recent_data(
83+
data["Ports"], "PortStateLastSeen", thirty_days_ago
84+
)
85+
data["Locations"] = filter_recent_data(
86+
data["Locations"], "LastSeen", thirty_days_ago
87+
)
88+
7089
return data

frontend/src/guidance/AdditionalFindings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ export function AdditionalFindings({ domain }) {
363363
>
364364
External Attack Surface Management
365365
</Link>{' '}
366-
tool. Updates to these findings occur weekly.
366+
tool. Updates to these findings occur daily.
367367
</Trans>
368368
</ModalBody>
369369
<ModalFooter />

frontend/src/locales/en.po

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4267,8 +4267,12 @@ msgid "The user's role has been successfully updated"
42674267
msgstr "The user's role has been successfully updated"
42684268

42694269
#: src/guidance/AdditionalFindings.js:357
4270-
msgid "These findings are imported from Microsoft's <0>External Attack Surface Management</0> tool. Updates to these findings occur weekly."
4271-
msgstr "These findings are imported from Microsoft's <0>External Attack Surface Management</0> tool. Updates to these findings occur weekly."
4270+
msgid "These findings are imported from Microsoft's <0>External Attack Surface Management</0> tool. Updates to these findings occur daily."
4271+
msgstr "These findings are imported from Microsoft's <0>External Attack Surface Management</0> tool. Updates to these findings occur daily."
4272+
4273+
#: src/guidance/AdditionalFindings.js:357
4274+
#~ msgid "These findings are imported from Microsoft's <0>External Attack Surface Management</0> tool. Updates to these findings occur weekly."
4275+
#~ msgstr "These findings are imported from Microsoft's <0>External Attack Surface Management</0> tool. Updates to these findings occur weekly."
42724276

42734277
#: src/app/ReadGuidancePage.js:154
42744278
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."

frontend/src/locales/fr.po

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4209,8 +4209,8 @@ msgid "The user's role has been successfully updated"
42094209
msgstr "Le rôle de l'utilisateur a été mis à jour avec succès"
42104210

42114211
#: src/guidance/AdditionalFindings.js:357
4212-
msgid "These findings are imported from Microsoft's <0>External Attack Surface Management</0> tool. Updates to these findings occur weekly."
4213-
msgstr "Ces résultats sont importés de l'outil <0>External Attack Surface Management</0> de Microsoft. Ces résultats sont mis à jour chaque semaine."
4212+
msgid "These findings are imported from Microsoft's <0>External Attack Surface Management</0> tool. Updates to these findings occur daily."
4213+
msgstr "Ces résultats sont importés de l'outil <0>External Attack Surface Management</0> de Microsoft. Ces résultats sont mis à jour quotidiennement."
42144214

42154215
#: src/app/ReadGuidancePage.js:154
42164216
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."

0 commit comments

Comments
 (0)