function getKernelVersion { $vsplit = $(uname -r).split('-') if ($vsplit[1] -match '\.') { # Fedora $vsplit[1] = $vsplit[1].split('.')[0] } return [version]($vsplit[0] + '.' + $vsplit[1]) } function commandExists { param ( $command ) return [bool](Get-Command -Name $command -ErrorAction SilentlyContinue) } [AuditTest] @{ Id = "DSBD-001" Task = "Ensure the system is booting in UEFI mode." Test = { if (Test-Path -Path /sys/firmware/efi) { $status = @{ Message = "Compliant" Status = "True" } } else { $status = @{ Message = "System is not booting using UEFI mode." Status = "False" } } return $status } } [AuditTest] @{ Id = "DSBD-002" Task = "Ensure the system is using SecureBoot." Test = { if (Test-Path -Path /sys/firmware/efi) { if ($(mokutil --sb-state) -eq "SecureBoot enabled") { $status = @{ Message = "Compliant" Status = "True" } } else { $status = @{ Message = "System is not booting using UEFI mode." Status = "False" } } } else { $status = @{ Message = "SecureBoot is only supported on UEFI." Status= "False" } } return $status } } [AuditTest] @{ Id = "DSBD-003" Task = "Ensure the system has a TPM Chip." Test = { if (Test-Path -Path /dev/tpm0) { # /dev/tpmrm0 is _only_ for TPM 2.0 $status = @{ Message = "Compliant" Status = "True" } } else { $status = @{ Message = "Could not detect a TPM chip" Status = "False" } } return $status } } [AuditTest] @{ Id = "DSBD-004" Task = "Ensure the TPM Chip is implementing specification version 2.0 or higher." Test = { if ($(getKernelVersion) -ge [version]'5.6.0.0') { # For Ubuntu 20.04 e.g. $spec = [float](Get-Content -Path '/sys/class/tpm/tpm0/tpm_version_major') } else { $tpm2toolsMajorVersion = [int]($(tpm2_getcap -v) | Select-String -Pattern '^.+version=\"(\d)\..+$').Matches.Groups[1].Value if ($tpm2toolsMajorVersion -le 3) { # old versions up to 3.x had a different syntax (Debian 9) $text = $(tpm2_getcap -c properties-fixed) $match = [regex]::matches($text, '(?smi)TPM_PT_FAMILY_INDICATOR: as UINT32: +0[xX][0-9a-fA-F]+ as string: +\"(\d\.\d)\"').Groups[1].Value } else { # new versions 4.x (RHEL 8) $text = $(tpm2_getcap properties-fixed) $match = [regex]::matches($text, '(?smi)TPM2_PT_FAMILY_INDICATOR: raw: +0[xX][0-9a-fA-F]+ value: +\"(\d\.\d)\"').Groups[1].Value } $spec = [float]$match } if ($spec -ge 2.0) { return @{ Message = "Compliant" Status = "True" } } elseif ($spec -gt 0) { return @{ Message = "Specification version lower than 2.0 found." Status = "Warning" } } else { return @{ Message = "No implemented specification version found." Status = "False" } } } } [AuditTest] @{ Id = "DSBD-005" Task = "Report the count of local users on the system." Test = { # Linux native alternative: grep -c ^ /etc/passwd $countUsers = (Get-Content /etc/passwd).Count return @{ Message = "System has $countUsers local users" Status = "None" } } } [AuditTest] @{ Id = "DSBD-006" Task = "Report the count of local interactive users on the system." Test = { $countUsers = (Get-Content /etc/passwd | Where-Object {-not ($_ -match "/usr/sbin/nologin" -or $_ -match "/bin/false" -or $_ -match "/bin/sync")}).Count $status = switch ($countUsers) { {($PSItem -ge 0) -and ($PSItem -le 2)}{ # 0, 1, 2 @{ Message = "Compliant" Status = "True" } } {($PSItem -gt 2) -and ($PSItem -le 5)}{ # 3, 4, 5 @{ Message = "System has 3-5 local users." Status = "Warning" } } {$PSItem -gt 5}{ # 6, ... @{ Message = "System has 6 or more local users." Status = "False" } } Default { @{ Message = "Cannot determine the count of local users" Status = "Error" } } } return $status } } [AuditTest] @{ Id = "DSBD-007" Task = "Get the count of admin users on the system." Test = { $usersSudo = ($(getent group sudo) -split ":")[3] $usersRoot = ($(getent group root) -split ":")[3] $usersWheel = ($(getent group wheel) -split ":")[3] $usersAdmin = ($(getent group admin) -split ":")[3] $usersAdm = ($(getent group adm) -split ":")[3] $userIdZero = ($(getent passwd 0) -split ":")[0] $allUsersArr = @($usersSudo, $usersRoot, $usersWheel, $usersAdmin, $usersAdm, $userIdZero) | Where-Object {$_ -ne "" -and $_ -ne $null} | Sort-Object | Get-Unique $status = switch ($allUsersArr.Count) { {($PSItem -ge 0) -and ($PSItem -le 2)}{ # 0, 1, 2 @{ Message = "Compliant" Status = "True" } } {($PSItem -gt 2) -and ($PSItem -le 5)}{ # 3, 4, 5 @{ Message = "System has 3-5 admin users." Status = "Warning" } } {$PSItem -gt 5}{ # 6, ... @{ Message = "System has 6 or more admin users." Status = "False" } } Default { @{ Message = "Cannot determine the count of admin users" Status = "Error" } } } return $status } } [AuditTest] @{ Id = "DSBD-008" Task = "Ensure the NX bit is set." Test = { $query = (-split (Get-Content /proc/cpuinfo | Where-Object {$_ -match '^flags.*$'} | Get-Unique)) -Contains 'nx' if ($query) { return @{ Message = "Compliant" Status = "True" } } else { return @{ Message = "The NX bit is not set." Status = "False" } } } } [AuditTest] @{ Id = "DSBD-009" Task = "Ensure the ASLR is enabled." Test = { $query = [int](Get-Content /proc/sys/kernel/randomize_va_space) if ($query -ge 2) { return @{ Message = "Compliant" Status = "True" } } elseif ($query -eq 1) { return @{ Message = "ASLR is partially enabled." Status = "Warning" } } else { return @{ Message = "ASLR is not enabled." Status = "False" } } } } [AuditTest] @{ Id = "DSBD-010" Task = "Ensure AppArmor or SELinux is enabled." Test = { if (commandExists 'aa-status') { $AppArmorStatus = ($(aa-status) -match '^apparmor module is loaded.*$').Count -gt 0 } if (commandExists 'getenforce') { $SELinuxStatus = ($(getenforce) -match 'Enforcing$').Count -gt 0 } if ($AppArmorStatus -or $SELinuxStatus) { return @{ Message = "Compliant" Status = "True" } } else { return @{ Message = "Neither AppArmor nor SELinux are enabled." Status = "False" } } } } [AuditTest] @{ Id = "DSBD-011" Task = "Ensure CPU has no known vulnerabilities." Test = { $query = ((Get-Content /sys/devices/system/cpu/vulnerabilities/*) -match '^Vulnerable.*$').Count if ($query -eq 0) { return @{ Message = "Compliant" Status = "True" } } else { return @{ Message = "System has $query known CPU vulnerabilities." Status = "False" } } } } [AuditTest] @{ Id = "DSBD-012" Task = "Ensure root login using SSH is not permitted." Test = { $rootLoginDisabled = [bool](Get-Content /etc/ssh/sshd_config | Select-String -Pattern '^PermitRootLogin no').Matches.Length if ($rootLoginDisabled) { return @{ Message = "Compliant" Status = "True" } } else { return @{ Message = "Login for root using SSH is permitted." Status = "False" } } } } [AuditTest] @{ Id = "DSBD-013" Task = "Ensure a firewall is installed (ufw, iptables, nftables)." Test = { if (commandExists dpkg) { $ufwInstalled = [bool]($(dpkg -s ufw 2> /dev/zero) -match 'Status: install').Count $iptablesInstalled = [bool]($(dpkg -s iptables 2> /dev/zero) -match 'Status: install').Count $nftablesInstalled = [bool]($(dpkg -s nftables 2> /dev/zero) -match 'Status: install').Count } if (commandExists rpm) { $ufwInstalled = [bool]($(rpm -qa) -match '^ufw.+$').Count $iptablesInstalled = [bool]($(rpm -qa) -match '^iptables.+$').Count $nftablesInstalled = [bool]($(rpm -qa) -match '^nftables.+$').Count } if ($ufwInstalled -or $iptablesInstalled -or $nftablesInstalled) { return @{ Message = "Compliant" Status = "True" } } else { return @{ Message = "Login for root using SSH is permitted." Status = "False" } } } } function getFilePermissionsRegex { Param( [Parameter(Mandatory=$true)][String][ValidateNotNullOrEmpty()]$filePath ) $text = stat $filePath $match = [regex]::matches($text, '\S+:\s+\((\d+)\/\S+\)\s+Uid:\s+\(\s*(\d+)\/\s+(\S+)\)\s+Gid:\s+\(\s+(\d+)\/\s+(\S+)\)') return [ordered]@{ permissionsOct = $match.Groups[1].Value ownerUserId = $match.Groups[2].Value ownerUserName = $match.Groups[3].Value ownerGroupId = $match.Groups[4].Value ownerGroupName = $match.Groups[5].Value } } function checkFilePermissions { Param( [Parameter(Mandatory=$true)][String][ValidateNotNullOrEmpty()]$filePath, [Parameter(Mandatory=$true)][String][ValidateNotNullOrEmpty()]$permissionsOct, [Parameter(Mandatory=$true)][String][ValidateNotNullOrEmpty()]$ownerUserName, [Parameter(Mandatory=$true)][String][ValidateNotNullOrEmpty()]$ownerGroupName ) # calculate mode $item = Get-Item $filePath $modeLowerBits = $item.UnixStat.Mode -band 4095 # 4095_(10) = 111111111111_(2) = 7777_(8) = FFF_(16) $mode = [Convert]::ToString($modeLowerBits, 8) # Conversion not necessary in future: https://github.com/PowerShell/PowerShell/issues/16757 , alternative: stat -c '%a' /etc/passwd # check for same or more restricted permissions foreach ($i in 0..($mode.Length - 1)) { if ($mode[$i] -gt $permissionsOct[$i]) { return $false # = less restrictive } } # check owning user and group return $item.User -eq $ownerUserName -and $item.Group -eq $ownerGroupName } [AuditTest] @{ Id = "DSBD-014" Task = "Ensure /etc/passwd and /etc/passwd- have proper file permissions." Test = { $result = checkFilePermissions '/etc/passwd' '644' 'root' 'root' if (Test-Path -Path '/etc/passwd-' -PathType Leaf) { $result = $result -and (checkFilePermissions '/etc/passwd-' '644' 'root' 'root') } if ($result) { return @{ Message = "Compliant" Status = "True" } } else { return @{ Message = "The file permissions are not set correctly." Status = "False" } } } } [AuditTest] @{ Id = "DSBD-015" Task = "Ensure /etc/shadow and /etc/shadow- have proper file permissions." Test = { $result = (checkFilePermissions '/etc/shadow' '640' 'root' 'root') -or (checkFilePermissions '/etc/shadow' '640' 'root' 'shadow') if (Test-Path -Path '/etc/shadow-' -PathType Leaf) { $resultDash = (checkFilePermissions '/etc/shadow-' '640' 'root' 'root') -or (checkFilePermissions '/etc/shadow-' '640' 'root' 'shadow') $result = $result -and $resultDash } if ($result) { return @{ Message = "Compliant" Status = "True" } } else { return @{ Message = "The file permissions are not set correctly." Status = "False" } } } } [AuditTest] @{ Id = "DSBD-016" Task = "Ensure /etc/group and /etc/group- have proper file permissions." Test = { $result = checkFilePermissions '/etc/group' '644' 'root' 'root' if (Test-Path -Path '/etc/group-' -PathType Leaf) { $result = $result -and (checkFilePermissions '/etc/group-' '644' 'root' 'root') } if ($result) { return @{ Message = "Compliant" Status = "True" } } else { return @{ Message = "The file permissions are not set correctly." Status = "False" } } } } [AuditTest] @{ Id = "DSBD-017" Task = "Ensure /etc/gshadow and /etc/gshadow- have proper file permissions." Test = { $result = (checkFilePermissions '/etc/gshadow' '640' 'root' 'root') -or (checkFilePermissions '/etc/gshadow' '640' 'root' 'shadow') if (Test-Path -Path '/etc/gshadow-' -PathType Leaf) { $resultDash = (checkFilePermissions '/etc/gshadow-' '640' 'root' 'root') -or (checkFilePermissions '/etc/gshadow-' '640' 'root' 'shadow') $result = $result -and $resultDash } if ($result) { return @{ Message = "Compliant" Status = "True" } } else { return @{ Message = "The file permissions are not set correctly." Status = "False" } } } } [AuditTest] @{ Id = "DSBD-018" Task = "Ensure /etc/ssh/sshd_config has proper file permissions." Test = { $result = checkFilePermissions '/etc/ssh/sshd_config' '600' 'root' 'root' if ($result) { return @{ Message = "Compliant" Status = "True" } } else { return @{ Message = "The file permissions are not set correctly." Status = "False" } } } }