# encoding: utf-8 # # Security Configuration and Analysis # # Export local security policy: # secedit /export /cfg secpol.cfg # # @link http://www.microsoft.com/en-us/download/details.aspx?id=25250 # # In Windows, some security options are managed differently that the local GPO # All local GPO parameters can be examined via Registry, but not all security # parameters. Therefore we need a combination of Registry and secedit output require 'hashie' module Inspec::Resources # known and supported MS privilege rights # @see https://technet.microsoft.com/en-us/library/dd277311.aspx # @see https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx MS_PRIVILEGES_RIGHTS = [ 'SeNetworkLogonRight', 'SeBackupPrivilege', 'SeChangeNotifyPrivilege', 'SeSystemtimePrivilege', 'SeCreatePagefilePrivilege', 'SeDebugPrivilege', 'SeRemoteShutdownPrivilege', 'SeAuditPrivilege', 'SeIncreaseQuotaPrivilege', 'SeIncreaseBasePriorityPrivilege', 'SeLoadDriverPrivilege', 'SeBatchLogonRight', 'SeServiceLogonRight', 'SeInteractiveLogonRight', 'SeSecurityPrivilege', 'SeSystemEnvironmentPrivilege', 'SeProfileSingleProcessPrivilege', 'SeSystemProfilePrivilege', 'SeAssignPrimaryTokenPrivilege', 'SeRestorePrivilege', 'SeShutdownPrivilege', 'SeTakeOwnershipPrivilege', 'SeUndockPrivilege', 'SeManageVolumePrivilege', 'SeRemoteInteractiveLogonRight', 'SeImpersonatePrivilege', 'SeCreateGlobalPrivilege', 'SeIncreaseWorking', 'SeTimeZonePrivilege', 'SeCreateSymbolicLinkPrivilege', 'SeDenyNetworkLogonRight', # Deny access to this computer from the network 'SeDenyInteractiveLogonRight', # Deny logon locally 'SeDenyBatchLogonRight', # Deny logon as a batch job 'SeDenyServiceLogonRight', # Deny logon as a service 'SeTcbPrivilege', 'SeMachineAccountPrivilege', 'SeCreateTokenPrivilege', 'SeCreatePermanentPrivilege', 'SeEnableDelegationPrivilege', 'SeLockMemoryPrivilege', 'SeSyncAgentPrivilege', 'SeUnsolicitedInputPrivilege', 'SeTrustedCredManAccessPrivilege', 'SeRelabelPrivilege', # the privilege to change a Windows integrity label (new to Windows Vista) 'SeDenyRemoteInteractiveLogonRight', # Deny logon through Terminal Services ].freeze class SecurityPolicy < Inspec.resource(1) name 'security_policy' supports platform: 'windows' desc 'Use the security_policy InSpec audit resource to test security policies on the Microsoft Windows platform.' example " describe security_policy do its('SeNetworkLogonRight') { should include 'S-1-5-11' } end describe security_policy(translate_sid: true) do its('SeNetworkLogonRight') { should include 'NT AUTHORITY\\Authenticated Users' } end " def initialize(opts = {}) @translate_sid = opts[:translate_sid] || false end def content read_content end def params(*opts) opts.inject(read_params) do |res, nxt| res.respond_to?(:key) ? res[nxt] : nil end end def method_missing(name) params = read_params return nil if params.nil? # deep search for hash key params.extend Hashie::Extensions::DeepFind res = params.deep_find(name.to_s) # return an empty array if configuration does not include rights configuration return [] if res.nil? && MS_PRIVILEGES_RIGHTS.include?(name.to_s) res end def to_s 'Security Policy' end private def read_content return @content if defined?(@content) # using process pid to prevent any race conditions with multiple runners export_file = "win_secpol-#{Process.pid}.cfg" # export the security policy cmd = inspec.command("secedit /export /cfg #{export_file}") return nil if cmd.exit_status.to_i != 0 # store file content cmd = inspec.command("Get-Content #{export_file}") return skip_resource "Can't read security policy" if cmd.exit_status.to_i != 0 @content = cmd.stdout ensure # delete temp file inspec.command("Remove-Item #{export_file}").exit_status.to_i end def read_params return @params if defined?(@params) return @params = {} if read_content.nil? conf = SimpleConfig.new( @content, assignment_regex: /^\s*(.*)=\s*(\S*)\s*$/, ) @params = convert_hash(conf.params) end # extracts the values, this methods detects: # numbers and SIDs and optimizes them for further usage def extract_value(val) if val =~ /^\d+$/ val.to_i # special handling for SID array elsif val =~ /[,]{0,1}\*\S/ if @translate_sid val.split(',').map { |v| object_name = inspec.command("(New-Object System.Security.Principal.SecurityIdentifier(\"#{v.sub('*S', 'S')}\")).Translate( [System.Security.Principal.NTAccount]).Value").stdout.to_s.strip object_name.empty? || object_name.nil? ? v.sub('*S', 'S') : object_name } else val.split(',').map { |v| v.sub('*S', 'S') } end # special handling for string values with " elsif !(m = /^\"(.*)\"$/.match(val)).nil? m[1] else val end end def convert_hash(hash) new_hash = {} hash.each do |k, v| v.is_a?(Hash) ? value = convert_hash(v) : value = extract_value(v) new_hash[k.strip] = value end new_hash end end end