lynis/include/functions

1797 lines
72 KiB
Bash

#!/bin/sh
#################################################################################
#
# Lynis
# ------------------
#
# Copyright 2007-2016, Michael Boelen - CISOfy (michael.boelen@cisofy.com)
# Website: https://cisofy.com
#
# This software is licensed under GPL, version 3. See LICENSE file for
# usage of this software.
#
#################################################################################
#
# Functions
#
#################################################################################
#
# Function Description
# ----------------------- -------------------------------------------------
# AddHP Add Hardening points to plot a graph later
# AddSystemGroup Adds a system to a group
# CheckFilePermissions Check file permissions
# CheckUpdates Determine if a new version of Lynis is available
# counttests Count number of performed tests
# Debug Display additional information on the screen (not suited for cronjob)
# DigitsOnly Return only the digits from a string
# DirectoryExists Check if a directory exists on the disk
# Display Output text to screen with colors and identation
# DisplayManual Output text to screen without any layout
# ExitClean Stop the program (cleanly), with exit code 0
# ExitCustom Stop the program (cleanly), with custom exit code
# ExitFatal Stop the program (cleanly), with exit code 1
# FileExists Check if a file exists on the disk
# FileIsEmpty Check if a file is empty
# FileIsReadable Check if a file is readable or directory accessible
# GetHostID Retrieve an unique ID for this host
# InsertSection Insert a section block
# InsertPluginSection Insert a section block for plugins
# IsRunning Check if a process is running
# IsVirtualMachine Check if this system is a virtual machine
# IsWorldExecutable Check if a file is world executable
# IsWorldReadable Check if a file is world readable
# IsWorldWritable Check if a file is world writable
# LogText Log text strings to logfile, prefixed with date/time
# ParseNginx Parse nginx configuration lines
# Progress Show progress on screen
# RandomString Show a random string
# Report Add string of data to report file
# ReportException Add an exception to the report file (for debugging purposes)
# ReportSuggestion Add a suggestion to report file
# ReportWarning Add a warning and priority to report file
# Register Register a test (for logging and execution)
# SafePerms Check if a directory has safe permissions
# SearchItem Search a string in a file
# ShowComplianceFinding Display a particular finding regarding compliance or a security standard
# ShowSymlinkPath Show a path behind a symlink
# TestValue Evaluate a value in a string or key
# ViewCategories Display tests categories
# WaitForKeypress Wait for user to press a key to continue
#
#################################################################################
# Add Hardening Points
AddHP()
{
HPADD=$1; HPADDMAX=$2
HPPOINTS=`expr ${HPPOINTS} + ${HPADD}`
HPTOTAL=`expr ${HPTOTAL} + ${HPADDMAX}`
LogText "Hardening: assigned ${HPADD} hardening points (max for this item: ${HPADDMAX}), current: ${HPPOINTS}, total: ${HPTOTAL}"
}
################################################################################
# Name : AddSystemGroup
# Description : Adds a system to a group, which can be used for categorizing
# Returns : <nothing>
################################################################################
AddSystemGroup()
{
Report "system_group[]=$1"
}
# Check file permissions
# Parameter 1 is file/dir
# Result: FILE_NOT_FOUND | OK | BAD
CheckFilePermissions()
{
CHECKFILE=$1
if [ ! -d $CHECKFILE -a ! -f $CHECKFILE ]; then
PERMS="FILE_NOT_FOUND"
else
# If 'file' is an directory, use -d
if [ -d ${CHECKFILE} ]; then
FILEVALUE=`ls -d -l ${CHECKFILE} | cut -c 2-10`
PROFILEVALUE=`grep '^permdir' ${PROFILE} | grep ":${CHECKFILE}:" | cut -d: -f3`
else
FILEVALUE=`ls -l ${CHECKFILE} | cut -c 2-10`
PROFILEVALUE=`grep '^permfile' ${PROFILE} | grep ":${CHECKFILE}:" | cut -d: -f3`
fi
if [ "${FILEVALUE}" = "${PROFILEVALUE}" ]; then PERMS="OK"; else PERMS="BAD"; fi
fi
}
################################################################################
# Name : CheckItem()
# Description : Check if a specific item exists in the report
# Returns : <nothing>
################################################################################
CheckItem()
{
ITEM_FOUND=0
if [ $# -eq 2 ]; then
# Don't search in /dev/null, it's too empty there
if [ ! "${REPORTFILE}" = "/dev/null" ]; then
# Check if we can find the main type (with or without brackets)
LogText "Test: search string $2 in earlier discovered results"
FIND=`egrep "^$1(\[\])?=" ${REPORTFILE} | egrep "$2"`
if [ ! "${FIND}" = "" ]; then
ITEM_FOUND=1
LogText "Result: found search string (result: $FIND)"
else
LogText "Result: search string NOT found"
fi
else
LogText "Skipping search, as /dev/null is being used"
fi
else
ReportException ${TEST_NO} "Error in function call to CheckItem"
fi
}
# Check updates
CheckUpdates()
{
PROGRAM_LV="0000000000"; DB_MALWARE_LV="0000000000"; DB_FILEPERMS_LV="0000000000"
LYNIS_LV_RECORD="lynis-latest-version.cisofy.com."
FIND=`which dig 2> /dev/null`
if [ ! "${FIND}" = "" ]; then
PROGRAM_LV=`dig +short +time=3 -t txt lynis-latest-version.cisofy.com 2> /dev/null | grep -v "connection timed out" | sed 's/[".]//g' | grep "^[1-9][0-9][0-9]$"`
else
FIND=`which host 2> /dev/null`
if [ ! "${FIND}" = "" ]; then
PROGRAM_LV=`host -t txt -W 3 lynis-latest-version.cisofy.com 2> /dev/null | grep -v "connection timed out" | awk '{ if ($1=="lynis-latest-version.cisofy.com" && $3=="text") { print $4 }}' | sed 's/"//g' | grep "^[1-9][0-9][0-9]$"`
if [ "${PROGRAM_LV}" = "" ]; then PROGRAM_LV=0; fi
else
FIND=`which drill 2> /dev/null`
if [ ! "${FIND}" = "" ]; then
PROGRAM_LV=`drill txt ${LYNIS_LV_RECORD} | awk '{ if ($1=="lynis-latest-version.cisofy.com." && $4=="TXT") { print $5 }}' | tr -d '"' | grep "^[1-9][0-9][0-9]$"`
if [ "${PROGRAM_LV}" = "" ]; then PROGRAM_LV=0; fi
else
LogText "Result: dig, drill or host not installed, update check skipped"
UPDATE_CHECK_SKIPPED=1
fi
fi
fi
}
# Count the number of performed tests
counttests()
{
CTESTS_PERFORMED=`expr ${CTESTS_PERFORMED} + 1`
}
# Determine if a directory exists
DirectoryExists()
{
DIRECTORY_FOUND=0
LogText "Test: checking if directory $1 exists"
if [ -d $1 ]; then
LogText "Result: directory $1 exists"
DIRECTORY_FOUND=1
else
LogText "Result: directory $1 NOT found"
fi
}
################################################################################
# Name : Debug
# Description : Show additional information on screen
# Returns : Nothing
################################################################################
Debug()
{
if [ ${DEBUG} -eq 1 ]; then echo "DEBUG: $1"; fi
}
################################################################################
# Name : DigitsOnly
# Description : Only extract numbers from a string
# Returns : Digits only string
################################################################################
DigitsOnly()
{
VALUE=$1
LogText "Value is now: ${VALUE}"
if [ ! "${AWKBINARY}" = "" ]; then
VALUE=`echo ${VALUE} | grep -Eo '[0-9]{1,}'`
fi
LogText "Returning value: ${VALUE}"
}
################################################################################
# Name : Display
# Description : Show text on screen, with markup
# Returns : Nothing
################################################################################
Display()
{
INDENT=0; TEXT=""; RESULT=""; COLOR=""; SPACES=0
while [ $# -ge 1 ]; do
case $1 in
--color)
shift
case $1 in
GREEN) COLOR=$GREEN ;;
RED) COLOR=$RED ;;
WHITE) COLOR=$WHITE ;;
YELLOW) COLOR=$YELLOW ;;
esac
;;
--indent)
shift
INDENT=$1
;;
--result)
shift
RESULT=$1
;;
--text)
shift
TEXT=$1
;;
*)
echo "INVALID OPTION (Display): $1"
exit 1
;;
esac
# Go to next parameter
shift
done
if [ "${RESULT}" = "" ]; then
RESULTPART=""
else
if [ ${CRONJOB} -eq 0 ]; then
RESULTPART=" [ ${COLOR}${RESULT}${NORMAL} ]"
else
RESULTPART=" [ ${RESULT} ]"
fi
fi
if [ ! "${TEXT}" = "" ]; then
# Show warnings always, and other messages if no quiet is being used
if [ ${QUIET} -eq 0 -o "${RESULT}" = "WARNING" ]; then
# Display (counting with -m instead of -c, to support language locale)
LINESIZE=`echo "${TEXT}" | wc -m | tr -d ' '`
if [ ${INDENT} -gt 0 ]; then SPACES=`expr 62 - ${INDENT} - ${LINESIZE}`; fi
if [ ${CRONJOB} -eq 0 ]; then
# Check if we already have already discovered a proper echo command tool. It not, set it default to 'echo'.
if [ "${ECHOCMD}" = "" ]; then ECHOCMD="echo"; fi
${ECHOCMD} "\033[${INDENT}C${TEXT}\033[${SPACES}C${RESULTPART}"
else
echo "${TEXT}${RESULTPART}"
fi
fi
fi
}
################################################################################
# Name : DisplayManual
# Description : Show text on screen, without any markup
# Returns : Nothing
################################################################################
DisplayManual()
{
if [ ${QUIET} -eq 0 ]; then
${ECHOCMD} "$1"
fi
}
# Clean exit (removing temp files, PID files)
ExitClean()
{
RemovePIDFile
exit 0
}
# Clean exit with custom code
ExitCustom()
{
RemovePIDFile
# Exit with the exit code given, otherwise use 1
if [ $# -eq 1 ]; then
exit $1
else
exit 1
fi
}
# Clean exit (removing temp files, PID files), with error code 1
ExitFatal()
{
RemovePIDFile
exit 1
}
# Determine if a file exists
FileExists()
{
FILE_FOUND=0
LogText "Test: checking if file $1 exists"
if [ -f $1 ]; then
LogText "Result: file $1 exists"
FILE_FOUND=1
else
LogText "Result: file $1 NOT found"
fi
}
################################################################################
# Name : FileIsEmpty
# Description : Check if a file is empty
# Returns : EMPTY (0 or 1)
################################################################################
FileIsEmpty()
{
EMPTY=0
LogText "Test: checking if file $1 is empty"
if [ -z $1 ]; then
LogText "Result: file $1 is empty"
EMPTY=1
else
LogText "Result: file $1 is NOT empty"
fi
}
################################################################################
# Name : FileIsReadable
# Description : Check if a file readable or directory is accessible
# Returns : Return code (0 = readable, 1 = not readable)
# Usage : if FileIsReadable /etc/shadow; then echo "File is readable"; fi
################################################################################
FileIsReadable()
{
sFILE=$1
CANREAD=0
LogText "Test: testing if we can access ${sFILE}"
# Check for symlink
if [ -L ${sFILE} ]; then
ShowSymlinkPath ${sFILE}
if [ ! "${SYMLINK}" = "" ]; then sFILE="${SYMLINK}"; fi
fi
# Only check the file if it isn't a symlink (after previous check)
if [ -L ${sFILE} ]; then
OTHERPERMS="-"
LogText "Result: unclear if we can read this file, as this is a symlink"
ReportException "FileIsReadable" "Can not determine symlink ${sFILE}"
elif [ -d ${sFILE} ]; then
OTHERPERMS=`ls -d -l ${sFILE} | cut -c 8`
elif [ -f ${sFILE} ]; then
OTHERPERMS=`ls -d -l ${sFILE} | cut -c 8`
else
OTHERPERMS="-"
fi
# Also check if we are the actual owner of the file
FILEOWNER=`ls -ln ${sFILE} | awk -F" " '{ print $3 }'`
if [ "${FILEOWNER}" = "${MYID}" ]; then
LogText "Result: file is owned by our current user ID (${MYID}), checking if it is readable"
if [ -d ${sFILE} ]; then
OTHERPERMS=`ls -d -l ${sFILE} | cut -c 2`
elif [ -f ${sFILE} ]; then
OTHERPERMS=`ls -d -l ${sFILE} | cut -c 2`
fi
else
LogText "Result: file is not owned by current user ID (${MYID}), but UID ${FILEOWNER}"
fi
# Check if we have the read bit
if [ "${OTHERPERMS}" = "r" ]; then
CANREAD=1
return 0
LogText "Result: file ${sFILE} is readable (or directory accessible)."
else
LogText "Result: file ${sFILE} is NOT readable (or directory accessible), symlink, or does not exist. (OTHERPERMS: ${OTHERPERMS})"
return 1
fi
}
# Get Host ID
GetHostID()
{
HOSTID="-"
FIND=""
# Avoid some hashes (empty, only zeros)
BLACKLISTED_HASHES="6ef1338f520d075957424741d7ed35ab5966ae97 adc83b19e793491b1c6ea0fd8b46cd9f32e592fc"
if [ ! "${SHA1SUMBINARY}" = "" -o ! "${OPENSSLBINARY}" = "" -o ! "${CSUMBINARY}" = "" ]; then
case "${OS}" in
"AIX")
# Common interfaces: en0 en1 en2, ent0 ent1 ent2
FIND=`entstat en0 2>/dev/null | grep "Hardware Address" | awk -F ": " '{ print $2 }'`
if [ "${FIND}" = "" ]; then
FIND=`entstat ent0 2>/dev/null | grep "Hardware Address" | awk -F ": " '{ print $2 }'`
fi
if [ ! "${FIND}" = "" ]; then
# We have a MAC address, now hashing it
if [ ! "${SHA1SUMBINARY}" = "" ]; then
HOSTID=`echo ${FIND} | ${SHA1SUMBINARY} | awk '{ print $1 }'`
elif [ ! "${CSUMBINARY}" = "" ]; then
HOSTID=`echo ${FIND} | ${CSUMBINARY} -h SHA1 - | awk '{ print $1 }'`
elif [ ! "${OPENSSLBINARY}" = "" ]; then
HOSTID=`echo ${FIND} | ${OPENSSLBINARY} sha -sha1 | awk '{ print $2 }'`
else
ReportException "GetHostID" "No sha1, sha1sum, csum or openssl binary available on AIX"
fi
else
ReportException "GetHostID" "No output from entstat on interfaces: en0, ent0"
fi
;;
"DragonFly" | "FreeBSD")
FIND=`${IFCONFIGBINARY} | grep ether | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]'`
if [ ! "${FIND}" = "" ]; then
HOSTID=`echo ${FIND} | sha1`
else
ReportException "GetHostID" "No MAC address returned on DragonFly or FreeBSD"
fi
;;
"Linux")
# Define preferred interfaces
#PREFERRED_INTERFACES="eth0 eth1 eth2 enp0s25"
# Only use ifconfig if no ip binary has been found
if [ ! "${IFCONFIGBINARY}" = "" ]; then
# Determine if we have ETH0 at all (not all Linux distro have this, e.g. Arch)
HASETH0=`${IFCONFIGBINARY} | grep "^eth0"`
# Check if we can find it with HWaddr on the line
FIND=`${IFCONFIGBINARY} 2> /dev/null | grep "^eth0" | grep -v "eth0:" | grep HWaddr | awk '{ print $5 }' | tr '[:upper:]' '[:lower:]'`
# If nothing found, then try first for alternative interface. Else other versions of ifconfig (e.g. Slackware/Arch)
if [ "${FIND}" = "" ]; then
FIND=`${IFCONFIGBINARY} 2> /dev/null | grep HWaddr`
if [ "${FIND}" = "" ]; then
# If possible directly address eth0 to avoid risking gathering the incorrect MAC address.
# If not, then falling back to getting first interface. Better than nothing.
if [ ! "${HASETH0}" = "" ]; then
FIND=`${IFCONFIGBINARY} eth0 2> /dev/null | grep "ether " | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]'`
else
FIND=`${IFCONFIGBINARY} 2> /dev/null | grep "ether " | awk '{ print $2 }' | head -1 | tr '[:upper:]' '[:lower:]'`
if [ "${FIND}" = "" ]; then
ReportException "GetHostID" "No eth0 found (and no ether was found with ifconfig)"
else
LogText "Result: No eth0 found (ether found), using first network interface to determine hostid (with ifconfig)"
fi
fi
else
FIND=`${IFCONFIGBINARY} 2> /dev/null | grep HWaddr | head -1 | awk '{ print $5 }' | tr '[:upper:]' '[:lower:]'`
ReportException "GetHostID" "No eth0 found (but HWaddr was found), using first network interface to determine hostid, with ifconfig"
fi
fi
else
# See if we can use ip binary instead
if [ ! "${IPBINARY}" = "" ]; then
# Determine if we have the common available eth0 interface
FIND=`${IPBINARY} addr show eth0 2> /dev/null | egrep "link/ether " | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]'`
if [ "${FIND}" = "" ]; then
# Determine the MAC address of first interface with the ip command
FIND=`${IPBINARY} addr show 2> /dev/null | egrep "link/ether " | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]'`
if [ "${FIND}" = "" ]; then
ReportException "GetHostID" "Can't create hostid (no MAC addresses found)"
fi
fi
else
ReportException "GetHostID" "Can't create hostid, missing both ifconfig and ip binary"
fi
fi
# Check if we found a HostID
if [ ! "${FIND}" = "" ]; then
HOSTID=`echo ${FIND} | ${SHA1SUMBINARY} | awk '{ print $1 }'`
LogText "Result: Found HostID: ${HOSTID}"
else
ReportException "GetHostID" "Can't create HOSTID, command ip not found"
fi
;;
"MacOS")
FIND=`${IFCONFIGBINARY} en0 | grep ether | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]'`
if [ ! "${FIND}" = "" ]; then
HOSTID=`echo ${FIND} | shasum | awk '{ print $1 }'`
else
ReportException "GetHostID" "No MAC address returned on Mac OS"
fi
;;
"NetBSD")
FIND=`${IFCONFIGBINARY} -a | grep "address:" | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]'`
if [ ! "${FIND}" = "" ]; then
HOSTID=`echo ${FIND} | sha1`
else
ReportException "GetHostID" "No MAC address returned on NetBSD"
fi
;;
"OpenBSD")
FIND=`${IFCONFIGBINARY} | grep "lladdr " | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]'`
if [ ! "${FIND}" = "" ]; then
HOSTID=`echo ${FIND} | sha1`
else
ReportException "GetHostID" "No MAC address returned on OpenBSD"
fi
;;
"Solaris")
INTERFACES_TO_TEST="e1000g1 net0"
FOUND=0
for I in ${INTERFACES_TO_TEST}; do
FIND=`${IFCONFIGBINARY} -a | grep "^${I}"`
if [ ! "${FIND}" = "" ]; then
FOUND=1; LogText "Found interface ${I} on Solaris"
fi
done
if [ ${FOUND} -eq 1 ]; then
FIND=`${IFCONFIGBINARY} ${I} | grep ether | awk '{ if ($1=="ether") { print $2 }}'`
if [ ! "${SHA1SUMBINARY}" = "" ]; then
HOSTID=`echo ${FIND} | ${SHA1SUMBINARY} | awk '{ print $1 }'`
else
if [ ! "${OPENSSLBINARY}" = "" ]; then
HOSTID=`echo ${FIND} | ${OPENSSLBINARY} sha -sha1 | awk '{ print $2 }'`
else
ReportException "GetHostID" "Can not find sha1/sha1sum or openssl"
fi
fi
else
ReportException "GetHostID" "No interface found op Solaris to create HostID"
fi
;;
*)
ReportException "GetHostID" "Can't create HOSTID as OS is not supported yet by this function"
;;
esac
# Remove HOSTID if it contains a default MAC address with a related hash value
if [ ! "${HOSTID}" = "" ]; then
for CHECKHASH in ${BLACKLISTED_HASHES}; do
if [ "${CHECKHASH}" = "${HOSTID}" ]; then
LogText "Result: hostid is a blacklisted value"
HOSTID=""
fi
done
fi
else
ReportException "GetHostID" "Can't create HOSTID as there is no SHA1 hash tool available (sha1, sha1sum, openssl)"
fi
# Search machine ID
# This applies to IDs generated for systemd
# Optional: DBUS creates ID as well with dbus-uuidgen and is stored in /var/lib/dbus-machine-id (might be symlinked to /etc/machine-id)
sMACHINEIDFILE="/etc/machine-id"
if [ -f ${sMACHINEIDFILE} ]; then
FIND=`head -1 ${sMACHINEIDFILE} | grep "^[a-f0-9]"`
if [ "${FIND}" = "" ]; then
MACHINEID="${FIND}"
fi
fi
if [ "${HOSTID}" = "" ]; then
LogText "Result: no HOSTID available, trying to use SSH key as unique source"
# Create host ID when a MAC address was not found
SSH_KEY_FILES="ssh_host_ed25519_key ssh_host_ed25519_key.pub ssh_host_ecdsa_key ssh_host_ecdsa_key.pub ssh_host_dsa_key ssh_host_dsa_key.pub ssh_host_rsa_key ssh_host_rsa_key.pub"
if [ -d /etc/ssh ]; then
for I in ${SSH_KEY_FILES}; do
if [ "${HOSTID}" = "" ]; then
if [ -f /etc/ssh/${I} ]; then
LogText "Result: found ${I} in /etc/ssh"
if [ ! "${SHA1SUMBINARY}" = "" ]; then
HOSTID=`cat /etc/ssh/${I} | ${SHA1SUMBINARY} | awk '{ print $1 }'`
LogText "result: Created HostID with SSH key ($I): ${HOSTID}"
else
ReportException "GetHostID" "Can't create HOSTID with SSH key, as sha1sum binary is missing"
fi
fi
fi
done
else
LogText "Result: no /etc/ssh directory found, skipping"
fi
fi
# Show an exception if no HostID could be created, to ensure each system (and scan) has one
if [ "${HOSTID}" = "" ]; then
ReportException "GetHostID" "No unique host identifier could be created."
fi
}
# Insert section block
InsertSection()
{
if [ ${QUIET} -eq 0 ]; then
echo ""
echo "[+] ${SECTION}$1${NORMAL}"
echo "------------------------------------"
fi
logtextbreak
LogText "Action: Performing tests from category: $1"
}
# Insert section block for plugins
InsertPluginSection()
{
if [ ${QUIET} -eq 0 ]; then
echo ""
echo "[+] ${MAGENTA}$1${NORMAL}"
echo "------------------------------------"
fi
LogText "Action: Performing plugin tests"
}
# Is a process running?
# Returns: RUNNING
IsRunning()
{
RUNNING=0
PSOPTIONS=""
if [ ${SHELL_IS_BUSYBOX} -eq 0 ]; then PSOPTIONS=" ax"; fi
FIND=`${PSBINARY} ${PSOPTIONS} | egrep "( |/)$1" | grep -v "grep"`
if [ ! "${FIND}" = "" ]; then
RUNNING=1
LogText "IsRunning: process '$1' found (${FIND})"
else
LogText "IsRunning: process '$1' not found"
fi
}
################################################################################
# Name : IsVirtualMachine()
# Description : Check if a specific item exists in the report
# Returns : ISVIRTUALMACHINE (0-2)
# VMTYPE
# VMFULLTYPE
################################################################################
IsVirtualMachine()
{
LogText "Test: Determine if this system is a virtual machine"
# 0 = no, 1 = yes, 2 = unknown
ISVIRTUALMACHINE=2; VMTYPE="unknown"; VMFULLTYPE="Unknown"
SHORT=""
# facter
if [ "${SHORT}" = "" ]; then
if [ -x /usr/bin/facter ]; then
case "`facter is_virtual`" in
"true")
SHORT=`facter virtual`
LogText "Result: found ${SHORT}"
;;
"false")
LogText "Result: facter says this machine is not a virtual"
;;
esac
else
LogText "Result: facter utility not found"
fi
else
LogText "Result: skipped facter test, as we already found machine type"
fi
# systemd
if [ "${SHORT}" = "" ]; then
if [ -x /usr/bin/systemd-detect-virt ]; then
LogText "Test: trying to guess virtualization technology with systemd-detect-virt"
FIND=`/usr/bin/systemd-detect-virt`
if [ ! "${FIND}" = "" ]; then
LogText "Result: found ${FIND}"
SHORT="${FIND}"
fi
else
LogText "Result: systemd-detect-virt not found"
fi
else
LogText "Result: skipped systemd test, as we already found machine type"
fi
# lscpu
# Values: VMware
if [ "${SHORT}" = "" ]; then
if [ -x /usr/bin/lscpu ]; then
LogText "Test: trying to guess virtualization with lscpu"
FIND=`lscpu | grep "^Hypervisor Vendor" | awk -F: '{ print $2 }' | sed 's/ //g'`
if [ ! "${FIND}" = "" ]; then
LogText "Result: found ${FIND}"
SHORT="${FIND}"
else
LogText "Result: can't find hypervisor vendor with lscpu"
fi
else
LogText "Result: lscpu not found"
fi
else
LogText "Result: skipped lscpu test, as we already found machine type"
fi
# dmidecode
# Values: VMware Virtual Platform / VirtualBox
if [ "${SHORT}" = "" ]; then
if [ -x /usr/sbin/dmidecode ]; then
LogText "Test: trying to guess virtualization with dmidecode"
FIND=`dmidecode -s system-product-name | awk '{ print $1 }'`
if [ ! "${FIND}" = "" ]; then
LogText "Result: found ${FIND}"
SHORT="${FIND}"
else
LogText "Result: can't find product name with dmidecode"
fi
else
LogText "Result: dmidecode not found"
fi
else
LogText "Result: skipped dmidecode test, as we already found machine type"
fi
# lshw
if [ "${SHORT}" = "" ]; then
if [ -x /usr/bin/lshw ]; then
LogText "Test: trying to guess virtualization with lshw"
FIND=`lshw -quiet -class system | awk '{ if ($1=="product:") { print $2 }}'`
if [ ! "${FIND}" = "" ]; then
LogText "Result: found ${FIND}"
SHORT="${FIND}"
fi
else
LogText "Result: lshw not found"
fi
else
LogText "Result: skipped lshw test, as we already found machine type"
fi
# Other options
# SaltStack: salt-call grains.get virtual
# < needs snippet >
# Try common guest processes
if [ "${SHORT}" = "" ]; then
LogText "Test: trying to guess virtual machine type by running processes"
# VMware
IsRunning vmware-guestd
if [ ${RUNNING} -eq 1 ]; then SHORT="vmware"; fi
IsRunning vmtoolsd
if [ ${RUNNING} -eq 1 ]; then SHORT="vmware"; fi
# VirtualBox based on guest services
IsRunning vboxguest-service
if [ ${RUNNING} -eq 1 ]; then SHORT="virtualbox"; fi
IsRunning VBoxClient
if [ ${RUNNING} -eq 1 ]; then SHORT="virtualbox"; fi
else
LogText "Result: skipped processes test, as we already found platform"
fi
# Amazon EC2
if [ "${SHORT}" = "" ]; then
LogText "Test: checking specific files for Amazon"
if [ -f /etc/ec2_version -a ! -z /etc/ec2_version ]; then
SHORT="amazon-ec2"
else
LogText "Result: system not hosted on Amazon"
fi
else
LogText "Result: skipped Amazon EC2 test, as we already found platform"
fi
# sysctl values
if [ "${SHORT}" = "" ]; then
LogText "Test: trying to guess virtual machine type by sysctl keys"
# FreeBSD: hw.hv_vendor (remains empty for VirtualBox)
# NetBSD: machdep.dmi.system-product
# OpenBSD: hw.product
FIND=`sysctl -a 2> /dev/null | egrep "(hw.product|machdep.dmi.system-product)" | head -1 | sed 's/ = /=/' | awk -F= '{ print $2 }'`
if [ ! "${FIND}" = "" ]; then
SHORT="${FIND}"
fi
else
LogText "Result: skipped sysctl test, as we already found platform"
fi
# Check if we catched some string along all tests
if [ ! "${SHORT}" = "" ]; then
# Lowercase and see if we found a match
SHORT=`echo ${SHORT} | awk '{ print $1 }' | tr [[:upper:]] [[:lower:]]`
case ${SHORT} in
amazon-ec2) ISVIRTUALMACHINE=1; VMTYPE="amazon-ec2"; VMFULLTYPE="Amazon AWS EC2 Instance" ;;
bochs) ISVIRTUALMACHINE=1; VMTYPE="bochs"; VMFULLTYPE="Bochs CPU emulation" ;;
docker) ISVIRTUALMACHINE=1; VMTYPE="docker"; VMFULLTYPE="Docker container" ;;
kvm) ISVIRTUALMACHINE=1; VMTYPE="kvm"; VMFULLTYPE="KVM" ;;
lxc) ISVIRTUALMACHINE=1; VMTYPE="lxc"; VMFULLTYPE="Linux Containers" ;;
lxc-libvirt) ISVIRTUALMACHINE=1; VMTYPE="lxc-libvirt"; VMFULLTYPE="libvirt LXC driver (Linux Containers)" ;;
microsoft) ISVIRTUALMACHINE=1; VMTYPE="microsoft"; VMFULLTYPE="Microsoft Virtual PC" ;;
openvz) ISVIRTUALMACHINE=1; VMTYPE="openvz"; VMFULLTYPE="OpenVZ" ;;
oracle|virtualbox) ISVIRTUALMACHINE=1; VMTYPE="virtualbox"; VMFULLTYPE="Oracle VM VirtualBox" ;;
qemu) ISVIRTUALMACHINE=1; VMTYPE="qemu"; VMFULLTYPE="QEMU" ;;
systemd-nspawn) ISVIRTUALMACHINE=1; VMTYPE="systemd-nspawn"; VMFULLTYPE="Systemd Namespace container" ;;
uml) ISVIRTUALMACHINE=1; VMTYPE="uml"; VMFULLTYPE="User-Mode Linux (UML)" ;;
vmware) ISVIRTUALMACHINE=1; VMTYPE="vmware"; VMFULLTYPE="VMware product" ;;
xen) ISVIRTUALMACHINE=1; VMTYPE="xen"; VMFULLTYPE="XEN" ;;
zvm) ISVIRTUALMACHINE=1; VMTYPE="zvm"; VMFULLTYPE="IBM z/VM" ;;
*) LogText "Result: Unknown virtualization type, so most likely system is physical" ;;
esac
fi
# Check final status
if [ ${ISVIRTUALMACHINE} -eq 1 ]; then
LogText "Result: found virtual machine (type: ${VMTYPE}, ${VMFULLTYPE})"
Report "vm=1"
Report "vmtype=${VMTYPE}"
elif [ ${ISVIRTUALMACHINE} -eq 2 ]; then
LogText "Result: unknown if this system is a virtual machine"
Report "vm=2"
else
LogText "Result: system seems to be non-virtual"
fi
}
# Function IsWorldReadable
IsWorldReadable()
{
sFILE=$1
# Check for symlink
if [ -L ${sFILE} ]; then
ShowSymlinkPath ${sFILE}
if [ ! "${SYMLINK}" = "" ]; then
sFILE="${SYMLINK}"
fi
fi
# Only check the file if it isn't a symlink (after previous check)
if [ -f ${sFILE} -a ! -L ${sFILE} ]; then
FINDVAL=`ls -l ${sFILE} | cut -c 8`
if [ "${FINDVAL}" = "r" ]; then return 0; else return 1; fi
else
return 255
fi
}
# Function IsWorldExecutable
IsWorldExecutable()
{
sFILE=$1
# Check for symlink
if [ -L ${sFILE} ]; then
ShowSymlinkPath ${sFILE}
if [ ! "${SYMLINK}" = "" ]; then
sFILE="${SYMLINK}"
fi
fi
# Only check the file if it isn't a symlink (after previous check)
if [ -f ${sFILE} -a ! -L ${sFILE} ]; then
FINDVAL=`ls -l ${sFILE} | cut -c 10`
if [ "${FINDVAL}" = "x" ]; then return 0; else return 1; fi
else
return 255
fi
}
################################################################################
# Name : IsWorldWritable()
# Description : Determines if a file is writable for all users
# Returns : exit code (0 = writable, 1 = not writable, 255 = error)
# Usage : if IsWorldWritable /etc/motd; then echo "File is writable"; fi
################################################################################
IsWorldWritable()
{
sFILE=$1
FileIsWorldWritable=""
# Only check the file if it isn't a symlink (after previous check)
if [ -f ${sFILE} -a ! -L ${sFILE} ]; then
FINDVAL=`ls -l ${sFILE} | cut -c 9`
if [ "${FINDVAL}" = "w" ]; then return 0; else return 1; fi
else
return 255
fi
}
################################################################################
# Name : LogText()
# Description : Function logtext (redirect data ($1) to log file)
# Returns : Nothing
# Usage : LogText "This goes into the log file"
################################################################################
LogText()
{
if [ ! "${LOGFILE}" = "" ]; then
CDATE=`date "+[%H:%M:%S]"`
echo "${CDATE} $1" >> ${LOGFILE}
fi
}
# Alias for older tests (do no longer use this as it will be deprecated)
logtext()
{
LogText "$1"
}
################################################################################
# Name : logtextbreak()
# Description : Add a separator to log file between sections, tests etc
# Returns : <nothing>
logtextbreak()
{
if [ ! "${LOGFILE}" = "" ]; then
CDATE=`date "+[%H:%M:%S]"`
echo "${CDATE} ===---------------------------------------------------------------===" >> ${LOGFILE}
fi
}
################################################################################
# Name : Maid()
# Description : Cleanup service
# Returns : <nothing>
Maid()
{
echo ""; echo "Interrupt detected."
# Remove PID
RemovePIDFile
# Clean up temp files
if [ ! "${TMPFILE}" = "" ]; then if [ -f ${TMPFILE} ]; then rm -f ${TMPFILE}; fi; fi
if [ ! "${TMPFILE2}" = "" ]; then if [ -f ${TMPFILE2} ]; then rm -f ${TMPFILE2}; fi; fi
Display --text "Cleaning up..." --result DONE --color GREEN
# Exit with exit code 1
exit 1
}
# Parse nginx configuration lines
ParseNginx()
{
FIND=`awk -F= '/^nginx_config_option=/ { print $2 }' ${REPORTFILE} | sed 's/ /:space:/g'`
for I in ${FIND}; do
I=`echo ${I} | sed 's/:space:/ /g' | sed 's/;$//'`
OPTION=`echo ${I} | awk '{ print $1 }'`
VALUE=`echo ${I}| cut -d' ' -f2-`
LogText "Result: found option ${OPTION} with parameters ${VALUE}"
case ${OPTION} in
access_log)
if [ "${VALUE}" = "off" ]; then
LogText "Result: found logging disabled for one virtual host"
NGINX_ACCESS_LOG_DISABLED=1
else
if [ ! "${VALUE}" = "" ]; then
# If multiple values follow, select first one
VALUE=`echo ${VALUE} | awk '{ print $1 }'`
if [ ! -f ${VALUE} ]; then
LogText "Result: could not find referenced log file ${VALUE} in nginx configuration"
NGINX_ACCESS_LOG_MISSING=1
fi
fi
fi
;;
# Headers
add_header)
HEADER=`echo ${VALUE} | awk '{ print $1 }'`
HEADER_VALUE=`echo ${VALUE} | cut -d' ' -f2-`
LogText "Result: found header ${HEADER} with value ${HEADER_VALUE}"
#Report "nginx_header[]=${HEADER}|${HEADER_VALUE}|"
;;
alias)
NGINX_ALIAS_FOUND=1
;;
allow)
NGINX_ALLOW_FOUND=1
;;
autoindex)
;;
deny)
NGINX_DENY_FOUND=1
;;
expires)
NGINX_EXPIRES_FOUND=1
;;
error_log)
# Check if debug is appended
FIND=`echo ${VALUE} | awk '{ if ($2=="debug") { print 1 } else { print 0 }}'`
if [ ${FIND} -eq 1 ]; then
NGINX_ERROR_LOG_DEBUG=1
fi
# Check if log file exists
FILE=`echo ${VALUE} | awk '{ print $1 }'`
if [ ! "${FILE}" = "" ]; then
if [ ! -f ${FILE} ]; then
NGINX_ERROR_LOG_MISSING=1
fi
else
LogText "Warning: did not find a filename after error_log in nginx configuration"
fi
;;
error_page)
;;
fastcgi_intercept_errors)
;;
fastcgi_param)
NGINX_FASTCGI_FOUND=1
NGINX_FASTCGI_PARAMS_FOUND=1
;;
fastcgi_pass)
NGINX_FASTCGI_FOUND=1
NGINX_FASTCGI_PASS_FOUND=1
;;
fastcgi_pass_header)
;;
index)
;;
keepalive_timeout)
;;
listen)
NGINX_LISTEN_FOUND=1
# Test for ssl on listen statement
FIND_SSL=`echo ${VALUE} | grep ssl`
if [ ! "${FIND_SSL}" = "" ]; then NGINX_SSL_ON=1; fi
;;
location)
NGINX_LOCATION_FOUND=1
;;
return)
NGINX_RETURN_FOUND=1
;;
root)
NGINX_ROOT_FOUND=1
;;
server_name)
;;
ssl)
if [ "${VALUE}" = "on" ]; then NGINX_SSL_ON=1; fi
;;
ssl_certificate)
LogText "Found SSL certificate in nginx configuration"
;;
ssl_certificate_key)
;;
ssl_ciphers)
NGINX_SSL_CIPHERS=1
;;
ssl_prefer_server_ciphers)
if [ "${VALUE}" = "on" ]; then NGINX_SSL_PREFER_SERVER_CIPHERS=1; fi
;;
ssl_protocols)
NGINX_SSL_PROTOCOLS=1
#Report "nginx_ssl_protocols=${VALUE}"
;;
ssl_session_cache)
;;
ssl_session_timeout)
;;
types)
;;
*)
LogText "Found unknown option ${OPTION} in nginx configuration"
;;
esac
done
}
################################################################################
# Name : Progress()
# Description : Displays progress on screen with dots
# Input : finish or text
# Returns : nothing
# Tip : Use this function from Register with the --progress parameter
################################################################################
Progress()
{
if [ ${CRONJOB} -eq 0 ]; then
if [ "$1" = "--finish" ]; then
${ECHOCMD} ""
else
# If the No-Break version of echo is known, use that (usually breaks in combination with -e)
if [ ! "${ECHONB}" = "" ]; then
${ECHONB} "$1"
else
${ECHOCMD} -en "$1"
fi
fi
fi
}
################################################################################
# Name : Progress()
# Description : Displays progress on screen with dots
# Input : Amount of characters (optional)
# Returns : RANDOMSTRING
# Usage : RandomString 32
################################################################################
RandomString() {
# Check a (pseudo) random character device
if [ -c /dev/urandom ]; then local FILE="/dev/urandom"
elif [ -c /dev/random ]; then local FILE="/dev/random"
else
Display "Can not use RandomString function, as there is no random device to be used"
fi
if [ $# -eq 0 ]; then local SIZE=16; else local SIZE=$1; fi
local CSIZE=`expr ${SIZE} / 2`
RANDOMSTRING=`head -c ${CSIZE} /dev/urandom | od -An -x | tr -d ' ' | cut -c 1-${SIZE}`
}
################################################################################
# Name : RealFilename()
# Description : Return file behind a symlink
# Returns : sFILE
# Notes : This function is unused, use ShowSymlinkPath instead
#RealFilename()
# {
# sFILE=$1
# FileIsWorldExecutable=""
# SYMLINK=0
#
# # Check for symlink
# if [ -L ${sFILE} ]; then
# if [ ! "${READLINKBINARY}" = "" ]; then
# tFILE=`${READLINKBINARY} -f ${sFILE}`
# # Check if we can find the file now
# if [ -f ${tFILE} ]; then
# rFILE="${tFILE}"
# LogText "Result: symlink found, pointing to ${sFILE}"
# SYMLINK=1
# else
# # Check the full path of the symlink, strip the filename, copy the path and linked filename together
# tDIR=`echo ${sFILE} | awk '{match($1, "^.*/"); print substr($1, 1, RLENGTH-1)}'`
# tFILE="${tDIR}/${tFILE}"
# if [ -f ${tFILE} ]; then
# rFILE="${tFILE}"
# LogText "Result: symlink found, seems to be ${rFILE}"
# fi
# fi
# fi
# else
# # No symlink
# rFILE="${sFILE}"
# fi
# }
################################################################################
# Name : Register()
# Description : Register a test and see if it has to be run
# Returns : SKIPTEST (0 or 1)
Register()
{
# Do not insert a log break, if previous test was not logged
if [ ${SKIPLOGTEST} -eq 0 ]; then logtextbreak; fi
ROOT_ONLY=0; SKIPTEST=0; SKIPLOGTEST=0; TEST_NEED_OS=""; PREQS_MET=""
TEST_NEED_NETWORK=""; TEST_NEED_PLATFORM=""
TOTAL_TESTS=`expr ${TOTAL_TESTS} + 1`
while [ $# -ge 1 ]; do
case $1 in
--description)
shift
TEST_DESCRIPTION=$1
;;
--platform)
shift
TEST_NEED_PLATFORM=$1
;;
--network)
shift
TEST_NEED_NETWORK=$1
;;
--os)
shift
TEST_NEED_OS=$1
;;
--preqs-met)
shift
PREQS_MET=$1
;;
--progress)
Progress "."
;;
--root-only)
shift
if [ "$1" = "YES" -o "$1" = "yes" ]; then
ROOT_ONLY=1
elif [ "$1" = "NO" -o "$1" = "no" ]; then
ROOT_ONLY=0
else
Debug "Invalid option for --root-only parameter of Register function"
fi
;;
--test-no)
shift
TEST_NO=$1
;;
--weight)
shift
TEST_WEIGHT=$1
;;
*)
echo "INVALID OPTION (Register): $1"
exit 1
;;
esac
# Go to next parameter
shift
done
# Skip if a test is root only and we are running a non-privileged test
if [ ${ROOT_ONLY} -eq 1 -a ! ${MYID} = "0" ]; then
SKIPTEST=1; SKIPREASON="This test needs root permissions"
SKIPPED_TESTS_ROOTONLY="${SKIPPED_TESTS_ROOTONLY}====${TEST_NO}:space:-:space:${TEST_DESCRIPTION}"
#SkipTest "${TEST_NO}:Test:space:requires:space:root:space:permissions:-:-:"
fi
# Skip test if it's configured in profile
if [ ${SKIPTEST} -eq 0 ]; then
FIND=`echo "${TEST_SKIP_ALWAYS}" | grep "${TEST_NO}"`
if [ ! "${FIND}" = "" ]; then SKIPTEST=1; SKIPREASON="Skipped by configuration"; fi
fi
# Skip if test is not in the list
if [ ${SKIPTEST} -eq 0 -a ! "${TESTS_TO_PERFORM}" = "" ]; then
FIND=`echo "${TESTS_TO_PERFORM}" | grep "${TEST_NO}"`
if [ "${FIND}" = "" ]; then SKIPTEST=1; SKIPREASON="Test not in list of tests to perform"; fi
fi
# Do not run scans which have a higher intensity than what we prefer
if [ ${SKIPTEST} -eq 0 -a "${TEST_WEIGHT}" = "H" -a "${SCAN_TEST_HEAVY}" = "NO" ]; then SKIPTEST=1; SKIPREASON="Test to system intensive for scan mode (H)"; fi
if [ ${SKIPTEST} -eq 0 -a "${TEST_WEIGHT}" = "M" -a "${SCAN_TEST_MEDIUM}" = "NO" ]; then SKIPTEST=1; SKIPREASON="Test to system intensive for scan mode (M)"; fi
# Skip test if OS is different than requested
if [ ${SKIPTEST} -eq 0 -a ! -z "${TEST_NEED_OS}" -a ! "${OS}" = "${TEST_NEED_OS}" ]; then
SKIPTEST=1; SKIPREASON="Incorrect guest OS (${TEST_NEED_OS} only)"
if [ ${LOG_INCORRECT_OS} -eq 0 ]; then
SKIPLOGTEST=1
fi
fi
# Check for correct hardware platform
if [ ${SKIPTEST} -eq 0 -a ! -z "${TEST_NEED_PLATFORM}" -a ! "${HARDWARE}" = "${TEST_NEED_PLATFORM}" ]; then SKIPTEST=1; SKIPREASON="Incorrect hardware platform"; fi
# Not all prerequisites met, like missing tool
if [ ${SKIPTEST} -eq 0 -a "${PREQS_MET}" = "NO" ]; then SKIPTEST=1; SKIPREASON="Prerequisities not met (ie missing tool, other type of Linux distribution)"; fi
# Skip test?
if [ ${SKIPTEST} -eq 0 ]; then
# First wait X seconds (depending pause_between_tests)
if [ ${TEST_PAUSE_TIME} -gt 0 ]; then sleep ${TEST_PAUSE_TIME}; fi
# Increase counter for every registered test which is performed
counttests
if [ ${SKIPLOGTEST} -eq 0 ]; then LogText "Performing test ID ${TEST_NO} ($TEST_DESCRIPTION)"; fi
TESTS_EXECUTED="${TEST_NO}|${TESTS_EXECUTED}"
else
if [ ${SKIPLOGTEST} -eq 0 ]; then LogText "Skipped test ${TEST_NO} ($TEST_DESCRIPTION)"; fi
if [ ${SKIPLOGTEST} -eq 0 ]; then LogText "Reason to skip: ${SKIPREASON}"; fi
TESTS_SKIPPED="${TEST_NO}|${TESTS_SKIPPED}"
fi
}
# Remove PID file
RemovePIDFile()
{
# Test if PIDFILE is defined, before checking file presence
if [ ! "${PIDFILE}" = "" ]; then
if [ -f ${PIDFILE} ]; then
rm -f $PIDFILE;
LogText "PID file removed (${PIDFILE})"
else
LogText "PID file not found (${PIDFILE})"
fi
fi
}
# Dump to report file
Report()
{
echo "$1" >> ${REPORTFILE}
}
# Old alias for Report function (will be deprecated)
report()
{
Report "$1"
}
# Log exceptions
ReportException()
{
# 1 parameters
# <ID>:<2 char numeric>|text|
Report "exception_event[]=$1|$2|"
LogText "Exception: test has an exceptional event ($1) with text $2"
}
# Log manual actions to report file
ReportManual()
{
# 1 parameters
# <ID>:<2 char numeric>
Report "manual_event[]=$1"
LogText "Manual: one or more manual actions are required for further testing of this control/plugin"
}
# Report data (TESTID STATUS IMPACT MESSAGE)
ReportResult()
{
if [ $1 = "" ]; then TESTID="UNKNOWN"; fi
# Status: OK, WARNING, NEUTRAL, SUGGESTION
# Impact: HIGH, SEVERE, LOW,
#Report "result[]=TESTID-${TESTID},STATUS-$2,IMPACT-$3,MESSAGE-$4-"
# Reset ID before next test
TESTID=""
}
# Log suggestions to report file
ReportSuggestion()
{
TOTAL_SUGGESTIONS=`expr ${TOTAL_SUGGESTIONS} + 1`
# 4 parameters
# <ID> <Suggestion> <Details> <Solution>
# <ID> Lynis ID (use CUST-.... for your own tests)
# <Suggestion> Suggestion text to be displayed
# <Details> Specific item or details
# <Solution> Optional link for additional information:
# * url:http://site/link
# * text:Additional explanation
# * - for none
if [ "$1" = "" ]; then TEST="UNKNOWN"; else TEST="$1"; fi
if [ "$2" = "" ]; then MESSAGE="UNKNOWN"; else MESSAGE="$2"; fi
if [ "$3" = "" ]; then DETAILS="-"; else DETAILS="$3"; fi
if [ "$4" = "" ]; then SOLUTION="-"; else SOLUTION="$4"; fi
Report "suggestion[]=${TEST}|${MESSAGE}|${DETAILS}|${SOLUTION}|"
LogText "Suggestion: ${MESSAGE} [test:$1] [details:${DETAILS}] [solution:${SOLUTION}]"
}
# Log warning to report file
ReportWarning()
{
TOTAL_WARNINGS=`expr ${TOTAL_WARNINGS} + 1`
# Old style
# <ID> <priority/impact> <warning text>
if [ "$2" = "L" -o "$2" = "M" -o "$2" = "H" ]; then
DETAILS="$2"
MESSAGE="$3"
TEST="$1"
SOLUTION="-"
else
# New style warning format:
# <ID> <Warning> <Details> <Solution>
#
# <ID> Lynis ID (use CUST-.... for your own tests)
# <Warning> Warning text to be displayed
# <Details> Specific item or details
# <Solution> Optional link for additional information:
# * url:http://site/link
# * text:Additional explanation
# * - for none
if [ "$1" = "" ]; then TEST="UNKNOWN"; else TEST="$1"; fi
if [ "$2" = "" ]; then MESSAGE="UNKNOWN"; else MESSAGE="$2"; fi
if [ "$3" = "" ]; then DETAILS="-"; else DETAILS="$3"; fi
if [ "$4" = "" ]; then SOLUTION="-"; else SOLUTION="$4"; fi
fi
Report "warning[]=${TEST}|${MESSAGE}|${DETAILS}|${SOLUTION}|"
LogText "Warning: ${MESSAGE} [test:${TEST}] [details:${DETAILS}] [solution:${SOLUTION}]"
}
SafePerms()
{
PERMS_OK=0
LogText "Checking permissions of $1"
if [ $# -eq 1 ]; then
IS_PARAMETERS_FILE=`echo $1 | grep "/parameters"`
# Check file permissions
if [ ! -f "$1" ]; then
LogText "Fatal error: file $1 does not exist. Quitting."
echo "Fatal error: file $1 does not exist"
ExitFatal
else
PERMS=`ls -l $1`
# Owner permissions
OWNER=`echo ${PERMS} | awk -F" " '{ print $3 }'`
OWNERID=`ls -n $1 | awk -F" " '{ print $3 }'`
if [ ${PENTESTINGMODE} -eq 0 -a "${IS_PARAMETERS_FILE}" = "" ]; then
if [ ! "${OWNER}" = "root" -a ! "${OWNERID}" = "0" ]; then
echo "Fatal error: file $1 should be owned by user 'root' when running it as root (found: ${OWNER})."
ExitFatal
fi
else
LogText "Note: Owner permissions of file $1 to be expected similar as the UID executing the process"
fi
# Group permissions
GROUP=`echo ${PERMS} | awk -F" " '{ print $4 }'`
GROUPID=`ls -n $1 | awk -F" " '{ print $4 }'`
if [ ${PENTESTINGMODE} -eq 0 -a "${IS_PARAMETERS_FILE}" = "" ]; then
if [ ! "${GROUP}" = "root" -a ! "${GROUP}" = "wheel" -a ! "${GROUPID}" = "0" ]; then
echo "Fatal error: group owner of directory $1 should be owned by root user, wheel or similar (found: ${GROUP})."
ExitFatal
fi
else
LogText "Note: Group permissions of file $1 to be expected similar as the UID executing the process"
fi
# Other permissions
OTHER_PERMS=`echo ${PERMS} | cut -c8-10`
if [ ! "${OTHER_PERMS}" = "---" -a ! "${OTHER_PERMS}" = "r--" ]; then
echo "Fatal error: permissions of file $1 are not strict enough. Access to 'other' should be denied or read-only."
ExitFatal
fi
# Set PERMS_OK to 1 if no fatal errors occurred
PERMS_OK=1
LogText "File permissions are OK"
fi
else
LogText "Fatal error: invalid amount of parameters when calling function SafePerms()"
echo "Invalid amount of parameters for function SafePerms()"
ExitFatal
fi
}
################################################################################
# Name : SearchItem()
# Description : Search if a specific string exists in in a file
# Parameters : $1 = search string
# : $2 = file
# Returns : <nothing>
################################################################################
SearchItem()
{
ITEM_FOUND=0
if [ $# -eq 2 ]; then
# Don't search in /dev/null, it's too empty there
if [ -f $2 ]; then
# Check if we can find the main type (with or without brackets)
LogText "Test: search string $1 in file $2"
FIND=`egrep "$1" $2`
if [ ! "${FIND}" = "" ]; then
ITEM_FOUND=1
LogText "Result: found string"
LogText "Full string: ${FIND}"
else
LogText "Result: search string NOT found"
fi
else
LogText "Skipping search, file does not exist"
ReportException ${TEST_NO} "Test is trying to search for a string in nonexistent file"
fi
else
ReportException ${TEST_NO} "Error in function call to CheckItem"
fi
}
# Show result code
ShowResult()
{
case $1 in
OK)
echo "[ ${OK}OK${NORMAL} ]"
;;
WARNING)
echo "[ ${WARNING}WARNING${NORMAL} ]"
# log the warning to our log file
#LogText "Warning: $2"
# add the warning to our report file
#Report "warning=$2"
;;
esac
}
################################################################################
# Name : ShowComplianceFinding()
# Description : Display a section of a compliance standard which is not fulfilled
# Parameters : <misc>
# Returns : Nothing
################################################################################
ShowComplianceFinding()
{
REASON=""
STANDARD_NAME=""
STANDARD_VERSION=""
STANDARD_SECTION=""
STANDARD_SECTION_TITLE=""
ACTUAL_VALUE=""
EXPECTED_VALUE=""
while [ $# -ge 1 ]; do
case $1 in
--standard)
shift
STANDARD_NAME=$1
;;
--version)
shift
STANDARD_VERSION=$1
;;
--section)
shift
STANDARD_SECTION=$1
;;
--section-title)
shift
STANDARD_SECTION_TITLE=$1
;;
--reason)
shift
REASON=$1
;;
--actual)
shift
ACTUAL_VALUE=$1
;;
--expected)
shift
EXPECTED_VALUE=$1
;;
*)
echo "INVALID OPTION (ShowComplianceFinding): $1"
exit 1
;;
esac
# Go to next parameter
shift
done
# Should we show this non-compliance on screen?
SHOW=0
case ${STANDARD_NAME} in
cis)
if [ ${COMPLIANCE_ENABLE_CIS} -eq 1 ]; then SHOW=1; fi
STANDARD_FRIENDLY_NAME="CIS"
;;
hipaa)
if [ ${COMPLIANCE_ENABLE_HIPAA} -eq 1 ]; then SHOW=1; fi
STANDARD_FRIENDLY_NAME="HIPAA"
;;
iso27001)
if [ ${COMPLIANCE_ENABLE_ISO27001} -eq 1 ]; then SHOW=1; fi
STANDARD_FRIENDLY_NAME="ISO27001"
;;
pci-dss)
if [ ${COMPLIANCE_ENABLE_PCI_DSS} -eq 1 ]; then SHOW=1; fi
STANDARD_FRIENDLY_NAME="PCI DSS"
;;
esac
# Only display if standard is enabled in the profile and mark system as non-compliant
if [ ${SHOW} -eq 1 ]; then
COMPLIANCE_FINDINGS_FOUND=1
DisplayManual " [${WHITE}${STANDARD_FRIENDLY_NAME} ${STANDARD_VERSION}${NORMAL}] - ${CYAN}Section ${STANDARD_SECTION}${NORMAL} - ${WHITE}${STANDARD_SECTION_TITLE}${NORMAL}"
DisplayManual " - Details: ${REASON}"
DisplayManual " - Configuration: ${RED}${ACTUAL_VALUE}${NORMAL} / ${EXPECTED_VALUE}"
DisplayManual ""
fi
}
################################################################################
# Name : ShowSymlinkPath()
# Description : Check if we can find the path behind a symlink
# Parameters : $1 = file
# Returns : FOUNDPATH (0 not found, 1 found path)
################################################################################
ShowSymlinkPath()
{
sFILE=$1
FOUNDPATH=0
SYMLINK_USE_PYTHON=0
SYMLINK_USE_READLINK=0
# Check for symlink
if [ -L ${sFILE} ]; then
# Mac OS does not know -f option, nor do some others
if [ "${OS}" = "MacOS" ]; then
# If a Python binary is found, use the one in path
if [ ${BINARY_SCAN_FINISHED} -eq 0 -a "${PYTHONBINARY}" = "" ]; then
FIND=`which python 2> /dev/null`
if [ ! "${FIND}" = "" ]; then LogText "Setting temporary pythonbinary variable"; PYTHONBINARY="${FIND}"; fi
fi
if [ ! "${PYTHONBINARY}" = "" ]; then
SYMLINK_USE_PYTHON=1
LogText "Note: using Python to determine symlinks"
tFILE=`python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" $1`
fi
else
if [ ${BINARY_SCAN_FINISHED} -eq 0 -a "${READLINKBINARY}" = "" ]; then
FIND=`which readlink 2> /dev/null`
if [ ! "${FIND}" = "" ]; then LogText "Setting temporary readlinkbinary variable"; READLINKBINARY="${FIND}"; fi
fi
if [ ! "${READLINKBINARY}" = "" ]; then
SYMLINK_USE_READLINK=1
LogText "Note: Using real readlink binary to determine symlinks"
tFILE=`${READLINKBINARY} -f ${sFILE}`
LogText "Result: readlink shows ${tFILE} as output"
fi
fi
# Check if we can find the file now
if [ "${tFILE}" = "" ]; then
LogText "Result: command did not return any value"
elif [ -f ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, pointing to file ${sFILE}"
FOUNDPATH=1
elif [ -b ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, pointing to block device ${sFILE}"
FOUNDPATH=1
elif [ -c ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, pointing to character device ${sFILE}"
FOUNDPATH=1
elif [ -d ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, pointing to directory ${sFILE}"
FOUNDPATH=1
else
# Check the full path of the symlink, strip the filename, copy the path and linked filename together
tDIR=`echo ${sFILE} | awk '{match($1, "^.*/"); print substr($1, 1, RLENGTH-1)}'`
tFILE="${tDIR}/${tFILE}"
if [ -L ${tFILE} ]; then
LogText "Result: this symlink links to another symlink"
# Ensure that we use a second try with the right tool as well
if [ ${SYMLINK_USE_PYTHON} -eq 1 ]; then
tFILE=`python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" ${tFILE}`
elif [ ${SYMLINK_USE_READLINK} -eq 1 ]; then
tFILE=`${READLINKBINARY} -f ${tFILE}`
fi
# Check if we now have a normal file
if [ -f ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink finally found, seems to be file ${sFILE}"
FOUNDPATH=1
elif [ -d ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink finally found, seems to be directory ${sFILE}"
FOUNDPATH=1
else
LogText "Result: could not find file ${tFILE}, most likely too complicated symlink or too often linked"
fi
elif [ -f ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, seems to be file ${sFILE}"
FOUNDPATH=1
elif [ -d ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, seems to be directory ${sFILE}"
FOUNDPATH=1
else
LogText "Result: file ${tFILE} in ${tDIR} not found"
fi
fi
else
LogText "Result: file not a symlink"
fi
# Now check if our new location is actually a file or directory destination
if [ -L ${sFILE} ]; then
LogText "Result: unable to determine symlink, or location ${sFILE} is just another symlink"
FOUNDPATH=0
fi
if [ ${FOUNDPATH} -eq 1 ]; then
SYMLINK="${sFILE}"
else
SYMLINK=""
fi
}
################################################################################
# Name : TestValue
# Description : Test if a value is good/bad (e.g. according to best practices)
# Returns : 0 (True) or 1 (False)
# Usage : if TestValue --function contains --value "Full Text" --search "Text"; then echo "Found!"; fi
################################################################################
TestValue()
{
local FIND=""
local VALUE=""
local RESULT=""
local SEARCH=""
while [ $# -ge 1 ]; do
case $1 in
--function)
shift
local FUNCTION=$1
;;
--value)
shift
VALUE=$1
;;
--search)
shift
SEARCH=$1
;;
*)
echo "INVALID OPTION USED (TestValue): $1"
exit 1
;;
esac
# Go to next parameter
shift
done
if [ "${VALUE}" = "" ]; then echo "No value provided to function (TestValue)"; fi
# Apply the related function
case ${FUNCTION} in
"contains")
FIND=`echo ${VALUE} | egrep "${SEARCH}"`
if [ "${FIND}" = "" ]; then RESULT=1; else RESULT=0; fi
;;
#"gt" | "greater-than") COLOR=$GREEN ;;
#"equals") COLOR=$RED ;;
#"not-equal") COLOR=$WHITE ;;
#"lt" | "less-than") COLOR=$YELLOW ;;
*) echo "INVALID OPTION USED (TestValue, parameter of function: $1)"; exit 1 ;;
esac
if [ ! "${RESULT}" = "" ]; then
return ${RESULT}
else
echo "ERROR: No result returned from function (TestValue). Incorrect usage?"; exit 1
fi
}
ViewCategories()
{
if [ ! "${INCLUDEDIR}" = "" ]; then
InsertSection "Available test categories"
for I in `ls ${INCLUDEDIR}/tests_* | xargs -n 1 basename | sed 's/tests_//' | grep -v "custom.template"`; do
echo " - ${I}"
done
fi
echo ""
exit 0
}
# Wait for [ENTER] or manually break
wait_for_keypress()
{
if [ ! ${QUICKMODE} -eq 1 ]; then
echo ""; echo "[ ${WHITE}Press [ENTER] to continue, or [CTRL]+C to stop${NORMAL} ]"
read void
fi
}
# Wait for [ENTER] or manually break
WaitForKeypress()
{
if [ ! ${QUICKMODE} -eq 1 ]; then
echo ""; echo "[ ${WHITE}Press [ENTER] to continue, or [CTRL]+C to stop${NORMAL} ]"
read void
fi
}
#================================================================================
# Lynis is part of Lynis Enterprise and released under GPLv3 license
# Copyright 2007-2016 - Michael Boelen, CISOfy - https://cisofy.com