Thursday, July 9, 2009

MacOS Security Compliance: Shell Scripting Compliance Checks

Creative Commons attribution licensed photo courtesy of juanpol

While many compliance tools exist for Windows, MacOS often gets less attention. Despite its overall lower rate of compromise, ensuring MacOS compliance with security configuration standards is still important. Tools like the CIS benchmarks exist, but may not be exactly what you want, or you may want the ability to check other arbitrary settings or files.

Since manually testing MacOS for security compliance can be a chore, an automated approach via scripting can make things easier. One of the talented MacOS administrators that I work with recently built a compliance check tool that integrates with an internally designed and built inventory and compliance check application. This allows any MacOS system that runs the inventory script to also pull an up to date copy of the compliance script and to report back to the inventory server about its current status. The local run also lists anything that is out of compliance, as well as the setting required by the standard.

The good news is that his techniques are broadly applicable, and easily adapted to your own compliance standards. If the CIS benchmarks aren't well suited to your needs, or if you can't deploy CIS-CAT to the platform, this shell code can check many of the common configuration variables that you may use in your environment.

Here's an example of how to check whether the login window displays name and password fields, of if it is defaulting to a list of users. Note that the code is modularized, with each compliance check separately annotated and documented internally, allowing additional compliance settings to be added, or for values to change easily if the standard is updated.
Display Login Window as Name and Password Fields
DISPLOGIN=`defaults read /Library/Preferences/com.apple.loginwindow SHOWFULLNAME` ## should return 1
if [[ "${DISPLOGIN}" != 1 ]] ; then
DISPLOGIN="List of Users"
SecStandardCompliance="false"
echo " ** SecStandardCompliance failed: Login Window displays as List of Users. Please set to \"Name and Password\" in Accounts System Preference."
arrSecurityStandardExceptions=( "${arrSecurityStandardExceptions[@]}" "1011" "${DISPLOGIN}" "Name and Password Fields" ) ## Login Window as Name and Password fields
fi
This script does a few simple, but clever things - first:
DISPLOGIN=`defaults read /Library/Preferences/com.apple.loginwindow SHOWFULLNAME`
This checks the preferences - basically a true/false check for the name display. Each step in the script uses a similar check to verify one or more settings related to a configuration option required by the standard. These steps are then used to build a true/false answer to the question: is the system compliant with the setting required by the standard?

Next, the script handles non-compliant systems:
DISPLOGIN="List of Users"

SecStandardCompliance="false"

This sets a variable to a string describing the non-compliant setting, and sets the overall compliance of the system to false - any single setting can take a machine out of compliance. For some organizations, this might be a score based approach, but in our environment, we want to flag any setting that doesn't match.

The script then notifies the local console of the exception:
echo " ** SecStandardCompliance failed: Login Window displays as List of Users. Please set to \"Name and Password\" in Accounts System Preference."
The important action here is that a local user would be told what was wrong, where to find it, and what the required setting is. This means that printing out the results of this script, or simply leaving the window open will provide the local user or administrator with a checklist and instructions that most users should be able to follow.

Finally, the script talks to the central inventory server - not a necessity, but in our case, a great way to add more power to the compliance check. This allows central reporting and long term tracking, which is attractive to our IT organization, and to our system administrators.
arrSecurityStandardExceptions=( "${arrSecurityStandardExceptions[@]}" "1011" "${DISPLOGIN}" "Name and Password Fields" ) ## Login Window as Name and Password fields
This code segment simply flags the important issues to add to the database when the wrapper script run around the compliance check script runs.

What else can you check? With a little cleverness, you can check:
  • AV installation status and version
  • If OS 9 is installed
  • Software update settings and frequency, and if all "recommended" patches are installed
  • If UID accounts othe than root are allowed
  • If fast user switching or autologin are enabled
  • Screensaver settings
  • Non-essential service status
  • Internet sharing status to determine if bridging or sharing are enabled
  • Default umask settings
  • Firewall settings
  • Encrypted swap space use
Some of these tests are more complex than others - the following code checks for use of encrypted swap space:
## Use Encrypted Swap Space
if [[ "${OSVERS}" == "Leopard" || "${OSVERS}" == "SnowLeopard" ]] ; then
ENC_SWAP=`defaults read /Library/Preferences/com.apple.VirtualMemory UseEncryptedSwap`
if [[ "${ENC_SWAP}" != "1" ]] ; then
ENC_SWAP="Disabled"
SecStandardCompliance="false"
echo " ** SecStandardCompliance failed: Please enable the use of encrypted virtual memory."
arrSecurityStandardExceptions=( "${arrSecurityStandardExceptions[@]}" "1028" "${ENC_SWAP}" "Enabled" ) ## Encrypted Swap Space
fi
elif [[ "${OSVERS}" == "Tiger" ]] ; then
ENC_SWAP=`grep ENCRYPTSWAP=-YES- /etc/hostconfig 2>/dev/null`
if [[ -z "${ENC_SWAP}" ]] ; then ## if the string is blank; that is, ENCRYPTSWAP=-YES- was not found
ENC_SWAP="Disabled"
SecStandardCompliance="false"
echo " ** SecStandardCompliance failed: Please enable the use of encrypted virtual memory."
arrSecurityStandardExceptions=( "${arrSecurityStandardExceptions[@]}" "1028" "${ENC_SWAP}" "Enabled" ) ## Encrypted Swap Space
fi
fi
There are still a number of items that you might have in your security standard that might not be easy to check with a script - either because they don't generate a file, because the settings that they require are not stored in a location that can be processed by a script, or because they are external to the system itself, but a script like this can give MacOS administrators a real leg up on checking their configurations.

No comments: