From 44041c881f92021b7d8874dc961ec5e793a2cacf Mon Sep 17 00:00:00 2001 From: George Dias Date: Fri, 24 Mar 2023 23:33:02 -0500 Subject: [PATCH 01/13] fix return value in updates.ts, there are 3 test failing for getExistingDescribeFromControl.spec.ts Signed-off-by: George Dias --- src/utilities/update.ts | 144 +- .../controls-cookstyle/SV-205653.rb | 27 + .../json/cookstyle-controls-profile.json | 3 +- ...ows_Server_2019_STIG_V2R5_Manual-xccdf.xml | 5501 +++++++++++++++++ test/tests/control.spec.ts | 10 +- 5 files changed, 5610 insertions(+), 75 deletions(-) create mode 100644 test/sample_data/controls-cookstyle/SV-205653.rb create mode 100644 test/sample_data/xccdf/input/STIG/U_MS_Windows_Server_2019_STIG_V2R5_Manual-xccdf.xml diff --git a/src/utilities/update.ts b/src/utilities/update.ts index 2e61703c..91843c8f 100644 --- a/src/utilities/update.ts +++ b/src/utilities/update.ts @@ -39,12 +39,11 @@ function projectValuesOntoExistingObj(dst: Record, src: Record< return dst } -function getRangesForLines(text: string): number[][] { +function getRangesFromStackUpdate(text: string): number[][] { /* Returns an array containing two numerical indices (i.e., start and stop line numbers) for each string or multi-line comment, given raw text as - an input parameter. The raw text is a string containing the entirety of an - InSpec control. + an input parameter. Algorithm utilizes a pair of stacks (i.e., `stack`, `rangeStack`) to keep track of string delimiters and their associated line numbers, respectively. @@ -55,20 +54,18 @@ function getRangesForLines(text: string): number[][] { - Back ticks (`) - Mixed quotes ("`'") - Percent strings (%; keys: q, Q, r, i, I, w, W, x; delimiters: (), {}, - [], <>, most non-alphanumeric characters); (e.g., "%q()") + [], <>, most non-alphanumeric characters) - Percent literals (%; delimiters: (), {}, [], <>, most non- - alphanumeric characters); (e.g., "%()") + alphanumeric characters) - Multi-line comments (e.g., =begin\nSome comment\n=end) - - Variable delimiters (i.e., paranthesis: (); array: []; hash: {}) */ - const stringDelimiters: {[key: string]: string} = {'(': ')', '{': '}', '[': ']', '<': '>'} - const variableDelimiters: {[key: string]: string} = {'(': ')', '{': '}', '[': ']'} + const delims: {[key: string]: string} = {'(': ')', '{': '}', '[': ']', '<': '>'} const quotes = '\'"`' const strings = 'qQriIwWxs' - enum skipCharLength { - string = '('.length, - percentString = 'q('.length, - commentBegin = '=begin'.length + enum skipChar { + stringSkipChar = '('.length, + specialSkipChar = 'q('.length, + commentBeginSkipChar = '=begin'.length } const stack: string[] = [] @@ -81,57 +78,52 @@ function getRangesForLines(text: string): number[][] { while (j < lines[i].length) { const line = lines[i] let char = line[j] - + const isEmptyStack = (stack.length == 0) const isNotEmptyStack = (stack.length > 0) const isQuoteChar = quotes.includes(char) const isNotEscapeChar = ((j == 0) || (j > 0 && line[j - 1] != '\\')) const isPercentChar = (char == '%') - const isVariableDelimiterChar = Object.keys(variableDelimiters).includes(char) const isStringDelimiterChar = ((j < line.length - 1) && (/^[^A-Za-z0-9]$/.test(line[j + 1]))) const isCommentBeginChar = ((j == 0) && (line.length >= 6) && (line.slice(0, 6) == '=begin')) - const isPercentStringKeyChar = ((j < line.length - 1) && (strings.includes(line[j + 1]))) - const isPercentStringDelimiterChar = ((j < line.length - 2) && (/^[^A-Za-z0-9]$/.test(line[j + 2]))) - const isPercentString = (isPercentStringKeyChar && isPercentStringDelimiterChar) - + const conditionOne = ((j < line.length - 1) && (strings.includes(line[j + 1]))) + const conditionTwo = ((j < line.length - 2) && (/^[^A-Za-z0-9]$/.test(line[j + 2]))) + const isSpecialDelimiterChar = (conditionOne && conditionTwo) + let baseCondition = (isEmptyStack && isNotEscapeChar) const quotePushCondition = (baseCondition && isQuoteChar) - const variablePushCondition = (baseCondition && isVariableDelimiterChar) const stringPushCondition = (baseCondition && isPercentChar && isStringDelimiterChar) - const percentStringPushCondition = (baseCondition && isPercentChar && isPercentString) + const specialPushCondition = (baseCondition && isPercentChar && isSpecialDelimiterChar) const commentBeginCondition = (baseCondition && isCommentBeginChar) - + if (stringPushCondition) { - j += skipCharLength.string // j += 1 - } else if (percentStringPushCondition) { - j += skipCharLength.percentString // j += 2 + j += skipChar.stringSkipChar // j += 1 + } else if (specialPushCondition) { + j += skipChar.specialSkipChar // j += 2 } else if (commentBeginCondition) { - j += skipCharLength.commentBegin // j += 6 + j += skipChar.commentBeginSkipChar // j += 6 } char = line[j] - + baseCondition = (isNotEmptyStack && isNotEscapeChar) - const delimiterCondition = (baseCondition && Object.keys(stringDelimiters).includes(stack[stack.length - 1])) + const delimiterCondition = (baseCondition && Object.keys(delims).includes(stack[stack.length - 1])) const delimiterPushCondition = (delimiterCondition && (stack[stack.length - 1] == char)) - const delimiterPopCondition = (delimiterCondition && (stringDelimiters[stack[stack.length - 1] as string] == char)) - const basePopCondition = (baseCondition && (stack[stack.length - 1] == char) && !Object.keys(stringDelimiters).includes(char)) + const delimiterPopCondition = (delimiterCondition && (delims[stack[stack.length - 1] as string] == char)) + const basePopCondition = (baseCondition && (stack[stack.length - 1] == char) && !Object.keys(delims).includes(char)) const isCommentEndChar = ((j == 0) && (line.length >= 4) && (line.slice(0, 4) == '=end')) const commentEndCondition = (baseCondition && isCommentEndChar && (stack[stack.length - 1] == '=begin')) - const popCondition = (basePopCondition || delimiterPopCondition || commentEndCondition) - const pushCondition = (quotePushCondition || variablePushCondition || stringPushCondition || - percentStringPushCondition || delimiterPushCondition || commentBeginCondition) - - if (popCondition) { + if (basePopCondition || delimiterPopCondition || commentEndCondition) { stack.pop() rangeStack[rangeStack.length -1].push(i) const range_ = rangeStack.pop() as number[] if (rangeStack.length == 0) { ranges.push(range_) } - } else if (pushCondition) { + } else if (quotePushCondition || stringPushCondition || specialPushCondition || + delimiterPushCondition || commentBeginCondition) { if (commentBeginCondition) { stack.push('=begin') } else { @@ -145,44 +137,44 @@ function getRangesForLines(text: string): number[][] { return ranges } -function joinMultiLineStringsFromRanges(text: string, ranges: number[][]): string[] { +function getDistinctRanges(ranges: number[][]): number[][] { + /* + Drops ranges with the same start and stop line numbers (i.e., strings + that populate a single line) + */ + const output: number[][] = [] + for (const [x, y] of ranges) { + if (x !== y) { + output.push([x, y]) + } + } + return output +} + +function joinStringsFromRanges(text: string, ranges: number[][]): string[] { /* Returns an array of strings and joined strings at specified ranges, given raw text as an input parameter. */ - const originalLines = text.split('\n') - const joinedLines: string[] = [] + const lines = text.split('\n') + const output: string[] = [] let i = 0 - while (i < originalLines.length) { - let foundInRanges = false - for (const [startIndex, stopIndex] of ranges) { - if (i >= startIndex && i <= stopIndex) { - joinedLines.push(originalLines.slice(startIndex, stopIndex + 1).join('\n')) - foundInRanges = true - i = stopIndex + while (i < lines.length) { + let found = false + for (const [x, y] of ranges) { + if (i >= x && i <= y) { + output.push(lines.slice(x, y + 1).join('\n')) + found = true + i = y break } } - if (!foundInRanges) { - joinedLines.push(originalLines[i]) + if (!found) { + output.push(lines[i]) } i++ } - return joinedLines -} - -function getMultiLineRanges(ranges: number[][]): number[][] { - /* - Drops ranges with the same start and stop line numbers (i.e., strings - that populate a single line) - */ - const multiLineRanges: number[][] = [] - for (const [start, stop] of ranges) { - if (start !== stop) { - multiLineRanges.push([start, stop]) - } - } - return multiLineRanges + return output } /* @@ -191,31 +183,39 @@ function getMultiLineRanges(ranges: number[][]): number[][] { */ export function getExistingDescribeFromControl(control: Control): string { if (control.code) { - // Join multi-line strings in InSpec control. - const ranges = getRangesForLines(control.code) - const multiLineRanges = getMultiLineRanges(ranges) - const lines = joinMultiLineStringsFromRanges(control.code, multiLineRanges) // Array of lines representing the full InSpec control, with multi-line strings collapsed + // Join multi-line strings in Control block. + let ranges = getRangesFromStackUpdate(control.code) + ranges = getDistinctRanges(ranges) + const lines = joinStringsFromRanges(control.code, ranges) - // Define RegExp for lines to skip when searching for describe block. + // Define RegExp for lines to skip. const skip = ['control\\W', ' title\\W', ' desc\\W', ' impact\\W', ' tag\\W', ' ref\\W'] const skipRegExp = RegExp(skip.map(x => `(^${x})`).join('|')) - // Extract describe block from InSpec control with collapsed multiline strings. - const describeBlock: string[] = [] + // Extract logic from code. + const logic: string[] = [] let ignoreNewLine = true - for (const line of lines) { + for (let i = 0; i < lines.length; i++) { + const line = lines[i] const checkRegExp = ((line.trim() !== '') && !skipRegExp.test(line)) const checkNewLine = ((line.trim() === '') && !ignoreNewLine) // Include '\n' if it is part of describe block, otherwise skip line. if (checkRegExp || checkNewLine) { - describeBlock.push(line) + logic.push(line) ignoreNewLine = false } else { ignoreNewLine = true } } - return describeBlock.slice(0, describeBlock.length - 2).join('\n') // Drop trailing ['end', '\n'] from Control block. + + // Return synthesized logic as describe block + const slicePad = logic.at(logic.length-1)?.trim() == '' ? 2 : 1 + + console.log(`logic before: ${logic}`) + console.log(`logic after: ${logic.slice(0, logic.length - slicePad).join('\n')}`) + + return logic.slice(0, logic.length - slicePad).join('\n') // Drop trailing ['end', '\n'] from Control block. } else { return '' } diff --git a/test/sample_data/controls-cookstyle/SV-205653.rb b/test/sample_data/controls-cookstyle/SV-205653.rb new file mode 100644 index 00000000..6976c172 --- /dev/null +++ b/test/sample_data/controls-cookstyle/SV-205653.rb @@ -0,0 +1,27 @@ +control 'SV-205653' do + title 'Windows Server 2019 reversible password encryption must be disabled.' + desc 'Storing passwords using reversible encryption is essentially the same as storing clear-text versions of the passwords, which are easily compromised. For this reason, this policy must never be enabled.' + desc 'check', 'Verify the effective setting in Local Group Policy Editor. + + Run "gpedit.msc". + + Navigate to Local Computer Policy >> Computer Configuration >> Windows Settings >> Security Settings >> Account Policies >> Password Policy. + If the value for "Store passwords using reversible encryption" is not set to "Disabled", this is a finding. + + For server core installations, run the following command: + Secedit /Export /Areas SecurityPolicy /CFG C:\\Path\\FileName.Txt + If "ClearTextPassword" equals "1" in the file, this is a finding.' + desc 'fix', 'Configure the policy value for Computer Configuration >> Windows Settings >> Security Settings >> Account Policies >> Password Policy >> "Store passwords using reversible encryption" to "Disabled".' + impact 0.7 + tag gtitle: 'SRG-OS-000073-GPOS-00041' + tag gid: 'V-93465' + tag rid: 'SV-103551r1_rule' + tag stig_id: 'WN19-AC-000090' + tag fix_id: 'F-99709r1_fix' + tag cci: ['CCI-000196'] + tag nist: ['IA-5 (1) (c)', 'Rev_4'] + + describe security_policy do + its('ClearTextPassword') { should eq 0 } + end +end diff --git a/test/sample_data/inspec/json/cookstyle-controls-profile.json b/test/sample_data/inspec/json/cookstyle-controls-profile.json index d334de23..d82451af 100644 --- a/test/sample_data/inspec/json/cookstyle-controls-profile.json +++ b/test/sample_data/inspec/json/cookstyle-controls-profile.json @@ -1 +1,2 @@ -{"supports":[],"title":"tests from ./test/sample_data/controls-cookstyle","name":"tests from ..test.sample_data.controls-cookstyle","controls":[{"title":"The Red Hat Enterprise Linux operating system must be configured so that all local initialization files for\n interactive users are owned by the home directory user or root.","desc":"Local initialization files are used to configure the user's shell environment upon logon. Malicious\n modification of these files could compromise accounts upon logon.","descriptions":{"default":"Local initialization files are used to configure the user's shell environment upon logon. Malicious\n modification of these files could compromise accounts upon logon.","check":"Verify the local initialization files of all local interactive users are owned by that user.\n Check the home directory assignment for all non-privileged users on the system with the following command:\n Note: The example will be for the smithj user, who has a home directory of \"/home/smithj\".\n # awk -F: '($3>=1000)&&($7 !~ /nologin/){print $1, $3, $6}' /etc/passwd\n smithj 1000 /home/smithj\n Note: This may miss interactive users that have been assigned a privileged User Identifier (UID). Evidence of\n interactive use may be obtained from a number of log files containing system logon information.\n Check the owner of all local interactive user's initialization files with the following command:\n # ls -al /home/smithj/.[^.]* | more\n -rwxr-xr-x 1 smithj users 896 Mar 10 2011 .profile\n -rwxr-xr-x 1 smithj users 497 Jan 6 2007 .login\n -rwxr-xr-x 1 smithj users 886 Jan 6 2007 .something\n If all local interactive user's initialization files are not owned by that user or root, this is a finding.","fix":"Set the owner of the local initialization files for interactive users to\neither the directory owner or root with the following command:\n Note: The example will be for the smithj user, who has a home directory of\n\"/home/smithj\".\n # chown smithj /home/smithj/.[^.]*"},"impact":0.5,"refs":[],"tags":{"legacy":["V-72029","SV-86653"],"severity":"medium","gtitle":"SRG-OS-000480-GPOS-00227","gid":"V-204474","rid":"SV-204474r603834_rule","stig_id":"RHEL-07-020690","fix_i":"F-4598r462464_fix","cci":["CCI-000366"],"nist":["CM-6 b"],"subsystems":["init_files"]},"code":"control 'SV-204474' do\n title 'The Red Hat Enterprise Linux operating system must be configured so that all local initialization files for\n interactive users are owned by the home directory user or root.'\n desc \"Local initialization files are used to configure the user's shell environment upon logon. Malicious\n modification of these files could compromise accounts upon logon.\"\n desc 'check', %q(Verify the local initialization files of all local interactive users are owned by that user.\n Check the home directory assignment for all non-privileged users on the system with the following command:\n Note: The example will be for the smithj user, who has a home directory of \"/home/smithj\".\n # awk -F: '($3>=1000)&&($7 !~ /nologin/){print $1, $3, $6}' /etc/passwd\n smithj 1000 /home/smithj\n Note: This may miss interactive users that have been assigned a privileged User Identifier (UID). Evidence of\n interactive use may be obtained from a number of log files containing system logon information.\n Check the owner of all local interactive user's initialization files with the following command:\n # ls -al /home/smithj/.[^.]* | more\n -rwxr-xr-x 1 smithj users 896 Mar 10 2011 .profile\n -rwxr-xr-x 1 smithj users 497 Jan 6 2007 .login\n -rwxr-xr-x 1 smithj users 886 Jan 6 2007 .something\n If all local interactive user's initialization files are not owned by that user or root, this is a finding.)\n desc 'fix', 'Set the owner of the local initialization files for interactive users to\neither the directory owner or root with the following command:\n Note: The example will be for the smithj user, who has a home directory of\n\"/home/smithj\".\n # chown smithj /home/smithj/.[^.]*'\n impact 0.5\n tag legacy: ['V-72029', 'SV-86653']\n tag severity: 'medium'\n tag gtitle: 'SRG-OS-000480-GPOS-00227'\n tag gid: 'V-204474'\n tag rid: 'SV-204474r603834_rule'\n tag stig_id: 'RHEL-07-020690'\n tag fix_i: 'F-4598r462464_fix'\n tag cci: ['CCI-000366']\n tag nist: ['CM-6 b']\n tag subsystems: ['init_files']\n\n if virtualization.system.eql?('docker')\n impact 0.0\n describe 'Control not applicable to a container' do\n skip 'Control not applicable to a container'\n end\n else\n\n exempt_home_users = input('exempt_home_users')\n non_interactive_shells = input('non_interactive_shells')\n\n ignore_shells = non_interactive_shells.join('|')\n\n findings = Set[]\n users.where { !shell.match(ignore_shells) && (uid >= 1000 || uid == 0) }.entries.each do |user_info|\n next if exempt_home_users.include?(user_info.username.to_s)\n\n findings += command(\"find #{user_info.home} -name '.*' -not -user #{user_info.username} -a -not -user root\").stdout.split(\"\\n\")\n end\n describe 'Files and Directories not owned by the user or root of the parent home directory' do\n subject { findings.to_a }\n it { should be_empty }\n end\n end\nend\n","source_location":{"ref":"./test/sample_data/controls-cookstyle/SV-204474.rb","line":1},"id":"SV-204474"},{"title":"RHEL 8 must define default permissions for logon and non-logon shells.","desc":"The umask controls the default access mode assigned to newly created\nfiles. A umask of 077 limits new files to mode 600 or less permissive. Although\numask can be represented as a four-digit number, the first digit representing\nspecial access modes is typically ignored or required to be \"0\". This\nrequirement applies to the globally configured system defaults and the local\ninteractive user defaults for each account on the system.","descriptions":{"default":"The umask controls the default access mode assigned to newly created\nfiles. A umask of 077 limits new files to mode 600 or less permissive. Although\numask can be represented as a four-digit number, the first digit representing\nspecial access modes is typically ignored or required to be \"0\". This\nrequirement applies to the globally configured system defaults and the local\ninteractive user defaults for each account on the system.","rationale":"","check":"Verify that the umask default for installed shells is \"077\".\n\n Check for the value of the \"UMASK\" parameter in the \"/etc/bashrc\" and\n\"/etc/csh.cshrc\" files with the following command:\n\n Note: If the value of the \"UMASK\" parameter is set to \"000\" in either\nthe \"/etc/bashrc\" or the \"/etc/csh.cshrc\" files, the Severity is raised to\na CAT I.\n\n # grep -i umask /etc/bashrc /etc/csh.cshrc\n\n /etc/bashrc: umask 077\n /etc/bashrc: umask 077\n /etc/csh.cshrc: umask 077\n /etc/csh.cshrc: umask 077\n\n If the value for the \"UMASK\" parameter is not \"077\", or the \"UMASK\"\nparameter is missing or is commented out, this is a finding.","fix":"Configure the operating system to define default permissions for all\nauthenticated users in such a way that the user can only read and modify their\nown files.\n\n Add or edit the lines for the \"UMASK\" parameter in the \"/etc/bashrc\"\nand \"etc/csh.cshrc\" files to \"077\":\n\n UMASK 077"},"impact":0.5,"refs":[],"tags":{"severity":"medium","gtitle":"SRG-OS-000480-GPOS-00227","gid":"V-230385","rid":"SV-230385r627750_rule","stig_id":"RHEL-08-020353","fix_id":"F-33029r567902_fix","cci":["CCI-000366"],"nist":["CM-6 b"]},"code":"control 'SV-230385' do\n title 'RHEL 8 must define default permissions for logon and non-logon shells.'\n desc \"The umask controls the default access mode assigned to newly created\nfiles. A umask of 077 limits new files to mode 600 or less permissive. Although\numask can be represented as a four-digit number, the first digit representing\nspecial access modes is typically ignored or required to be \\\"0\\\". This\nrequirement applies to the globally configured system defaults and the local\ninteractive user defaults for each account on the system.\"\n desc 'rationale', ''\n desc 'check', \"\n Verify that the umask default for installed shells is \\\"077\\\".\n\n Check for the value of the \\\"UMASK\\\" parameter in the \\\"/etc/bashrc\\\" and\n\\\"/etc/csh.cshrc\\\" files with the following command:\n\n Note: If the value of the \\\"UMASK\\\" parameter is set to \\\"000\\\" in either\nthe \\\"/etc/bashrc\\\" or the \\\"/etc/csh.cshrc\\\" files, the Severity is raised to\na CAT I.\n\n # grep -i umask /etc/bashrc /etc/csh.cshrc\n\n /etc/bashrc: umask 077\n /etc/bashrc: umask 077\n /etc/csh.cshrc: umask 077\n /etc/csh.cshrc: umask 077\n\n If the value for the \\\"UMASK\\\" parameter is not \\\"077\\\", or the \\\"UMASK\\\"\nparameter is missing or is commented out, this is a finding.\n \"\n desc 'fix', \"\n Configure the operating system to define default permissions for all\nauthenticated users in such a way that the user can only read and modify their\nown files.\n\n Add or edit the lines for the \\\"UMASK\\\" parameter in the \\\"/etc/bashrc\\\"\nand \\\"etc/csh.cshrc\\\" files to \\\"077\\\":\n\n UMASK 077\n \"\n impact 0.5\n tag severity: 'medium'\n tag gtitle: 'SRG-OS-000480-GPOS-00227'\n tag gid: 'V-230385'\n tag rid: 'SV-230385r627750_rule'\n tag stig_id: 'RHEL-08-020353'\n tag fix_id: 'F-33029r567902_fix'\n tag cci: ['CCI-000366']\n tag nist: ['CM-6 b']\n\n umask_regexp = /umask\\s*(?\\d\\d\\d)/\n\n bashrc_umask = file('/etc/bashrc').content.match(umask_regexp)[:umask_code]\n cshrc_umask = file('/etc/csh.cshrc').content.match(umask_regexp)[:umask_code]\n\n if bashrc_umask == '000' || cshrc_umask == '000'\n impact 0.7\n tag severity: 'high'\n end\n\n describe 'umask value defined in /etc/bashrc' do\n subject { bashrc_umask }\n it { should cmp '077' }\n end\n describe 'umask value defined in /etc/csh.cshrc' do\n subject { cshrc_umask }\n it { should cmp '077' }\n end\nend\n","source_location":{"ref":"./test/sample_data/controls-cookstyle/SV-230385.rb","line":1},"id":"SV-230385"},{"title":"The Red Hat Enterprise Linux operating system must be configured so that the file permissions, ownership,\n and group membership of system files and commands match the vendor values.","desc":"Discretionary access control is weakened if a user or group has access permissions to system files and\n directories greater than the default.","descriptions":{"default":"Discretionary access control is weakened if a user or group has access permissions to system files and\n directories greater than the default.","rationale":"","check":"Verify the file permissions, ownership, and group membership of system files and commands match the\n vendor values.\n Check the default file permissions, ownership, and group membership of system files and commands with the following\n command:\n # for i in `rpm -Va | egrep '^.{1}M|^.{5}U|^.{6}G' | cut -d \" \" -f 4,5`;do for j in `rpm -qf $i`;do rpm -ql $j\n --dump | cut -d \" \" -f 1,5,6,7 | grep $i;done;done\n /var/log/gdm 040755 root root\n /etc/audisp/audisp-remote.conf 0100640 root root\n /usr/bin/passwd 0104755 root root\n For each file returned, verify the current permissions, ownership, and group membership:\n # ls -la \n -rw-------. 1 root root 133 Jan 11 13:25 /etc/audisp/audisp-remote.conf\n If the file is more permissive than the default permissions, this is a finding.\n If the file is not owned by the default owner and is not documented with the Information System Security Officer\n (ISSO), this is a finding.\n If the file is not a member of the default group and is not documented with the Information System Security Officer\n (ISSO), this is a finding.","fix":"Run the following command to determine which package owns the file:\n\n # rpm -qf \n\n Reset the user and group ownership of files within a package with the\nfollowing command:\n\n #rpm --setugids \n\n\n Reset the permissions of files within a package with the following command:\n\n #rpm --setperms "},"impact":0.7,"refs":[],"tags":{"legacy":["V-71849","SV-86473"],"severity":"high","gtitle":"SRG-OS-000257-GPOS-00098","satisfies":["SRG-OS-000257-GPOS-00098","SRG-OS-000278-GPOS-00108"],"gid":"V-204392","rid":"SV-204392r646841_rule","stig_id":"RHEL-07-010010","fix_id":"F-36302r646840_fix","cci":["CCI-001494","CCI-001496","CCI-002165","CCI-002235"],"nist":["AU-9","AU-9 (3)","AC-3 (4)","AC-6 (10)"],"subsystems":["permissions","package","rpm"],"host":null,"container":null},"code":"control 'SV-204392' do\n title 'The Red Hat Enterprise Linux operating system must be configured so that the file permissions, ownership,\n and group membership of system files and commands match the vendor values.'\n desc 'Discretionary access control is weakened if a user or group has access permissions to system files and\n directories greater than the default.'\n desc 'rationale', ''\n desc 'check', %\n -rw-------. 1 root root 133 Jan 11 13:25 /etc/audisp/audisp-remote.conf\n If the file is more permissive than the default permissions, this is a finding.\n If the file is not owned by the default owner and is not documented with the Information System Security Officer\n (ISSO), this is a finding.\n If the file is not a member of the default group and is not documented with the Information System Security Officer\n (ISSO), this is a finding.>\n desc 'fix', \"\n Run the following command to determine which package owns the file:\n\n # rpm -qf \n\n Reset the user and group ownership of files within a package with the\n following command:\n\n #rpm --setugids \n\n\n Reset the permissions of files within a package with the following command:\n\n #rpm --setperms \n \"\n impact 0.7\n tag 'legacy': ['V-71849', 'SV-86473']\n tag 'severity': 'high'\n tag 'gtitle': 'SRG-OS-000257-GPOS-00098'\n tag 'satisfies': ['SRG-OS-000257-GPOS-00098', 'SRG-OS-000278-GPOS-00108']\n tag 'gid': 'V-204392'\n tag 'rid': 'SV-204392r646841_rule'\n tag 'stig_id': 'RHEL-07-010010'\n tag 'fix_id': 'F-36302r646840_fix'\n tag 'cci': ['CCI-001494', 'CCI-001496', 'CCI-002165', 'CCI-002235']\n tag nist: ['AU-9', 'AU-9 (3)', 'AC-3 (4)', 'AC-6 (10)']\n tag subsystems: ['permissions', 'package', 'rpm']\n tag 'host', 'container'\n\n if input('disable_slow_controls')\n describe \"This control consistently takes a long time to run and has been disabled\n using the disable_slow_controls attribute.\" do\n skip \"This control consistently takes a long time to run and has been disabled\n using the disable_slow_controls attribute. You must enable this control for a\n full accredidation for production.\"\n end\n else\n\n allowlist = input('rpm_verify_perms_except')\n\n misconfigured_packages = command('rpm -Va').stdout.split(\"\\n\")\n .select { |package| package[0..7].match(/M|U|G/) }\n .map { |package| package.match(/\\S+$/)[0] }\n\n if misconfigured_packages.empty?\n describe 'The list of rpm packages with permissions changed from the vendor values' do\n subject { misconfigured_packages }\n it { should be_empty }\n end\n else\n describe 'The list of rpm packages with permissions changed from the vendor values' do\n fail_msg = \"Files that have been modified from vendor-approved permissions but are not in the allowlist: #{(misconfigured_packages - allowlist).join(', ')}\"\n it 'should all appear in the allowlist' do\n expect(misconfigured_packages).to all(be_in(allowlist)), fail_msg\n end\n end\n end\n end\nend\n","source_location":{"ref":"./test/sample_data/controls-cookstyle/SV-204392.rb","line":1},"id":"SV-204392"}],"groups":[{"title":null,"controls":["SV-204474"],"id":"SV-204474.rb"},{"title":null,"controls":["SV-230385"],"id":"SV-230385.rb"},{"title":null,"controls":["SV-204392"],"id":"SV-204392.rb"}],"inputs":[],"sha256":"39d0b9b657c297fc336c249794b4c9bb5c8f7dfbb2ad83faf7723d79cae683dd","status_message":"","status":"loaded","generator":{"name":"inspec","version":"5.12.2"}} +{"supports":[],"title":"tests from .","name":"tests from .","controls":[{"title":"The Red Hat Enterprise Linux operating system must be configured so that the file permissions, ownership,\n and group membership of system files and commands match the vendor values.","desc":"Discretionary access control is weakened if a user or group has access permissions to system files and\n directories greater than the default.","descriptions":{"default":"Discretionary access control is weakened if a user or group has access permissions to system files and\n directories greater than the default.","rationale":"","check":"Verify the file permissions, ownership, and group membership of system files and commands match the\n vendor values.\n Check the default file permissions, ownership, and group membership of system files and commands with the following\n command:\n # for i in `rpm -Va | egrep '^.{1}M|^.{5}U|^.{6}G' | cut -d \" \" -f 4,5`;do for j in `rpm -qf $i`;do rpm -ql $j\n --dump | cut -d \" \" -f 1,5,6,7 | grep $i;done;done\n /var/log/gdm 040755 root root\n /etc/audisp/audisp-remote.conf 0100640 root root\n /usr/bin/passwd 0104755 root root\n For each file returned, verify the current permissions, ownership, and group membership:\n # ls -la \n -rw-------. 1 root root 133 Jan 11 13:25 /etc/audisp/audisp-remote.conf\n If the file is more permissive than the default permissions, this is a finding.\n If the file is not owned by the default owner and is not documented with the Information System Security Officer\n (ISSO), this is a finding.\n If the file is not a member of the default group and is not documented with the Information System Security Officer\n (ISSO), this is a finding.","fix":"Run the following command to determine which package owns the file:\n\n # rpm -qf \n\n Reset the user and group ownership of files within a package with the\nfollowing command:\n\n #rpm --setugids \n\n\n Reset the permissions of files within a package with the following command:\n\n #rpm --setperms "},"impact":0.7,"refs":[],"tags":{"legacy":["V-71849","SV-86473"],"severity":"high","gtitle":"SRG-OS-000257-GPOS-00098","satisfies":["SRG-OS-000257-GPOS-00098","SRG-OS-000278-GPOS-00108"],"gid":"V-204392","rid":"SV-204392r646841_rule","stig_id":"RHEL-07-010010", +"fix_id":"F-36302r646840_fix","cci":["CCI-001494","CCI-001496","CCI-002165","CCI-002235"],"nist":["AU-9","AU-9 (3)","AC-3 (4)","AC-6 (10)"],"subsystems":["permissions","package","rpm"],"host":null,"container":null},"code":"control 'SV-204392' do\n title 'The Red Hat Enterprise Linux operating system must be configured so that the file permissions, ownership,\n and group membership of system files and commands match the vendor values.'\n desc 'Discretionary access control is weakened if a user or group has access permissions to system files and\n directories greater than the default.'\n desc 'rationale', ''\n desc 'check', %\n -rw-------. 1 root root 133 Jan 11 13:25 /etc/audisp/audisp-remote.conf\n If the file is more permissive than the default permissions, this is a finding.\n If the file is not owned by the default owner and is not documented with the Information System Security Officer\n (ISSO), this is a finding.\n If the file is not a member of the default group and is not documented with the Information System Security Officer\n (ISSO), this is a finding.>\n desc 'fix', \"\n Run the following command to determine which package owns the file:\n\n # rpm -qf \n\n Reset the user and group ownership of files within a package with the\n following command:\n\n #rpm --setugids \n\n\n Reset the permissions of files within a package with the following command:\n\n #rpm --setperms \n \"\n impact 0.7\n tag 'legacy': ['V-71849', 'SV-86473']\n tag 'severity': 'high'\n tag 'gtitle': 'SRG-OS-000257-GPOS-00098'\n tag 'satisfies': ['SRG-OS-000257-GPOS-00098', 'SRG-OS-000278-GPOS-00108']\n tag 'gid': 'V-204392'\n tag 'rid': 'SV-204392r646841_rule'\n tag 'stig_id': 'RHEL-07-010010'\n tag 'fix_id': 'F-36302r646840_fix'\n tag 'cci': ['CCI-001494', 'CCI-001496', 'CCI-002165', 'CCI-002235']\n tag nist: ['AU-9', 'AU-9 (3)', 'AC-3 (4)', 'AC-6 (10)']\n tag subsystems: ['permissions', 'package', 'rpm']\n tag 'host', 'container'\n\n if input('disable_slow_controls')\n describe \"This control consistently takes a long time to run and has been disabled\n using the disable_slow_controls attribute.\" do\n skip \"This control consistently takes a long time to run and has been disabled\n using the disable_slow_controls attribute. You must enable this control for a\n full accredidation for production.\"\n end\n else\n\n allowlist = input('rpm_verify_perms_except')\n\n misconfigured_packages = command('rpm -Va').stdout.split(\"\\n\")\n .select { |package| package[0..7].match(/M|U|G/) }\n .map { |package| package.match(/\\S+$/)[0] }\n\n if misconfigured_packages.empty?\n describe 'The list of rpm packages with permissions changed from the vendor values' do\n subject { misconfigured_packages }\n it { should be_empty }\n end\n else\n describe 'The list of rpm packages with permissions changed from the vendor values' do\n fail_msg = \"Files that have been modified from vendor-approved permissions but are not in the allowlist: #{(misconfigured_packages - allowlist).join(', ')}\"\n it 'should all appear in the allowlist' do\n expect(misconfigured_packages).to all(be_in(allowlist)), fail_msg\n end\n end\n end\n end\nend\n","source_location":{"ref":"./SV-204392.rb","line":1},"id":"SV-204392"},{"title":"The Red Hat Enterprise Linux operating system must be configured so that all local initialization files for\n interactive users are owned by the home directory user or root.","desc":"Local initialization files are used to configure the user's shell environment upon logon. Malicious\n modification of these files could compromise accounts upon logon.","descriptions":{"default":"Local initialization files are used to configure the user's shell environment upon logon. Malicious\n modification of these files could compromise accounts upon logon.","check":"Verify the local initialization files of all local interactive users are owned by that user.\n Check the home directory assignment for all non-privileged users on the system with the following command:\n Note: The example will be for the smithj user, who has a home directory of \"/home/smithj\".\n # awk -F: '($3>=1000)&&($7 !~ /nologin/){print $1, $3, $6}' /etc/passwd\n smithj 1000 /home/smithj\n Note: This may miss interactive users that have been assigned a privileged User Identifier (UID). Evidence of\n interactive use may be obtained from a number of log files containing system logon information.\n Check the owner of all local interactive user's initialization files with the following command:\n # ls -al /home/smithj/.[^.]* | more\n -rwxr-xr-x 1 smithj users 896 Mar 10 2011 .profile\n -rwxr-xr-x 1 smithj users 497 Jan 6 2007 .login\n -rwxr-xr-x 1 smithj users 886 Jan 6 2007 .something\n If all local interactive user's initialization files are not owned by that user or root, this is a finding.","fix":"Set the owner of the local initialization files for interactive users to\neither the directory owner or root with the following command:\n Note: The example will be for the smithj user, who has a home directory of\n\"/home/smithj\".\n # chown smithj /home/smithj/.[^.]*"},"impact":0.5,"refs":[],"tags":{"legacy":["V-72029","SV-86653"],"severity":"medium","gtitle":"SRG-OS-000480-GPOS-00227","gid":"V-204474","rid":"SV-204474r603834_rule","stig_id":"RHEL-07-020690","fix_i":"F-4598r462464_fix","cci":["CCI-000366"],"nist":["CM-6 b"],"subsystems":["init_files"]},"code":"control 'SV-204474' do\n title 'The Red Hat Enterprise Linux operating system must be configured so that all local initialization files for\n interactive users are owned by the home directory user or root.'\n desc \"Local initialization files are used to configure the user's shell environment upon logon. Malicious\n modification of these files could compromise accounts upon logon.\"\n desc 'check', %q(Verify the local initialization files of all local interactive users are owned by that user.\n Check the home directory assignment for all non-privileged users on the system with the following command:\n Note: The example will be for the smithj user, who has a home directory of \"/home/smithj\".\n # awk -F: '($3>=1000)&&($7 !~ /nologin/){print $1, $3, $6}' /etc/passwd\n smithj 1000 /home/smithj\n Note: This may miss interactive users that have been assigned a privileged User Identifier (UID). Evidence of\n interactive use may be obtained from a number of log files containing system logon information.\n Check the owner of all local interactive user's initialization files with the following command:\n # ls -al /home/smithj/.[^.]* | more\n -rwxr-xr-x 1 smithj users 896 Mar 10 2011 .profile\n -rwxr-xr-x 1 smithj users 497 Jan 6 2007 .login\n -rwxr-xr-x 1 smithj users 886 Jan 6 2007 .something\n If all local interactive user's initialization files are not owned by that user or root, this is a finding.)\n desc 'fix', 'Set the owner of the local initialization files for interactive users to\neither the directory owner or root with the following command:\n Note: The example will be for the smithj user, who has a home directory of\n\"/home/smithj\".\n # chown smithj /home/smithj/.[^.]*'\n impact 0.5\n tag legacy: ['V-72029', 'SV-86653']\n tag severity: 'medium'\n tag gtitle: 'SRG-OS-000480-GPOS-00227'\n tag gid: 'V-204474'\n tag rid: 'SV-204474r603834_rule'\n tag stig_id: 'RHEL-07-020690'\n tag fix_i: 'F-4598r462464_fix'\n tag cci: ['CCI-000366']\n tag nist: ['CM-6 b']\n tag subsystems: ['init_files']\n\n if virtualization.system.eql?('docker')\n impact 0.0\n describe 'Control not applicable to a container' do\n skip 'Control not applicable to a container'\n end\n else\n\n exempt_home_users = input('exempt_home_users')\n non_interactive_shells = input('non_interactive_shells')\n\n ignore_shells = non_interactive_shells.join('|')\n\n findings = Set[]\n users.where { !shell.match(ignore_shells) && (uid >= 1000 || uid == 0) }.entries.each do |user_info|\n next if exempt_home_users.include?(user_info.username.to_s)\n\n findings += command(\"find #{user_info.home} -name '.*' -not -user #{user_info.username} -a -not -user root\").stdout.split(\"\\n\")\n end\n describe 'Files and Directories not owned by the user or root of the parent home directory' do\n subject { findings.to_a }\n it { should be_empty }\n end\n end\nend\n","source_location":{"ref":"./SV-204474.rb","line":1},"id":"SV-204474"},{"title":"Windows Server 2019 reversible password encryption must be disabled.","desc":"Storing passwords using reversible encryption is essentially the same as storing clear-text versions of the passwords, which are easily compromised. For this reason, this policy must never be enabled.","descriptions":{"default":"Storing passwords using reversible encryption is essentially the same as storing clear-text versions of the passwords, which are easily compromised. For this reason, this policy must never be enabled.","check":"Verify the effective setting in Local Group Policy Editor.\n\n Run \"gpedit.msc\".\n\n Navigate to Local Computer Policy >> Computer Configuration >> Windows Settings >> Security Settings >> Account Policies >> Password Policy.\n If the value for \"Store passwords using reversible encryption\" is not set to \"Disabled\", this is a finding.\n\n For server core installations, run the following command:\n Secedit /Export /Areas SecurityPolicy /CFG C:\\Path\\FileName.Txt\n If \"ClearTextPassword\" equals \"1\" in the file, this is a finding.","fix":"Configure the policy value for Computer Configuration >> Windows Settings >> Security Settings >> Account Policies >> Password Policy >> \"Store passwords using reversible encryption\" to \"Disabled\"."},"impact":0.7,"refs":[],"tags":{"gtitle":"SRG-OS-000073-GPOS-00041","gid":"V-93465","rid":"SV-103551r1_rule","stig_id":"WN19-AC-000090","fix_id":"F-99709r1_fix","cci":["CCI-000196"],"nist":["IA-5 (1) (c)","Rev_4"]},"code": "control \"SV-205653\" do\n title \"Windows Server 2019 reversible password encryption must be disabled.\"\n desc \"Storing passwords using reversible encryption is essentially the same as storing clear-text versions of the passwords, which are easily compromised. For this reason, this policy must never be enabled.\"\n desc \"rationale\", \"\"\n desc \"check\", \"Verify the effective setting in Local Group Policy Editor.\n\n Run \\\"gpedit.msc\\\".\n\n Navigate to Local Computer Policy >> Computer Configuration >> Windows Settings >> Security Settings >> Account Policies >> Password Policy.\n If the value for \\\"Store passwords using reversible encryption\\\" is not set to \\\"Disabled\\\", this is a finding.\n\n For server core installations, run the following command:\n Secedit /Export /Areas SecurityPolicy /CFG C:\\\\Path\\\\FileName.Txt\n If \\\"ClearTextPassword\\\" equals \\\"1\\\" in the file, this is a finding.\"\n desc \"fix\", \"Configure the policy value for Computer Configuration >> Windows Settings >> Security Settings >> Account Policies >> Password Policy >> \\\"Store passwords using reversible encryption\\\" to \\\"Disabled\\\".\"\n impact 0.7\n tag severity: nil\n tag gtitle: \"SRG-OS-000073-GPOS-00041\"\n tag gid: \"V-93465\"\n tag rid: \"SV-103551r1_rule\"\n tag stig_id: \"WN19-AC-000090\"\n tag fix_id: \"F-99709r1_fix\"\n tag cci: [\"CCI-000196\"]\n tag nist: [\"IA-5 (1) (c)\", \"Rev_4\"]\n\n describe security_policy do\n its('ClearTextPassword') { should eq 0 }\n end\nend","source_location":{"ref":"./SV-205653.rb","line":1},"id":"SV-205653"},{"title":"RHEL 8 must define default permissions for logon and non-logon shells.","desc":"The umask controls the default access mode assigned to newly created\nfiles. A umask of 077 limits new files to mode 600 or less permissive. Although\numask can be represented as a four-digit number, the first digit representing\nspecial access modes is typically ignored or required to be \"0\". This\nrequirement applies to the globally configured system defaults and the local\ninteractive user defaults for each account on the system.","descriptions":{"default":"The umask controls the default access mode assigned to newly created\nfiles. A umask of 077 limits new files to mode 600 or less permissive. Although\numask can be represented as a four-digit number, the first digit representing\nspecial access modes is typically ignored or required to be \"0\". This\nrequirement applies to the globally configured system defaults and the local\ninteractive user defaults for each account on the system.","rationale":"","check":"Verify that the umask default for installed shells is \"077\".\n\n Check for the value of the \"UMASK\" parameter in the \"/etc/bashrc\" and\n\"/etc/csh.cshrc\" files with the following command:\n\n Note: If the value of the \"UMASK\" parameter is set to \"000\" in either\nthe \"/etc/bashrc\" or the \"/etc/csh.cshrc\" files, the Severity is raised to\na CAT I.\n\n # grep -i umask /etc/bashrc /etc/csh.cshrc\n\n /etc/bashrc: umask 077\n /etc/bashrc: umask 077\n /etc/csh.cshrc: umask 077\n /etc/csh.cshrc: umask 077\n\n If the value for the \"UMASK\" parameter is not \"077\", or the \"UMASK\"\nparameter is missing or is commented out, this is a finding.","fix":"Configure the operating system to define default permissions for all\nauthenticated users in such a way that the user can only read and modify their\nown files.\n\n Add or edit the lines for the \"UMASK\" parameter in the \"/etc/bashrc\"\nand \"etc/csh.cshrc\" files to \"077\":\n\n UMASK 077"},"impact":0.5,"refs":[],"tags":{"severity":"medium","gtitle":"SRG-OS-000480-GPOS-00227","gid":"V-230385","rid":"SV-230385r627750_rule","stig_id":"RHEL-08-020353","fix_id":"F-33029r567902_fix","cci":["CCI-000366"],"nist":["CM-6 b"]},"code":"control 'SV-230385' do\n title 'RHEL 8 must define default permissions for logon and non-logon shells.'\n desc \"The umask controls the default access mode assigned to newly created\nfiles. A umask of 077 limits new files to mode 600 or less permissive. Although\numask can be represented as a four-digit number, the first digit representing\nspecial access modes is typically ignored or required to be \\\"0\\\". This\nrequirement applies to the globally configured system defaults and the local\ninteractive user defaults for each account on the system.\"\n desc 'rationale', ''\n desc 'check', \"\n Verify that the umask default for installed shells is \\\"077\\\".\n\n Check for the value of the \\\"UMASK\\\" parameter in the \\\"/etc/bashrc\\\" and\n\\\"/etc/csh.cshrc\\\" files with the following command:\n\n Note: If the value of the \\\"UMASK\\\" parameter is set to \\\"000\\\" in either\nthe \\\"/etc/bashrc\\\" or the \\\"/etc/csh.cshrc\\\" files, the Severity is raised to\na CAT I.\n\n # grep -i umask /etc/bashrc /etc/csh.cshrc\n\n /etc/bashrc: umask 077\n /etc/bashrc: umask 077\n /etc/csh.cshrc: umask 077\n /etc/csh.cshrc: umask 077\n\n If the value for the \\\"UMASK\\\" parameter is not \\\"077\\\", or the \\\"UMASK\\\"\nparameter is missing or is commented out, this is a finding.\n \"\n desc 'fix', \"\n Configure the operating system to define default permissions for all\nauthenticated users in such a way that the user can only read and modify their\nown files.\n\n Add or edit the lines for the \\\"UMASK\\\" parameter in the \\\"/etc/bashrc\\\"\nand \\\"etc/csh.cshrc\\\" files to \\\"077\\\":\n\n UMASK 077\n \"\n impact 0.5\n tag severity: 'medium'\n tag gtitle: 'SRG-OS-000480-GPOS-00227'\n tag gid: 'V-230385'\n tag rid: 'SV-230385r627750_rule'\n tag stig_id: 'RHEL-08-020353'\n tag fix_id: 'F-33029r567902_fix'\n tag cci: ['CCI-000366']\n tag nist: ['CM-6 b']\n\n umask_regexp = /umask\\s*(?\\d\\d\\d)/\n\n bashrc_umask = file('/etc/bashrc').content.match(umask_regexp)[:umask_code]\n cshrc_umask = file('/etc/csh.cshrc').content.match(umask_regexp)[:umask_code]\n\n if bashrc_umask == '000' || cshrc_umask == '000'\n impact 0.7\n tag severity: 'high'\n end\n\n describe 'umask value defined in /etc/bashrc' do\n subject { bashrc_umask }\n it { should cmp '077' }\n end\n describe 'umask value defined in /etc/csh.cshrc' do\n subject { cshrc_umask }\n it { should cmp '077' }\n end\nend\n","source_location":{"ref":"./SV-230385.rb","line":1},"id":"SV-230385"}],"groups":[{"title":null,"controls":["SV-204392"],"id":"SV-204392.rb"},{"title":null,"controls":["SV-204474"],"id":"SV-204474.rb"},{"title":null,"controls":["SV-205653"],"id":"SV-205653.rb"},{"title":null,"controls":["SV-230385"],"id":"SV-230385.rb"}],"inputs":[{"name":"disable_slow_controls","options":{"value":"Input 'disable_slow_controls' does not have a value. Skipping test."}},{"name":"exempt_home_users","options":{"value":"Input 'exempt_home_users' does not have a value. Skipping test."}},{"name":"non_interactive_shells","options":{"value":"Input 'non_interactive_shells' does not have a value. Skipping test."}}],"sha256":"cb908468b2c1b3e04324ea781026cd6adbf1bc46979d16aa2b6f97597a110ad1","status_message":"","status":"loaded","generator":{"name":"inspec","version":"5.18.14"}} diff --git a/test/sample_data/xccdf/input/STIG/U_MS_Windows_Server_2019_STIG_V2R5_Manual-xccdf.xml b/test/sample_data/xccdf/input/STIG/U_MS_Windows_Server_2019_STIG_V2R5_Manual-xccdf.xml new file mode 100644 index 00000000..2333028f --- /dev/null +++ b/test/sample_data/xccdf/input/STIG/U_MS_Windows_Server_2019_STIG_V2R5_Manual-xccdf.xml @@ -0,0 +1,5501 @@ +acceptedMicrosoft Windows Server 2019 Security Technical Implementation GuideThis Security Technical Implementation Guide is published as a tool to improve the security of Department of Defense (DoD) information systems. The requirements are derived from the National Institute of Standards and Technology (NIST) 800-53 and related documents. Comments or proposed revisions to this document should be sent via email to the following address: disa.stig_spt@mail.mil.DISASTIG.DOD.MILRelease: 5 Benchmark Date: 14 Nov 20223.4.0.342221.10.02I - Mission Critical Classified<ProfileDescription></ProfileDescription>I - Mission Critical Sensitive<ProfileDescription></ProfileDescription>II - Mission Support Public<ProfileDescription></ProfileDescription>III - Administrative Classified<ProfileDescription></ProfileDescription>III - Administrative Sensitive<ProfileDescription></ProfileDescription>