diff --git a/src/MsrcSecurityUpdates/MsrcSecurityUpdates.psd1 b/src/MsrcSecurityUpdates/MsrcSecurityUpdates.psd1 index 364c03d..07b9efc 100644 Binary files a/src/MsrcSecurityUpdates/MsrcSecurityUpdates.psd1 and b/src/MsrcSecurityUpdates/MsrcSecurityUpdates.psd1 differ diff --git a/src/MsrcSecurityUpdates/MsrcSecurityUpdates.tests.ps1 b/src/MsrcSecurityUpdates/MsrcSecurityUpdates.tests.ps1 index aa1572c..ebaef0b 100644 --- a/src/MsrcSecurityUpdates/MsrcSecurityUpdates.tests.ps1 +++ b/src/MsrcSecurityUpdates/MsrcSecurityUpdates.tests.ps1 @@ -5,6 +5,23 @@ $Error.Clear() Get-Module -Name MsrcSecurityUpdates | Remove-Module -Force -Verbose:$false Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'MsrcSecurityUpdates.psd1') -Verbose:$false -Force +Describe 'MSAL Authentication Migration' { + It 'Set-MSRCMsalAccessToken function should be available' { + Get-Command Set-MSRCMsalAccessToken -ErrorAction SilentlyContinue | Should Not BeNullOrEmpty + } + + It 'Module should not have legacy authentication DLL requirements' { + $manifest = Import-PowerShellDataFile (Join-Path -Path $PSScriptRoot -ChildPath 'MsrcSecurityUpdates.psd1') + $manifest.RequiredAssemblies | Where-Object { $_ -like '*ActiveDirectory*' } | Should BeNullOrEmpty + } + + It 'Module should require MSAL.PS module' { + $manifest = Import-PowerShellDataFile (Join-Path -Path $PSScriptRoot -ChildPath 'MsrcSecurityUpdates.psd1') + $msalRequired = $manifest.RequiredModules | Where-Object { $_.ModuleName -eq 'MSAL.PS' } + $msalRequired | Should Not BeNullOrEmpty + } +} + Describe 'API version after module loading' { It '$msrcApiUrl = https://api.msrc.microsoft.com/cvrf/v3.0' { $msrcApiUrl -eq 'https://api.msrc.microsoft.com/cvrf/v3.0' | Should Be $true diff --git a/src/MsrcSecurityUpdates/Private/Get-CVRFID.ps1 b/src/MsrcSecurityUpdates/Private/Get-CVRFID.ps1 index fa683b3..6951285 100644 --- a/src/MsrcSecurityUpdates/Private/Get-CVRFID.ps1 +++ b/src/MsrcSecurityUpdates/Private/Get-CVRFID.ps1 @@ -23,10 +23,10 @@ Process { $RestMethod.Add('ProxyCredential',$global:msrcProxyCredential) - } elseif ($global:MSRCAdalAccessToken) { - - $RestMethod.Headers.Add('Authorization',$($global:MSRCAdalAccessToken.CreateAuthorizationHeader())) + } elseif ($script:MSRCMsalAccessToken) { + $RestMethod.Headers.Add('Authorization',"Bearer $($script:MSRCMsalAccessToken.AccessToken)") + } try { diff --git a/src/MsrcSecurityUpdates/Public/Get-KBDownloadUrl.ps1 b/src/MsrcSecurityUpdates/Public/Get-KBDownloadUrl.ps1 index 982223f..6ff598f 100644 --- a/src/MsrcSecurityUpdates/Public/Get-KBDownloadUrl.ps1 +++ b/src/MsrcSecurityUpdates/Public/Get-KBDownloadUrl.ps1 @@ -1,27 +1,27 @@ -Function Get-KBDownloadUrl { -<# - .SYNOPSIS - Takes the kb output from Get-MsrcCvrfAffectedSoftware and builds the html to insert into a document. - - .DESCRIPTION - Takes the kb output from Get-MsrcCvrfAffectedSoftware and builds the html to insert into a document. - - .PARAMETER KBArticleObject - The KB Article object that contains the id, url, and subtype. - - .EXAMPLE - [PSCustomObject]@{ID="kb123456"; URL="microsoft.com"; SubType="Required"} | Get-KBDownloadUrl -#> -[CmdletBinding()] -[OutputType([System.String])] -Param ( - [Parameter(Mandatory,ValueFromPipeline)] - [PSCustomObject]$KBArticleObject -) -Begin { - $HTML_TO_RETURN = @() -} -Process { +Function Get-KBDownloadUrl { +<# + .SYNOPSIS + Takes the kb output from Get-MsrcCvrfAffectedSoftware and builds the html to insert into a document. + + .DESCRIPTION + Takes the kb output from Get-MsrcCvrfAffectedSoftware and builds the html to insert into a document. + + .PARAMETER KBArticleObject + The KB Article object that contains the id, url, and subtype. + + .EXAMPLE + [PSCustomObject]@{ID="kb123456"; URL="microsoft.com"; SubType="Required"} | Get-KBDownloadUrl +#> +[CmdletBinding()] +[OutputType([System.String])] +Param ( + [Parameter(Mandatory,ValueFromPipeline)] + [PSCustomObject]$KBArticleObject +) +Begin { + $HTML_TO_RETURN = @() +} +Process { if (-not($KBArticleObject)){ 'None' } else { @@ -36,9 +36,9 @@ Process { $HTML_TO_RETURN += $('{1}' -f $kb.URL, $kb.ID) } } - } -} -End { - $HTML_TO_RETURN -join '
' -} + } +} +End { + $HTML_TO_RETURN -join '
' +} } \ No newline at end of file diff --git a/src/MsrcSecurityUpdates/Public/Get-MsrcCvrfAffectedSoftware.ps1 b/src/MsrcSecurityUpdates/Public/Get-MsrcCvrfAffectedSoftware.ps1 index 03d7b87..0db8ec3 100644 --- a/src/MsrcSecurityUpdates/Public/Get-MsrcCvrfAffectedSoftware.ps1 +++ b/src/MsrcSecurityUpdates/Public/Get-MsrcCvrfAffectedSoftware.ps1 @@ -81,7 +81,7 @@ Process { Where-Object {$_.Type -eq $ThreatsImpactType } | Where-Object { $_.ProductID -contains $id } ).Description.Value - ); + ); Weakness = $v.CWE.Value ; 'Customer Action Required' = if ($customerActionNotes = $v.Notes | Where-Object { $_.Title -eq "Customer Action Required" }) { $customerActionNotes.Value diff --git a/src/MsrcSecurityUpdates/Public/Get-MsrcCvrfDocument.ps1 b/src/MsrcSecurityUpdates/Public/Get-MsrcCvrfDocument.ps1 index 2edfff7..c03fe77 100644 --- a/src/MsrcSecurityUpdates/Public/Get-MsrcCvrfDocument.ps1 +++ b/src/MsrcSecurityUpdates/Public/Get-MsrcCvrfDocument.ps1 @@ -80,9 +80,9 @@ Process { } - if ($global:MSRCAdalAccessToken) { - - $RestMethod.Headers.Add('Authorization', $global:MSRCAdalAccessToken.CreateAuthorizationHeader()) + if ($script:MSRCMsalAccessToken) { + + $RestMethod.Headers.Add('Authorization', "Bearer $($script:MSRCMsalAccessToken.AccessToken)") } diff --git a/src/MsrcSecurityUpdates/Public/Get-MsrcSecurityUpdate.ps1 b/src/MsrcSecurityUpdates/Public/Get-MsrcSecurityUpdate.ps1 index 3f52a49..18ada7b 100644 --- a/src/MsrcSecurityUpdates/Public/Get-MsrcSecurityUpdate.ps1 +++ b/src/MsrcSecurityUpdates/Public/Get-MsrcSecurityUpdate.ps1 @@ -171,9 +171,8 @@ Process { if ($global:msrcProxyCredential){ $RestMethod.Add('ProxyCredential' , $global:msrcProxyCredential) } - if ($global:MSRCAdalAccessToken) - { - $RestMethod.Headers.Add('Authorization' , $global:MSRCAdalAccessToken.CreateAuthorizationHeader()) + if ($script:MSRCMsalAccessToken){ + $RestMethod.Headers.Add('Authorization' , "Bearer $($script:MSRCMsalAccessToken.AccessToken)") } try { diff --git a/src/MsrcSecurityUpdates/Public/Get-MsrcVulnerabilityReportHtml.ps1 b/src/MsrcSecurityUpdates/Public/Get-MsrcVulnerabilityReportHtml.ps1 index 3bf9f1e..0708bca 100644 --- a/src/MsrcSecurityUpdates/Public/Get-MsrcVulnerabilityReportHtml.ps1 +++ b/src/MsrcSecurityUpdates/Public/Get-MsrcVulnerabilityReportHtml.ps1 @@ -1,5 +1,5 @@ Function Get-MsrcVulnerabilityReportHtml { -<# + <# .SYNOPSIS Use a CVRF document to create a Vulnerability summary @@ -41,41 +41,42 @@ Function Get-MsrcVulnerabilityReportHtml { It creates a report for specific Vulnerabilities in a CVRF document #> -[CmdletBinding()] -[OutputType([string])] -Param( - - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] - $Vulnerability, - - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] - $ProductTree, - - [Switch]$ShowNoProgress -) -Begin{ - - $HT = @{ ErrorAction = 'Stop'} - - $MaximumSeverityType = 3 - $ThreatsImpactType = 0 - $ThreatsExploitStatusType = 1 - $TagType = 7 - $CNAType = 8 - $RemediationsMitigationType = 1 - $RemediationsWorkaroundType = 0 - - try { - $JsonMetrics = Get-Content -Path (Join-Path -Path $PSScriptRoot -ChildPath 'CVSS-Metrics.json' @HT) @HT | - Out-String @HT| ConvertFrom-Json @HT - - $JsonDescriptions = Get-Content -Path (Join-Path -Path $PSScriptRoot -ChildPath 'CVSS-Descriptions.json'@HT) @HT | - Out-String @HT| ConvertFrom-Json @HT - } catch { - Throw "Failed to get required json files content because $($_.Exception.Message)" - } + [CmdletBinding()] + [OutputType([string])] + Param( + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + $Vulnerability, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + $ProductTree, + + [Switch]$ShowNoProgress + ) + Begin { - $css = @' + $HT = @{ ErrorAction = 'Stop' } + + $MaximumSeverityType = 3 + $ThreatsImpactType = 0 + $ThreatsExploitStatusType = 1 + $TagType = 7 + $CNAType = 8 + $RemediationsMitigationType = 1 + $RemediationsWorkaroundType = 0 + + try { + $JsonMetrics = Get-Content -Path (Join-Path -Path $PSScriptRoot -ChildPath 'CVSS-Metrics.json' @HT) @HT | + Out-String @HT | ConvertFrom-Json @HT + + $JsonDescriptions = Get-Content -Path (Join-Path -Path $PSScriptRoot -ChildPath 'CVSS-Descriptions.json'@HT) @HT | + Out-String @HT | ConvertFrom-Json @HT + } + catch { + Throw "Failed to get required json files content because $($_.Exception.Message)" + } + + $css = @' body { background-color: white; font-family: sans-serif; @@ -108,9 +109,9 @@ Begin{ background-color: #C0C0C0; } '@ -} -Process { - $htmlDocumentTemplate = @' + } + Process { + $htmlDocumentTemplate = @' @@ -167,27 +168,27 @@ Process { '@ - $cveListHtmlObjects = @() - - $cveSectionHtml = '' - - $TotalCVE = $Vulnerability.Count - $count = 0 - $Vulnerability | ForEach-Object -Process { - $count++ - $v = $_ - $Progress = @{ - Activity = 'Getting Msrc Vulnerability Html Report' - Status = "$($count)/$($TotalCVE) => $($v.CVE) " - PercentComplete = ($count/$TotalCVE*100) - ErrorAction = 'SilentlyContinue' - } - if (-not($ShowNoProgress)) { Write-Progress @Progress } - Write-Verbose -Message "Dealing with $($_.CVE)" + $cveListHtmlObjects = @() + + $cveSectionHtml = '' + + $TotalCVE = $Vulnerability.Count + $count = 0 + $Vulnerability | ForEach-Object -Process { + $count++ + $v = $_ + $Progress = @{ + Activity = 'Getting Msrc Vulnerability Html Report' + Status = "$($count)/$($TotalCVE) => $($v.CVE) " + PercentComplete = ($count / $TotalCVE * 100) + ErrorAction = 'SilentlyContinue' + } + if (-not($ShowNoProgress)) { Write-Progress @Progress } + Write-Verbose -Message "Dealing with $($_.CVE)" - #region CVE Summary Table + #region CVE Summary Table - $cveSummaryTableHtml = @' + $cveSummaryTableHtml = @' @@ -206,32 +207,33 @@ Process {
'@ - $MaximumSeverity = Switch ( - ($_.Threats | Where-Object {$_.Type -eq $MaximumSeverityType}).Description.Value | Select-Object -Unique - ) { - 'Critical' { 'Critical' ; break } - 'Important' { 'Important' ; break } - 'Moderate' { 'Moderate' ; break } - 'Low' { 'Low' ; break } - 'None' { 'None' ; break } - default { - Write-Warning "Could not determine the Maximum Severity from the Threats for $($v.CVE)" - 'Unknown' + $MaximumSeverity = Switch ( + ($_.Threats | Where-Object { $_.Type -eq $MaximumSeverityType }).Description.Value | Select-Object -Unique + ) { + 'Critical' { 'Critical' ; break } + 'Important' { 'Important' ; break } + 'Moderate' { 'Moderate' ; break } + 'Low' { 'Low' ; break } + 'None' { 'None' ; break } + default { + Write-Warning "Could not determine the Maximum Severity from the Threats for $($v.CVE)" + 'Unknown' + } + } + if (-not($MaximumSeverity)) { + $MaximumSeverity = 'Unknown' } - } - if (-not($MaximumSeverity)) { - $MaximumSeverity = 'Unknown' - } - if ($ImpactValues = ($v.Threats | Where-Object { $_.Type -eq $ThreatsImpactType }).Description.Value | Select-Object -Unique) { - $impactColumn = $ImpactValues -join ',
' - } else { - Write-Warning -Message "Could not determine the Impact from the Threats for $($v.CVE)" - $impactColumn = 'Unknown' - } + if ($ImpactValues = ($v.Threats | Where-Object { $_.Type -eq $ThreatsImpactType }).Description.Value | Select-Object -Unique) { + $impactColumn = $ImpactValues -join ',
' + } + else { + Write-Warning -Message "Could not determine the Impact from the Threats for $($v.CVE)" + $impactColumn = 'Unknown' + } - $vulnDescriptionColumnTemplate = @' + $vulnDescriptionColumnTemplate = @' CVE Title: {0}
Weakness: {7} @@ -252,32 +254,33 @@ Process {
'@ - $vulnDescriptionColumn = $vulnDescriptionColumnTemplate -f @( - # $cveTitle - $( - if ($cveTitle = $v.Title.Value) { - $cveTitle - } else { - Write-Warning -Message "Missing Title for $($v.CVE)" - ($cveTitle = 'Unknown') - } - ), - # $cvssScoreSet - $( - #Scores among the affected products can be different. So, just find the most severe. - $highestBase = 0.0 - $highestCvssScore = $null - ForEach($score in $v.CvssScoreSets) { - if ($score.BaseScore -gt $highestBase) { - $highestBase = $score.BaseScore - $highestCvssScore = $score + $vulnDescriptionColumn = $vulnDescriptionColumnTemplate -f @( + # $cveTitle + $( + if ($cveTitle = $v.Title.Value) { + $cveTitle + } + else { + Write-Warning -Message "Missing Title for $($v.CVE)" + ($cveTitle = 'Unknown') + } + ), + # $cvssScoreSet + $( + #Scores among the affected products can be different. So, just find the most severe. + $highestBase = 0.0 + $highestCvssScore = $null + ForEach ($score in $v.CvssScoreSets) { + if ($score.BaseScore -gt $highestBase) { + $highestBase = $score.BaseScore + $highestCvssScore = $score + } } - } - if (($null -ne $highestCvssScore) -and ($null -ne $highestCvssScore.Vector) -and ($highestCvssScore.Vector.Split('/').Length -gt 1)) { - $cvssArray = $highestCvssScore.Vector.Split('/') + if (($null -ne $highestCvssScore) -and ($null -ne $highestCvssScore.Vector) -and ($highestCvssScore.Vector.Split('/').Length -gt 1)) { + $cvssArray = $highestCvssScore.Vector.Split('/') - $cvssScoreTemplate = @' + $cvssScoreTemplate = @'
{0} @@ -296,131 +299,139 @@ Process { {2}
'@ - $cvssScoreSet = $cvssScoreTemplate -f @( - $rowTemplate = '{1}{3}' + $cvssScoreSet = $cvssScoreTemplate -f @( + $rowTemplate = '{1}{3}' - $baseTags = 'AC', 'AV', 'A', 'C', 'I', 'PR', 'S', 'UI' - $temporalTags = 'E', 'RC', 'RL' - $baseRows = '' - $temporalRows = '' - for($i = 1; $i -lt $cvssArray.Length; $i++) { + $baseTags = 'AC', 'AV', 'A', 'C', 'I', 'PR', 'S', 'UI' + $temporalTags = 'E', 'RC', 'RL' + $baseRows = '' + $temporalRows = '' + for ($i = 1; $i -lt $cvssArray.Length; $i++) { - $element = $cvssArray[$i] - $split0 = $element.Split(':')[0] + $element = $cvssArray[$i] + $split0 = $element.Split(':')[0] - $metric = $JsonMetrics.$split0 - $value = $JsonMetrics.$element + $metric = $JsonMetrics.$split0 + $value = $JsonMetrics.$element - $metricDescription = $JsonDescriptions.$split0 - $valueDescription = $JsonDescriptions.$element + $metricDescription = $JsonDescriptions.$split0 + $valueDescription = $JsonDescriptions.$element - $row = '' + $metric + '' - $row += '' + $value + '' + $row = '' + $metric + '' + $row += '' + $value + '' - if (($null -ne $metricDescription) -and ($null -ne $valueDescription)) { - if ($baseTags.Contains($split0)) { - $baseRows += $rowTemplate -f $metricDescription, $metric, $valueDescription, $value - } else { - if ($temporalTags.Contains($split0)) { - $temporalRows += $rowTemplate -f $metricDescription, $metric, $valueDescription, $value + if (($null -ne $metricDescription) -and ($null -ne $valueDescription)) { + if ($baseTags.Contains($split0)) { + $baseRows += $rowTemplate -f $metricDescription, $metric, $valueDescription, $value + } + else { + if ($temporalTags.Contains($split0)) { + $temporalRows += $rowTemplate -f $metricDescription, $metric, $valueDescription, $value + } } } } - } - $formattedScore = '{0} Highest BaseScore:{1}/TemporalScore:{2}' -f $cvssArray[0], $highestCvssScore.BaseScore, $highestCvssScore.TemporalScore + $formattedScore = '{0} Highest BaseScore:{1}/TemporalScore:{2}' -f $cvssArray[0], $highestCvssScore.BaseScore, $highestCvssScore.TemporalScore - $formattedScore, $baseRows, $temporalRows - ) - $cvssScoreSet - } else { - 'None' -join '
' - } - ), - # $cveFaq - $( - if ($cveFaq = ($v.Notes | Where-Object {$_.Title -eq 'FAQ'}).Value) { - $cveFaq -join '
' - } else { - 'None' -join '
' - } - ), - # $cveMitigation - $( - if ($cveMitigation = $v.Remediations | Where-Object { $_.Type -eq $RemediationsMitigationType }) { - $cveMitigation.Description.Value -join '
' - - } else { - 'None' -join '
' - } - ), - # $cveWorkaround - $( - if ( $cveWorkaround = ($v.Remediations | Where-Object {$_.Type -eq $RemediationsWorkaroundType }).Description.Value) { - $cveWorkaround -join '
' - } else { - 'None' -join '
' - } - ), - # $Revision - $( - $RevisionStrings = @() - $v.RevisionHistory | - ForEach-Object { - $_ | Add-Member -MemberType NoteProperty -Name RevisionDate -Value ([datetime]$_.Date) -Force -PassThru - } | Sort-Object RevisionDate | - ForEach-Object { - if ( $revision = $($_.Number, $_.RevisionDate.ToString('d'), $_.Description.Value) ) { - $RevisionStrings += $($revision -join '    ') + $formattedScore, $baseRows, $temporalRows + ) + $cvssScoreSet } - } - if ( $RevisionStrings ) { - $RevisionStrings -join '
' - } else { - 'Unknown' -join '
' - } - ), - # Executive Summary - $( - if ($cveExecSummary = ($v.Notes | Where-Object {$_.Title -eq 'Description'}).Value) { - $cveExecSummary -join '
' - } else { - 'None' -join '
' - } - ) - # CWE Weakness - $( - if ($cveWeakness = $(if ($v.CWE) { '{0} : {1}' -f "$($v.CWE.ID)","$($v.CWE.Value)"})) { - $cveWeakness -join '
' - } else { - 'N/A' -join '
' - } - ) - # Customer Action Required - $( - if ($cveCustomerActionRequired = $v.Notes | Where-Object { $_.Title -eq "Customer Action Required" }) { - $cveCustomerActionRequired.Value -join '
' - } else { - 'Yes' -join '
' - } - ) + else { + 'None' -join '
' + } + ), + # $cveFaq + $( + if ($cveFaq = ($v.Notes | Where-Object { $_.Title -eq 'FAQ' }).Value) { + $cveFaq -join '
' + } + else { + 'None' -join '
' + } + ), + # $cveMitigation + $( + if ($cveMitigation = $v.Remediations | Where-Object { $_.Type -eq $RemediationsMitigationType }) { + $cveMitigation.Description.Value -join '
' - ) + } + else { + 'None' -join '
' + } + ), + # $cveWorkaround + $( + if ( $cveWorkaround = ($v.Remediations | Where-Object { $_.Type -eq $RemediationsWorkaroundType }).Description.Value) { + $cveWorkaround -join '
' + } + else { + 'None' -join '
' + } + ), + # $Revision + $( + $RevisionStrings = @() + $v.RevisionHistory | + ForEach-Object { + $_ | Add-Member -MemberType NoteProperty -Name RevisionDate -Value ([datetime]$_.Date) -Force -PassThru + } | Sort-Object RevisionDate | + ForEach-Object { + if ( $revision = $($_.Number, $_.RevisionDate.ToString('d'), $_.Description.Value) ) { + $RevisionStrings += $($revision -join '    ') + } + } + if ( $RevisionStrings ) { + $RevisionStrings -join '
' + } + else { + 'Unknown' -join '
' + } + ), + # Executive Summary + $( + if ($cveExecSummary = ($v.Notes | Where-Object { $_.Title -eq 'Description' }).Value) { + $cveExecSummary -join '
' + } + else { + 'None' -join '
' + } + ) + # CWE Weakness + $( + if ($cveWeakness = $(if ($v.CWE) { '{0} : {1}' -f "$($v.CWE.ID)", "$($v.CWE.Value)" })) { + $cveWeakness -join '
' + } + else { + 'N/A' -join '
' + } + ) + # Customer Action Required + $( + if ($cveCustomerActionRequired = $v.Notes | Where-Object { $_.Title -eq "Customer Action Required" }) { + $cveCustomerActionRequired.Value -join '
' + } + else { + 'Yes' -join '
' + } + ) + ) - $cveSectionHtml += '

{0} - {1}

(
top)' -f $v.CVE, $cveTitle + $cveSectionHtml += '

{0} - {1}

(top)' -f $v.CVE, $cveTitle - #region CVE Summary List - $cveListHtmlObjects += [PSCustomObject]@{ - Tag = $($v.Notes | Where-Object type -eq $TagType).Value - CNA = $($v.Notes | Where-Object type -eq $CNAType).Value - CVEID = $v.CVE - CVETitle = $cveTitle - } - #endregion + #region CVE Summary List + $cveListHtmlObjects += [PSCustomObject]@{ + Tag = $($v.Notes | Where-Object type -eq $TagType).Value + CNA = $($v.Notes | Where-Object type -eq $CNAType).Value + CVEID = $v.CVE + CVETitle = $cveTitle + } + #endregion - $cveSectionHtml += $cveSummaryTableHtml -f @( - @" + $cveSectionHtml += $cveSummaryTableHtml -f @( + @" $($_.CVE)
MITRE @@ -428,14 +439,24 @@ Process { NVD

Issuing CNA: $($($v.Notes | Where-Object type -eq $CNAType).Value)

"@, - $vulnDescriptionColumn, - $MaximumSeverity, - $impactColumn - ) - #endregion - - #region Exploitability Index Table - $exploitabilityIndexTableHtml = @' + $vulnDescriptionColumn, + $MaximumSeverity, + $impactColumn + ) + #endregion + + #region Exploitability Index Table + + #Reset exploitability state for this CVE + $ExploitStatus = [PSCustomObject]@{ + + PubliclyDisclosed = $null + Exploited = $null + LatestSoftwareRelease = $null + OlderSoftwareRelease = $null + DenialOfService = $null + } + $exploitabilityIndexTableHtml = @'

Exploitability Index

The following table provides an exploitability assessment for this vulnerability at the time of original publication.

@@ -456,43 +477,47 @@ Process { '@ - if ($ExploitStatusThreat = ($v.Threats | Where-Object { $_.Type -eq $ThreatsExploitStatusType } | Select-Object -Last 1).Description.Value) { - $ExploitStatus = Get-MsrcThreatExploitStatus -ExploitStatusString $ExploitStatusThreat - } else { - Write-Warning -Message "Missing ExploitStatus for $($v.CVE)" - } - - $cveSectionHtml += $exploitabilityIndexTableHtml -f @( - # $LatestSoftwareRelease - $( - if ($ExploitStatus.LatestSoftwareRelease) { - $ExploitStatus.LatestSoftwareRelease - } else { - 'Not Found' - } - ), - # $publicly disclosed - $( - if ($ExploitStatus.PubliclyDisclosed) { - $ExploitStatus.PubliclyDisclosed - } else { - 'Not Found' - } - ), - # $Exploited - $( - if ($ExploitStatus.Exploited) { - $ExploitStatus.Exploited - } else { - 'Not Found' - } + if ($ExploitStatusThreat = ($v.Threats | Where-Object { $_.Type -eq $ThreatsExploitStatusType } | Select-Object -Last 1).Description.Value) { + $ExploitStatus = Get-MsrcThreatExploitStatus -ExploitStatusString $ExploitStatusThreat + } + else { + Write-Warning -Message "Missing ExploitStatus for $($v.CVE)" + } + + $cveSectionHtml += $exploitabilityIndexTableHtml -f @( + # $LatestSoftwareRelease + $( + if ($ExploitStatus.LatestSoftwareRelease) { + $ExploitStatus.LatestSoftwareRelease + } + else { + 'Not Found' + } + ), + # $publicly disclosed + $( + if ($ExploitStatus.PubliclyDisclosed) { + $ExploitStatus.PubliclyDisclosed + } + else { + 'Not Found' + } + ), + # $Exploited + $( + if ($ExploitStatus.Exploited) { + $ExploitStatus.Exploited + } + else { + 'Not Found' + } + ) ) - ) - #endregion + #endregion - #region Affected Software Table + #region Affected Software Table - $affectedSoftwareTableTemplate = @' + $affectedSoftwareTableTemplate = @' @@ -515,7 +540,7 @@ Process {
'@ - $affectedSoftwareRowTemplate = @' + $affectedSoftwareRowTemplate = @' @@ -529,117 +554,127 @@ Process { '@ - $cveSectionHtml += @' + $cveSectionHtml += @'

Affected Software

The following tables list the affected software details for the vulnerability.

'@ - $affectedSoftware = Get-MsrcCvrfAffectedSoftware -Vulnerability $v -ProductTree $ProductTree - $affectedSoftwareTableHtml = '' + $affectedSoftware = Get-MsrcCvrfAffectedSoftware -Vulnerability $v -ProductTree $ProductTree + $affectedSoftwareTableHtml = '' - $affectedSoftware.FullProductName | Sort-Object -Unique | ForEach-Object { + $affectedSoftware.FullProductName | Sort-Object -Unique | ForEach-Object { - $PN = $_ + $PN = $_ - $affectedSoftware | Where-Object {$_.FullProductName -eq $PN} | ForEach-Object { + $affectedSoftware | Where-Object { $_.FullProductName -eq $PN } | ForEach-Object { - $affectedSoftwareTableHtml += $affectedSoftwareRowTemplate -f @( - $PN, - $( -if ($PN -eq 'Microsoft Edge (Chromium-based)') { - @( - '{1} ({2})' -f 'https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel', - "$($_.KBArticle.ID)", "$($_.KBArticle.SubType)" - ) -} else { - $_.KBArticle | Get-KBDownloadUrl -} - ), - $( - if (-not($_.Severity)) { - 'Unknown' - } else { - $($_.Severity | Select-Object -Unique) -join '
' - } - ), - $( - if (-not($_.Impact)) { - 'Unknown' - } else { - $($_.Impact | Select-Object -Unique) -join '
' - } - ), - $( - if (-not($_.Supercedence)) { - 'None' - } else { - $($_.Supercedence | Select-Object -Unique) -join '
' - } - ), - $( - - 'Base: {0}
Temporal: {1}
Vector: {2}
' -f ( - $( - if(-not($_.CvssScoreSet.base)) { - 'N/A' - } else{ - $_.CvssScoreSet.base - } - ) + $affectedSoftwareTableHtml += $affectedSoftwareRowTemplate -f @( + $PN, + $( + if ($PN -eq 'Microsoft Edge (Chromium-based)') { + @( + '
{1} ({2})' -f 'https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel', + "$($_.KBArticle.ID)", "$($_.KBArticle.SubType)" + ) + } + else { + $_.KBArticle | Get-KBDownloadUrl + } ), - ( - $( - if(-not($_.CvssScoreSet.temporal)) { - 'N/A' - } else { - $_.CvssScoreSet.temporal - } - ) + $( + if (-not($_.Severity)) { + 'Unknown' + } + else { + $($_.Severity | Select-Object -Unique) -join '
' + } ), - ( - $( - if(-not($_.CvssScoreSet.vector)) { - 'N/A' - } else { - $_.CvssScoreSet.vector - } + $( + if (-not($_.Impact)) { + 'Unknown' + } + else { + $($_.Impact | Select-Object -Unique) -join '
' + } + ), + $( + if (-not($_.Supercedence)) { + 'None' + } + else { + $($_.Supercedence | Select-Object -Unique) -join '
' + } + ), + $( + + 'Base: {0}
Temporal: {1}
Vector: {2}
' -f ( + $( + if (-not($_.CvssScoreSet.base)) { + 'N/A' + } + else { + $_.CvssScoreSet.base + } + ) + ), + ( + $( + if (-not($_.CvssScoreSet.temporal)) { + 'N/A' + } + else { + $_.CvssScoreSet.temporal + } + ) + ), + ( + $( + if (-not($_.CvssScoreSet.vector)) { + 'N/A' + } + else { + $_.CvssScoreSet.vector + } + ) ) + ), + $( + if (-not($_.FixedBuild)) { + 'Unknown' + } + else { + $($_.FixedBuild | Select-Object -Unique) -join '
' + } + ), + $( + if (-not($_.RestartRequired)) { + 'Unknown' + } + else { + $($_.RestartRequired | Select-Object -Unique) -join '
' + } + ), + $( + if (-not($_.'Known Issue')) { + 'None' + } + else { + $_.'Known Issue' | Get-KBDownloadUrl + } ) - ), - $( - if (-not($_.FixedBuild)) { - 'Unknown' - } else { - $($_.FixedBuild | Select-Object -Unique) -join '
' - } - ), - $( - if (-not($_.RestartRequired)) { - 'Unknown' - } else { - $($_.RestartRequired | Select-Object -Unique) -join '
' - } - ), - $( - if (-not($_.'Known Issue')) { - 'None' - } else { - $_.'Known Issue' | Get-KBDownloadUrl - } ) - ) + } } - } - $cveSectionHtml += $affectedSoftwareTableTemplate -f @( - $v.CVE, - $affectedSoftwareTableHtml - ) - #endregion + $cveSectionHtml += $affectedSoftwareTableTemplate -f @( + $v.CVE, + $affectedSoftwareTableHtml + ) + #endregion - #region Acknowledgments Table - $acknowledgmentsTableTemplate = @' + #region Acknowledgments Table + $acknowledgmentsTableTemplate = @'

Acknowledgements

{0} {1}
@@ -655,47 +690,48 @@ if ($PN -eq 'Microsoft Edge (Chromium-based)') {
'@ - if ($v.Acknowledgments) { - $ackVal = '' - $v.Acknowledgments | ForEach-Object { + if ($v.Acknowledgments) { + $ackVal = '' + $v.Acknowledgments | ForEach-Object { - if ($_.Name.Value) { - $ackVal += $_.Name.Value - $ackVal += '
' - } - if ($_.URL) { - $ackVal += $_.URL - $ackVal += '
' + if ($_.Name.Value) { + $ackVal += $_.Name.Value + $ackVal += '
' + } + if ($_.URL) { + $ackVal += $_.URL + $ackVal += '
' + } + $ackVal += '

' } - $ackVal += '

' } - } else { - Write-Warning -Message "No Acknowledgments for $($v.CVE)" - $ackVal = 'None' + else { + Write-Warning -Message "No Acknowledgments for $($v.CVE)" + $ackVal = 'None' + } + + $cveSectionHtml += $acknowledgmentsTableTemplate -f @( + $v.CVE, + $ackVal + ) + } -End { + Write-Progress -Activity 'Getting Msrc Vulnerability Html Report' -Completed } + #endregion - $cveSectionHtml += $acknowledgmentsTableTemplate -f @( - $v.CVE, - $ackVal - ) - } -End { - Write-Progress -Activity 'Getting Msrc Vulnerability Html Report' -Completed - } - #endregion - - ( - $htmlDocumentTemplate -f @( - #sort the objects and put them into the table of contents format before injecting into the document template: - ($( $cveListHtmlObjects | Sort-Object -Property Tag | - ForEach-Object { - '{3}{0}
{1} {2}' -f $_.Tag,$_.CVEID,$_.CVETitle,$_.CNA - }) -join "`n"), - $cveSectionHtml, - "$($MyInvocation.MyCommand.Version.ToString())", - $css,$global:msrcApiUrl + ( + $htmlDocumentTemplate -f @( + #sort the objects and put them into the table of contents format before injecting into the document template: + ($( $cveListHtmlObjects | Sort-Object -Property Tag | + ForEach-Object { + '{3}{0} {1} {2}' -f $_.Tag, $_.CVEID, $_.CVETitle, $_.CNA + }) -join "`n"), + $cveSectionHtml, + "$($MyInvocation.MyCommand.Version.ToString())", + $css, $global:msrcApiUrl + ) ) - ) -} -End {} + } + End {} } diff --git a/src/MsrcSecurityUpdates/Public/Set-MSRCApiKey.ps1 b/src/MsrcSecurityUpdates/Public/Set-MSRCApiKey.ps1 index e2c132a..3b629be 100644 --- a/src/MsrcSecurityUpdates/Public/Set-MSRCApiKey.ps1 +++ b/src/MsrcSecurityUpdates/Public/Set-MSRCApiKey.ps1 @@ -45,9 +45,8 @@ Process { Write-Verbose -Message "Successfully defined a msrcProxyCredential global variable that points to $($global:msrcProxy)" } - if ($global:MSRCAdalAccessToken) - { - Remove-Variable -Name MSRCAdalAccessToken -Scope Global + if ($script:MSRCMsalAccessToken){ + Remove-Variable -Name MSRCMsalAccessToken -Scope Script } } } diff --git a/src/MsrcSecurityUpdates/Public/Set-MsrcAdalAccessToken.ps1 b/src/MsrcSecurityUpdates/Public/Set-MsrcAdalAccessToken.ps1 deleted file mode 100644 index 9ec6c90..0000000 --- a/src/MsrcSecurityUpdates/Public/Set-MsrcAdalAccessToken.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -Function Set-MSRCAdalAccessToken { -[CmdletBinding(SupportsShouldProcess)] -Param() -Begin {} -Process { - if ([AppDomain]::CurrentDomain.SetupInformation.TargetFrameworkName -like "*v5.*") { - throw ".Net Core v5.x is not currently supported" - } - - if ($PSCmdlet.ShouldProcess('Set the MSRCApiKey using MSRCAdalAccessToken')) { - Add-Type -Path "$PSScriptRoot/../Microsoft.IdentityModel.Clients.ActiveDirectory.dll" -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - - $authority = 'https://login.windows.net/microsoft.onmicrosoft.com/' - - $authContext = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext($authority) - - $rUri = New-Object System.Uri -ArgumentList 'https://msrc-api-powershell' - - $promptBehavior = [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Auto - - - $ResourceId = 'https://msrc-api-prod.azurewebsites.net' - - $ClientId = 'c7fe3b9e-4d97-462d-ae1b-c16e679be355' - - $global:MSRCAdalAccessToken = $null - - if ($null -ne $authContext.AcquireToken) { - $global:MSRCAdalAccessToken = $authContext.AcquireToken($ResourceId, $ClientId, $rUri,$promptBehavior) - } elseif ($null -ne $authContext.AcquireTokenAsync) { - $platformParams = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters($promptBehavior) - $task = $authContext.AcquireTokenAsync($ResourceId, $ClientId, $rUri,$platformParams) - $task.Wait() - $global:MSRCAdalAccessToken = $task.Result - } - - if ($null -ne $global:MSRCAdalAccessToken) { - Write-Verbose -Message "Successfully set your Access Token required by cmdlets of this module. Calls to the MSRC APIs will now use your access token." - } else { - throw "Failed Acquiring Access Token!" - } - } -} -End {} -} diff --git a/src/MsrcSecurityUpdates/Public/Set-MsrcMsalAccessToken.ps1 b/src/MsrcSecurityUpdates/Public/Set-MsrcMsalAccessToken.ps1 new file mode 100644 index 0000000..dbf142c --- /dev/null +++ b/src/MsrcSecurityUpdates/Public/Set-MsrcMsalAccessToken.ps1 @@ -0,0 +1,55 @@ +Function Set-MSRCMsalAccessToken { +[CmdletBinding(SupportsShouldProcess)] +Param( + [Parameter()] + [Alias('ClientId')] + [string]$ID, + + [Parameter()] + [string]$TenantId = 'microsoft.onmicrosoft.com', + + [Parameter()] + [string]$RedirectUri = 'http://localhost:50000' +) +Begin {} +Process { + if ($PSCmdlet.ShouldProcess('Set the MSRCApiKey using MSRCMsalAccessToken')) { + # Check if MSAL.PS module is available + if (-not (Get-Module -ListAvailable -Name MSAL.PS)) { + throw "MSAL.PS module is required. Please install it using: Install-Module -Name MSAL.PS" + } + + Import-Module MSAL.PS -ErrorAction Stop + + # Clear any existing cached token + $script:MSRCMsalAccessToken = $null + + try { + # Use MSAL.PS to acquire token interactively + $msalParams = @{ + ClientId = $ID + TenantId = $TenantId + Scopes = @("$ID/.default") + RedirectUri = $RedirectUri + Interactive = $true + } + + $tokenResult = Get-MsalToken @msalParams + + if ($tokenResult -and $tokenResult.AccessToken) { + # Store only the access token string, not the full object + $script:MSRCMsalAccessToken = @{ + AccessToken = $tokenResult.AccessToken + } + Write-Verbose -Message "Successfully set your Access Token required by cmdlets of this module. Calls to the MSRC APIs will now use your access token." + } else { + throw "Failed Acquiring Access Token!" + } + } + catch { + throw + } + } +} +End {} +} diff --git a/src/README.md b/src/README.md index fcd8ba3..6ee694b 100644 --- a/src/README.md +++ b/src/README.md @@ -44,16 +44,16 @@ Get-Command -Module MsrcSecurityUpdates CommandType Name Version Source ----------- ---- ------- ------ -Function Get-KBDownloadUrl 1.8.7 MsrcSecurityUpdates -Function Get-MsrcCvrfAffectedSoftware 1.8.7 MsrcSecurityUpdates -Function Get-MsrcCvrfCVESummary 1.8.7 MsrcSecurityUpdates -Function Get-MsrcCvrfDocument 1.8.7 MsrcSecurityUpdates -Function Get-MsrcCvrfExploitabilityIndex 1.8.7 MsrcSecurityUpdates -Function Get-MsrcSecurityBulletinHtml 1.8.7 MsrcSecurityUpdates -Function Get-MsrcSecurityUpdate 1.8.7 MsrcSecurityUpdates -Function Get-MsrcVulnerabilityReportHtml 1.8.7 MsrcSecurityUpdates -Function Set-MSRCAdalAccessToken 1.8.7 MsrcSecurityUpdates -Function Set-MSRCApiKey 1.8.7 MsrcSecurityUpdates +Function Get-KBDownloadUrl 2.0.0 MsrcSecurityUpdates +Function Get-MsrcCvrfAffectedSoftware 2.0.0 MsrcSecurityUpdates +Function Get-MsrcCvrfCVESummary 2.0.0 MsrcSecurityUpdates +Function Get-MsrcCvrfDocument 2.0.0 MsrcSecurityUpdates +Function Get-MsrcCvrfExploitabilityIndex 2.0.0 MsrcSecurityUpdates +Function Get-MsrcSecurityBulletinHtml 2.0.0 MsrcSecurityUpdates +Function Get-MsrcSecurityUpdate 2.0.0 MsrcSecurityUpdates +Function Get-MsrcVulnerabilityReportHtml 2.0.1 MsrcSecurityUpdates +Function Set-MSRCMsalAccessToken 2.0.0 MsrcSecurityUpdates +Function Set-MSRCApiKey 2.0.0 MsrcSecurityUpdates ``` ## Generating a HTML document of Monthly Updates