microsoft / PowerStig

STIG Automation
https://www.powershellgallery.com/packages/PowerSTIG
Other
541 stars 116 forks source link

Using powershell to audit STIG rules #729

Open IvanDrag0 opened 4 years ago

IvanDrag0 commented 4 years ago

While PowerStig is able to audit and configure many STIG rules, it leaves out rules that can only be audited and not changed (for example "WN10-00-000005 - Domain-joined systems must use Windows 10 Enterprise Edition 64-bit version." or "WN10-00-000015 - Windows 10 systems must have Unified Extensible Firmware Interface (UEFI) firmware and be configured to run in UEFI mode, not Legacy BIOS."). This is obviously not a shortcoming of the tool since we can't expect DSC to be able to change Windows version or install UEFI BIOS. However, with that said, PowerStig should still be capable to audit those settings using the Script DSC (or a new resource based on it).

The Script/New DSC would accept a Powershell command/script and the expected return, and use that to verify that the system is compliant with the STIG.

For example, for WN10-00-000015, the following DSC could be used to verify the result:

 Script $resourceTitle
    {
        GetScript = {
            $Audit = bcdedit | Select-String 'path\s*.*winload'
            if ($Audit.Contains("winload.efi") { return $True }
        }

        TestScript = {
            return $true
        }

        SetScript = { }
    }

WN10-00-000020 (Secure Boot must be enabled on Windows 10 systems.) would look similar to this:

 Script $resourceTitle
    {
        GetScript = {
            if (Confirm-SecureBootUEFI) { return $True }
        }

        TestScript = {
            return $true
        }

        SetScript = { }
    }

This new DSC approach would also need to be able to use regex since, for example, for WN10-00-000005, you'll need to run the following regex:

Since this is similar to how PowerStig handles skipped rules, I would think that implementing a new DSC resource based on this should require major rewrites of existing code (at least based on me looking through the code). I'm very familiar with the STIG rules (especially the ones that can only be audited) so I'd be happy to help provide the necessary Powershell/Regex commands to perform the audits (I've been wanting to find a way to help the project for awhile).

erjenkin commented 4 years ago

Hello @IvanDrag0 ,

Thanks for the issue. I agree that we could have better coverage by auditing some settings, that we know will not be able to be made "Compliant". The skipped rule settings is a a good example of the use of the script resource to audit, but not change. I think we could create a composite that would have two parameters, getScript and testValue. Basically the getScript would be the query and the testValue would test the results of the get script based on a known good value. An example based on your logic above.


$rules = $stig.RuleList | Select-Rule -Type AuditOnlyRule

foreach ( $rule in $rules )
{
    Script (Get-ResourceTitle -Rule $rule)
    {
        GetScript = {
            $getScript = $rule.GetScript
            return $getScript
        }

        TestScript = {
            $inDesiredState = $true
            if($getScript -ne $rule.TestValue)
            {
                $inDesiredState = $false
            }

            return $inDesiredState
        }

        SetScript = { }
    }
}

If you wanted to provide a list of the rules that could be automated using the method you described above, we could gauge community interest/impact on coverage. Please include your "getScript" and "testValues" if you have handy.

Thanks,

Eric

IvanDrag0 commented 4 years ago

@erjenkin Thank you for the reply. That solution looks like it would work. Since I need this functionality for some of the things that I'm doing, I don't mind trying and write a proof-of-concept. To make sure that I stay within the PowerStig coding guidelines, it looks like I'd need to do the following:

1) Create a new Rule module (Module/Rule.AuditOnly/AuditOnlyRule.psm1):

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
using module .\..\Common\Common.psm1
using module .\..\Rule\Rule.psm1
#header

<#
    .SYNOPSIS
        Convert the contents of an xccdf check-content element into an AuditOnlyRule
    .DESCRIPTION
        The AuditOnlyRule class is used to extract the auditing settings
        from the check-content of the xccdf. Once a STIG rule is identified an
        audit rule, it is passed to the AuditOnlyRuleclass for parsing
        and validation.
    .PARAMETER Script
        The script to be evaluated
    .PARAMETER Result
        The expected script result
#>
class AuditOnlyRule : Rule
{
    [string] $Script
    [string] $Result

    <#
        .SYNOPSIS
            Default constructor to support the AsRule cast method
    #>
    AuditOnlyRule()
    {
    }

    <#
        .SYNOPSIS
            Used to load PowerSTIG data from the processed data directory
        .PARAMETER Rule
            The STIG rule to load
    #>
    AuditOnlyRule ([xml.xmlelement] $Rule) : base ($Rule)
    {
    }

    <#
        .SYNOPSIS
            The Convert child class constructor
        .PARAMETER Rule
            The STIG rule to convert
        .PARAMETER Convert
            A simple bool flag to create a unique constructor signature
    #>
    AuditOnlyRule ([xml.xmlelement] $Rule, [switch] $Convert) : base ($Rule, $Convert)
    {
    }

    <#
        .SYNOPSIS
            Creates class specifc help content
    #>
    [PSObject] GetExceptionHelp()
    {
        return @{
            Value = "1"
            Notes = $null
        }
    }
}

2) Add a new resource (DSCResources/Resources/windows.Script.AuditOnly.ps1):

$rules = $stig.RuleList | Select-Rule -Type AuditOnlyRule

foreach ( $rule in $rules )
{
    Script (Get-ResourceTitle -Rule $rule)
    {
        GetScript = {
            $getScript = $rule.Script
            return $getScript
        }

        TestScript = {
            $inDesiredState = $true
            if($getScript -ne $rule.Result)
            {
                $inDesiredState = $false
            }

            return $inDesiredState
        }

        SetScript = { }
    }
}

3) Modify the STIG XML to have the appropriate values. For example:

<Rule id="V-77083" severity="medium" conversionstatus="pass" title="WN10-00-000015" dscresource="AuditOnly">
      <Description>&lt;VulnDiscussion&gt;UEFI provides additional security features in comparison to legacy BIOS firmware, including Secure Boot. UEFI is required to support additional security features in Windows 10, including Virtualization Based Security and Credential Guard. Systems with UEFI that are operating in Legacy BIOS mode will not support these security features.&lt;/VulnDiscussion&gt;&lt;FalsePositives&gt;&lt;/FalsePositives&gt;&lt;FalseNegatives&gt;&lt;/FalseNegatives&gt;&lt;Documentable&gt;false&lt;/Documentable&gt;&lt;Mitigations&gt;&lt;/Mitigations&gt;&lt;SeverityOverrideGuidance&gt;&lt;/SeverityOverrideGuidance&gt;&lt;PotentialImpacts&gt;&lt;/PotentialImpacts&gt;&lt;ThirdPartyTools&gt;&lt;/ThirdPartyTools&gt;&lt;MitigationControl&gt;&lt;/MitigationControl&gt;&lt;Responsibility&gt;&lt;/Responsibility&gt;&lt;IAControls&gt;&lt;/IAControls&gt;</Description>
      <DuplicateOf />
<Script>if (Confirm-SecureBootUEFI) { return $True }</Script>
<Result>True</Result>
      <IsNullOrEmpty>False</IsNullOrEmpty>
      <OrganizationValueRequired>False</OrganizationValueRequired>
      <OrganizationValueTestString />
      <RawString>For virtual desktop implementations (VDIs) where the virtual desktop instance is deleted or refreshed upon logoff, this is NA.

Verify the system firmware is configured to run in UEFI mode, not Legacy BIOS.

Run "System Information".

Under "System Summary", if "BIOS Mode" does not display "UEFI", this is finding.</RawString>
    </Rule>

Is that about right?

erjenkin commented 4 years ago

@IvanDrag0 ,

That's a good start. We would need to create the convert portion for that rule to dynamically generate the xml based on the raw xccdf. How many rules do you think could be targeted with this new composite including the 3 you referenced earlier, just trying to gauge impact.

Thanks,

Eric

IvanDrag0 commented 4 years ago

@erjenkin ,

All of them. I've done this exercise at work, which is why I'm confident that you could combine powershell, and in some cases the organizational values, to get the results that you're looking for. Currently, when running PowerStig with the latest WindowsClient processed XML file, 85 VIDs are left as Not Reviewed (since PowerStig skipps them). I've done a quick review and you could write a powershell command/function to verify all of them.

Here are some examples:

WN10-00-000025 - Windows 10 must employ automated mechanisms to determine the state of system components with regard to flaw remediation using the following frequency: continuously, where HBSS is used; 30 days, for any additional internal network scans not covered by HBSS; and annually, for external scans by Computer Network Defense Service Provider (CNDSP).

For this STIG, you could use the WMI root\SecurityCenter to get the overall security software health of the system. So the script would like something like this:

$inDesiredState = $true ; $AVProduct = Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntivirusProduct ; if ($AVProduct.Count -gt 1) { ForEach ($AV in $AVProduct) { if ($AV.displayName -ne "Windows Defender") { if ($AV.productState -ne "397312") { $inDesiredState = $false } } } } else { if ($AV.productState -ne "397312") { $inDesiredState = $false } } ; return $inDesiredState ` This will return True if the AV is enabled and up-to-date.

WN10-00-000035 - The operating system must employ a deny-all, permit-by-exception policy to allow the execution of authorized software programs.

$inDesiredState = $true ; if (((Get-AppLockerPolicy -Effective).RuleCollctions).Count -eq 0) { $inDesiredState = $false } ; return $inDesiredState

This script would check that AppLocker is enabled and has rules in it.

The Convert portion should only identify STIGs that need to be marked as AuditOnly (which it already does, unelss they're manually marked in the XML). To make things "cleaner", I would create a separate XML file which would contain those powershell commands (in the same way that PowerStig has a separate XML file for the main STIG and the Org values).

While it is a bit time consuming, PowerStig should at least introduct the ability of doing so and let the community either contribute or populate their own. If someone could create a test branch for me with that capability, I should be able to generate an XML file as a proof-of-concept to let the PowerStig team and community review it and decide on the path forward.

erjenkin commented 4 years ago

@IvanDrag0 ,

Here are the contribution guidelines for PowerSTIG, you start at the Forking Repo section. We currently use a resource AuditSystemDsc that does a pretty good job at testing settings that are found in CIM, so expanding that resource to handle some of these may be beneficial to look at.

I personally do not like the idea of having a separate XML as it adds to the complexity of PowerSTIG when applying configurations and creates additional maintenance items during quarterly updates . I would prefer a method that uses the existing conversion process to create XML and OrgSettings, but I am not the only voice on the team.

https://github.com/microsoft/PowerStig/blob/dev/README.CONTRIBUTING.md

If you have any questions while working through the submission let me know.

Thanks, Eric

IvanDrag0 commented 4 years ago

@erjenkin ,

I've forked the repo and made the updates as we discussed, however, when I test it, it looks like the script DSC is not populating correctly.

WindowsClient-10-1.23.xml:

<AuditOnlyRule dscresourcemodule="PSDscResources">
    <Rule id="V-63345" severity="medium" conversionstatus="pass" title="WN10-00-000035" dscresource="Script">
      <Description>&lt;VulnDiscussion&gt;Utilizing a whitelist provides a configuration management method for allowing the execution of only authorized software. Using only authorized software decreases risk by limiting the number of potential vulnerabilities.

The organization must identify authorized software programs and only permit execution of authorized software. The process used to identify software programs that are authorized to execute on organizational information systems is commonly referred to as whitelisting.&lt;/VulnDiscussion&gt;&lt;FalsePositives&gt;&lt;/FalsePositives&gt;&lt;FalseNegatives&gt;&lt;/FalseNegatives&gt;&lt;Documentable&gt;false&lt;/Documentable&gt;&lt;Mitigations&gt;&lt;/Mitigations&gt;&lt;SeverityOverrideGuidance&gt;&lt;/SeverityOverrideGuidance&gt;&lt;PotentialImpacts&gt;&lt;/PotentialImpacts&gt;&lt;ThirdPartyTools&gt;&lt;/ThirdPartyTools&gt;&lt;MitigationControl&gt;&lt;/MitigationControl&gt;&lt;Responsibility&gt;&lt;/Responsibility&gt;&lt;IAControls&gt;&lt;/IAControls&gt;</Description>
      <DuplicateOf />
      <IsNullOrEmpty>False</IsNullOrEmpty>
      <OrganizationValueRequired>False</OrganizationValueRequired>
      <OrganizationValueTestString />
      <Query>$inDesiredState = $true ; if (((Get-AppLockerPolicy -Effective).RuleCollctions).Count -eq 0) { $inDesiredState = $false } ; return $inDesiredState</Query>
      <ExpectedValue>True</ExpectedValue>
      <RawString>This is applicable to unclassified systems; for other systems this is NA.

Verify the operating system employs a deny-all, permit-by-exception policy to allow the execution of authorized software programs. This must include packaged apps such as the universals apps installed by default on systems.

If an application whitelisting program is not in use on the system, this is a finding.

Configuration of whitelisting applications will vary by the program.

AppLocker is a whitelisting application built into Windows 10 Enterprise.  A deny-by-default implementation is initiated by enabling any AppLocker rules within a category, only allowing what is specified by defined rules.

If AppLocker is used, perform the following to view the configuration of AppLocker:
Run "PowerShell".

Execute the following command, substituting [c:\temp\file.xml] with a location and file name appropriate for the system:
Get-AppLockerPolicy -Effective -XML &gt; c:\temp\file.xml

This will produce an xml file with the effective settings that can be viewed in a browser or opened in a program such as Excel for review.

Implementation guidance for AppLocker is available in the NSA paper "Application Whitelisting using Microsoft AppLocker" at the following link:

https://www.iad.gov/iad/library/ia-guidance/tech-briefs/application-whitelisting-using-microsoft-applocker.cfm</RawString>
    </Rule>
  </AuditOnlyRule>

Test config:

configuration STIGTest
{
    param
    (
        [parameter()]
        [string]
        $NodeName = 'localhost'
    )

    Import-DscResource -ModuleName PowerStig

    Node $NodeName
    {
        WindowsClient BaseLine {
            OsVersion   = '10'
            OrgSettings = "$PSScriptRoot\WindowsClient-10-1.23.org.default.xml"
            StigVersion = '1.23'
        }
    }
}

STIGTest

The generated MOF file:

instance of MSFT_ScriptResource as $MSFT_ScriptResource1ref
{
ResourceID = "[Script][V-63345][medium][WN10-00-000035]::[WindowsClient]BaseLine";
 GetScript = "\n            $getScript = $rule.Query\n            return $getScript\n        ";
 TestScript = "\n            $inDesiredState = $true\n            if ($getScript -ne $rule.ExpectedValue) {\n                $inDesiredState = $false\n            }\n\n            return $inDesiredState\n        ";
 SourceInfo = "C:\\Program Files\\WindowsPowerShell\\Modules\\PowerStig\\0.0.1\\DSCResources\\Resources\\windows.Script.AuditOnly.ps1::4::5::Script";
 SetScript = " ";
 ModuleName = "PSDscResources";
 ModuleVersion = "2.12.0.0";

 ConfigurationName = "STIGTest";

};

It looks like PowerStig is populating the GetScript and TestScript variables verbatim instead of with the provided values for Query and ExpectedValue.

Is there something I forgot to change?

IvanDrag0 commented 4 years ago

@erjenkin I figured it out!