SAM.STIGChecklist.psm1
# [Variables] ----------------------------------------------------------------------------------------------------- [String]$SAMRoot = $PSScriptRoot [String]$cklSchemaPath = [IO.Path]::Combine("$SAMRoot", 'Data', 'U_Checklist_Schema_V2.xsd') [String]$regexFQDN = "^(?!.*?_.*?)(?!(?:[\w]+?\.)?\-[\w\.\-]*?)(?![\w]+?\-\.(?:[\w\.\-]+?))(?=[\w])(?=[\w\.\-]*?\.+[\w\.\-]*?)(?![\w\.\-]{254})(?!(?:\.?[\w\-\.]*?[\w\-]{64,}\.)+?)[\w\.\-]+?(?<![\w\-\.]*?\.[\d]+?)(?<=[\w\-]{2,})(?<![\w\-]{25})$" [String]$regexMAC = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}\\.[0-9a-fA-F]{4}\\.[0-9a-fA-F]{4})$" # [Functions] ----------------------------------------------------------------------------------------------------- function ConvertTo-NewChecklistVersion { <# .SYNOPSIS Convert Checklist to a new version. .DESCRIPTION Convert Checklist to a new version while keeping the orginal values. This function handles searching LegacyIDs as well if you are importing a checklist before their IDs were converted. .EXAMPLE PS C:\> ConvertTo-NewChecklistVersion -XCCDF "E:\U_MS_DotNet_Framework_4-0_V2R1_STIG.zip" -OrginalChecklist "E:\U_MS_Dot_Net_Framework_4-0_STIG_V1R9-Convert.ckl" -Destination E:\ConvertTest -Force -Verbose This example demonstrates how to convert a single checklist the new checklist version. #> [CmdletBinding()] [OutputType([System.Void])] param ( # Specifies the path to the XCCDF benchmark file. The file must end in '*_Manual-xccdf.xml' or .zip. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateScript( { if (-Not ($_ | Test-Path) ) { throw "XCCDF does not exist" } if ($_ -notmatch "\-xccdf.xml|\.zip") { throw "The file specified in the Path argument must be '*-xccdf.xml' or '.zip'" } return $true })] [String] $XCCDF, # Specifies the path to the orginal checklist to convert. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateScript( { if (-Not ($_ | Test-Path) ) { throw "Checklist does not exist" } if ($_ -notmatch "\.ckl") { throw "The file specified in the Path argument must be '.ckl'" } return $true })] [Alias('FullName', 'PSPath')] [String] $OrginalChecklist, # Specifies the destination to save new STIG checklits. If the destination doesent exist the function will create it. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [String] $Destination, # Specifies the custom checklist name to use when creationg the new STIG checklist. [Parameter(ValueFromPipelineByPropertyName)] [String] $ChecklistName, # Specifies to overwrite the checklist if there is one of the same name. [Parameter(ValueFromPipelineByPropertyName)] [Switch] $Force, # Specifies the regex string used to pull the xccdf file from the STIG Archive. By default it will pull all files that end in '_Manual-xccdf.xml'. If the zip archive has multiple xccdf files you will want to set the search. [Parameter(ValueFromPipelineByPropertyName)] [String] $Search, # Specifies the Classification for the checklist. Default value is 'UNCLASSIFIED'. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('UNCLASSIFIED', 'CLASSIFIED')] [string] $Classification = "UNCLASSIFIED" ) process { # Import Orginal Checklist $orginalCKL = Import-Checklist -Path $OrginalChecklist $oAsset = $orginalCKL.XML.CHECKLIST.ASSET # Create New Checklist and Import $newParams = [ordered]@{ Path = $XCCDF Destination = $Destination Classification = $Classification PassThru = $true } if ($ChecklistName) { $newParams.Add('ChecklistName', $ChecklistName) } if ($force) { $newParams.Add('Force', $true) } if ($Search) { $newParams.Add('Search', $Search) } if ($oAsset.ROLE ) { $newParams.Add('Role', $oAsset.ROLE) } if ($oAsset.ASSET_TYPE) { $newParams.Add('AssetType', $oAsset.ASSET_TYPE) } if ($oAsset.HOST_NAME) { $newParams.Add('HostName', $oAsset.HOST_NAME) } if ($oAsset.HOST_IP) { $newParams.Add('HostIP', $oAsset.HOST_IP) } if ($oAsset.HOST_MAC) { $newParams.Add('HostMAC', $oAsset.HOST_MAC) } if ($oAsset.HOST_FQDN) { $newParams.Add('HostFQDN', $oAsset.HOST_FQDN) } if ($oAsset.TARGET_COMMENT) { $newParams.Add('Comment', $oAsset.TARGET_COMMENT) } if ($oAsset.TECH_AREA) { $newParams.Add('TechArea', $oAsset.TECH_AREA) } #TODO: Look into a method to determine if the CKL is for a website or database if ($oAsset.WEB_DB_SITE) { $newParams.Add('Website', $oAsset.WEB_DB_SITE) } if ($oAsset.WEB_DB_INSTANCE) { $newParams.Add('DatabaseInstance', $oAsset.WEB_DB_INSTANCE) } $newCKL = New-Checklist @newParams # Get new Checklist Vulns $nVulns = Get-ChecklistItem -Checklist $newCKL # Loop through old checklists and update new checklist $orginalCKL.XML.CHECKLIST.STIGS.iSTIG.VULN | ForEach-Object { $vuln = $_ $id = ($vuln.STIG_DATA | Where-Object { $_.VULN_ATTRIBUTE -eq 'Vuln_Num' }).ATTRIBUTE_DATA # Search the new checklist for the Vuln and Legacy IDs $nVuln = $nVulns | Where-Object { $_.Vuln_Num -eq $id -or $_.LEGACY_ID -contains $id } if ($nVuln) { if ($nVuln.Count -eq 1) { $params = @{ Checklist = $newCKL VulnID = $nVuln.Vuln_Num Status = $vuln.STATUS Details = $vuln.FINDING_DETAILS Comments = $vuln.COMMENTS } if ($vuln.Severity_Overide) { $params.add('SeverityOverride', $vuln.Severity_Overide) $params.add('SeverityJustification', $vuln.Severity_Justification) } Set-ChecklistItem @params } else { Write-Verbose "Found multiple entries for VULN_ID '$id'" } } else { Write-Verbose "No VULN_ID Found for '$id'" } } Export-Checklist -Checklist $newCKL } } function Export-Checklist { <# .SYNOPSIS Export STIG Checklist to File .DESCRIPTION Export STIG Checklist to File .EXAMPLE $CKL Object Name : Windows_Server-2016 STIGID : Windows_Server_2016_STIG XML : #document Path : E:\_Temp\Windows_Server-2016.ckl PS C:\> Export-Checklist -Checklist $CKL This example shows how to Export a checklist. $CKL is passed with the xml data and then it is saved to the location listed in $CKL.Path. .EXAMPLE $CKL Object Name : Windows_Server-2016 STIGID : Windows_Server_2016_STIG XML : #document Path : E:\_Temp\Windows_Server-2016.ckl PS C:\> $ckl | Export-Checklist -Destination "E:\$($ckl.Name)`-v1.ckl" This example shows how you can use an imported checklist to export it to another file to create a copy. .EXAMPLE $CKL Object Name : Windows_Server-2016 STIGID : Windows_Server_2016_STIG XML : #document Path : E:\_Temp\Windows_Server-2016.ckl PS C:\> $ckl | Export-Checklist This example shows how you can use an imported checklist and pipe it to Export-Checklist. In this example, Export-Checklist will export the Checklist to whats listed in $CKL.Path #> [CmdletBinding()] [OutputType([System.Void])] param ( # Specifies the XML Object created from Import-Checklist. [Parameter(Mandatory, Position=0, ValueFromPipeline)] [PSObject] $Checklist, # Specifies the path to the STIG Checklist. [Parameter()] [ValidateScript( { if ($_ -notmatch "\.ckl") { throw "The file specified in the Checklist argument must be '.ckl'" } return $true })] [String] $Destination ) process { if (!$PSBoundParameters.Destination) { $Destination = $Checklist.Path } try { $XMLSettings = New-Object -TypeName System.XML.XMLWriterSettings $XMLSettings.Indent = $true; $XMLSettings.IndentChars = "`t" $XMLSettings.NewLineChars = "`n" $XMLSettings.Encoding = New-Object -TypeName System.Text.UTF8Encoding -ArgumentList @($false) $XMLSettings.ConformanceLevel = [System.Xml.ConformanceLevel]::Document $XMLWriter = [System.XML.XMLTextWriter]::Create("$Destination", $XMLSettings) $Checklist.XML.InnerXml = $Checklist.XML.InnerXml -replace "�", "[0x00]" $Checklist.XML.Save($XMLWriter) } catch { Throw $_ } finally { if ($XMLWriter) { $XMLWriter.Flush() $XMLWriter.Dispose() } } } } function Get-ChecklistAsset { <# .SYNOPSIS Get Checklist Asset Information .DESCRIPTION Get Checklist Asset Information across a single checklist or multiple checklists. .EXAMPLE PS C:> Get-ChildItem -Path E:\_Temp\Test -Filter "*.ckl" | Get-ChecklistAsset This example demonstrates how to pull Asset Data from multiple checklists in a directory. .EXAMPLE PS C:> Get-ChecklistAsset -Path "E:\U_MS_Windows_10_V2R2_STIG.ckl" ROLE : Member Server ASSET_TYPE : Computing HOST_NAME : COMPUTER123 HOST_IP : 10.0.0.4 HOST_MAC : 00-15-5D-E8-33-9D HOST_FQDN : COMPUTER123.FQDN.COM TARGET_COMMENT : This is a test WEB_OR_DATABASE : false WEB_DB_SITE : WEB_DB_INSTANCE : This example demonstrates how to pull Asset Data from a single checklist. .EXAMPLE PS C:> Import-Checklist -Path "E:\U_MS_Windows_10_V2R2_STIG.ckl" | Get-ChecklistAsset ROLE : Member Server ASSET_TYPE : Computing HOST_NAME : COMPUTER123 HOST_IP : 10.0.0.4 HOST_MAC : 00-15-5D-E8-33-9D HOST_FQDN : COMPUTER123.FQDN.COM TARGET_COMMENT : This is a test WEB_OR_DATABASE : false WEB_DB_SITE : WEB_DB_INSTANCE : This example demonstrates how to pull Asset Data from a single checklist that was imported using Import-Checklist and piped to Get-ChecklistAsset. #> [CmdletBinding(DefaultParameterSetName = 'FromFile')] [OutputType([System.Management.Automation.PSCustomObject])] param ( # Specify the Checklist XML object to be search. [Parameter(Mandatory, ParameterSetName = 'FromObject', ValueFromPipelineByPropertyName)] [System.Object[]] $Checklist, # Specify the Checklist Path, When path is provided the function will import the checklist. [Parameter(Mandatory, ParameterSetName = 'FromFile', ValueFromPipelineByPropertyName)] [Alias('FullName', 'PSPath')] [String[]] $Path ) process { if ($Path) { $Checklist = Import-Checklist -Path "$Path" } $asset = $Checklist.XML.CHECKLIST.ASSET $outputData = [ordered] @{ ROLE = $asset.ROLE ASSET_TYPE = $asset.ASSET_TYPE HOST_NAME = $asset.HOST_NAME HOST_IP = $asset.HOST_IP HOST_MAC = $asset.HOST_MAC HOST_FQDN = $asset.HOST_FQDN TECH_AREA = $asset.TECH_AREA TARGET_KEY = $asset.TARGET_KEY TARGET_COMMENT = $asset.TARGET_COMMENT WEB_OR_DATABASE = $asset.WEB_OR_DATABASE WEB_DB_SITE = $asset.WEB_DB_SITE WEB_DB_INSTANCE = $asset.WEB_DB_INSTANCE ChecklistPath = $Checklist.Path ChecklistName = $Checklist.Name } #Convert to PSObject $assetInfo = New-Object -TypeName PSObject -Property $outputData # Set Custom Format for object $assetInfo.PSObject.TypeNames.Insert(0,'Checklist.Asset') Write-Output $assetInfo } } function Get-ChecklistItem { <# .SYNOPSIS Get Checklist item details .DESCRIPTION Get all Checklist Vulns or search for specific ones across a single checklist or multiple checklists, .EXAMPLE $CKL Object Name : Windows_Server-2016 STIGID : Windows_Server_2016_STIG Checklist : #document Path : E:\_Temp\Windows_Server-2016.ckl PS C:\> Get-ChecklistItem -Checklist $ckl This example shows how get all vuln items from a single checklist. .EXAMPLE PS C:\> Get-ChildItem -Path E:\_Temp\Test -Filter "*.ckl" | Get-ChecklistItem -VulnID "V-224820" This example shows how to use Get-ChildItem to pull .ckl files from a directory and get a specific VulnID from each checklist. .EXAMPLE $CKL Object Name : Windows_Server-2016 STIGID : Windows_Server_2016_STIG Checklist : #document Path : E:\_Temp\Windows_Server-2016.ckl PS C:\> Get-ChecklistItem -Checklist $ckl -VulnID 'V-224820' Output: HostName : COMPUTER123 Vuln_Num : V-224820 Status : NotAFinding Severity : medium Finding_Details : This is a detail Comments : This is a Comment Severity_Overide : Severity_Justification : This example shows how get a single vuln item from a STIG Checklist. .EXAMPLE $CKL Object Name : Windows_Server-2016 STIGID : Windows_Server_2016_STIG Checklist : #document Path : E:\_Temp\Windows_Server-2016.ckl PS C:\> Get-ChecklistItem -Checklist $ckl -VulnID "V-224820", "V-224825" Output: HostName : COMPUTER123 Vuln_Num : V-224820 Status : NotAFinding Severity : medium Finding_Details : This is a detail Comments : This is a Comment Severity_Overide : Severity_Justification : HostName : COMPUTER123 Vuln_Num : V-224825 Status : Not_Reviewed Severity : medium Finding_Details : Comments : Severity_Overide : Severity_Justification : This example shows how get a multiple vuln items from a STIG Checklist. #> [CmdletBinding(DefaultParameterSetName = 'FromFile')] [OutputType([System.Management.Automation.PSCustomObject])] param ( # Specify the Checklist XML object to be search. [Parameter(ParameterSetName = "FromObject", ValueFromPipelineByPropertyName)] [System.Object[]] $Checklist, # Specify the Checklist Path, When path is provided the function will import the checklist. [Parameter(ParameterSetName = "FromFile", ValueFromPipelineByPropertyName)] [Alias('FullName', 'PSPath')] [String[]] $Path, # Specify the STIG VulnID to search for. [Parameter(ParameterSetName = "FromObject", ValueFromPipelineByPropertyName)] [Parameter(ParameterSetName = "FromFile", ValueFromPipelineByPropertyName)] [String[]] $VulnID ) process { if ($Path) { $Checklist = Import-Checklist -Path "$Path" } $cklHost = "$($Checklist.XML.CHECKLIST.ASSET.HOST_NAME)" $cklPath = $Checklist.Path if ($PSBoundParameters.VulnID) { # Poormans xPath Builder $xPath = "//STIG_DATA[VULN_ATTRIBUTE='Vuln_Num'" $count = 0 $VulnID | ForEach-Object { if ($count -eq 0) { $xPath = $xPath + " and ATTRIBUTE_DATA='$_'" } else { $xPath = $xPath + " or ATTRIBUTE_DATA='$_'" } $count++ } $xPath = $xPath + "]" Select-Xml -XML $Checklist.XML -XPath $xPath | ForEach-Object { Get-VulnItem -Object $_.Node.ParentNode -ChecklistPath $cklPath -HostName $cklHost } } else { $Checklist.XML.CHECKLIST.STIGS.iSTIG.VULN | ForEach-Object { Get-VulnItem -Object $_ -ChecklistPath $cklPath -HostName $cklHost } } } } function Get-ChecklistStatus { <# .SYNOPSIS Get Metrics from a STIG Checklist .DESCRIPTION Get Metrics on Total Items, Item Status, and Open CAT Findings from a STIG Checklist. CATI, CATII, and CATIII are pulled from items that are in an Open status, this wont include items that are still not reviewd. .EXAMPLE PS C:\> Get-ChecklistStatus -Path "E:\_Temp\U_MS_Windows_Server_2016_STIG_V2R2.ckl" This example demonstrates how to get metrics from a single Output HostName : Computer1 STIGID : Windows_Server_2016_STIG FileName : U_MS_Windows_Server_2016_STIG_V2R2.ckl VulnCount : 273 Open : 13 NotAFinding : 197 NotReviewed : 52 NotApplicable : 11 CATI : 2 CATII : 11 CATIII : 0 VuldDetails : {NotReviewed, Open, NotAFinding, NotApplicable} FileInfo : {CreationTime, LastAccessTime, LastWriteTime, FileHash…} #> [CmdletBinding(DefaultParameterSetName = 'FromFile')] [OutputType([System.Management.Automation.PSCustomObject])] param ( # Specify the Checklist XML object to be search. [Parameter(Mandatory, ParameterSetName = 'FromObject', ValueFromPipelineByPropertyName)] [System.Object[]] $Checklist, # Specify the Checklist Path, When path is provided the function will import the checklist. [Parameter(Mandatory, ParameterSetName = 'FromFile', ValueFromPipelineByPropertyName)] [Alias('FullName', 'PSPath')] [String[]] $Path ) process { $openFinding = @() $notAFinding = @() $notApplicable = @() $notReviewed = @() if ($Path) { $Checklist = Import-Checklist -Path "$Path" } $cklHostName = $($Checklist.XML.CHECKLIST.ASSET.HOST_NAME) $vulnCount = 0 $catIFinding = 0 $catIFinding = 0 $catIIIFinding = 0 $checklist.XML.CHECKLIST.STIGS.iSTIG.VULN | ForEach-Object { $vuln = $_ $severity = $($vuln.STIG_DATA | Where-Object { $_.VULN_ATTRIBUTE -eq "Severity" }).ATTRIBUTE_DATA switch ($vuln.Status) { "Open" { $openFinding += $(Get-VulnItem -Object $vuln -ChecklistPath $Checklist.Path -HostName $cklHostName) Switch -regex ($severity) { 'high|critical' { $catIFinding++ } 'medium' { $catIIFinding++ } 'low' { $catIIIFinding++ } } } "NotAFinding" { $notAFinding += $(Get-VulnItem -Object $vuln -ChecklistPath $Checklist.Path -HostName $cklHostName) } "Not_Reviewed" { $notReviewed += $(Get-VulnItem -Object $vuln -ChecklistPath $Checklist.Path -HostName $cklHostName) } "Not_Applicable" { $notApplicable += $(Get-VulnItem -Object $vuln -ChecklistPath $Checklist.Path -HostName $cklHostName) } } $vulnCount++ } # Get File Details $cklFile = Get-Item -Path $Checklist.Path $cklFileHash = Get-FileHash -Path $Checklist.Path $cklFileDetails = [Ordered]@{ CreationTime = $cklFile.CreationTime LastAccessTime = $cklFile.LastAccessTime LastWriteTime = $cklFile.LastWriteTime FileHash = $cklFileHash.Hash HashAlgorithm = $cklFileHash.Algorithm } # Compile Data $params = [Ordered]@{ HostName = $cklHostName STIGID = $(Select-Xml -Xml $Checklist.XML -XPath "//SI_DATA[SID_NAME='stigid']").Node.SID_Data FileName = $(Split-Path -Path $Checklist.Path -Leaf) VulnCount = $vulnCount Open = $openFinding.Count NotAFinding = $notAFinding.Count NotReviewed = $notReviewed.Count NotApplicable = $notApplicable.Count CATI = $catIFinding CATII = $catIIFinding CATIII = $catIIIFinding VulnDetails = @{ Open = $openFinding NotAFinding = $notAFinding NotReviewed = $notReviewed NotApplicable = $notApplicable } FileInfo = $cklFileDetails } Write-Output $(New-Object -TypeName psobject -Property $params) } } Function Import-Checklist { <# .SYNOPSIS Import STIG Checklist (CKL) .DESCRIPTION Load a CKL file as an [XML] element. This can then be passed to other functions in this module. .EXAMPLE PS:> $ckl = Import-Checklist -Checklist C:\Temp\U_Windows10.ckl This example shows how to import a checklist. OUTPUT ------ Name : Path STIGID : IIS_10-0_Site_STIG Checklist : #document Path : E:\_Temp\Checklists\U_MS_IIS_10-0_Site_STIG_V2R2.ckl #> [CmdletBinding()] [OutputType([PSObject])] Param( # Specify the path to the STIG Checklist. When function is ran, it will validate the file passed exists and is a .ckl file. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateScript( { if (-Not ($_ | Test-Path) ) { throw "Checklist does not exist" } if ($_ -notmatch "\.ckl") { throw "The file specified in the Checklist argument must be '.ckl'" } return $true })] [Alias('FullName', 'PSPath')] [String] $Path ) process { $results = Test-XMLFile -XmlPath $Path -SchemaPath $cklSchemaPath If ($results.Valid -eq $false) { throw $results.Exceptions } else { $xml = [XML](Get-Content -Encoding UTF8 -Path $Path) $params = [Ordered]@{ Name = (Split-Path $Path -Leaf).Split(".")[0] STIGID = (Select-Xml -Xml $xml.Checklist -XPath "//SI_DATA[SID_NAME='stigid']").Node.SID_Data XML = $xml Path = $Path } #Convert to PSObject $import = New-Object -TypeName PSObject -Property $params # Set Custom Format for object $import.PSObject.TypeNames.Insert(0, 'Checklist.Import') Write-Output $import } } } function New-Checklist { <# .SYNOPSIS Create a New STIG Checklist .DESCRIPTION This function creates a new STIG Checklist from the DoD Cyber Exchange. .EXAMPLE PS C:> $ckl = New-Checklist -Path "E:\_Temp\U_MS_Windows_Server_2016_V2R2_STIG.zip" -Destination E:\_Temp -PassThru This example shows how to create a new checklist from the zip file downloaded from Cyber.mil. This example also leverages the -PassThru switch that will output the Checklist object to use with other functions. .EXAMPLE Directory: E:\_Temp\Test Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 5/12/2021 9:56 AM 535864 U_A10_Networks_ADC_ALG_V2R1_STIG.zip -a--- 5/12/2021 9:56 AM 276032 U_A10_Networks_ADC_NDM_V1R1_STIG.zip -a--- 5/12/2021 9:56 AM 681814 U_AAA_Services_V1R2_SRG.zip -a--- 5/12/2021 9:56 AM 640328 U_Active_Directory_Domain_V2R13_STIG.zip -a--- 5/12/2021 9:56 AM 444331 U_Active_Directory_Forest_V2R8_STIG.zip -a--- 5/12/2021 9:57 AM 680792 U_Adobe_Acrobat_Pro_DC_Classic_V2R1_STIG.zip PS C:>Get-ChildItem -Path E:\_Temp\Test -Filter "*.zip" | New-Checklist -Destination E:\_Temp\Checklists This example shows how to pull a directory full of zip files from Cyber.mil and convert them to Checklists. This will use the name of the zip file for the Checklist name Directory: E:\_Temp\Checklists Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 5/19/2021 8:20 AM 186660 U_A10_Networks_ADC_ALG_STIG_V2R1.ckl -a--- 5/19/2021 8:20 AM 214704 U_A10_Networks_ADC_NDM_STIG_V1R1.ckl -a--- 5/19/2021 8:20 AM 371142 U_AAA_Services_SRG_V1R2.ckl -a--- 5/19/2021 8:20 AM 207120 U_Active_Directory_Domain_STIG_V2R13.ckl -a--- 5/19/2021 8:20 AM 33416 U_Active_Directory_Forest_STIG_V2R8.ckl -a--- 5/19/2021 8:20 AM 141849 U_Adobe_Acrobat_Pro_DC_Classic_V2R1.ckl #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'None')] [OutputType([System.Management.Automation.PSCustomObject])] param ( # Specifies the path to the XCCDF Benchmark file. The file must end in '*_Manual-xccdf.xml' or .zip. [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = "FromFile")] [ValidateScript( { if (-Not ($_ | Test-Path) ) { throw "XCCDF does not exist" } if ($_ -notmatch "\-xccdf.xml|\.zip") { throw "The file specified in the Path argument must be '*-xccdf.xml' or '.zip'" } return $true })] [Alias('FullName', 'PSPath')] [String] $Path, # Specifies the destination to save new STIG Checklits. If the Destination doesent exist the function will create it. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [String] $Destination, # Specifies the custom Checklist name to use when creationg the Stig Checklist. [Parameter(ValueFromPipelineByPropertyName)] [String] $ChecklistName, # Specifies the Classification for the checklist. Default value is 'UNCLASSIFIED'. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('UNCLASSIFIED', 'CLASSIFIED')] [string] $Classification = "UNCLASSIFIED", # Specifies to overwrite the Checklist if there is one of the same name. [Parameter(ValueFromPipelineByPropertyName)] [Switch] $Force, # Specifies if the function should return the checklist information [Parameter(ValueFromPipelineByPropertyName)] [Switch] $PassThru, # Specifies the regex string used to pull the xccdf file from the STIG Archive. By default it will pull all files that end in '_Manual-xccdf.xml'. If the zip archive has multiple xccdf files you will want to set the search. [Parameter(ValueFromPipelineByPropertyName)] [String] $Search, # Specifies the host name for the checklist for either computer or non-computer checklists. [Parameter(ValueFromPipelineByPropertyName)] [String] $HostName, # Specifies the Host IP Address for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [String] $HostIP, # Specifies the Host MAC Address for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [String] $HostMAC, # Specifies the Host FQDN for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [String] $HostFQDN, # Specifies the Target Comment for the given host. [Parameter(ValueFromPipelineByPropertyName)] [String] $Comment, # Specifies the Host WebSite for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [String] $WebSite, # Specifies the Host Database Instance for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [String] $DatabaseInstance, # Specifies the Host Database for Checklist. [Parameter(ValueFromPipelineByPropertyName)] [String] $Database, # Specifies the Asset Type for the checklist. Default value is Computing. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('Non-Computing', 'Computing')] [String] $AssetType = 'Computing', # Specifies the Asset Role for the checklist. Default value is 'None'. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('None', 'Workstation', 'Member Server', 'Domain Controller')] [String] $Role, # Specifies the Technology Area the STIG Applies to. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet( "Application Review", "Boundary Security", "CDS Admin Review", "CDS Technical Review", "Database Review", "Domain Name System (DNS)", "Exchange Server", "Host Based System Security (HBSS)", "Internal Network", "Mobility", "Releasable Networks (REL)", "Traditional Security", "UNIX OS", "VVOIP Review", "Web Review", "Windows OS", "Other Review" )] [String] $TechnologyArea ) begin { function Get-DisscussionItem ($Data, $Search) { $(Select-String -InputObject $Data -Pattern "<$Search>(.|\n)*?</$Search>" | ForEach-Object { $_.Matches } | ForEach-Object { $_.Value }) -replace "<(/|)$Search>" } # Create XML Settings $xmlWriterSettings = [System.Xml.XmlWriterSettings]::new() $xmlWriterSettings.Indent = $true $xmlWriterSettings.IndentChars = "`t" $xmlWriterSettings.NewLineChars = "`n" } process { $xccdf = @() $pathExtenstion = $($path.Split(".")[-1]).ToLower() # Test Destination Path, Create if Missing if (!(Test-Path -Path $Destination)) { New-Item -Path $Destination -ItemType Directory | Out-Null Write-Verbose "[$($MyInvocation.MyCommand)] Created Missing Destination Directory" } switch ($pathExtenstion) { 'zip' { $params = @{ Path = $Path } if ($Search) { $params.add('Search', $Search) } $xccdf += Get-XCCDFFromArchive @params } 'xml' { $params = [Ordered]@{ Name = (Split-Path $Path -Leaf).Split(".")[0] Path = $Path XML = [XML](Get-Content -Encoding UTF8 -Path $Path) } $xccdf += New-Object -TypeName PSObject -Property $params } } $xccdf | ForEach-Object { $benchmark = $_ $defaultName = $benchmark.Name -replace "_Manual-xccdf.xml" # Generate Checklist Name if not Provided $fileName = if ($PSBoundParameters.ChecklistName) { $ChecklistName } else { if ($PSBoundParameters.HostName) { $fnParams = @{ ChecklistName = $defaultName HostName = $HostName } if ($PSBoundParameters.WebSite) { $fnParams.add('Website', $WebSite) } elseif ($PSBoundParameters.Database) { $fnParams.add('Database', $Database) } elseif ($PSBoundParameters.DatabaseInstance) { $fnParams.add('DatabaseInstance', $DatabaseInstance) } New-ChecklistFileName @fnParams } else { "{0}.ckl" -f $defaultName } } [System.IO.FileInfo]$cklPath = Join-Path -Path $Destination -ChildPath $fileName # Check if a file already exists if ($(Test-Path $cklPath.FullName) -eq $true -and !$force) { $errMessage = @{ Message = "Another Checklist with the Same name '$fileName' already exist at the destination. Use -Force to overide it." ErrorID = "SAM0001" Category = "ResourceExists" ErrorAction = "Stop" } Write-Error @errMessage } elseif ($(Test-Path $cklPath.FullName) -eq $true -and $Force) { if ($PSCmdlet.ShouldProcess($cklPath.FullName, "Remove Old checklist")) { Remove-Item -Path $cklPath.FullName -Force -ErrorAction Stop -Confirm:$false | Out-Null } } # Generate Checklist $xccdfBenchmark = $([xml]$benchmark.XML).Benchmark $xccdfFileName = $benchmark.Name $stigID = $xccdfBenchmark.id [xml]$cklSchema = Get-Content -Path $cklSchemaPath if ($PSCmdlet.ShouldProcess($cklPath.FullName, "Create Checklist")) { $writer = [System.Xml.XmlWriter]::Create($cklPath.FullName, $xmlWriterSettings) $module = $(Get-Command -Name $MyInvocation.MyCommand).ModuleName $writer.WriteComment(" Created by '$module' - Version: $((Get-Module -Name $module).Version.ToString()) ") $writer.WriteComment(" Following Cyber.Mil STIGViewer Checklist Schema V$($cklSchema.schema.annotation.appinfo.version) - $($cklSchema.schema.annotation.appinfo.date)") # [Checklist Element] ----------------------------------------------------- $writer.WriteStartElement('CHECKLIST') # [Asset Element] ----------------------------------------------------- $writer.WriteStartElement("ASSET") $assetData = [ordered] @{ 'ROLE' = "None" 'ASSET_TYPE' = "$AssetType" 'HOST_NAME' = "" 'HOST_IP' = "" 'HOST_MAC' = "" 'HOST_FQDN' = "" 'TARGET_COMMENT' = "" 'TECH_AREA' = "" 'TARGET_KEY' = $($xccdfBenchmark.Group[0].Rule.reference.identifier) 'WEB_OR_DATABASE' = 'false' 'WEB_DB_SITE' = "" 'WEB_DB_INSTANCE' = "" } if ($PSBoundParameters.Role) { $assetData.ROLE = $Role } if ($PSBoundParameters.AssetType) { $assetData.ASSET_TYPE = $AssetType } if ($PSBoundParameters.HostName) { $assetData.HOST_NAME = $HostName } if ($AssetType -eq 'Computing') { if ($PSBoundParameters.HostIP) { $assetData.HOST_IP = $HostIP } if ($PSBoundParameters.HostMAC) { $assetData.HOST_MAC = $HostMAC } if ($PSBoundParameters.HostFQDN) { $assetData.HOST_FQDN = $HostFQDN } if ($PSBoundParameters.Comment) { $assetData.TARGET_COMMENT = $Comment } if ($PSBoundParameters.TechnologyArea) { $assetData.TECH_AREA = $TechnologyArea } if ($PSBoundParameters.WebSite) { $assetData.WEB_OR_DATABASE = 'true' $assetData.WEB_DB_SITE = $Website } if ($PSBoundParameters.DatabaseInstance) { $assetData.WEB_OR_DATABASE = 'true' $assetData.WEB_DB_INSTANCE = $DatabaseInstance } if ($PSBoundParameters.Database) { $assetData.WEB_OR_DATABASE = 'true' $assetData.WEB_DB_SITE = $Database } } # Create Asset Elements foreach ($asset in $assetData.GetEnumerator()) { $writer.WriteStartElement($asset.name) $writer.WriteString($asset.value) $writer.WriteEndElement() } $writer.WriteEndElement(<#ASSET#>) $writer.WriteStartElement("STIGS") $writer.WriteStartElement("iSTIG") $writer.WriteStartElement("STIG_INFO") $cklInfo = [ordered] @{ 'version' = $xccdfBenchmark.version 'classification' = $Classification 'customname' = "" 'stigid' = $xccdfBenchmark.id 'description' = $xccdfBenchmark.description #$(ConvertTo-SafeXML -String $xccdfBenchmark.description) 'filename' = $XCCDFFileName 'releaseinfo' = $xccdfBenchmark.'plain-text'.'#text' 'title' = $xccdfBenchmark.title 'uuid' = (New-Guid).Guid 'notice' = $xccdfBenchmark.notice.id 'source' = $xccdfBenchmark.reference.source } foreach ($info in $cklInfo.GetEnumerator()) { $writer.WriteStartElement("SI_DATA") $writer.WriteStartElement('SID_NAME') $writer.WriteString($info.name) $writer.WriteEndElement(<#SID_NAME#>) if ($info.value -ne "") { $writer.WriteStartElement('SID_DATA') $writer.WriteString($info.value) $writer.WriteEndElement(<#SID_DATA#>) } $writer.WriteEndElement(<#SI_DATA#>) } $writer.WriteEndElement(<#STIG_INFO#>) $xccdfBenchmark.Group | ForEach-Object { $vulnId = $_ $stigRef = "{0} :: Version {1}, {2}" -f $cklInfo.Title, $cklInfo.Version, $cklInfo.ReleaseInfo $writer.WriteStartElement("VULN") $cciList = $_.Rule.ident | Where-Object { $_.System -eq 'http://iase.disa.mil/cci' } $legList = $_.Rule.ident | Where-Object { $_.system -eq 'http://cyber.mil/legacy' } $vulnInfo = [ordered] @{ Vuln_Num = $vulnId.id Severity = $vulnId.Rule.severity Group_Title = $vulnId.title Rule_ID = $vulnId.Rule.id Rule_Ver = $vulnId.Rule.version Rule_Title = $vulnId.Rule.title Vuln_Discuss = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "VulnDiscussion") IA_Controls = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "IAControls") Check_Content = $vulnId.Rule.check.'check-content' Fix_Text = $vulnId.Rule.fixtext.InnerText False_Positives = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "FalsePositives") False_Negatives = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "FalseNegatives") Documentable = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "Documentable") Mitigations = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "Mitigations") Potential_Impact = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "PotentialImpacts") Third_Party_Tools = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "ThirdPartyTools") Mitigation_Control = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "MitigationControl") Responsibility = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "Responsibility") Security_Override_Guidance = $(Get-DisscussionItem -Data "$($vulnId.Rule.description)" -Search "SeverityOverrideGuidance") Check_Content_Ref = $vulnId.Rule.check.'check-content-ref'.name Weight = $vulnId.Rule.Weight Class = 'Unclass' STIGRef = $stigRef TargetKey = $_.Rule.reference.identifier STIG_UUID = (New-Guid).Guid } foreach ($vuln in $vulnInfo.GetEnumerator()) { $writer.WriteStartElement("STIG_DATA") $writer.WriteStartElement("VULN_ATTRIBUTE") $writer.WriteString($vuln.Name) $writer.WriteEndElement(<#VULN_ATTRIBUTE#>) $writer.WriteStartElement("ATTRIBUTE_DATA") $writer.WriteString($vuln.Value) $writer.WriteEndElement(<#ATTRIBUTE_DATA#>) $writer.WriteEndElement(<#STIG_DATA#>) } if ($legList) { $legList.'#text' | ForEach-Object { $writer.WriteStartElement("STIG_DATA") $writer.WriteStartElement("VULN_ATTRIBUTE") $writer.WriteString('LEGACY_ID') $writer.WriteEndElement(<#VULN_ATTRIBUTE#>) $writer.WriteStartElement("ATTRIBUTE_DATA") $writer.WriteString($_) $writer.WriteEndElement(<#ATTRIBUTE_DATA#>) $writer.WriteEndElement(<#STIG_DATA#>) } } else { # Mimics STIGViewer and put two empty Legacy_IDs. $writer.WriteStartElement("STIG_DATA") $writer.WriteStartElement("VULN_ATTRIBUTE") $writer.WriteString('LEGACY_ID') $writer.WriteEndElement(<#VULN_ATTRIBUTE#>) $writer.WriteStartElement("ATTRIBUTE_DATA") $writer.WriteString("") $writer.WriteEndElement(<#ATTRIBUTE_DATA#>) $writer.WriteEndElement(<#STIG_DATA#>) $writer.WriteStartElement("STIG_DATA") $writer.WriteStartElement("VULN_ATTRIBUTE") $writer.WriteString('LEGACY_ID') $writer.WriteEndElement(<#VULN_ATTRIBUTE#>) $writer.WriteStartElement("ATTRIBUTE_DATA") $writer.WriteString("") $writer.WriteEndElement(<#ATTRIBUTE_DATA#>) $writer.WriteEndElement(<#STIG_DATA#>) } $cciList.'#text' | ForEach-Object { $writer.WriteStartElement("STIG_DATA") $writer.WriteStartElement("VULN_ATTRIBUTE") $writer.WriteString('CCI_REF') $writer.WriteEndElement(<#VULN_ATTRIBUTE#>) $writer.WriteStartElement("ATTRIBUTE_DATA") $writer.WriteString($_) $writer.WriteEndElement(<#ATTRIBUTE_DATA#>) $writer.WriteEndElement(<#STIG_DATA#>) } $writer.WriteStartElement("STATUS") $writer.WriteString('Not_Reviewed') $writer.WriteEndElement(<#STATUS#>) $writer.WriteStartElement("FINDING_DETAILS") $writer.WriteString('') $writer.WriteEndElement(<#FINDING_DETAILS#>) $writer.WriteStartElement("COMMENTS") $writer.WriteString('') $writer.WriteEndElement(<#COMMENTS#>) $writer.WriteStartElement("SEVERITY_OVERRIDE") $writer.WriteString('') $writer.WriteEndElement(<#SEVERITY_OVERRIDE#>) $writer.WriteStartElement("SEVERITY_JUSTIFICATION") $writer.WriteString('') $writer.WriteEndElement(<#SEVERITY_JUSTIFICATION#>) $writer.WriteEndElement(<#VULN#>) } $writer.WriteEndElement(<#iSTIG#>) $writer.WriteEndElement(<#STIGS#>) $writer.WriteEndElement(<#CHECKLIST#>) # Close XML Writer $writer.Flush() $writer.Close() # Test Checklist $results = Test-XMLFile -XmlPath $cklPath.FullName -SchemaPath $cklSchemaPath If ($results.Valid -eq $false) { throw $results.Exceptions } else { if ($PassThru) { $params = [Ordered]@{ Name = $cklPath.BaseName STIGID = $stigID XML = [XML](Get-Content -Encoding UTF8 -Path $cklPath.FullName) Path = $cklPath.FullName } #Convert to PSObject $import = New-Object -TypeName PSObject -Property $params # Set Custom Format for object $import.PSObject.TypeNames.Insert(0, 'Checklist.Import') Write-Output $import } } } } } } function Set-ChecklistAsset { <# .SYNOPSIS Sets Checklist Asset Information .DESCRIPTION Sets Checklist Asset Information across a single checklist or multiple checklists. .EXAMPLE PS E:> Set-ChecklistAsset -Path "E:\U_MS_Windows_10_V2R2_STIG.ckl" -HostName Computer123 -HostIP 10.0.0.32 This example demonstrates how to set the Hostname and Host IP for a given checklist. .EXAMPLE PS E:> Set-ChecklistAsset -Path "E:\U_MS_Windows_10_V2R2_STIG.ckl" -HostName Computer123 -HostIP 10.0.0.32 -PassThru This example demonstrates how to set the Hostname and Host IP for a set of checklists in a directory. #> [CmdletBinding(DefaultParameterSetName = 'FromFile')] [OutputType([System.Void])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Several Parameters are used just to trigger an error' )] param ( # Specifies the Checklist XML object to be updated. [Parameter(Mandatory, ParameterSetName = 'FromObject', ValueFromPipelineByPropertyName)] [PSObject] $Checklist, # Specifies the Checklist Path, When path is provided the function will import the checklist. [Parameter(Mandatory, ParameterSetName = 'FromFile', ValueFromPipelineByPropertyName)] [Alias('FullName', 'PSPath')] [String] $Path, # Specifies the host name for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [string] $HostName, # Specifies the Host IP Address for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [string] $HostIP, # Specifies the Host MAC Address for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [string] $HostMAC, # Specifies the Host FQDN for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [string] $HostFQDN, # Specifies the Target Comment for the given host. [Parameter(ValueFromPipelineByPropertyName)] [string] $Comment, # Specifies the Host WebSite for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [string] $WebSite, # Specifies the Host Database Instance for the checklist. [Parameter(ValueFromPipelineByPropertyName)] [string] $DatabaseInstance, # Specifies the Host Database for Checklist. [Parameter(ValueFromPipelineByPropertyName)] [string] $Database, # Specifies the Asset Type for the checklist.. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('Non-Computing', 'Computing')] [string] $AssetType, # Specifies the Asset Role for the checklist. Default value is 'None'. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('None', 'Workstation', 'Member Server', 'Domain Controller')] [string] $Role, # Specifies the Technology Area the STIG Applies to. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet( "Application Review", "Boundary Security", "CDS Admin Review", "CDS Technical Review", "Database Review", "Domain Name System (DNS)", "Exchange Server", "Host Based System Security (HBSS)", "Internal Network", "Mobility", "Releasable Networks (REL)", "Traditional Security", "UNIX OS", "VVOIP Review", "Web Review", "Windows OS", "Other Review" )] [string] $TechnologyArea ) process { if ($Path) { $Checklist = Import-Checklist -Path "$Path" } $modified = $false $asset = $Checklist.XML.CHECKLIST.ASSET If ($HostName) { $escaped = $(ConvertTo-SafeXML -String $HostName) if ( $asset.HOST_NAME -ne $escaped ) { $asset.HOST_NAME = "$($escaped.ToUpper())" $modified = $true } else { Write-Verbose "$($Checklist.Name) - HostName '$($asset.HOST_NAME)' already matches '$escaped', no change." } } If ($HostIP) { # Test if valid IP Address try { # This will throw an error if its in the wrong format. [System.Net.IPAddress]$IP = $HostIP if ( $asset.HOST_IP -ne $HostIP ) { $asset.HOST_IP = "$HostIP" $modified = $true } else { Write-Verbose "$($Checklist.Name) - HostIP '$($asset.HOST_IP)' already matches '$HostIP', no change." } } catch { Write-Verbose "$($Checklist.Name) - HostIP - $_" } } if ($HostMAC) { if ($HostMAC -match $regexMAC) { $escaped = $(ConvertTo-SafeXML -String $HostMAC) if ( $asset.HOST_MAC -ne $escaped ) { $asset.HOST_MAC = "$escaped" $modified = $true } else { Write-Verbose "$($Checklist.Name) - MAC '$($asset.HOST_MAC)' already matches '$escaped', no change." } } else { Write-Verbose "$($Checklist.Name) - MAC '$HostMAC' is not a valid format, no change." } } if ($HostFQDN) { if ($HostFQDN -match $regexFQDN) { $escaped = $(ConvertTo-SafeXML -String $HostFQDN) if ( $asset.HOST_FQDN -ne $escaped ) { $asset.HOST_FQDN = "$($escaped.ToUpper())" $modified = $true } else { Write-Verbose "$($Checklist.Name) - FQDN '$($asset.HOST_FQDN)' already matches '$escaped', no change." } } else { Write-Verbose "$($Checklist.Name) - FQDN '$HostFQDN' is not a valid format, no change." } } if ($Comment) { $escaped = $(ConvertTo-SafeXML -String $Comment) if ( $asset.TARGET_COMMENT -ne $escaped ) { $asset.TARGET_COMMENT = "$escaped" $modified = $true } else { Write-Verbose "$($Checklist.Name) - Comment '$($asset.TARGET_COMMENT)' already matches '$escaped', no change." } } if ($Role) { $escaped = $(ConvertTo-SafeXML -String $Role) if ( $asset.ROLE -ne $escaped) { $asset.ROLE = "$escaped" $modified = $true } else { Write-Verbose "$($Checklist.Name) - Role '$($asset.ROLE)' already matches '$escaped', no change." } } if ($Website) { $escaped = $(ConvertTo-SafeXML -String $WebSite) if ( $asset.WEB_DB_SITE -ne $escaped -or $asset.WEB_OR_DATABASE -eq 'false') { $asset.WEB_DB_SITE = "$($escaped.ToUpper())" $asset.WEB_OR_DATABASE = 'true' $modified = $true } else { Write-Verbose "$($Checklist.Name) - Website '$($asset.WEB_DB_SITE)' already matches '$escaped', no change." } } If ($Database) { $escaped = $(ConvertTo-SafeXML -String $Database) if ( $asset.WEB_DB_SITE -ne $escaped -or $asset.WEB_OR_DATABASE -eq 'false') { $asset.WEB_DB_SITE = "$escaped" $asset.WEB_OR_DATABASE = 'true' $modified = $true } else { Write-Verbose "$($Checklist.Name) - Database '$($asset.WEB_DB_SITE)' already matches '$escaped', no change." } } If ($DatabaseInstance) { $escaped = $(ConvertTo-SafeXML -String $DatabaseInstance) if ( $asset.WEB_DB_INSTANCE -ne $escaped -or $asset.WEB_OR_DATABASE -eq 'false') { $asset.WEB_DB_INSTANCE = "$escaped" $asset.WEB_OR_DATABASE = 'true' $modified = $true } else { Write-Verbose "$($Checklist.Name) - Database Instance '$($asset.WEB_DB_INSTANCE)' already matches '$escaped', no change." } } if ($AssetType) { $escaped = $(ConvertTo-SafeXML -String $AssetType) if ( $asset.ASSET_TYPE -ne $escaped ) { if ($AssetType -eq "Non-Computing") { $asset.HOST_IP = "" $asset.HOST_MAC = "" $asset.HOST_FQDN = "" $asset.TARGET_COMMENT = "" $asset.ROLE = 'None' $asset.WEB_OR_DATABASE = 'false' $asset.WEB_DB_SITE = "" $asset.WEB_DB_INSTANCE = "" } $asset.ASSET_TYPE = "$escaped" $modified = $true } else { Write-Verbose "$($Checklist.Name) - ASSET_TYPE '$($asset.ASSET_TYPE)' already matches '$escaped', no change." } } if ($TechnologyArea) { $escaped = $(ConvertTo-SafeXML -String $TechnologyArea) if ( $asset.TECH_AREA -ne $escaped) { $asset.TECH_AREA = "$escaped" $modified = $true } else { Write-Verbose "$($Checklist.Name) - Technology Area '$($asset.TECH_AREA)' already matches '$escaped', no change." } } If ($modified -eq $true -and $Path) { Write-Verbose "$($Checklist.Name) was modified, saving file changes" Export-Checklist -Checklist $Checklist Write-Verbose "Saved Changes to '$($Checklist.Path)'" } } } function Set-ChecklistItem { <# .SYNOPSIS Set Checklist item details .DESCRIPTION Set Checklist item details for a single or multiple checklists. .EXAMPLE PS C:\> Set-ChecklistItem -Path "E:\_Temp\U_A10_Networks_ADC_ALG_STIG_V2R1.ckl" -VulnID V-237032 -Details "This is not a finding" -Status NotAFinding This example demonstrates how to set a single checklist item. .EXAMPLE PS C:\> Get-ChildItem -Path E:\_Temp\Checklists -Filter "*.ckl" | Set-ChecklistItem -VulnID V-237032 -Details "This is not a finding" -Status NotAFinding This example demonstrates how to set a single checklist item on multiple checklists of the same type. .EXAMPLE PS C:\> $ckl = Import-Checklist -Path "E:\_Temp\U_A10_Networks_ADC_ALG_STIG_V2R1.ckl" PS C:\> Set-ChecklistItem -Checklist $ckl -VulnID V-237032 -Details "This is not a finding" -Status NotAFinding PS C:\> $ckl | Export-Checklist This example demonstrates how to import a checklist, calling Set-ChecklistItem, and then exporting the checklist to save the changes. This is useful for when your setting multiple items and want to save time on save to the file each time a change is made. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( # Specifies the Checklist XML object to be search. [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName="FromImport")] [System.Object[]] $Checklist, # Specifies the Checklist Path, When path is provided the function will import the checklist. [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName="FromFile")] [Alias('FullName', 'PSPath')] [String[]] $Path, # Specifies the STIG VulnID to update. [Parameter(ValueFromPipelineByPropertyName)] [String] $VulnID, # Specifies the Details to update. [Parameter(ValueFromPipelineByPropertyName)] [String] $Details, # Specifies the Comments to update. [Parameter(ValueFromPipelineByPropertyName)] [String] $Comments, # Specifies the Status of the Checklist Item. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet("Open", "NotAFinding", "Not_Reviewed", "Not_Applicable")] [String] $Status, # Specifies the Severity Override value for the checklist item. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet("High", "Medium", "Low")] [String] $SeverityOverride, # Specifies the Severity Override Justification for the checklist item. [Parameter(ValueFromPipelineByPropertyName)] [String] $SeverityJustification ) process { $modified = $false if ($Path) { $Checklist = Import-Checklist -Path "$Path" } $xPath = "//STIG_DATA[VULN_ATTRIBUTE='Vuln_Num' and ATTRIBUTE_DATA='$VulnID']" Select-Xml -Xml $Checklist.XML -XPath $xPath | ForEach-Object { $item = $_.Node.ParentNode if ($PSBoundParameters.Details) { $escaped = $(ConvertTo-SafeXML -String $Details) if ( $item.FINDING_DETAILS -ne $escaped ) { $item.FINDING_DETAILS = "$escaped" $modified = $true } else { Write-Verbose "$vulnId - FINDING_DETAILS '$($item.FINDING_DETAILS)' already matches '$escaped', no change." } } if ($PSBoundParameters.Comments) { $escaped = $(ConvertTo-SafeXML -String $Comments) if ( $item.COMMENTS -ne $escaped ) { $item.COMMENTS = "$escaped" $modified = $true } else { Write-Verbose "$vulnId - COMMENTS '$($item.COMMENTS)' already matches '$escaped', no change." } } if ($PSBoundParameters.Status) { if ( $item.STATUS -ne $STATUS ) { $item.STATUS = "$STATUS" $modified = $true } else { Write-Verbose "$vulnId - STATUS '$($item.STATUS)' already matches '$Status', no change." } } if ($PSBoundParameters.SeverityOverride) { $escaped = $(ConvertTo-SafeXML -String $SeverityOverride) if ( $item.SeverityOverride -ne $escaped ) { $item.SeverityOverride = "$escaped" $modified = $true } else { Write-Verbose "$vulnId - SeverityOverride '$($item.SeverityOverride)' already matches '$escaped', no change." } } if ($PSBoundParameters.SeverityJustification) { $escaped = $(ConvertTo-SafeXML -String $SeverityJustification) if ( $item.SeverityJustification -ne $escaped ) { $item.SeverityJustification = "$escaped" $modified = $true } else { Write-Verbose "$vulnId - SeverityOverride '$($item.SeverityJustification)' already matches '$escaped', no change." } } } If ($modified -eq $true -and $Path) { Write-Verbose "$($Checklist.Name) was modified, saving file changes" Export-Checklist -Checklist $Checklist Write-Verbose "Saved Changes to '$($Checklist.Path)'" } } } function ConvertTo-SafeXml { <# .SYNOPSIS Escapes invalid characters in the input to create safe XML output. .DESCRIPTION Escapes invalid characters in the input to create safe XML output. .EXAMPLE $escaped = $(ConvertTo-SafeXML -String $Database) This example shows how to call the function. #> [CmdletBinding()] [OutputType([string])] param( # Specify the text string to escape invlaid characters. [Parameter(Mandatory)] [String] [AllowEmptyString()] $String ) Write-Output $([System.Security.SecurityElement]::Escape($String)) } function Get-VulnItem { <# .SYNOPSIS Converts xml Object to Checklist.Item .EXAMPLE $Checklist.XML.CHECKLIST.STIGS.iSTIG.VULN | ForEach-Object { Get-VulnItem -Object $_ -ChecklistPath $cklPath -HostName $cklHost } This example demonstrates how to call the function. #> param ( # Specifies the Vuln Object to convert. [Parameter(Mandatory)] [System.Object] $object, # Specifies the HostName for the checklist [Parameter()] [String] $HostName = "", # Specifies the Checklist Path [Parameter(Mandatory)] [String] $ChecklistPath ) $checklistName = Split-Path -Path $ChecklistPath -Leaf $vulnId = $($object.STIG_Data | Where-Object { $_.VULN_ATTRIBUTE -eq "Vuln_Num" }).ATTRIBUTE_DATA $vuln = [Ordered]@{ HostName = $HostName Vuln_Num = $vulnId Status = $object.Status Finding_Details = $object.Finding_Details Comments = $object.Comments Severity_Overide = $object.Severity_Override Severity_Justification = $object.Severity_Justification ChecklistPath = $ChecklistPath Checklist = $checklistName } $object.STIG_Data | ForEach-Object { if ($_.VULN_ATTRIBUTE -notmatch "Vuln_Num|CCI_REF|LEGACY_ID") { $vuln.add($_.VULN_ATTRIBUTE, $_.ATTRIBUTE_DATA) } } # Add CCI and LegacyID $cci = $($object.STIG_Data | Where-Object { $_.VULN_ATTRIBUTE -eq "CCI_REF" }).ATTRIBUTE_DATA $lID = $($object.STIG_Data | Where-Object { $_.VULN_ATTRIBUTE -eq "LEGACY_ID" }).ATTRIBUTE_DATA $vuln.add("CCI_REF", $cci) $vuln.add("LEGACY_ID", $lID) #Convert to PSObject $vulnData = New-Object -TypeName PSObject -Property $vuln # Set Custom Format for object $vulnData.PSObject.TypeNames.Insert(0,'Checklist.Item') Write-Output $vulnData } function Get-XCCDFFromArchive { [CmdletBinding()] param ( # Specifies the STIG Zip file to use. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateScript( { if (-Not ($_ | Test-Path) ) { throw "Archive does not exist" } if ($_ -notmatch "\.zip") { throw "The file specified in the Path argument must be '.zip'" } return $true })] [Alias('FullName', 'PSPath')] [System.IO.FileInfo[]] $Path, # Specifies the regex string used to pull the xccdf file from the STIG Archive. By default the search pulls all files that are like '_Manual-xccdf.xml', then the search is used to pull from just those files. [Parameter()] [String] $Search = '_Manual-xccdf.xml' ) begin { Add-Type -AssemblyName System.IO.Compression.FileSystem } process { $Path | ForEach-Object { $archivePath = $_ $zip = [System.IO.Compression.ZipFile]::OpenRead($archivePath.FullName) $zip.Entries | Where-Object { $_.Name -like "*_Manual-xccdf.xml" -and $_.Name -Match $Search } | ForEach-Object { try { $stream = $_.Open() $reader = New-Object IO.StreamReader($stream) $xccdf = $reader.ReadToEnd() } catch { $_ } finally { $reader.Close() $stream.Close() } $fileData = [Ordered]@{ Name = $_.Name Path = $archivePath.FullName XML = $xccdf } Write-Output (New-Object -TypeName PSObject -Property $fileData) } $zip.Dispose() } } } function New-ChecklistFileName { <# .SYNOPSIS Create a file name for STIG checklists .DESCRIPTION Create a file name for STIG checklists that include the Hostname, and STIG Name. For the Checklist specific for Websites, Databases, or Database instance will be added to the name as well. .EXAMPLE PS C:\> New-ChecklistFileName -ChecklistName "U_MS_Dot_Net_Framework_4-0_STIG_V1R9" -HostName "Computer1" Output: Computer1-U_MS_Dot_Net_Framework_4-0_STIG_V1R9.ckl Thie example demonstrates how to create a file name. #> [CmdletBinding(DefaultParameterSetName = "Host")] [OutputType([String])] param ( # Specifies the default checklist name [Parameter(Mandatory)] [String] $ChecklistName, # Specifies the Host Name for the checklist. [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Host")] [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Website")] [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "DBInstance")] [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Database")] [string] $HostName, # Specifies the Host WebSite for the checklist. [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Website")] [string] $WebSite, # Specifies the Host Database Instance for the checklist. [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "DBInstance")] [string] $DatabaseInstance, # Specifies the Host Database for Checklist. [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Database")] [string] $Database ) process { $ckItem = $null switch ($PsCmdlet.ParameterSetName) { 'Website' { $ckItem = $WebSite } 'DBInstance' { $ckItem = $DatabaseInstance } 'Database' { $ckItem = $Database } } if ($ckItem) { "{0}-{1}-{2}.ckl" -f $HostName, $ckItem, $ChecklistName } else { "{0}-{1}.ckl" -f $HostName, $ChecklistName } } } function Test-XMLFile { [CmdletBinding()] param ( # Specifies the path to the xml file to be tested. [Parameter(Mandatory, ValueFromPipeline)] [System.IO.FileInfo[]] $XmlPath, # Specifies the path to the xml schema file to use to validate the xml files. [Parameter(Mandatory)] [System.IO.FileInfo] $SchemaPath ) begin { [Scriptblock] $ValidationEventHandler = { If ($_.Exception.LineNumber) { $Message = "$($_.Exception.Message) Line $($_.Exception.LineNumber), Position $($_.Exception.LinePosition)." } Else { $Message = ($_.Exception.Message) } $schemaErrors.Add([PSCustomObject]@{Message = $Message }) } } process { $XmlPath | ForEach-Object { $xmlFile = $_ $schemaErrors = New-Object System.Collections.Generic.List[System.Object] $ReaderSettings = New-Object -TypeName System.Xml.XmlReaderSettings $ReaderSettings.ValidationType = [System.Xml.ValidationType]::Schema $ReaderSettings.ValidationFlags = [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessIdentityConstraints -bor [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation -bor [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings $ReaderSettings.Schemas.Add($null, $SchemaPath.FullName) | Out-Null $readerSettings.add_ValidationEventHandler($ValidationEventHandler) Try { $Reader = [System.Xml.XmlReader]::Create($xmlFile.FullName, $ReaderSettings) While ($Reader.Read()) {} } Catch { $schemaErrors.Add([PSCustomObject]@{ Message = ($_.Exception.Message) }) } Finally { $Reader.Close() } $xmlOutput = [ordered]@{ XMLFile = $xmlFile.Name Valid = $true } If ($schemaErrors) { $xmlOutput.Valid = $false $xmlOutput.Add("Exceptions", $schemaErrors.Message) } Write-Output $(New-Object -TypeName PSObject -Property $xmlOutput) } } } |