diff --git a/CHANGELOG.md b/CHANGELOG.md index a3522551..2d4d7ad8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,115 @@ # Lynis Changelog +## Lynis 3.0.0 (not released yet) + +This is a major release of Lynis and includes several big changes. +Some of these changes may break your current usage of the tool, so test before +deployment! + +### Breaking changes +- Some commands or switches are deprecated or removed +- Format of all profile options converted (from key:value to key=value) +- Non-interactive by default (use --wait option to pause between groups of tests) + +### Security +An important focus area for this release is on security. We added several +measures to further tighten any possible misuse. + +### Added +- Security: test PATH and warn or exit on discovery of dangerous location +- Security: additional safeguard by testing if common system tools are available +- Security: test parameters and arguments for presence of control characters +- Security: filtering out unexpected characters from profiles +- Security: test if setuid bit is set on Lynis binary +- New function: DisplayWarning - show a warning on the screen +- New function: Equals - compare two strings +- New function: Readonly - mark variable read-only (security) +- New function: SafeFile - test file type and call permission check +- New function: SafeInput - check for safe input (security) +- New profile option: disable-plugin - disables a single plugin +- New profile option: ssl-certificate-paths-to-ignore - ignore a path +- New test: CRYP-7930 - disk or file system encryption testing +- New test: PROC-3802 - Check presence of prelink tooling +- New report key: openssh_daemon_running +- New command: lynis generate systemd-units +- Measure timing of tests and report slow tests (10+ seconds) + +### Changed +- Function: CheckItem - returns only exit code (ITEM_FOUND value is dropped) +- Profiles: unused options removed +- Profiles: message is displayed when old format "key:value" is used +- Security: the 'nounset' (set -u) parameter is now activated by default +- Use only locations from PATH environment variable, unless it is not defined +- Show 'lynis generate hostids' when they are missing +- NAME-4408 - corrected Report function call +- NETW-3032 - small rewrite of test and extended with addrwatch +- PROC-3602 - allow different root directory +- PROC-3612 - show 'Not found' instead of 'OK' +- PROC-3614 - show 'Not found' instead of 'OK' +- SSH-7402 - detect other SSH daemons like dropbear +- Whow changelog works again for newer versions +- systemd service file adjusted +- bash completion script extended + +--------------------------------------------------------------------------------- + +## Lynis 2.7.5 (2019-06-24) + +### Added +- Danish translation +- Slackware end-of-life information +- Detect BSD-style (rc.d) init in Linux systems +- Detection of Bro and Suricata (IDS) + +### Changed +- Corrected end-of-life entries for CentOS 5 and 6 +- AUTH-9204 - change name to check in /etc/passwd file for QNAP devices +- AUTH-9268 - AIX enhancement to use correct find statement +- FILE-6310 - Filter on correct field for AIX +- NETW-3012 - set ss command as preferred option for Linux and changed output format +- List of PHP ini file locations has been extended +- Removed several pieces of the code as part of cleanup and code health +- Extended help + +--------------------------------------------------------------------------------- + + +## Lynis 2.7.4 (2019-04-21) + +This is a bigger release than usual, including several new tests created by +Capashenn (GitHub). It is a coincidence that it is released exactly one month +after the previous version and on Easter. No easter eggs, only improvements! + +### Added +- FILE-6324 - Discover XFS mount points +- INSE-8000 - Installed inetd package +- INSE-8100 - Installed xinetd package +- INSE-8102 - Status of xinet daemon +- INSE-8104 - xinetd configuration file +- INSE-8106 - xinetd configuration for inactive daemon +- INSE-8200 - Usage of TCP wrappers +- INSE-8300 - Presence of rsh client +- INSE-8302 - Presence of rsh server +- Detect equery binary detection +- New 'generate' command + +### Changed +- AUTH-9278 - Test LDAP in all PAM components on Red Hat and other systems +- PKGS-7410 - Add support for DPKG-based systems to gather installed kernel packages +- PKGS-7420 - Detect toolkit to automatically download and apply upgrades +- PKGS-7328 - Added global Zypper option --non-interactive +- PKGS-7330 - Added global Zypper option --non-interactive +- PKGS-7386 - Only show warning when vulnerable packages were discovered +- PKGS-7392 - Skip test for Zypper-based systems +- Minor changes to improve text output, test descriptions, and logging +- Changed CentOS identifiers in end-of-life database +- AIX enhancement for IsRunning function +- Extended PackageIsInstalled function +- Improve text output on AIX systems +- Corrected lsvg binary detection + +--------------------------------------------------------------------------------- + ## Lynis 2.7.3 (2019-03-21) ### Added diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..7f5895cd --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 3.x.x | :white_check_mark: | +| 2.x.x | :white_check_mark: | +| < 2.x | :x: | + +## Reporting a Vulnerability + +To report a vulnerability, use security@cisofy.com + +See our [security page](https://cisofy.com/security/) for more details. + +## Preferred language + +English + +## Acknowledgments + +https://cisofy.com/security/#thanks + +## Other + +See the latest 'security.txt' at https://cisofy.com/.well-known/security.txt diff --git a/db/languages/da b/db/languages/da new file mode 100644 index 00000000..d26c1220 --- /dev/null +++ b/db/languages/da @@ -0,0 +1,41 @@ +ERROR_NO_LICENSE="Ingen licensnøgle konfigureret" +ERROR_NO_UPLOAD_SERVER="Ingen upload server konfigureret" +GEN_CHECKING="Tjekker" +GEN_CURRENT_VERSION="Nuværende version" +GEN_DEBUG_MODE="Fejlfindingstilstand" +GEN_INITIALIZE_PROGRAM="Initialiserer program" +GEN_LATEST_VERSION="Seneste version" +GEN_PHASE="Fase" +GEN_PLUGINS_ENABLED="Plugins aktiverede" +GEN_UPDATE_AVAILABLE="opdatering tilgængelig" +GEN_VERBOSE_MODE="Detaljeret tilstand" +GEN_WHAT_TO_DO="At gøre" +NOTE_EXCEPTIONS_FOUND="Undtagelser fundet" +NOTE_EXCEPTIONS_FOUND_DETAILED="Nogle usædvanlige hændelser eller information var fundet" +NOTE_PLUGINS_TAKE_TIME="Bemærk: plugins har mere omfattende tests og kan tage flere minutter at fuldføre" +NOTE_SKIPPED_TESTS_NON_PRIVILEGED="Sprang over tests på grund af ikke-privilegeret tilstand" +SECTION_CUSTOM_TESTS="Brugerdefinerede Tests" +SECTION_MALWARE="Malware" +SECTION_MEMORY_AND_PROCESSES="Hukommelse og Processer" +STATUS_DISABLED="DEAKTIVERET" +STATUS_DONE="FÆRDIG" +STATUS_ENABLED="AKTIVERET" +STATUS_NOT_ENABLED="IKKE AKTIVERET" +STATUS_ERROR="FEJL" +STATUS_FOUND="FUNDET" +STATUS_YES="JA" +STATUS_NO="NEJ" +STATUS_OFF="FRA" +STATUS_OK="OK" +STATUS_ON="TIL" +STATUS_NONE="INGEN" +STATUS_NOT_FOUND="IKKE FUNDET" +STATUS_NOT_RUNNING="KØRER IKKE" +STATUS_RUNNING="KØRER" +STATUS_SKIPPED="SPRUNGET OVER" +STATUS_SUGGESTION="FORSLAG" +STATUS_UNKNOWN="UKENDT" +STATUS_WARNING="ADVARSEL" +STATUS_WEAK="SVAG" +TEXT_YOU_CAN_HELP_LOGFILE="Du kan hjælpe ved at bidrage med din logfil" +TEXT_UPDATE_AVAILABLE="opdatering tilgængelig" diff --git a/db/software-eol.db b/db/software-eol.db index 39edb2da..7b511237 100644 --- a/db/software-eol.db +++ b/db/software-eol.db @@ -11,9 +11,9 @@ # # CentOS # -os:CentOS 5:2017-03-31:1490911200: -os:CentOS 6:2020-11-30:1606690800: -os:CentOS 7:2024-06-30:1719698400: +os:CentOS release 5:2017-03-31:1490911200: +os:CentOS release 6:2020-11-30:1606690800: +os:CentOS Linux release 7:2024-06-30:1719698400: # # FreeBSD - https://www.freebsd.org/security/unsupported.html # @@ -42,5 +42,21 @@ os:Ubuntu 16.10:2017-07-01:1498860000: os:Ubuntu 17.04:2018-01-01:1514761200: os:Ubuntu 17.10:2018-07-01:1530396000: os:Ubuntu 18.04:2023-05-01:1682892000: -os:Ubuntu 18.10:2019-07-01:1561932000: -os:Ubuntu 19.04:2020-01-01:1577833200: \ No newline at end of file +os:Ubuntu 18.10:2019-07-18:1563400800: +os:Ubuntu 19.04:2020-01-01:1577833200: +# +# Slackware - https://en.wikipedia.org/wiki/Slackware#Releases +# +os:Slackware Linux 8.1:2012-08-01:1343768400: +os:Slackware Linux 9.0:2012-08-01:1343768400: +os:Slackware Linux 9.1:2012-08-01:1343768400: +os:Slackware Linux 10.0:2012-08-01:1343768400: +os:Slackware Linux 10.1:2012-08-01:1343768400: +os:Slackware Linux 10.2:2012-08-01:1343768400: +os:Slackware Linux 11.0:2012-08-01:1343768400: +os:Slackware Linux 12.0:2012-08-01:1343768400: +os:Slackware Linux 12.1:2013-12-09:1386540000: +os:Slackware Linux 12.2:2013-12-09:1386540000: +os:Slackware Linux 13.0:2018-07-05:1530738000: +os:Slackware Linux 13.1:2018-07-05:1530738000: +os:Slackware Linux 13.37:2018-07-05:1530738000: diff --git a/db/tests.db b/db/tests.db index 641ea927..cd978c50 100644 --- a/db/tests.db +++ b/db/tests.db @@ -84,6 +84,7 @@ CONT-8107:test:performance:containers::Check number of unused Docker containers: CONT-8108:test:security:containers::Check file permissions for Docker files: CORE-1000:test:performance:system_integrity::Check all system binaries: CRYP-7902:test:security:crypto::Check expire date of SSL certificates: +CRYP-7930:test:security:crypto::Determine if system uses disk or file encryption: DNS-1600:test:security:dns::Validating that the DNSSEC signatures are checked: DBS-1804:test:security:databases::Checking active MySQL process: DBS-1816:test:security:databases::Checking MySQL root password: @@ -169,11 +170,17 @@ HTTP-6712:test:security:webservers::Check nginx access logging: HTTP-6714:test:security:webservers::Check for missing error logs in nginx: HTTP-6716:test:security:webservers::Check for debug mode on error log in nginx: HTTP-6720:test:security:webservers::Check Nginx log files: -INSE-8002:test:security:insecure_services::Check for enabled inet daemon: -INSE-8004:test:security:insecure_services::Check for enabled inet daemon: -INSE-8006:test:security:insecure_services::Check configuration of inetd when disabled: +INSE-8000:test:security:insecure_services::Installed inetd package: +INSE-8002:test:security:insecure_services::Status of inet daemon: +INSE-8004:test:security:insecure_services::Presence of inetd configuration file: +INSE-8006:test:security:insecure_services::Check configuration of inetd when it is disabled: INSE-8016:test:security:insecure_services::Check for telnet via inetd: INSE-8050:test:security:insecure_services:MacOS:Check for insecure services on macOS systems: +INSE-8100:test:security:insecure_services::Installed xinetd package: +INSE-8116:test:security:insecure_services::Insecure services enabled via xinetd: +INSE-8200:test:security:insecure_services::Usage of TCP wrappers: +INSE-8300:test:security:insecure_services::Presence of rsh client: +INSE-8302:test:security:insecure_services::Presence of rsh server: KRNL-5622:test:security:kernel:Linux:Determine Linux default run level: KRNL-5677:test:security:kernel:Linux:Check CPU options and support: KRNL-5695:test:security:kernel:Linux:Determine Linux kernel version and release number: @@ -319,6 +326,7 @@ PKGS-7393:test:security:ports_packages::Check for Gentoo vulnerable packages: PKGS-7394:test:security:ports_packages:Linux:Check for Ubuntu updates: PKGS-7398:test:security:ports_packages::Check for package audit tool: PKGS-7410:test:security:ports_packages::Count installed kernel packages: +PKGS-7420:test:security:ports_packages::Detect toolkit to automatically download and apply upgrades: PRNT-2302:test:security:printers_spools:FreeBSD:Check for printcap consistency: PRNT-2304:test:security:printers_spools::Check cupsd status: PRNT-2306:test:security:printers_spools::Check CUPSd configuration file: @@ -332,6 +340,7 @@ PROC-3602:test:security:memory_processes:Linux:Checking /proc/meminfo for memory PROC-3604:test:security:memory_processes:Solaris:Query prtconf for memory details: PROC-3612:test:security:memory_processes::Check dead or zombie processes: PROC-3614:test:security:memory_processes::Check heavy IO waiting based processes: +PROC-3802:test:security:memory_processes::Check presence of prelink tooling: RBAC-6272:test:security:mac_frameworks::Check grsecurity presence: SCHD-7702:test:security:scheduling::Check status of cron daemon: SCHD-7704:test:security:scheduling::Check crontab/cronjobs: diff --git a/default.prf b/default.prf index ef474b1f..1431beb6 100644 --- a/default.prf +++ b/default.prf @@ -1,30 +1,33 @@ ################################################################################# # # -# Lynis - Scan Profile (default) -# -# This is the default profile and contains default values. +# Lynis - Default scan profile # # ################################################################################# # # -# SUGGESTION +# This profile provides Lynis with most of its initial values to perform a +# system audit. +# +# +# WARNINGS # ---------- # -# Do NOT make changes to this file, instead copy your preferred settings to -# custom.prf and put it in the same directory as default.prf +# Do NOT make changes to this file. Instead, copy only your changes into +# the file custom.prf and put it in the same directory as default.prf # # To discover where your profiles are located: lynis show profiles # # +# Lynis performs a strict check on profiles to avoid the inclusion of +# possibly harmful injections. See include/profiles for details. +# +# ################################################################################# # # All empty lines or with the # prefix will be skipped # -# More information about this plugin can be found in the documentation: -# https://cisofy.com/documentation/lynis/ -# ################################################################################# # Use colored output @@ -33,6 +36,9 @@ colors=yes # Compressed uploads (set to zero when errors with uploading occur) compressed-uploads=yes +# Amount of connections in WAIT state before reporting it as a suggestion +#connections-max-wait-state=5000 + # Debug mode (for debugging purposes, extra data logged to screen) #debug=yes @@ -42,20 +48,27 @@ error-on-warnings=no # Use Lynis in your own language (by default auto-detected) language= -# Lynis Enterprise license key -license-key= +# Log tests from another guest operating system (default: yes) +#log-tests-incorrect-os=yes + +# Define if available NTP daemon is configured as a server or client on the network +# values: server or client (default: client) +#ntpd-role=client # Defines the role of the system (personal, workstation or server) machine-role=server +# Ignore some stratum 16 hosts (for example when running as time source itself) +#ntp-ignore-stratum-16-peer=127.0.0.1 + # Profile name, will be used as title/description profile-name=Default Audit Template # Number of seconds to pause between every test (0 is no pause) pause-between-tests=0 -# Enable quick mode (no waiting for keypresses, same as --quick option) -quick=no +# Quick mode (do not wait for keypresses) +quick=yes # Refresh software repositories to help detecting vulnerable packages refresh-repositories=yes @@ -76,39 +89,20 @@ skip-plugins=no #skip-test=SSH-7408:loglevel #skip-test=SSH-7408:permitrootlogin +# Skip Lynis upgrade availability test (default: no) +#skip-upgrade-test=yes + +# Locations where to search for SSL certificates (separate paths with a colon) +ssl-certificate-paths=/etc/apache2:/etc/dovecot:/etc/httpd:/etc/letsencrypt:/etc/pki:/etc/postfix:/etc/ssl:/opt/psa/var/certificates:/usr/local/psa/var/certificates:/usr/local/share/ca-certificates:/var/www:/srv/www +ssl-certificate-paths-to-ignore=/etc/letsencrypt/archive: + # Scan type - how deep the audit should be (light, normal or full) test-scan-mode=full -# Upload data to central server -upload=no - -# The hostname/IP address to receive the data -upload-server= - -# Provide options to cURL (or other upload tool) when uploading data. -# upload-options=--insecure --> use HTTPS, but skip certificate check (e.g. self-signed) -upload-options= - # Verbose output verbose=no -################################################################################# -# -# Upgrade and updating -# -------------------- -# -# The old settings to do automatic updating are deprecated. It is suggested to -# use a package or deploy your the tarball via a custom script. -# -# The latest packages can be found at: https://packages.cisofy.com -# -################################################################################# - -# Skip Lynis upgrade availability test (default: no) -#skip-upgrade-test=yes - - ################################################################################# # # Plugins @@ -119,10 +113,11 @@ verbose=no # - Nothing happens if plugin isn't available # - There is no order in execution of plugins # - See documentation about how to use plugins and phases +# - Some are for Lynis Enterprise users only # ################################################################################# -# Lynis Plugins (some are for Lynis Enterprise users only) +# Lynis plugins to enable plugin=authentication plugin=compliance plugin=configuration @@ -149,17 +144,22 @@ plugin=system-integrity plugin=systemd plugin=users +# Disable a particular plugin (will overrule an enabled plugin) +#disable-plugin=authentication ################################################################################# # # Kernel options # --------------- -# sysctl:<sysctl Key>:<Expected Value>:<Hardening Points>:<Description>: +# configdate=, followed by: # -# Sysctl key = name -# Expected value = value of sysctl key -# Hardening points = Number of hardening points. For most keys 1 HP will be suitable -# Description = Text description of key +# - Type = Set to 'sysctl' +# - Setting = value of sysctl key (e.g. kernel.sysrq) +# - Expected value = Preferred value for key (e.g. 0) +# - Hardening Points = Number of hardening points (typically 1 point per key) (1) +# - Description = Textual description about the sysctl key(Disable magic SysRQ) +# - Related file or command = For example, sysctl -a to retrieve more details +# - Solution field = Specifies more details or where to find them (url:URL, text:TEXT, or -) # ################################################################################# @@ -269,86 +269,58 @@ config-data=sysctl;security.bsd.hardlink_check_gid;1;1;Unprivileged processes ar config-data=sysctl;security.bsd.hardlink_check_uid;1;1;Unprivileged processes are not allowed to create hard links to files which are owned by other users;-;category:security; -################################################################################# -# -# Apache options -# columns: (1)apache : (2)option : (3)value -# -################################################################################# - -apache:ServerTokens:Prod: - - -################################################################################# -# -# OpenLDAP options -# columns: (1)openldap : (2)file : (3)option : (4)expected value(s) -# -################################################################################# - -openldap:slapd.conf:permissions:640-600: -openldap:slapd.conf:owner:ldap-root: - - - - -################################################################################# -# -# NTP options -# -################################################################################# - -# Ignore some stratum 16 hosts (for example when running as time source itself) -#ntp-ignore-stratum-16-peer=127.0.0.1 - - -################################################################################# -# -# File/directories permissions (currently not used yet) -# -################################################################################# - -# Scan for exact file name match -#[scanfiles] -#scanfile:/etc/rc.conf:FreeBSD configuration: - -# Scan for exact directory name match -#[scandirs] -#scandir:/etc:/etc directory: - - ################################################################################# # # permfile # --------------- -# permfile:file name:file permissions:owner:group:action: +# permfile=file name:file permissions:owner:group:action: # Action = NOTICE or WARN # Examples: -# permfile:/etc/test1.dat:600:root:wheel:NOTICE: -# permfile:/etc/test1.dat:640:root:-:WARN: +# permfile=/etc/test1.dat:600:root:wheel:NOTICE: +# permfile=/etc/test1.dat:640:root:-:WARN: # ################################################################################# -#permfile:/etc/inetd.conf:rw-------:root:-:WARN: -#permfile:/etc/fstab:rw-r--r--:root:-:WARN: -permfile:/etc/lilo.conf:rw-------:root:-:WARN: - +#permfile=/etc/inetd.conf:rw-------:root:-:WARN: +#permfile=/etc/fstab:rw-r--r--:root:-:WARN: +permfile=/boot/grub2/grub.cfg:rw-------:root:root:WARN: +permfile=/boot/grub/grub.cfg:rw-------:root:root:WARN: +permfile=/boot/grub2/user.cfg:rw-------:root:root:WARN: +permfile=/etc/at.allow:rw-------:root:-:WARN: +permfile=/etc/at.deny:rw-------:root:-:WARN: +permfile=/etc/cron.allow:rw-------:root:-:WARN: +permfile=/etc/cron.deny:rw-------:root:-:WARN: +permfile=/etc/crontab:rw-------:root:-:WARN: +permfile=/etc/group:rw-r--r--:root:-:WARN: +permfile=/etc/group-:rw-r--r--:root:-:WARN: +permfile=/etc/gshadow:---------:root:-:WARN: +permfile=/etc/gshadow-:---------:root:-:WARN: +permfile=/etc/hosts.allow:rw-r--r--:root:root:WARN: +permfile=/etc/hosts.deny:rw-r--r--:root:root:WARN: +permfile=/etc/issue:rw-r--r--:root:root:WARN: +permfile=/etc/issue.net:rw-r--r--:root:root:WARN: +permfile=/etc/lilo.conf:rw-------:root:-:WARN: +permfile=/etc/motd:rw-r--r--:root:root:WARN: +permfile=/etc/passwd:rw-r--r--:root:-:WARN: +permfile=/etc/passwd-:rw-r--r--:root:-:WARN: +permfile=/etc/shadow:---------:root:-:WARN: +permfile=/etc/shadow-:---------:root:-:WARN: +permfile=/etc/ssh/sshd_config:rw-------:root:-:WARN: ################################################################################# # # permdir # --------------- -# permdir:directory name:file permissions:owner:group:action when permissions are different: +# permdir=directory name:file permissions:owner:group:action when permissions are different: # ################################################################################# -permdir:/root/.ssh:rwx------:root:-:WARN: - -# Scan for a program/binary in BINPATHs -#scanbinary:Rootkit Hunter:rkhunter: - -# Amount of connections in WAIT state before reporting it as a suggestion -#connections-max-wait-state=5000 +permdir=/root/.ssh:rwx------:root:-:WARN: +permdir=/etc/cron.d:rwx------:root:root:WARN: +permdir=/etc/cron.daily:rwx------:root:root:WARN: +permdir=/etc/cron.hourly:rwx------:root:root:WARN: +permdir=/etc/cron.weekly:rwx------:root:root:WARN: +permdir=/etc/cron.monthly:rwx------:root:root:WARN: # Ignore some specific home directories @@ -356,12 +328,6 @@ permdir:/root/.ssh:rwx------:root:-:WARN: # checks, like file permissions, SSH and other configuration files #ignore-home-dir=/home/user -# Do not log tests with another guest operating system (default: yes) -#log-tests-incorrect-os=no - -# Define if available NTP daemon is configured as a server or client on the network -# values: server or client (default: client) -#ntpd-role=client # Allow promiscuous interfaces # <option>:<promiscuous interface name>:<description>: @@ -395,21 +361,10 @@ permdir:/root/.ssh:rwx------:root:-:WARN: -################################################################################# -# -# SSL certificates -# -################################################################################# - -# Locations where to search for SSL certificates -ssl-certificate-paths=/etc/apache2:/etc/dovecot:/etc/httpd:/etc/letsencrypt:/etc/pki:/etc/postfix:/etc/ssl:/opt/psa/var/certificates:/usr/local/psa/var/certificates:/usr/local/share/ca-certificates:/var/www:/srv/www - - - ################################################################################# # # Lynis Enterprise options -# ----------------- +# ------------------------ # ################################################################################# @@ -423,6 +378,9 @@ ssl-certificate-paths=/etc/apache2:/etc/dovecot:/etc/httpd:/etc/letsencrypt:/etc #hostid=40-char-hash #hostid2=64-char-hash +# Lynis Enterprise license key +license-key= + # Proxy settings # Protocol (http, https, socks5) #proxy-protocol=https @@ -443,9 +401,18 @@ compliance-standards=cis,hipaa,iso27001,pci-dss # Provide the name of the customer/client #system-customer-name=mycustomer +# Upload data to central server +upload=no + +# The hostname/IP address to receive the data +upload-server= + +# Provide options to cURL (or other upload tool) when uploading data. +# upload-options=--insecure (use HTTPS, but skip certificate check for self-signed certificates) +upload-options= + # Link one or more tags to a system #tags=db,production,ssn-1304 - #EOF diff --git a/developer.prf b/developer.prf index 856596a8..624e06e6 100644 --- a/developer.prf +++ b/developer.prf @@ -3,6 +3,5 @@ debug=yes developer-mode=yes -quick=yes strict=yes verbose=yes diff --git a/extras/bash_completion.d/lynis b/extras/bash_completion.d/lynis index 235a848a..8732ede3 100644 --- a/extras/bash_completion.d/lynis +++ b/extras/bash_completion.d/lynis @@ -1,6 +1,6 @@ # bash completion for lynis -# version 1.0.0 (22 September 2014) +# version 1.0.1 (2019-07-13) # Michael Boelen <michael.boelen@cisofy.com> # lynis(8) completion @@ -10,7 +10,7 @@ _lynis() # opts nodig nosig COMPREPLY=() - _get_comp_words_by_ref cur prev + _get_comp_words_by_ref cur prev words if [ $COMP_CWORD -eq 1 ]; then # first parameter on line @@ -19,24 +19,134 @@ _lynis() COMPREPLY=( $( compgen -W '--help --info --version' -- "$cur" ) ) ;; *) - COMPREPLY=( $( compgen -W 'audit --help --info --version' -- "$cur" ) ) + COMPREPLY=( $( compgen -W 'audit generate show' -- "$cur" ) ) ;; esac - - return 0 + return 0 + elif [ $COMP_CWORD -eq 4 ]; then + # Stop after some specifics + if [ "${COMP_WORDS[1]}" = "show" -a "${COMP_WORDS[2]}" = "details" ]; then + return 0 + fi fi + # Check previous argument to share the available options case $prev in audit) COMPREPLY=( $( compgen -W 'dockerfile system ' -- "$cur" ) ) ;; + show) - COMPREPLY=( $( compgen -W 'help version ' -- "$cur" ) ) + COMPREPLY=( $( compgen -W 'categories changelog commands dbdir details environment eol groups help hostids includedir language license logfile man options os pidfile plugindir profiles release releasedate report settings tests version workdir ' -- "$cur" ) ) ;; + # Related items to show (lynis show XYZ) + categories) + return 0 + ;; + changelog) + return 0 + ;; + commands) + return 0 + ;; + dbdir) + return 0 + ;; + details) + local dbfile="" + local dirs="/data/development/lynis /usr/local/lynis /usr/share/lynis" + for d in ${dirs}; do + if [ -f "${d}/db/tests.db" ]; then + local dbfile="/data/development/lynis/db/tests.db" + fi + done + if [ -f "${dbfile}" ]; then + local suggestions=($(compgen -W "$(awk -F: '$1 ~ /^[A-Z]/ {print $1}' ${dbfile})" -- "${cur}")) + COMPREPLY=("${suggestions[@]}") + else + COMPREPLY=($(compgen -W "TEST-1234" -- "$cur")) + fi + ;; + environment) + return 0 + ;; + eol) + return 0 + ;; + groups) + return 0 + ;; + help) + return 0 + ;; + hostids) + if [ "${COMP_WORDS[1]}" = "generate" -a "${COMP_WORDS[2]}" = "hostids" ]; then + COMPREPLY=($(compgen -W "save" -- "$cur")) + else + return 0 + fi + ;; + includedir) + return 0 + ;; + language) + return 0 + ;; + license) + return 0 + ;; + logfile) + return 0 + ;; + man) + return 0 + ;; + options) + return 0 + ;; + os) + return 0 + ;; + pidfile) + return 0 + ;; + plugindir) + return 0 + ;; + profiles) + return 0 + ;; + release) + return 0 + ;; + releasedate) + return 0 + ;; + report) + return 0 + ;; + settiings) + return 0 + ;; + tests) + return 0 + ;; + version) + return 0 + ;; + workdir) + return 0 + ;; + + generate) + COMPREPLY=( $( compgen -W 'hostids ' -- "$cur" ) ) + ;; + + # Options --auditor) COMPREPLY=( '"Mr. Auditor"' ) - return 0 + #return 0 ;; --check-update|--help|--info|--version) # all other options are noop with this command diff --git a/extras/systemd/lynis.service b/extras/systemd/lynis.service index 7ecc36ad..21432c22 100644 --- a/extras/systemd/lynis.service +++ b/extras/systemd/lynis.service @@ -5,19 +5,26 @@ ################################################################################# # # - Adjust path to link to location where Lynis binary is installed -# - Place this file together with the timer file in systemd directory -# - Run: systemctl enable lynis.service +# +# - Place this file together with the lynis.timer file in the related +# systemd directory (e.g. /etc/systemd/system/) +# +# - See details in lynis.timer file # ################################################################################# [Unit] -Description=Lynis security audit and vulnerability scan +Description=Security audit and vulnerability scanner +Documentation=https://cisofy.com/docs/ [Service] Nice=19 IOSchedulingClass=best-effort IOSchedulingPriority=7 Type=simple -ExecStart=/path/to/lynis -c --cronjob +ExecStart=/path/to/lynis audit system --cronjob + +[Install] +WantedBy=multi-user.target #EOF diff --git a/extras/systemd/lynis.timer b/extras/systemd/lynis.timer index 169b3c17..83f5c144 100644 --- a/extras/systemd/lynis.timer +++ b/extras/systemd/lynis.timer @@ -4,17 +4,23 @@ # ################################################################################# # -# - Place this file together with the service file in systemd directory -# - Run: systemctl enable lynis.timer -# systemctl start lynis.service +# - Place this file together with the lynis.service file in the related +# systemd directory (e.g. /etc/systemd/system) +# +# - Tell systemd you made changes +# systemctl daemon-reload +# +# - Enable and start the timer (so no reboot is needed): +# systemctl enable --now lynis.timer # ################################################################################# [Unit] -Description=Daily run for Lynis security audit and vulnerability scan +Description=Daily timer for the Lynis security audit and vulnerability scanner [Timer] OnCalendar=daily +RandomizedDelaySec=1800 Persistent=false [Install] diff --git a/include/binaries b/include/binaries index 18b54b2a..d894caf9 100644 --- a/include/binaries +++ b/include/binaries @@ -42,18 +42,39 @@ Display --indent 2 --text "- Checking system binaries..." LogText "Status: Starting binary scan..." - # Test if our PATH variable provides a set of paths - # If so, reverse the order. If we discover the same binary multiple times, the one first in PATH - # should be used. - # If PATH is empty, we use the predefined list in include/consts. Common paths first, then followed - # by more specific paths. This helps on the slightly ancient UNIX derivatives. + # Notes: + # - If PATH is empty, we use the predefined list in include/consts + # - Common paths first, then followed by more specific paths. This helps on the slightly ancient UNIX derivatives. + # - Avoid sorting the path list, as this might result in incorrect order of finding binaries (e.g. awk binary) + + # Test if our PATH variable provides a set of paths. If so, reverse the order. If we discover the same binary + # multiple times, the one first in PATH should be used. if [ ! -z "${PATH}" ]; then PATH_REVERSED=$(echo ${PATH} | awk -F: '{ for (i=NF; i>1; i--) printf("%s ",$i); print $1; }') - BIN_PATHS=$(echo "${PATH_REVERSED} ${BIN_PATHS}" | tr ':' ' ') + BIN_PATHS=$(echo "${PATH_REVERSED}" | tr ':' ' ') fi - # Avoid sorting, as this might result in incorrect order of finding binaries (e.g. awk binary) - #SORTED_BIN_PATHS=$(echo ${BIN_PATHS} | tr ' ' '\n' | sort | uniq | tr '\n' ' ') + # First test available locations that may be suspicious or dangerous + for SCANDIR in ${BIN_PATHS}; do + FOUND=0 + if [ "${SCANDIR}" = "." ]; then FOUND=1; MSG="Found single dot (.) in PATH" + elif [ "${SCANDIR}" = ".." ]; then FOUND=1; MSG="Found double dot (..) in PATH" + elif echo ${SCANDIR} | grep '^\.\.' > /dev/null; then FOUND=1; MSG="Found path starting with double dot (..) in PATH" + elif echo ${SCANDIR} | grep '^[a-zA-Z]' > /dev/null; then FOUND=1; MSG="Found relative path in PATH" + fi + if [ ${FOUND} -eq 1 ]; then + # Stop execution if privileged, otherwise continue but warn user + if [ ${PRIVILEGED} -eq 1 ]; then + ExitFatal "Possible riskful location (${SCANDIR}) in PATH discovered. Quitting..." + else + Display --indent 4 --text "Warning: suspicious location (${SCANDIR}) in PATH" + ReportWarning "${TEST_NO}" "Possible riskful location in PATH discovered" "text:${MSG}" + sleep 1 + fi + fi + done + + # Now perform binary detection for SCANDIR in ${BIN_PATHS}; do LogText "Test: Checking binaries in directory ${SCANDIR}" ORGPATH="" @@ -99,6 +120,7 @@ afick.pl) AFICKBINARY=${BINARY}; LogText " Found known binary: afick (file integrity checker) - ${BINARY}" ;; aide) AIDEBINARY=${BINARY}; LogText " Found known binary: aide (file integrity checker) - ${BINARY}" ;; apache2) HTTPDBINARY=${BINARY}; LogText " Found known binary: apache2 (web server) - ${BINARY}" ;; + apt) APTBINARY=${BINARY}; LogText " Found known binary: apt (package manager) - ${BINARY}" ;; arch-audit) ARCH_AUDIT_BINARY="${BINARY}"; LogText " Found known binary: arch-audit (auditing utility to test for vulnerable packages) - ${BINARY}" ;; auditd) AUDITDBINARY=${BINARY}; LogText " Found known binary: auditd (audit framework) - ${BINARY}" ;; awk) AWKBINARY=${BINARY}; LogText " Found known binary: awk (string tool) - ${BINARY}" ;; @@ -109,6 +131,7 @@ base64) BASE64BINARY="${BINARY}"; LogText " Found known binary: base64 (encoding tool) - ${BINARY}" ;; blkid) BLKIDBINARY="${BINARY}"; LogText " Found known binary: blkid (information about block devices) - ${BINARY}" ;; bootctl) BOOTCTLBINARY="${BINARY}"; LogText " Found known binary: bootctl (systemd-boot manager utility) - ${BINARY}" ;; + bro) BROBINARY="${BINARY}"; LogText " Found known binary: bro (IDS) - ${BINARY}" ;; cat) CAT_BINARY="${BINARY}"; LogText " Found known binary: cat (generic file handling) - ${BINARY}" ;; cc) CCBINARY="${BINARY}"; COMPILER_INSTALLED=1; LogText " Found known binary: cc (compiler) - ${BINARY}" ;; chkconfig) CHKCONFIGBINARY=${BINARY}; LogText " Found known binary: chkconfig (administration tool) - ${BINARY}" ;; @@ -131,7 +154,8 @@ domainname) DOMAINNAMEBINARY="${BINARY}"; LogText " Found known binary: domainname (NIS domain) - ${BINARY}" ;; dpkg) DPKGBINARY="${BINARY}"; LogText " Found known binary: dpkg (package management) - ${BINARY}" ;; egrep) EGREPBINARY=${BINARY}; LogText " Found known binary: egrep (text search) - ${BINARY}" ;; - exim) EXIMBINARY="${BINARY}"; EXIMVERSION=$(${BINARY} -bV | grep 'Exim version' | awk '{ print $3 }' | xargs); LogText "Found ${BINARY} (version ${EXIMVERSION})" ;; + equery) EQUERYBINARY="${BINARY}"; LogText " Found known binary: query (package manager) - ${BINARY}" ;; + exim) EXIMBINARY="${BINARY}"; EXIMVERSION=$(${BINARY} -bV | grep 'Exim version' | awk '{ print $3 }' | xargs); LogText " Found known binary ${BINARY} (version ${EXIMVERSION})" ;; fail2ban-server) FAIL2BANBINARY="${BINARY}"; LogText " Found known binary: fail2ban (IPS tool) - ${BINARY}" ;; file) FILEBINARY="${BINARY}"; LogText " Found known binary: file (file type detection) - ${BINARY}" ;; find) FINDBINARY="${BINARY}"; LogText " Found known binary: find (search tool) - ${BINARY}" ;; @@ -164,7 +188,7 @@ lsattr) LSATTRBINARY="${BINARY}"; LogText " Found known binary: lsattr (file attributes) - ${BINARY}" ;; lsmod) LSMODBINARY="${BINARY}"; LogText " Found known binary: lsmod (kernel modules) - ${BINARY}" ;; lsof) LSOFBINARY="${BINARY}"; LogText " Found known binary: lsof (open files) - ${BINARY}" ;; - lsvg) LVSGBINARY=${BINARY}; LogText " Found known binary: lsvg (volume manager) - ${BINARY}" ;; + lsvg) LSVGBINARY=${BINARY}; LogText " Found known binary: lsvg (volume manager) - ${BINARY}" ;; lvdisplay) LVDISPLAYBINARY="${BINARY}"; LogText " Found known binary: lvdisplay (LVM tool) - ${BINARY}" ;; lynx) LYNXBINARY="${BINARY}"; LYNXVERSION=$(${BINARY} -version | grep "^Lynx Version" | cut -d ' ' -f3); LogText "Found known binary: lynx (browser) - ${BINARY} (version ${LYNXVERSION})" ;; maldet) LMDBINARY="${BINARY}"; MALWARE_SCANNER_INSTALLED=1; LogText " Found known binary: maldet (Linux Malware Detect, malware scanner) - ${BINARY}" ;; @@ -225,6 +249,7 @@ sha1|sha1sum|shasum) SHA1SUMBINARY="${BINARY}"; LogText " Found known binary: sha1/sha1sum/shasum (crypto hashing) - ${BINARY}" ;; sha256|sha256sum) SHA256SUMBINARY="${BINARY}"; LogText " Found known binary: sha256/sha256sum (crypto hashing) - ${BINARY}" ;; ssh-keyscan) SSHKEYSCANBINARY="${BINARY}"; LogText " Found known binary: ssh-keyscan (scanner for SSH keys) - ${BINARY}" ;; + suricata) SURICATABINARY="${BINARY}"; LogText " Found known binary: suricata (IDS) - ${BINARY}" ;; sysctl) SYSCTLBINARY="${BINARY}"; LogText " Found known binary: sysctl (kernel parameters) - ${BINARY}" ;; syslog-ng) SYSLOGNGBINARY="${BINARY}"; SYSLOGNGVERSION=$(${BINARY} -V 2>&1 | grep "^syslog-ng" | awk '{ print $2 }'); LogText "Found ${BINARY} (version ${SYSLOGNGVERSION})" ;; systemctl) SYSTEMCTLBINARY="${BINARY}"; LogText " Found known binary: systemctl (client to systemd) - ${BINARY}" ;; @@ -254,6 +279,7 @@ LogText "Result: Directory ${SCANDIR} does NOT exist" fi done + # unset SORTED_BIN_PATHS BINARY_SCAN_FINISHED=1 BINARY_PATHS_FOUND=$(echo ${BINARY_PATHS_FOUND} | sed 's/^, //g' | sed 's/ //g') @@ -261,10 +287,35 @@ LogText "Result: found ${COUNT} binaries" Report "binaries_count=${COUNT}" Report "binary_paths=${BINARY_PATHS_FOUND}" + + # Test if the basic system tools are defined. These will be used during the audit. + [ "${AWKBINARY:-}" ] || ExitFatal "awk binary not found" + [ "${CUTBINARY:-}" ] || ExitFatal "cut binary not found" + [ "${EGREPBINARY:-}" ] || ExitFatal "grep binary not found" + [ "${FINDBINARY:-}" ] || ExitFatal "find binary not found" + [ "${GREPBINARY:-}" ] || ExitFatal "grep binary not found" + [ "${HEADBINARY:-}" ] || ExitFatal "head binary not found" + [ "${LSBINARY:-}" ] || ExitFatal "ls binary not found" + [ "${PSBINARY:-}" ] || ExitFatal "ps binary not found" + [ "${SEDBINARY:-}" ] || ExitFatal "sed binary not found" + [ "${SORTBINARY:-}" ] || ExitFatal "sort binary not found" + [ "${TRBINARY:-}" ] || ExitFatal "tr binary not found" + [ "${UNIQBINARY:-}" ] || ExitFatal "uniq binary not found" + [ "${WCBINARY:-}" ] || ExitFatal "wc binary not found" + + # Test a few other tools that we did not specifically define (yet) + TOOLS="xxd" + for T in ${TOOLS}; do + DATA=$(type ${T}) + if [ $? -gt 0 ]; then ExitFatal "${T} binary not found"; fi + done + + else LogText "Result: checking of binaries skipped in this mode" fi + # #================================================================================ # Lynis - Security Auditing and System Hardening for Linux and UNIX - https://cisofy.com diff --git a/include/consts b/include/consts index 671ed1ca..67a18733 100644 --- a/include/consts +++ b/include/consts @@ -46,6 +46,7 @@ unset LANG # # == Variable initializing == # + APTBINARY="" ARCH_AUDIT_BINARY="" AUDITORNAME="" AUDITCTLBINARY="" @@ -70,6 +71,7 @@ unset LANG CHKCONFIGBINARY="" CLAMCONF_BINARY="" CLAMSCANBINARY="" + CLANGBINARY="" COLORS=1 COMPLIANCE_ENABLE_CIS=0 COMPLIANCE_ENABLE_HIPAA=0 @@ -84,6 +86,7 @@ unset LANG CONTAINER_TYPE="" CREATE_REPORT_FILE=1 CSUMBINARY="" + CURRENT_TS=0 CUSTOM_URL_APPEND="" CUSTOM_URL_PREPEND="" CUSTOM_URL_PROTOCOL="" @@ -94,13 +97,17 @@ unset LANG DEBSECANBINARY="" DEBSUMSBINARY="" DEVELOPER_MODE=0 + DISABLED_PLUGINS="" DISCOVERED_BINARIES="" DMIDECODEBINARY="" DNFBINARY="" DOCKERBINARY="" DOCKER_DAEMON_RUNNING=0 + DPKGBINARY="" ECHOCMD="" ERROR_ON_WARNINGS=0 + EQUERYBINARY="" + EXIMBINARY="" FAIL2BANBINARY="" FILEBINARY="" FILEVALUE="" @@ -210,6 +217,8 @@ unset LANG PLUGIN_PHASE=0 POSTFIXBINARY="" POSTGRES_RUNNING=0 + PREVIOUS_TEST="No test ID" + PREVIOUS_TS=0 PRIVILEGED=0 PROFILES="" PROFILEVALUE="" @@ -248,8 +257,10 @@ unset LANG SHOW_REPORT_SOLUTION=1 SHOW_TOOL_TIPS=1 # Show inline tool tips (default true) SHOW_WARNINGS_ONLY=0 + SKIP_GETHOSTID=0 SKIP_PLUGINS=0 SKIP_TESTS="" + SKIP_VM_DETECTION=0 SKIPREASON="" SKIPPED_TESTS_ROOTONLY="" SMTPCTLBINARY="" @@ -257,6 +268,7 @@ unset LANG SSHKEYSCANBINARY="" SSHKEYSCANFOUND=0 SSL_CERTIFICATE_PATHS="" + SSL_CERTIFICATE_PATHS_TO_IGNORE="" STUNNELBINARY="" SYSLOGNGBINARY="" SYSTEMCTLBINARY="" @@ -270,6 +282,7 @@ unset LANG TESTS_EXECUTED="" TESTS_SKIPPED="" TMPFILE="" + TOMOYOINITBINARY="" TOOLTIP_SHOWED=0 TOTAL_SUGGESTIONS=0 TOTAL_WARNINGS=0 @@ -340,7 +353,8 @@ unset LANG ################################################################################# # - # Normal color names + # Normal color names (BG will color background) + BG_BLUE="$(printf '\033[0;44m')" CYAN="$(printf '\033[0;36m')" BLUE="$(printf '\033[0;34m')" BROWN="$(printf '\033[0;33m')" @@ -354,15 +368,13 @@ unset LANG YELLOW="$(printf '\033[1;33m')" WHITE="$(printf '\033[1;37m')" - # Markup + # Special markup BOLD="${WHITE}" - - # With background - BG_BLUE="$(printf '\033[0;44m')" + NORMAL="$(printf '\033[0m')" # Semantic names + BG_WARNING="$(printf '\033[30;43m')" # Yellow background with grey text HEADER="${WHITE}" - NORMAL="$(printf '\033[0m')" WARNING="${RED}" SECTION="${YELLOW}" NOTICE="${YELLOW}" diff --git a/include/data_upload b/include/data_upload index 5bc8e839..7698d13a 100644 --- a/include/data_upload +++ b/include/data_upload @@ -242,13 +242,9 @@ fi else echo "${RED}Error${NORMAL}: No hostid and/or hostid2 found. Can not upload report file." - echo "Suggested command: lynis show hostids" + echo "Suggested command: lynis generate hostids --save" echo "" - echo "If hostid2 is the only ID that is missing, use the following step:" - echo "Create hash and add it to custom.prf" - echo "echo \"hostid2=\$(cat /dev/urandom | tr -dc 'a-f0-9' | fold -w 64 | head -n 1)\" >> /etc/lynis/custom.prf" - echo "" - echo "Note: do not replicate this ID to other systems, as it needs to be unique per system" + echo "Note: do not replicate the values to other systems, as it needs to be unique per system" # Quit ExitFatal diff --git a/include/functions b/include/functions index 95083600..16cef804 100644 --- a/include/functions +++ b/include/functions @@ -42,6 +42,8 @@ # DisplayError Show an error on screen # DisplayManual Output text to screen without any layout # DisplayToolTip Show a tip for improving usage of the tool +# DisplayWarning Show a clear warning on screen +# Equals Compares two strings # 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 @@ -74,6 +76,7 @@ # ParseTestValues Parse a set of values # PortIsListening Check if machine is listening on specified protocol and port # Progress Show progress on screen +# Readonly Mark a variable as read-only data # Register Register a test (for logging and execution) # RandomString Show a random string # RemoveColors Reset all colors @@ -85,7 +88,9 @@ # ReportManual Log manual actions to report file # ReportSuggestion Add a suggestion to report file # ReportWarning Add a warning and priority to report file +# SafeFile Security tests to perform on a file before using it # SafePerms Check if a file has safe permissions +# SafeInput Test provided string to see if it contains unwanted characters # 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 @@ -114,7 +119,8 @@ # Name : AddHP() # Description : Add hardening points and count them # - # Input : $1 = points to add, $2 = maximum points for this item + # Parameters : $1 = points to add (0 or higher) + # $2 = maximum points (at least value of $1 or higher) # Returns : <nothing> # Usage : AddHP 1 3 ################################################################################ @@ -135,7 +141,9 @@ # Name : AddSetting() # Description : Addition of a setting for display with 'lynis show settings' # - # Input : $1 = setting, $2 = value, $3 description + # Parameters : $1 = setting + # $2 = value + # $3 = description # Returns : <nothing> # Usage : AddSetting debug 1 'Debug mode' ################################################################################ @@ -159,7 +167,7 @@ TEMP_SETTINGS_FILE="${TEMP_FILE}" cat ${SETTINGS_FILE} > ${TEMP_SETTINGS_FILE} sed -e '/^'"${SETTING}"';/d' ${TEMP_SETTINGS_FILE} > ${SETTINGS_FILE} - rm ${TEMP_SETTINGS_FILE} + rm "${TEMP_SETTINGS_FILE}" echo "${SETTING};${VALUE};${DESCRIPTION};" >> ${SETTINGS_FILE} fi else @@ -172,7 +180,7 @@ # Name : AddSystemGroup() # Description : Adds a system to a group, which can be used for categorizing # - # Input : Group name + # Parameters : $1 = group name # Returns : <nothing> # Usage : AddSystemGroup "test" ################################################################################ @@ -186,13 +194,13 @@ # Name : CheckFilePermissions() # Description : Check file permissions # - # Input : full path to file or directory + # Parameters : Full path to file or directory # Returns : PERMS (FILE_NOT_FOUND | OK | BAD) # Notes : This function might be replaced in future ################################################################################ CheckFilePermissions() { - CHECKFILE=$1 + CHECKFILE="$1" if [ ! -d ${CHECKFILE} -a ! -f ${CHECKFILE} ]; then PERMS="FILE_NOT_FOUND" else @@ -213,13 +221,13 @@ # Name : CheckItem() # Description : Check if a specific item exists in the report # - # Input : $1 = key, $2 = value - # Returns : ITEM_FOUND - # Usage : CheckItem "key" "value" + # Parameters : $1 = key + # $2 = value + # Returns : True (0) or False (1) + # Usage : if CheckItem "key" "value"; then ....; fi ################################################################################ CheckItem() { - ITEM_FOUND=0 RETVAL=255 if [ $# -eq 2 ]; then # Don't search in /dev/null, it's too empty there @@ -228,7 +236,6 @@ LogText "Test: search string $2 in earlier discovered results" FIND=$(egrep "^$1(\[\])?=" ${REPORTFILE} | egrep "$2") if HasData "${FIND}"; then - ITEM_FOUND=1 RETVAL=0 LogText "Result: found search string (result: $FIND)" else @@ -248,6 +255,7 @@ ################################################################################ # Name : CheckUpdates() # Description : Determine if there is an update available + # # Returns : <nothing> # Usage : CheckUpdates # Use PROGRAM_LV (latest version) and compare it with actual version (PROGRAM_AC) @@ -282,8 +290,9 @@ ################################################################################ # Name : CleanUp() - # Description : Cleanup service - # Returns : <nothing> + # Description : Delete PID and temporary files, stop execution (exit code 1) + # + # Usage : this function is triggered by a manual break by user ################################################################################ CleanUp() { @@ -298,7 +307,8 @@ ################################################################################ # Name : ContainsString() # Description : Search a specific string (or regular expression) in another - # Returns : (0 - True, 1 - False) + # + # Returns : True (0) or False (1) # Usage : if ContainsString "needle" "there is a needle in the haystack"; echo "Found"; else "Not found"; fi ################################################################################ @@ -313,11 +323,9 @@ ################################################################################ # Name : CountTests() - # Description : Count the number of tests performed + # Description : Counter for the number of tests performed # - # Input : <nothing> - # Returns : <nothing> - # Usage : CountTests + # Usage : Call CountTests to increase number by 1 ################################################################################ CountTests() { @@ -329,7 +337,6 @@ # Name : CreateTempFile() # Description : Creates a temporary file # - # Input : <nothing> # Returns : TEMP_FILE (variable) # Usage : CreateTempFile # if [ ! "${TEMP_FILE}" = "" ]; then @@ -361,7 +368,7 @@ # Name : DirectoryExists() # Description : Check if a directory exists # - # Returns : 0 (directory exists), 1 (directory does not exist) + # Returns : True (0) or False (1) # Usage : if DirectoryExists; then echo "it exists"; else echo "It does not exist"; fi ################################################################################ @@ -605,6 +612,46 @@ } + ################################################################################ + # Name : DisplayWarning + # Description : Show a warning on the screen + # + # Input : $1 = text + # Returns : <nothing> + ################################################################################ + + DisplayWarning() { + if [ ${CRONJOB} -eq 0 ]; then + printf "\n" + ${ECHOCMD:-echo} " ${BG_WARNING}[WARNING]${NORMAL}: $1${NORMAL}" + printf "\n" + else + ${ECHOCMD} " [WARNING]: $1" + fi + } + + + ################################################################################ + # Name : Equals() + # Description : Compare two strings + # + # Returns : (0 - True, 1 - False) + # Usage : if Equals "${MYDIR}" "/etc"; then echo "Found"; else "Not found"; fi + ################################################################################ + + Equals() { + RETVAL=1 + if [ $# -ne 2 ]; then ReportException "Equals" "Incorrect number of arguments for $0 function"; fi + + # Strip any strange control characters + INPUT1=$(echo $1 | tr -d '[:cntrl:]<>' | ${SEDBINARY} 's/__space__/ /g' | ${SEDBINARY} 's/:space:/ /g') + INPUT2=$(echo $2 | tr -d '[:cntrl:]<>' | ${SEDBINARY} 's/__space__/ /g' | ${SEDBINARY} 's/:space:/ /g') + if [ "${INPUT1}" = "${INPUT2}" ]; then RETVAL=0; fi + + return ${RETVAL} + } + + ################################################################################ # Name : ExitClean() # Description : Perform a normal exit of the program, and clean up resources @@ -659,9 +706,9 @@ RemoveTempFiles LogText "${PROGRAM_NAME} ended with exit code 1." if [ $# -eq 1 ]; then - ${ECHOCMD} "" - ${ECHOCMD} "${RED}Fatal error${NORMAL}: ${WHITE}$1${NORMAL}" - ${ECHOCMD} "" + ${ECHOCMD:-echo} "" + ${ECHOCMD:-echo} "${RED}Fatal error${NORMAL}: ${WHITE}$1${NORMAL}" + ${ECHOCMD:-echo} "" fi exit 1 } @@ -805,15 +852,26 @@ # Name : GetHostID() # Description : Create an unique id for the system # - # Returns : optional value + # Returns : 0 = fetched or created IDs, 1 = failed, 2 = skipped # Usage : GetHostID ################################################################################ GetHostID() { + if [ ${SKIP_GETHOSTID} -eq 1 ]; then + return 2 + fi + if [ ! -z "${HOSTID}" -a ! -z "${HOSTID2}" ]; then Debug "Skipping creation of host identifiers, as they are already configured (via profile)" - return 1 + return 2 + fi + + if [ -f "${ROOTDIR}etc/lynis/hostids" ]; then + Debug "Used hostids file to fetch values" + HOSTID=$(grep "^hostid=" ${ROOTDIR}etc/lynis/hostids | awk -F= '{print $2}') + HOSTID2=$(grep "^hostid2=" ${ROOTDIR}etc/lynis/hostids | awk -F= '{print $2}') + return 0 fi FIND="" @@ -1110,8 +1168,9 @@ fi # Show an exception if no HostID could be created, to ensure each system (and scan) has one - if [ "${HOSTID}" = "" ]; then + if [ -z "${HOSTID}" ]; then ReportException "GetHostID" "No unique host identifier could be created." + return 1 elif [ ! -z "${HOSTID2}" ]; then return 0 fi @@ -1284,7 +1343,8 @@ if [ -z "${search}" ]; then ExitFatal "Missing process to search for when using IsRunning function"; fi RUNNING=0 - if [ ! -z "${PGREPBINARY}" ]; then + # AIX does not fully support pgrep options, so using ps instead + if [ ! -z "${PGREPBINARY}" -a ! "${OS}" = "AIX" ]; then FIND=$(${PGREPBINARY} ${pgrep_options} "${search}" | ${TRBINARY} '\n' ' ') else if [ -z "${PSOPTIONS}" ]; then @@ -1392,6 +1452,10 @@ ISVIRTUALMACHINE=2; VMTYPE="unknown"; VMFULLTYPE="Unknown" SHORT="" + if [ ${SKIP_VM_DETECTION} -eq 1 ]; then + return 2 + fi + # lxc environ detection if [ -z "${SHORT}" ]; then if [ -f /proc/1/environ ]; then @@ -1699,7 +1763,7 @@ ################################################################################ # Name : PackageIsInstalled() - # Description : Add a separator to log file between sections, tests etc + # Description : Determines if a package is installed # Returns : exit code # Notes : this function is not used yet, but created in advance to allow # the addition of support for all operating systems @@ -1714,11 +1778,20 @@ Fatal "Incorrect usage of PackageIsInstalled function" fi - if [ ! -z "${RPMBINARY}" ]; then - output=$(${RPMBINARY} --quiet -q ${package} 2> /dev/null) + if [ ! -z "${DNFBINARY}" ]; then + output=$(${DNFBINARY} --quiet --cacheonly --noplugins --assumeno info --installed ${package} > /dev/null 2>&1) exit_code=$? - elif ! -z "${DPKGBINARY}" ]; then - output=$(${DPKGBINARY} -l ${package} 2> /dev/null) + elif [ ! -z "${DPKGBINARY}" ]; then + output=$(${DPKGBINARY} -l ${package} 2> /dev/null | ${GREPBINARY} "^ii") + exit_code=$? + elif [ ! -z "${EQUERYBINARY}" ]; then + output=$(${EQUERYBINARY} --quiet ${package} > /dev/null 2>&1) + exit_code=$? # 0=package installed, 3=package not installed + elif [ ! -z "${PKG_BINARY}" ]; then + output=$(${PKG_BINARY} -N info ${package} >/dev/null 2>&1) + exit_code=$? # 0=package installed, 70=invalid package + elif [ ! -z "${RPMBINARY}" ]; then + output=$(${RPMBINARY} --quiet -q ${package} > /dev/null 2>&1) exit_code=$? elif [ ! -z "${ZYPPERBINARY}" ]; then output=$(${ZYPPERBINARY} --quiet --non-interactive search --installed -i ${PACKAGE} 2> /dev/null | grep "^i") @@ -1983,7 +2056,7 @@ for ITEM in ${VALUE}; do LogText "Result: found protocol ${ITEM}" case ${ITEM} in - "sslv2" | "sslv3") + "sslv2" | "sslv3" | "tlsv1") NGINX_WEAK_SSL_PROTOCOL_FOUND=1 ;; esac @@ -2089,6 +2162,24 @@ } + ################################################################################ + # Name : Readonly() + # Description : Mark a variable as read-only data + # Returns : nothing + ################################################################################ + + Readonly() { + if [ $# -eq 1 ]; then + if type -t typeset; then + typeset -r $1 + else + Debug "No typeset available to mark variable '$1' as read-only variable" + fi + else + ExitFatal "Expected 1 parameter, received none or multiple" + fi + } + ################################################################################ # Name : Register() @@ -2096,6 +2187,19 @@ # Returns : SKIPTEST (0 or 1) ################################################################################ + GetTimestamp() { + ts=0 + case "${OS}" in + "Linux") + ts=$(date "+%s%N") + ;; + *) + ts=$(date "+%s") + ;; + esac + echo $ts + } + Register() { # Do not insert a log break, if previous test was not logged if [ ${SKIPLOGTEST} -eq 0 ]; then LogTextBreak; fi @@ -2163,6 +2267,35 @@ shift done + # Measure timing + CURRENT_TS=$(GetTimestamp) + if [ ${PREVIOUS_TS} -gt 0 ]; then + SLOW_TEST=0 + TIME_THRESHOLD=10 # seconds + + # Calculate timing and determine if we use seconds or nanoseconds (more precise) + TIME_DIFF=$((${CURRENT_TS} - ${PREVIOUS_TS})) + if [ ${CURRENT_TS} -gt 1000000000000000000 ]; then + TIME_DIFF_FORMAT="nanoseconds" + TIME_THRESHOLD=$((${TIME_THRESHOLD} * 1000000000)) + if [ ${TIME_DIFF} -gt ${TIME_THRESHOLD} ]; then + SLOW_TEST=1 + # Convert back to seconds for readability + TIME_DIFF_FORMAT="seconds" + TIME_DIFF=$(echo ${TIME_DIFF} | ${AWKBINARY} '{printf "%f",$1/1000000000}') + fi + else + TIME_DIFF_FORMAT="seconds" + if [ ${TIME_DIFF} -gt ${TIME_THRESHOLD} ]; then + SLOW_TEST=1 + fi + fi + if [ ${SLOW_TEST} -eq 1 ]; then + DisplayWarning "Test ${PREVIOUS_TEST} had a long execution: ${TIME_DIFF} ${TIME_DIFF_FORMAT}" + Report "slow_test[]=${PREVIOUS_TEST},${TIME_DIFF}" + fi + fi + # Skip test if it's configured in profile (old style) if [ ${SKIPTEST} -eq 0 ]; then FIND=$(echo "${TEST_SKIP_ALWAYS}" | grep "${TEST_NO}" | tr '[:lower:]' '[:upper:]') @@ -2235,6 +2368,10 @@ TESTS_SKIPPED="${TEST_NO}|${TESTS_SKIPPED}" fi unset SKIPREASON + + # Save timestamp for next time the Register function is called + PREVIOUS_TEST="${TEST_NO}" + PREVIOUS_TS="${CURRENT_TS}" } @@ -2262,6 +2399,7 @@ # Colors with background BG_BLUE="" + BG_WARNING="" # Semantic names BAD="" @@ -2276,14 +2414,15 @@ ################################################################################ # Name : RemovePIDFile() + # Description : When defined, remove the file storing the process ID ################################################################################ # Remove PID file RemovePIDFile() { # Test if PIDFILE is defined, before checking file presence - if [ ! "${PIDFILE}" = "" ]; then - if [ -f ${PIDFILE} ]; then - rm -f $PIDFILE; + if [ ! -z "${PIDFILE}" ]; then + if [ -f "${PIDFILE}" ]; then + rm -f "${PIDFILE}" LogText "PID file removed (${PIDFILE})" else LogText "PID file not found (${PIDFILE})" @@ -2294,6 +2433,7 @@ ################################################################################ # Name : RemoveTempFiles() + # Description : When created, delete any temporary file ################################################################################ # Remove any temporary files @@ -2304,10 +2444,10 @@ for FILE in ${TEMP_FILES}; do # Temporary files should be in /tmp TMPFILE=$(echo ${FILE} | egrep "^/tmp/lynis" | grep -v "\.\.") - if [ ! "${TMPFILE}" = "" ]; then - if [ -f ${TMPFILE} ]; then + if [ ! -z "${TMPFILE}" ]; then + if [ -f "${TMPFILE}" ]; then LogText "Action: removing temporary file ${TMPFILE}" - rm -f ${TMPFILE} + rm -f "${TMPFILE}" else LogText "Info: temporary file ${TMPFILE} was already removed" fi @@ -2323,6 +2463,7 @@ ################################################################################ # Name : Report() + # Description : Store data in the report file ################################################################################ Report() { @@ -2397,12 +2538,14 @@ ################################################################################ # Name : ReportException() + # Description : Store an exceptional event in the report + # + # Parameters : $1 = test ID + colon + 2 numeric characters (TEST-1234:01) + # $2 = string (text) ################################################################################ # 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" } @@ -2410,11 +2553,12 @@ ################################################################################ # Name : ReportManual() + # Description : Add an item to the report that requires manual intervention + # + # Parameters : $1 = string (text) ################################################################################ - # Log manual actions to report file ReportManual() { - # 1 parameter: Text Report "manual_event[]=$1" LogText "Manual: one or more manual actions are required for further testing of this control/plugin" } @@ -2422,20 +2566,20 @@ ################################################################################ # Name : ReportSuggestion() + # Description : Log a suggestion to the report file + # + # Parameters : <ID> <Suggestion> <Details> <Solution> + # $1 = Test ID - Lynis ID (use CUST-.... for your own tests) + # $2 = Suggestion - Suggestion text to be displayed + # $3 = Details - Specific item or details + # $4 = Solution - Optional link for additional information: + # * url:https://example.org/how-to-solve-link + # * text:Additional explanation + # * - (dash) for none ################################################################################ - # Log suggestions to report file ReportSuggestion() { TOTAL_SUGGESTIONS=$((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 [ $# -eq 0 ]; then echo "Not enough arguments provided for function ReportSuggestion"; ExitFatal; fi if [ $# -ge 1 ]; then TEST="$1"; else TEST="UNKNOWN"; fi if [ $# -ge 2 ]; then MESSAGE="$2"; else MESSAGE="UNKNOWN"; fi @@ -2449,9 +2593,9 @@ ################################################################################ # Name : ReportWarning() + # Description : Log a warning to the report file ################################################################################ - # Log warning to report file ReportWarning() { TOTAL_WARNINGS=$((TOTAL_WARNINGS + 1)) # Old style @@ -2484,76 +2628,180 @@ } + ################################################################################ + # Name : SafeInput() + # Description : Test provided string to see if it contains unwanted characters + # + # Input : string + optional class (parameter 2) + # Returns : 0 (input considered to be safe) or 1 (validation failed) + ################################################################################ + + SafeInput() { + exitcode=1 + # By default remove only control characters + if [ $# -eq 1 ]; then + input="$1" + cleaned=$(echo ${input} | tr -d '[:cntrl:]') + # If know what to test against, then see if input matches the specified class + elif [ $# -eq 2 ]; then + input="$1" + testchars="$2" + cleaned=$(echo $1 | tr -cd "${testchars}") + else + ExitFatal "No argument or too many arguments provided to SafeInput()" + fi + + if [ "${cleaned}" = "${input}" ]; then + exitcode=0 + fi + return ${exitcode} + } + + + ################################################################################ + # Name : SafeFile() + # Description : Check if a file is safe to use + # + ################################################################################ + + SafeFile() { + unsafe=0 + if [ $# -ne 1 ]; then + ExitFatal "No argument or too many arguments provided to SafeFile()" + else + FILE="$1" + + # Generic checks + if [ -g "${FILE}" ]; then + LogText "Security alert: file has setgid attribute" + unsafe=1 + # sticky bit + elif [ -k "${FILE}" ]; then + LogText "Security alert: file has sticky bit" + unsafe=1 + # symbolic link + elif [ -L "${FILE}" ]; then + LogText "Security alert: file is a symbolic link" + unsafe=1 + elif [ -f "${FILE}" ]; then + LogText "Security check: file is normal" + else + unsafe=1 + fi + + # Perform additional checks based on privilege level + if [ ${PRIVILEGED} -eq 0 ]; then + # File is not owned by active user, but still able to write + if [ ! -O "${FILE}" -a -w "${FILE}" ]; then + unsafe=1 + LogText "Security alert: file is not owned by active user, but can write to it" + fi + fi + + # Check file permissions + if ! SafePerms "${FILE}"; then + unsafe=1 + fi + + fi + + return ${unsafe} + } + + ################################################################################ # Name : SafePerms() - # Return : 0 (file OK) or break + # Description : Check if a file has safe permissions to be used + # + # Returns : 0 (file permissions OK) or break ################################################################################ SafePerms() { - if [ ${WARN_ON_FILE_ISSUES} -eq 1 ]; then + exitcode=1 + IS_PARAMETERS=0 + IS_PROFILE=0 + + if [ ${IGNORE_FILE_PERMISSION_ISSUES} -eq 0 ]; then PERMS_OK=0 LogText "Checking permissions of $1" - if [ $# -eq 1 ]; then - IS_PARAMETERS_FILE=$(echo $1 | grep "/parameters") + + if [ $# -gt 0 ]; then + + if [ $# -eq 2 ]; then + case "$2" in + "parameters") + IS_PARAMETERS=1 + ;; + "profile") + IS_PROFILE=1 + ;; + esac + else + FIND=$(echo $1 | grep "/parameters") + if [ $? -eq 0 ]; then IS_PARAMETERS=1; fi + fi # 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 - fi - # Group permissions - GROUP=$(echo ${PERMS} | awk -F" " '{ print $4 }') - GROUPID=$(ls -n $1 | awk -F" " '{ print $4 }') + 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) - 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 - fi + # Owner permissions + OWNER=$(echo ${PERMS} | awk -F" " '{ print $3 }') + OWNERID=$(ls -n $1 | awk -F" " '{ print $3 }') + if [ ${PENTESTINGMODE} -eq 0 -a ${IS_PARAMETERS} -eq 0 ]; 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 + fi + # Group permissions + GROUP=$(echo ${PERMS} | awk -F" " '{ print $4 }') + GROUPID=$(ls -n $1 | awk -F" " '{ print $4 }') - # Owner permissions - OWNER_PERMS=$(echo ${PERMS} | cut -c2-4) - if [ ! "${OWNER_PERMS}" = "rw-" -a ! "${OWNER_PERMS}" = "r--" ]; then - echo "Fatal error: permissions of file $1 are not strict enough. Access to 'owner' should be read-write, or read. Change with: chmod 600 $1" - ExitFatal - fi + if [ ${PENTESTINGMODE} -eq 0 -a ${IS_PARAMETERS} -eq 0 ]; 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 + fi - # Owner permissions - GROUP_PERMS=$(echo ${PERMS} | cut -c5-7) - if [ ! "${GROUP_PERMS}" = "rw-" -a ! "${GROUP_PERMS}" = "r--" -a ! "${GROUP_PERMS}" = "---" ]; then - echo "Fatal error: permissions of file $1 are not strict enough. Access to 'group' should be read-write, read, or none. Change with: chmod 600 $1" - ExitFatal - fi + # Owner permissions + OWNER_PERMS=$(echo ${PERMS} | cut -c2-4) + if [ ! "${OWNER_PERMS}" = "rw-" -a ! "${OWNER_PERMS}" = "r--" ]; then + echo "Fatal error: permissions of file $1 are not strict enough. Access to 'owner' should be read-write, or read. Change with: chmod 600 $1" + ExitFatal + 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. Change with: chmod 600 $1" - ExitFatal - fi - # Set PERMS_OK to 1 if no fatal errors occurred - PERMS_OK=1 - LogText "File permissions are OK" - return 0 - fi + # Owner permissions + GROUP_PERMS=$(echo ${PERMS} | cut -c5-7) + if [ ! "${GROUP_PERMS}" = "rw-" -a ! "${GROUP_PERMS}" = "r--" -a ! "${GROUP_PERMS}" = "---" ]; then + echo "Fatal error: permissions of file $1 are not strict enough. Access to 'group' should be read-write, read, or none. Change with: chmod 600 $1" + ExitFatal + 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. Change with: chmod 600 $1" + ExitFatal + fi + # Set PERMS_OK to 1 if no fatal errors occurred + PERMS_OK=1 + LogText "File permissions are OK" + exitcode=0 + fi else ReportException "SafePerms()" "Invalid number of arguments for function" fi else PERMS_OK=1 - return 0 + exitcode=0 fi + return ${exitcode} + } @@ -2561,14 +2809,15 @@ # Name : SearchItem() # Description : Search if a specific string exists in in a file # - # Input : $1 = search key (string), $2 = file (string), $3 and later - # are optional arguments + # Parameters : $1 = search key (string) + # $2 = file (string) + # $3 = optional arguments: + # --sensitive - don't store results in log # Returns : True (0) or False (1) ################################################################################ SearchItem() { PERFORM_SCAN=0 - ITEM_FOUND=0 MASK_LOG=0 RETVAL=1 if [ $# -lt 2 ]; then @@ -2596,8 +2845,7 @@ # Check if we can find the main type (with or without brackets) LogText "Test: search string ${STRING} in file ${FILE}" FIND=$(egrep "${STRING}" ${FILE}) - if [ ! "${FIND}" = "" ]; then - ITEM_FOUND=1 + if [ ! -z "${FIND}" ]; then LogText "Result: found search string '${STRING}'" if [ ${MASK_LOG} -eq 0 ]; then LogText "Full string returned: ${FIND}"; fi RETVAL=0 @@ -2616,21 +2864,6 @@ } - # Show result code (to be removed) - 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 - } ################################################################################ @@ -3191,35 +3424,44 @@ # ################################################################################ # For compatibility reasons they are listed here, but will be phased out in - # steps. If they still get used, they will trigger an alert when using the - # developer mode. In a later phase they will trigger errors on screen. + # steps. If they still get used, they will trigger errors on screen. ################################################################################ counttests() { + DisplayWarning "Deprecated function used (counttests)" if IsDeveloperMode; then Debug "Warning: old counttests function is used. Please replace any reference with CountTests."; fi CountTests } logtext() { + DisplayWarning "Deprecated function used (logtext)" if IsDeveloperMode; then Debug "Warning: old logtext function is used. Please replace any reference with LogText."; fi LogText "$1" } logtextbreak() { + DisplayWarning "Deprecated function used (logtextbreak)" if IsDeveloperMode; then Debug "Warning: old logtextbreak function is used. Please replace any reference with LogTextBreak."; fi LogTextBreak "$1" } report() { + DisplayWarning "Deprecated function used (report)" if IsDeveloperMode; then Debug "Warning: old report function is used. Please replace any reference with Report."; fi Report "$1" } wait_for_keypress() { + DisplayWarning "Deprecated function used (wait_for_keypress)" if IsDeveloperMode; then Debug "Warning: old wait_for_keypress function is used. Please replace any reference with WaitForKeyPress."; fi WaitForKeyPress } + ShowResult() { + DisplayWarning "Deprecated function used (ShowResult)" + if IsDeveloperMode; then Debug "Warning: old ShowResult() function is used. Please replace any reference with WaitForKeyPress."; fi + } + #================================================================================ diff --git a/include/helper_audit_dockerfile b/include/helper_audit_dockerfile index efe73d28..3d18556f 100644 --- a/include/helper_audit_dockerfile +++ b/include/helper_audit_dockerfile @@ -19,25 +19,14 @@ ################################################################################# if [ $# -eq 0 ]; then - Display --indent 2 --text "${RED}Error: ${WHITE}Provide URL or file${NORMAL}" + Display --indent 2 --text "${RED}Error: ${WHITE}Provide a file${NORMAL}" Display --text " "; Display --text " " ExitFatal else FILE=$(echo $1 | egrep "^http|https") if HasData "${FILE}"; then - CreateTempFile - TMP_FILE="${TEMP_FILE}" - Display --indent 2 --text "Downloading URL ${FILE} with wget" - wget -o ${TMP_FILE} ${FILE} - if [ $? -gt 0 ]; then - AUDIT_FILE="${TMP_FILE}" - else - if [ -f ${TMP_FILE} ]; then - rm -f ${TMP_FILE} - fi - Display --indent 2 --text "${RED}Error: ${WHITE}can not download file${NORMAL}" - ExitFatal - fi + echo "Provide a file (not a URL)" + ExitFatal else if [ -f $1 ]; then AUDIT_FILE="$1" @@ -70,13 +59,12 @@ fi IS_ALPINE=$(echo ${IMAGE} | grep -i alpine) IS_LATEST=$(echo ${TAG} | grep -i latest) - if [ ! "${IS_DEBIAN}" = "" ]; then IMAGE="debian"; fi - if [ ! "${IS_FEDORA}" = "" ]; then IMAGE="fedora"; fi - if [ ! "${IS_UBUNTU}" = "" ]; then IMAGE="ubuntu"; fi - if [ ! "${IS_ALPINE}" = "" ]; then IMAGE="alpine"; fi - - if [ ! "${IS_LATEST}" = "" ]; then - ReportWarning "dockerfile" "latest TAG used. Specifying the version is better." + if [ ! -z "${IS_DEBIAN}" ]; then IMAGE="debian"; fi + if [ ! -z "${IS_FEDORA}" ]; then IMAGE="fedora"; fi + if [ ! -z "${IS_UBUNTU}" ]; then IMAGE="ubuntu"; fi + if [ ! -z "${IS_ALPINE}" ]; then IMAGE="alpine"; fi + if [ ! -z "${IS_LATEST}" ]; then + ReportWarning "dockerfile" "latest TAG used. Specifying a targeted OS image and version is better for reproducible results." fi case ${IMAGE} in @@ -110,14 +98,14 @@ InsertSection "Basics" #FIND=$(egrep "^MAINTAINER" ${AUDIT_FILE} | sed 's/ /:space:/g') FIND=$(egrep -i "*MAINTAINER" ${AUDIT_FILE} | sed 's/=/ /g' | cut -d'"' -f 2) - if [ "${FIND}" = "" ]; then + if [ -z "${FIND}" ]; then ReportWarning "dockerfile" "No maintainer found. Unclear who created this file." else #MAINTAINER=$(echo ${FIND} | sed 's/:space:/ /g' | awk '{ if($1=="MAINTAINER") { print }}') MAINTAINER=$(echo ${FIND}) Display --indent 2 --text "Maintainer" --result "${MAINTAINER}" fi - + FIND=$(grep "^ENTRYPOINT" ${AUDIT_FILE} | cut -d' ' -f2 ) if [ "${FIND}" = "" ]; then ReportWarning "dockerfile" "No ENTRYPOINT defined in Dockerfile." @@ -127,7 +115,7 @@ InsertSection "Basics" fi FIND=$(grep "^CMD" ${AUDIT_FILE} | cut -d' ' -f2 ) - if [ "${FIND}" = "" ]; then + if [ -z "${FIND}" ]; then ReportWarning "dockerfile" "No CMD defines in Dockerfile." else CMD=$(echo ${FIND}) @@ -135,7 +123,7 @@ InsertSection "Basics" fi FIND=$(grep "^USER" ${AUDIT_FILE} | cut -d' ' -f2 ) - if [ "${FIND}" = "" ]; then + if [ -z "${FIND}" ]; then ReportWarning "dockerfile" "No user declared in Dockerfile. Container will execute command as root" else USER=$(echo ${FIND}) diff --git a/include/helper_generate b/include/helper_generate new file mode 100644 index 00000000..31dba7ec --- /dev/null +++ b/include/helper_generate @@ -0,0 +1,180 @@ +#!/bin/sh + +################################################################################# +# +# Lynis +# ------------------ +# +# Copyright 2007-2013, Michael Boelen +# Copyright 2007-2019, CISOfy +# +# Website : https://cisofy.com +# Blog : http://linux-audit.com +# GitHub : https://github.com/CISOfy/lynis +# +# Lynis comes with ABSOLUTELY NO WARRANTY. This is free software, and you are +# welcome to redistribute it under the terms of the GNU General Public License. +# See LICENSE file for usage of this software. +# +###################################################################### +# +# Helper program to generate specific details such as host IDs +# +###################################################################### +# +# How to use: +# ------------ +# Run: lynis generate <option> +# +###################################################################### + +SAVEFILE=0 +GENERATE_ARGS="hostids systemd-units" + +if [ $# -gt 0 ]; then + case $1 in + "hostids") + + if [ $# -gt 1 ]; then + shift + if [ $1 = "--save" ]; then + SAVEFILE=1 + fi + fi + + # Generate random host IDs + HOSTID=$(head -c20 < /dev/urandom | xxd -c 20 -p) + HOSTID2=$(head -c32 < /dev/urandom | xxd -c 32 -p) + + ${ECHOCMD} "Generated host identifiers" + ${ECHOCMD} "- hostid: ${HOSTID}" + ${ECHOCMD} "- hostid2: ${HOSTID2}" + + if [ ${SAVEFILE} -eq 1 ]; then + FILE="${ROOTDIR}etc/lynis/hostids" + if [ -f ${FILE} ]; then + ${ECHOCMD} "Error: hostids file already exists (${FILE})" + ${ECHOCMD} "Remove the file first and rerun command" + ExitFatal + else + OUTPUT=$(touch ${FILE} 2> /dev/null) + if [ $? -eq 0 ]; then + ${ECHOCMD} "Created hostids file (${FILE})" + echo "# generated using 'lynis generate hostids --save'" > ${FILE} + echo "hostid=${HOSTID}" >> ${FILE} + echo "hostid2=${HOSTID2}" >> ${FILE} + else + ExitFatal "Error: could not created hostids file (${FILE}). Issue with permissions?" + fi + fi + fi + + ExitClean + ;; + + "cronjob") + ${ECHOCMD} "Not implemented yet" + ;; + + "systemd-units") + + ${ECHOCMD} "" + + ${ECHOCMD} "${BG_BLUE}Step 1: create service unit (/etc/systemd/system/lynis.service)${NORMAL}" + + ${ECHOCMD} "" + ${ECHOCMD} "#################################################################################" + ${ECHOCMD} "#" + ${ECHOCMD} "# Lynis service file for systemd" + ${ECHOCMD} "#" + ${ECHOCMD} "#################################################################################" + ${ECHOCMD} "# Do not remove, so Lynis can provide a hint when a newer unit is available" + ${ECHOCMD} "# Generator=lynis" + ${ECHOCMD} "# Version=1" + ${ECHOCMD} "#################################################################################" + ${ECHOCMD} "" + ${ECHOCMD} "[Unit]" + ${ECHOCMD} "Description=Security audit and vulnerability scanner" + ${ECHOCMD} "Documentation=https://cisofy.com/docs/" + ${ECHOCMD} "" + ${ECHOCMD} "[Service]" + ${ECHOCMD} "Nice=19" + ${ECHOCMD} "IOSchedulingClass=best-effort" + ${ECHOCMD} "IOSchedulingPriority=7" + ${ECHOCMD} "Type=simple" + MYBINARY=$(which lynis 2>/dev/null) + MOREOPTIONS="" + if [ -n "${LICENSE_KEY}" ]; then + MOREOPTIONS=" --upload" + fi + ${ECHOCMD} "ExecStart=${MYBINARY:-/path/to/lynis} audit system --cronjob${MOREOPTIONS}" + ${ECHOCMD} "" + ${ECHOCMD} "[Install]" + ${ECHOCMD} "WantedBy=multi-user.target" + ${ECHOCMD} "" + ${ECHOCMD} "#################################################################################" + ${ECHOCMD} "" + ${ECHOCMD} "" + + ${ECHOCMD} "${BG_BLUE}Step 2: create timer unit (/etc/systemd/system/lynis.timer)${NORMAL}" + ${ECHOCMD} "" + + ${ECHOCMD} "#################################################################################" + ${ECHOCMD} "#" + ${ECHOCMD} "# Lynis timer file for systemd" + ${ECHOCMD} "#" + ${ECHOCMD} "#################################################################################" + ${ECHOCMD} "# Do not remove, so Lynis can provide a hint when a newer unit is available" + ${ECHOCMD} "# Generator=lynis" + ${ECHOCMD} "# Version=1" + ${ECHOCMD} "#################################################################################" + ${ECHOCMD} "" + ${ECHOCMD} "[Unit]" + ${ECHOCMD} "Description=Daily timer for the Lynis security audit and vulnerability scanner" + ${ECHOCMD} "" + ${ECHOCMD} "[Timer]" + ${ECHOCMD} "OnCalendar=daily" + ${ECHOCMD} "RandomizedDelaySec=1800" + ${ECHOCMD} "Persistent=false" + ${ECHOCMD} "" + ${ECHOCMD} "[Install]" + ${ECHOCMD} "WantedBy=timers.target" + ${ECHOCMD} "" + ${ECHOCMD} "#################################################################################" + ${ECHOCMD} "" + ${ECHOCMD} "" + + ${ECHOCMD} "${BG_BLUE}Step 3 - Enable the timer${NORMAL}" + + ${ECHOCMD} "" + ${ECHOCMD} "Tell systemd you made changes: systemctl daemon-reload" + ${ECHOCMD} "" + ${ECHOCMD} "Enable and start the timer (so no reboot is needed): systemctl enable --now lynis.timer" + ${ECHOCMD} "" + ${ECHOCMD} "" + ${ECHOCMD} "${BG_BLUE}Optional - Customize${NORMAL}" + ${ECHOCMD} "" + ${ECHOCMD} "Want to override the timer? Run: systemctl edit lynis.timer" + ${ECHOCMD} "Note: set the timer by first resetting it, then set the preferred value" + ${ECHOCMD} "" + ${ECHOCMD} "[Timer]" + ${ECHOCMD} "OnCalendar=" + ${ECHOCMD} "OnCalendar=*-*-* 03:00:00" + ${ECHOCMD} "" + ;; + *) ${ECHOCMD} "Unknown argument '${RED}$1${NORMAL}' for lynis generate" ;; + esac +else + ${ECHOCMD} "\n ${WHITE}Provide an additional argument${NORMAL}\n\n" + for ITEM in ${GENERATE_ARGS}; do + ${ECHOCMD} " lynis generate ${BROWN}${ITEM}${NORMAL}" + done + ${ECHOCMD} "\n" + ${ECHOCMD} "" + ${ECHOCMD} "Extended help about the generate command can be provided with: $0 show commands generate" +fi + + +ExitClean + +# The End diff --git a/include/helper_show b/include/helper_show index 6e0738e6..94d839be 100644 --- a/include/helper_show +++ b/include/helper_show @@ -28,7 +28,7 @@ # ###################################################################### -COMMANDS="audit configure show update upload-only" +COMMANDS="audit configure generate show update upload-only" HELPERS="audit configure show update" OPTIONS="--auditor\n--cronjob (--cron)\n--debug\n--developer\n--help (-h)\n--license-key\n--log-file\n--manpage (--man)\n--no-colors\n--no-log\n--pentest\n--profile\n--plugin-dir\n--quick (-Q)\n--quiet (-q)\n--report-file\n--reverse-colors\n--skip-plugins\n--tests\n--tests-from-category\n--tests-from-group\n--upload\n--verbose\n--version (-V)\n--wait\n--warnings-only" @@ -94,6 +94,20 @@ AUDIT_HELP=" " +GENERATE_ARGS="( --save )" +GENERATE_HELP=" + Generate random value for hostid and hostid2 + ${WHITE}lynis generate hostids${NORMAL} + + Generate and save values + ${WHITE}lynis generate hostids --save${NORMAL} + + Generate systemd units to run Lynis on a schedule (e.g. daily) + ${WHITE}lynis generate systemd-units${NORMAL} + +" + + UPDATE_ARGS="check info" UPDATE_HELP=" ${CYAN}update info${NORMAL} @@ -149,15 +163,15 @@ if [ $# -gt 0 ]; then if [ ! -z "${CHANGELOG}" ]; then LogText "Result: found changelog file: ${CHANGELOG}"; break; fi done if [ ! -z "${CHANGELOG}" ]; then - SEARCH=$(egrep "^${PROGRAM_NAME} ${SEARCH_VERSION}" ${CHANGELOG}) + SEARCH=$(sed 's/^## //' ${CHANGELOG} | grep -E "^${PROGRAM_NAME} ${SEARCH_VERSION}") if [ $? -eq 0 ]; then while read -r LINE; do if [ ${STARTED} -eq 0 ]; then - SEARCH=$(echo ${LINE} | egrep "^${PROGRAM_NAME} ${SEARCH_VERSION}") + SEARCH=$(echo ${LINE} | sed 's/^## //' | grep -E "^${PROGRAM_NAME} ${SEARCH_VERSION}") if [ $? -eq 0 ]; then STARTED=1; ${ECHOCMD} "${BOLD}${LINE}${NORMAL}"; fi else # Stop if we find the next Lynis version - SEARCH=$(echo ${LINE} | egrep "^${PROGRAM_NAME} [0-9]\.[0-9]\.[0-9]") + SEARCH=$(echo ${LINE} | sed 's/^## //' | grep -E "^${PROGRAM_NAME} [0-9]\.[0-9]\.[0-9]") if [ $? -eq 0 ]; then break else @@ -172,7 +186,7 @@ if [ $# -gt 0 ]; then ${ECHOCMD} "$0 lynis show changelog [version]" ${ECHOCMD} "" ${ECHOCMD} "${HEADER}${PROGRAM_NAME} versions:${NORMAL}" - SEARCH=$(egrep "^Lynis [0-9]\.[0-9]\.[0-9] " ${CHANGELOG} | awk '{print $2}' | sort -n) + SEARCH=$(sed 's/^## //' ${CHANGELOG} | grep -E "^Lynis [0-9]\.[0-9]\.[0-9] " | awk '{print $2}' | sort -n) ${ECHOCMD} ${SEARCH} ExitFatal fi @@ -274,6 +288,7 @@ if [ $# -gt 0 ]; then shift case $1 in "audit") ${ECHOCMD} "${AUDIT_HELP}" ;; + "generate") ${ECHOCMD} "${GENERATE_HELP}" ;; "show") ${ECHOCMD} "${SHOW_HELP}" ;; "update") ${ECHOCMD} "${UPDATE_HELP}" ;; "upload-only") ${ECHOCMD} "${UPLOAD_ONLY_HELP}" ;; diff --git a/include/osdetection b/include/osdetection index 5ab80293..a6c18bb5 100644 --- a/include/osdetection +++ b/include/osdetection @@ -477,7 +477,7 @@ ECHONB="" case ${OS} in - "AIX") ECHOCMD="echo" ;; + "AIX") ECHOCMD="echo"; ECHONB="printf" ;; "DragonFly"|"FreeBSD"|"NetBSD") ECHOCMD="echo -e"; ECHONB="echo -n" ;; "macOS" | "Mac OS X") ECHOCMD="echo"; ECHONB="/bin/echo -n" ;; "Solaris") ECHOCMD="echo" ; test -f /usr/ucb/echo && ECHONB="/usr/ucb/echo -n" ;; diff --git a/include/parameters b/include/parameters index 96d63524..001044a5 100644 --- a/include/parameters +++ b/include/parameters @@ -22,8 +22,21 @@ # ################################################################################# # - # Check number of parameters submitted (at least one is needed) PARAMCOUNT=$# + + + # Input validation on provided parameters and their arguments + COUNT=0 + for I in "$@"; do + COUNT=$((COUNT + 1)) + if ! SafeInput "${I}"; then + echo "Execution of ${PROGRAM_NAME} stopped as we found unexpected input or invalid characters in argument ${COUNT}" + echo "Do you believe this is in error? Let us know: ${PROGRAM_AUTHOR_CONTACT}" + ExitFatal "Program execution stopped due to security measure" + fi + done + + # Parse arguments while [ $# -ge 1 ]; do case $1 in # Helpers first @@ -36,12 +49,13 @@ if [ $# -gt 1 ]; then case $2 in "dockerfile") - if [ "$3" = "" ]; then + if [ $# = 2 ]; then echo "${RED}Error: ${WHITE}Missing file name or URL${NORMAL}" - echo "Example: $0 audit dockerfile /root/Dockerfile" + echo "Example: $0 audit dockerfile /path/to/Dockerfile" ExitFatal else shift; shift + CHECK_BINARIES=1 HELPER_PARAMS="$1" HELPER="audit_dockerfile" break @@ -111,6 +125,24 @@ break ;; + # Generate data + generate) + CHECK_BINARIES=0 + HELPER="generate" + LOGTEXT=0 + QUIET=1 + RUN_HELPERS=1 + RUN_TESTS=0 + RUN_UPDATE_CHECK=0 + SKIP_GETHOSTID=1 + SKIP_PLUGINS=1 + SKIP_VM_DETECTION=1 + SHOW_PROGRAM_DETAILS=0 + SHOW_TOOL_TIPS=0 + shift; HELPER_PARAMS="$@" + break + ;; + # Show Lynis details show) CHECK_BINARIES=0 @@ -201,7 +233,7 @@ # Cronjob support --cron-job | --cronjob | --cron) CRONJOB=1 - CHECK=1; QUICKMODE=1; COLORS=0; NEVERBREAK=1 # Use some defaults (-c, -Q, no colors) + CHECK=1; COLORS=0; NEVERBREAK=1 # Use some defaults ('audit system', -Q, no colors) RemoveColors ;; @@ -313,7 +345,6 @@ # Quiet mode --quiet | -q | --silent) QUIET=1 - QUICKMODE=1 # Run non-interactive ;; # Non-interactive mode @@ -412,7 +443,6 @@ # Warnings --warnings-only | --show-warnings-only) SHOW_WARNINGS_ONLY=1 - QUICKMODE=1 QUIET=1 ;; @@ -433,5 +463,15 @@ done + # Ensure non-interactive mode when running quietly or as cronjob + if [ ${CRONJOB} -eq 1 -o ${QUIET} -eq 1 ]; then + if [ ${QUICKMODE} -eq 0 ]; then + if [ ${QUIET} -eq 0 ]; then + echo "Switched back to quick mode (cron/non-interactive/quiet)" + fi + QUICKMODE=1 + fi + fi + #================================================================================ # Lynis - Security Auditing and System Hardening for Linux and UNIX - https://cisofy.com diff --git a/include/profiles b/include/profiles index eba67427..f8935ece 100644 --- a/include/profiles +++ b/include/profiles @@ -32,9 +32,38 @@ for PROFILE in ${PROFILES}; do LogText "Reading profile/configuration ${PROFILE}" - FIND=$(egrep "^config:|^[a-z-].*=" ${PROFILE} | sed 's/ /!space!/g') - for CONFIGOPTION in ${FIND}; do - if ContainsString "config:" "${CONFIGOPTION}"; then + + # Show deprecation message for old config entries + FOUND=0 + #DATA=$(egrep "^config:" ${PROFILE} | od --address-radix=none -t a | sed 's/ /!space!/g') + #if ! IsEmpty "${DATA}"; then FOUND=1; fi + # Items such as 'apache:' + DATA=$(egrep "^[a-z-]{1,}:" ${PROFILE} | od --address-radix=none -t a | sed 's/ /!space!/g') + if ! IsEmpty "${DATA}"; then FOUND=1; fi + + if [ ${FOUND} -eq 1 ]; then + DisplayWarning "Your profile contains old-style configuration entries. See log file for more details and how to convert these entries" + LogText "Your profile has one or more configuration items that are in an old format (lines starting with key:value). They need to be converted into the new format (key=value)." + LogText "Tip: Use egrep to see the relevant matches (egrep \"^[a-z-]{1,}:\" custom.prf)" + sleep 30 + fi + + # Security check for unexpected and possibly harmful escape characters + DATA=$(grep -v '^$\|^ \|^#\|^config:' ${PROFILE} | tr -d '[:alnum:]/\[\]\(\)\-_\|,\.:;= \n\r' | od --address-radix=none -t a | sed 's/ /!space!/g') + if ! IsEmpty "${DATA}"; then + DisplayWarning "Your profile '${PROFILE}' contains unexpected characters. See the log file for more information." + LogText "Found unexpected or possibly harmful characters in the profile. See output below." + for I in "${DATA}"; do + I=$(echo ${I} | sed 's/!space!/ /g') + LogText "Output: ${I}" + done + sleep 30 + fi + + # Now parse the profile and filter out unwanted characters + DATA=$(egrep "^config:|^[a-z-].*=" ${PROFILE} | tr -dc '[:alnum:]/\[\]\(\)\-_\|,\.:;= \n\r' | sed 's/ /!space!/g') + for CONFIGOPTION in ${DATA}; do + if ContainsString "^config:" "${CONFIGOPTION}"; then # Old style configuration OPTION=$(echo ${CONFIGOPTION} | cut -d ':' -f2) VALUE=$(echo ${CONFIGOPTION} | cut -d ':' -f3 | sed 's/!space!/ /g') @@ -90,7 +119,7 @@ ;; # Ignore configuration data - config-data) + config-data | permdir | permfile) Debug "Ignoring configuration option, as it will be used by a specific test" ;; @@ -239,6 +268,11 @@ LogText "Plugin '${VALUE}' enabled according profile (${PROFILE})" ;; + disable-plugin) + LogText "Plugin '${VALUE}' disabled according profile (${PROFILE})" + DISABLED_PLUGINS="${DISABLED_PLUGINS} ${VALUE}" + ;; + # Plugin directory plugindir | plugin-dir) if IsEmpty "${PLUGINDIR}"; then @@ -256,9 +290,9 @@ # Quick (no waiting for keypresses) quick) - # Quick mode (SKIP_PLUGINS) might already be set outside profile, so store in different variable - SETTING_QUICK_MODE=0 # default is no - FIND=$(echo "${VALUE}" | egrep "^(1|true|yes)$") && QUICKMODE=1 + # Quick mode might already be set outside profile, so store in different variable + SETTING_QUICK_MODE=1 # default is yes + FIND=$(echo "${VALUE}" | egrep "^(0|false|no)$") && QUICKMODE=0 if [ ! -z "${FIND}" ]; then SETTING_QUICK_MODE=1; fi Debug "Quickmode set to ${SETTING_QUICK_MODE}" AddSetting "quick" "${SETTING_QUICK_MODE}" "Quick mode (non-interactive)" @@ -328,6 +362,13 @@ AddSetting "ssl-certificate-paths" "${SSL_CERTIFICATE_PATHS}" "Paths for SSL certificates" ;; + ssl-certificate-paths-to-ignore) + # Retrieve paths to ignore when searching for certificates. Strip special characters, replace possible spaces + SSL_CERTIFICATE_PATHS_TO_IGNORE=$(echo ${VALUE} | tr -d '[:cntrl:]' | sed 's/ /__space__/g' | tr ':' ' ') + Debug "SSL paths to ignore: ${SSL_CERTIFICATE_PATHS_TO_IGNORE}" + AddSetting "ssl-certificate-paths-to-ignore" "${SSL_CERTIFICATE_PATHS_TO_IGNORE}" "Paths that should be ignored for SSL certificates" + ;; + # Set strict mode for development and quality purposes strict) FIND=$(echo "${VALUE}" | egrep "^(1|true|yes)") && SET_STRICT=1 @@ -441,10 +482,14 @@ # Catch all bad options and bail out *) LogText "Unknown option ${OPTION} (with value: ${VALUE})" - ${ECHOCMD} "" - ${ECHOCMD} "${RED}Error${NORMAL}: found one or more errors in profile ${PROFILE}" - ${ECHOCMD} "${WHITE}Details${NORMAL}: Unknown option '${YELLOW}${OPTION}${NORMAL}' found (with value: ${VALUE})" - ${ECHOCMD} "" + + ${ECHOCMD:-echo} "" + ${ECHOCMD:-echo} "${RED}Error${NORMAL}: found one or more errors in profile ${PROFILE}" + ${ECHOCMD:-echo} "" + ${ECHOCMD:-echo} "" + ${ECHOCMD:-echo} "Full line: ${CONFIGOPTION}" + ${ECHOCMD:-echo} "${WHITE}Details${NORMAL}: Unknown option '${YELLOW}${OPTION}${NORMAL}' found (with value: ${VALUE})" + ${ECHOCMD:-echo} "" ExitFatal ;; diff --git a/include/report b/include/report index b200f6be..2df666e4 100644 --- a/include/report +++ b/include/report @@ -22,55 +22,79 @@ # ################################################################################# # + + # Add data fields to report file + Report "dhcp_client_running=${DHCP_CLIENT_RUNNING}" + Report "arpwatch_running=${ARPWATCH_RUNNING}" + + # Report firewall installed for now, if we found one active. Next step would be determining binaries first and apply additional checks. + Report "firewall_active=${FIREWALL_ACTIVE}" + Report "firewall_empty_ruleset=${FIREWALL_EMPTY_RULESET}" + Report "firewall_installed=${FIREWALL_ACTIVE}" + + if [ ! -z "${INSTALLED_PACKAGES}" ]; then Report "installed_packages_array=${INSTALLED_PACKAGES}"; fi + + Report "package_audit_tool=${PACKAGE_AUDIT_TOOL}" + Report "package_audit_tool_found=${PACKAGE_AUDIT_TOOL_FOUND}" + Report "vulnerable_packages_found=${VULNERABLE_PACKAGES_FOUND}" + + # Hardening Index - # Define approximately how strong a machine has been hardened - # If no hardening has been found, set value to 1 - if [ ${HPPOINTS} -eq 0 ]; then HPPOINTS=1; HPTOTAL=100; fi - HPINDEX=$((HPPOINTS * 100 / HPTOTAL)) - HPAOBLOCKS=$((HPPOINTS * 20 / HPTOTAL)) - # Set color related to rating - if [ ${HPINDEX} -lt 50 ]; then - HPCOLOR="${RED}" - HIDESCRIPTION="System has not or a low amount been hardened" - elif [ ${HPINDEX} -gt 49 -a ${HPINDEX} -lt 80 ]; then - HPCOLOR="${YELLOW}" - HIDESCRIPTION="System has been hardened, but could use additional hardening" - elif [ ${HPINDEX} -gt 79 -a ${HPINDEX} -lt 90 ]; then - HPCOLOR="${GREEN}" - HIDESCRIPTION="System seem to be decent hardened" - elif [ ${HPINDEX} -gt 89 ]; then - HPCOLOR="${GREEN}" - HIDESCRIPTION="System seem to be well hardened" - fi + # Goal: + # Provide a visual way to show how much the system is hardened + # + # Important: + # The index gives a simplified version of the measures taken on the system. + # It should be used to get a first impression about the state of the system or to compare similar systems. + # Getting the maximum score (100 or full bar) does not indicate that the system is fully secured. - case ${HPAOBLOCKS} in - 0) HPBLOCKS="#"; HPEMPTY=" " ;; - 1) HPBLOCKS="#"; HPEMPTY=" " ;; - 2) HPBLOCKS="##"; HPEMPTY=" " ;; - 3) HPBLOCKS="###"; HPEMPTY=" " ;; - 4) HPBLOCKS="####"; HPEMPTY=" " ;; - 5) HPBLOCKS="#####"; HPEMPTY=" " ;; - 6) HPBLOCKS="######"; HPEMPTY=" " ;; - 7) HPBLOCKS="#######"; HPEMPTY=" " ;; - 8) HPBLOCKS="########"; HPEMPTY=" " ;; - 9) HPBLOCKS="#########"; HPEMPTY=" " ;; - 10) HPBLOCKS="##########"; HPEMPTY=" " ;; - 11) HPBLOCKS="###########"; HPEMPTY=" " ;; - 12) HPBLOCKS="############"; HPEMPTY=" " ;; - 13) HPBLOCKS="#############"; HPEMPTY=" " ;; - 14) HPBLOCKS="##############"; HPEMPTY=" " ;; - 15) HPBLOCKS="###############"; HPEMPTY=" " ;; - 16) HPBLOCKS="################"; HPEMPTY=" " ;; - 17) HPBLOCKS="#################"; HPEMPTY=" " ;; - 18) HPBLOCKS="##################"; HPEMPTY=" " ;; - 19) HPBLOCKS="###################"; HPEMPTY=" " ;; - 20) HPBLOCKS="####################"; HPEMPTY="" ;; - esac + # If no hardening has been found, set value to 1 + if [ ${HPPOINTS} -eq 0 ]; then HPPOINTS=1; HPTOTAL=100; fi + HPINDEX=$((HPPOINTS * 100 / HPTOTAL)) + HPAOBLOCKS=$((HPPOINTS * 20 / HPTOTAL)) + # Set color related to rating + if [ ${HPINDEX} -lt 50 ]; then + HPCOLOR="${RED}" + HIDESCRIPTION="System has not or a low amount been hardened" + elif [ ${HPINDEX} -gt 49 -a ${HPINDEX} -lt 80 ]; then + HPCOLOR="${YELLOW}" + HIDESCRIPTION="System has been hardened, but could use additional hardening" + elif [ ${HPINDEX} -gt 79 -a ${HPINDEX} -lt 90 ]; then + HPCOLOR="${GREEN}" + HIDESCRIPTION="System seem to be decent hardened" + elif [ ${HPINDEX} -gt 89 ]; then + HPCOLOR="${GREEN}" + HIDESCRIPTION="System seem to be well hardened" + fi - HPGRAPH="[${HPCOLOR}${HPBLOCKS}${NORMAL}${HPEMPTY}]" - LogText "Hardening index : [${HPINDEX}] [${HPBLOCKS}${HPEMPTY}]" - LogText "Hardening strength: ${HIDESCRIPTION}" + case ${HPAOBLOCKS} in + 0) HPBLOCKS="#"; HPEMPTY=" " ;; + 1) HPBLOCKS="#"; HPEMPTY=" " ;; + 2) HPBLOCKS="##"; HPEMPTY=" " ;; + 3) HPBLOCKS="###"; HPEMPTY=" " ;; + 4) HPBLOCKS="####"; HPEMPTY=" " ;; + 5) HPBLOCKS="#####"; HPEMPTY=" " ;; + 6) HPBLOCKS="######"; HPEMPTY=" " ;; + 7) HPBLOCKS="#######"; HPEMPTY=" " ;; + 8) HPBLOCKS="########"; HPEMPTY=" " ;; + 9) HPBLOCKS="#########"; HPEMPTY=" " ;; + 10) HPBLOCKS="##########"; HPEMPTY=" " ;; + 11) HPBLOCKS="###########"; HPEMPTY=" " ;; + 12) HPBLOCKS="############"; HPEMPTY=" " ;; + 13) HPBLOCKS="#############"; HPEMPTY=" " ;; + 14) HPBLOCKS="##############"; HPEMPTY=" " ;; + 15) HPBLOCKS="###############"; HPEMPTY=" " ;; + 16) HPBLOCKS="################"; HPEMPTY=" " ;; + 17) HPBLOCKS="#################"; HPEMPTY=" " ;; + 18) HPBLOCKS="##################"; HPEMPTY=" " ;; + 19) HPBLOCKS="###################"; HPEMPTY=" " ;; + 20) HPBLOCKS="####################"; HPEMPTY="" ;; + esac + + HPGRAPH="[${HPCOLOR}${HPBLOCKS}${NORMAL}${HPEMPTY}]" + LogText "Hardening index : [${HPINDEX}] [${HPBLOCKS}${HPEMPTY}]" + LogText "Hardening strength: ${HIDESCRIPTION}" # Only show overview if not running in quiet mode diff --git a/include/tests_authentication b/include/tests_authentication index fe8ece41..6c867da6 100644 --- a/include/tests_authentication +++ b/include/tests_authentication @@ -40,7 +40,12 @@ if [ ${SKIPTEST} -eq 0 ]; then # Search accounts with UID 0 LogText "Test: Searching accounts with UID 0" - FIND=$(${GREPBINARY} ':0:' ${ROOTDIR}etc/passwd | ${EGREPBINARY} -v '^#|^root:|^(\+:\*)?:0:0:::' | ${CUTBINARY} -d ":" -f1,3 | ${GREPBINARY} ':0') + # Check if device is a QNAP, as the root user is called admin, and not root + if [ ${QNAP_DEVICE} -eq 1 ]; then + FIND=$(${GREPBINARY} ':0:' ${ROOTDIR}etc/passwd | ${EGREPBINARY} -v '^#|^admin:|^(\+:\*)?:0:0:::' | ${CUTBINARY} -d ":" -f1,3 | ${GREPBINARY} ':0') + else + FIND=$(${GREPBINARY} ':0:' ${ROOTDIR}etc/passwd | ${EGREPBINARY} -v '^#|^root:|^(\+:\*)?:0:0:::' | ${CUTBINARY} -d ":" -f1,3 | ${GREPBINARY} ':0') + fi if [ ! -z "${FIND}" ]; then Display --indent 2 --text "- Administrator accounts" --result "${STATUS_WARNING}" --color RED LogText "Result: Found more than one administrator accounts" @@ -669,8 +674,8 @@ if [ -d ${DIR} -a ! -L ${DIR} ]; then LogText "Result: directory ${DIR} exists" # Search in the specified directory - if [ "${OS}" = "Solaris" ]; then - # Solaris does not support -maxdepth + if [ "${OS}" = "AIX" -o "${OS}" = "Solaris" ]; then + # AIX/Solaris does not support -maxdepth FIND=$(find ${DIR} -type f -name "pam_*.so" -print | sort) else FIND=$(find ${DIR} -maxdepth 1 -type f -name "pam_*.so" -print | sort) @@ -698,25 +703,32 @@ # # Test : AUTH-9278 # Description : Search LDAP support in PAM files - Register --test-no AUTH-9278 --weight L --network NO --category security --description "Checking LDAP pam status" + Register --test-no AUTH-9278 --weight L --network NO --category security --description "Determine LDAP support in PAM files" if [ ${SKIPTEST} -eq 0 ]; then - LogText "Test: checking presence /etc/pam.d/common-auth" - if [ -f /etc/pam.d/common-auth ]; then - LogText "Result: file /etc/pam.d/common-auth exists" - LogText "Test: checking presence LDAP module" - FIND=$(${GREPBINARY} "^auth.*ldap" /etc/pam.d/common-auth) - if [ ! "${FIND}" = "" ]; then - LogText "Result: LDAP module present" - LogText "Output: ${FIND}" - Display --indent 2 --text "- LDAP module in PAM" --result "${STATUS_FOUND}" --color GREEN - LDAP_AUTH_ENABLED=1 - LDAP_PAM_ENABLED=1 + AUTH_FILES="${ROOTDIR}etc/pam.d/common-auth ${ROOTDIR}etc/pam.d/system-auth" + for FILE in ${AUTH_FILES}; do + LogText "Test: checking presence ${FILE}" + if [ -f ${FILE} ]; then + LogText "Result: file ${FILE} exists" + LogText "Test: checking presence LDAP module" + FIND=$(${GREPBINARY} "^auth.*ldap" ${FILE}) + if [ ! -z "${FIND}" ]; then + LogText "Result: LDAP module present" + LogText "Output: ${FIND}" + LDAP_AUTH_ENABLED=1 + LDAP_PAM_ENABLED=1 + else + LogText "Result: LDAP module not found" + fi else - LogText "Result: LDAP module not found" - Display --indent 2 --text "- LDAP module in PAM" --result "${STATUS_NOT_FOUND}" --color WHITE + LogText "Result: file ${FILE} not found, skipping test" fi + done + + if [ ${LDAP_PAM_ENABLED} -eq 1 ]; then + Display --indent 2 --text "- LDAP module in PAM" --result "${STATUS_FOUND}" --color GREEN else - LogText "Result: file /etc/pam.d/common-auth not found, skipping test" + Display --indent 2 --text "- LDAP module in PAM" --result "${STATUS_NOT_FOUND}" --color WHITE fi fi # diff --git a/include/tests_boot_services b/include/tests_boot_services index 5495938c..42b8dab1 100644 --- a/include/tests_boot_services +++ b/include/tests_boot_services @@ -96,7 +96,11 @@ ;; "init" | "initsplash") - SERVICE_MANAGER="SysV Init" + if [ -d ${ROOTDIR}etc/rc.d ]; then + SERVICE_MANAGER="bsdrc.d" + else + SERVICE_MANAGER="SysV Init" + fi ;; systemd) SERVICE_MANAGER="systemd" diff --git a/include/tests_crypto b/include/tests_crypto index 4188dea9..6d0d5384 100644 --- a/include/tests_crypto +++ b/include/tests_crypto @@ -34,55 +34,78 @@ COUNT_EXPIRED=0 COUNT_TOTAL=0 FOUNDPROBLEM=0 - sSSL_PATHS=$(echo ${SSL_CERTIFICATE_PATHS} | ${SEDBINARY} 's/:/ /g') - sSSL_PATHS=$(echo ${sSSL_PATHS} | ${SEDBINARY} 's/^ //' | ${TRBINARY} " " "\n" | ${SORTBINARY} | uniq | ${TRBINARY} "\n" " ") + SKIP=0 + sSSL_PATHS=$(echo ${SSL_CERTIFICATE_PATHS} | ${SEDBINARY} 's/:space:/__space__/g' | ${SEDBINARY} 's/:/ /g') + sSSL_PATHS=$(echo ${sSSL_PATHS} | ${SEDBINARY} 's/^ //' | ${SORTBINARY} | ${UNIQBINARY}) LogText "Paths to scan: ${sSSL_PATHS}" + IGNORE_PATHS_PRINT=$(echo ${SSL_CERTIFICATE_PATHS_TO_IGNORE} | ${SEDBINARY} 's/:/, /g' | ${SEDBINARY} 's/__space__/ /g' | ${SEDBINARY} 's/^ //' | ${SORTBINARY} | ${UNIQBINARY}) + LogText "Paths to ignore: ${IGNORE_PATHS_PRINT}" + for DIR in ${sSSL_PATHS}; do COUNT_DIR=0 if [ -d ${DIR} ]; then FileIsReadable ${DIR} if [ ${CANREAD} -eq 1 ]; then + LASTSUBDIR="" LogText "Result: found directory ${DIR}" # Search for certificate files - FILES=$(${FINDBINARY} ${DIR} -type f 2> /dev/null | ${EGREPBINARY} ".crt$|.pem$|^cert" | ${SORTBINARY} | ${SEDBINARY} 's/ /:space:/g') + FILES=$(${FINDBINARY} ${DIR} -type f 2> /dev/null | ${EGREPBINARY} ".crt$|.pem$|^cert" | ${SORTBINARY} | ${SEDBINARY} 's/ /__space__/g') for FILE in ${FILES}; do - FILE=$(echo ${FILE} |${SEDBINARY} 's/:space:/ /g') - COUNT_DIR=$((COUNT_DIR + 1)) - FileIsReadable "${FILE}" - if [ ${CANREAD} -eq 1 ]; then - # Only check the files that are not installed by a package - if ! FileInstalledByPackage "${FILE}"; then - LogText "Test: test if file is a certificate" - OUTPUT=$(${GREPBINARY} -q 'BEGIN CERT' "${FILE}") - if [ $? -eq 0 ]; then - LogText "Result: file is a certificate" - LogText "Test: checking certificate details" - FIND=$(${OPENSSLBINARY} x509 -noout -in "${FILE}" -enddate 2> /dev/null | ${GREPBINARY} "^notAfter") + FILE=$(echo ${FILE} | ${SEDBINARY} 's/__space__/ /g') + # See if we need to skip this path + SUBDIR=$(echo ${FILE} | ${AWKBINARY} -F/ 'sub(FS $NF,x)' | ${SEDBINARY} 's/__space__/ /g') + # If we discover a new directory, do evaluation + #Debug "File : ${FILE}" + #Debug "Lastdir: ${LASTSUBDIR}" + #Debug "Curdir : ${SUBDIR}" + if [ ! "${SUBDIR}" = "${LASTSUBDIR}" ]; then + SKIP=0 + # Now check if this path is on the to-be-ignored list + for D in ${SSL_CERTIFICATE_PATHS_TO_IGNORE}; do + if Equals "${D}" "${SUBDIR}"; then + SKIP=1 + LogText "Result: skipping directory (${SUBDIR}) as it is on ignore list" + fi + done + fi + if [ ${SKIP} -eq 0 ]; then + #Debug "Testing ${FILE} in path: $SUBDIR" + COUNT_DIR=$((COUNT_DIR + 1)) + FileIsReadable "${FILE}" + if [ ${CANREAD} -eq 1 ]; then + # Only check the files that are not installed by a package + if ! FileInstalledByPackage "${FILE}"; then + OUTPUT=$(${GREPBINARY} -q 'BEGIN CERT' "${FILE}") if [ $? -eq 0 ]; then - # Check certificate where 'end date' has been expired - FIND=$(${OPENSSLBINARY} x509 -noout -checkend 0 -in "${FILE}" -enddate 2> /dev/null) - EXIT_CODE=$? - CERT_CN=$(${OPENSSLBINARY} x509 -noout -subject -in "${FILE}" 2> /dev/null | ${SEDBINARY} -e 's/^subject.*CN=\([a-zA-Z0-9\.\-\*]*\).*$/\1/') - CERT_NOTAFTER=$(${OPENSSLBINARY} x509 -noout -enddate -in "${FILE}" 2> /dev/null | ${AWKBINARY} -F= '{if ($1=="notAfter") { print $2 }}') - Report "certificate[]=${FILE}|${EXIT_CODE}|cn:${CERT_CN};notafter:${CERT_NOTAFTER};|" - if [ ${EXIT_CODE} -eq 0 ]; then - LogText "Result: certificate ${FILE} seems to be correct and still valid" + LogText "Result: file is a certificate file" + FIND=$(${OPENSSLBINARY} x509 -noout -in "${FILE}" -enddate 2> /dev/null | ${GREPBINARY} "^notAfter") + if [ $? -eq 0 ]; then + # Check certificate where 'end date' has been expired + FIND=$(${OPENSSLBINARY} x509 -noout -checkend 0 -in "${FILE}" -enddate 2> /dev/null) + EXIT_CODE=$? + CERT_CN=$(${OPENSSLBINARY} x509 -noout -subject -in "${FILE}" 2> /dev/null | ${SEDBINARY} -e 's/^subject.*CN=\([a-zA-Z0-9\.\-\*]*\).*$/\1/') + CERT_NOTAFTER=$(${OPENSSLBINARY} x509 -noout -enddate -in "${FILE}" 2> /dev/null | ${AWKBINARY} -F= '{if ($1=="notAfter") { print $2 }}') + Report "certificate[]=${FILE}|${EXIT_CODE}|cn:${CERT_CN};notafter:${CERT_NOTAFTER};|" + if [ ${EXIT_CODE} -eq 0 ]; then + LogText "Result: certificate ${FILE} seems to be correct and still valid" + else + FOUNDPROBLEM=1 + COUNT_EXPIRED=$((COUNT_EXPIRED + 1)) + LogText "Result: certificate ${FILE} has been expired" + fi else - FOUNDPROBLEM=1 - COUNT_EXPIRED=$((COUNT_EXPIRED + 1)) - LogText "Result: certificate ${FILE} has been expired" + LogText "Result: skipping tests for this file (${FILE}) as it is most likely not a certificate (is it a key file?)" fi else - LogText "Result: skipping tests for this file (${FILE}) as it is most likely not a certificate (a key file?)" + LogText "Result: skipping test for this file (${FILE}) as we could not find 'BEGIN CERT'" fi - else - LogText "Result: skipping test for this file (${FILE}) as we could not find 'BEGIN CERT'" fi + else + LogText "Result: can not read file ${FILE} (no permission)" fi - else - LogText "Result: can not read file ${FILE} (no permission)" fi + LASTSUBDIR="${SUBDIR}" done COUNT_TOTAL=$((COUNT_TOTAL + COUNT_DIR)) LogText "Result: found ${COUNT_DIR} certificates in ${DIR}" @@ -105,6 +128,30 @@ fi # ################################################################################# +# + # Test : CRYP-7930 + # Description : Determine if system uses disk or file encryption + Register --test-no CRYP-7930 --weight L --network NO --category security --description "Determine if system uses disk or file encryption" + if [ ${SKIPTEST} -eq 0 ]; then + FILE="${ROOTDIR}etc/crypttab" + if [ -f ${FILE} ]; then + LogText "Result: crypttab file (${FILE}) exists" + DATA=$(${GREPBINARY} "^[a-z]" ${FILE} | ${TRBINARY} -cd '[:alnum:]_\-=,\n\t ' | ${SEDBINARY} 's/[[:blank:]]/__space__/g') + for LINE in ${DATA}; do + LINE=$(echo ${LINE} | ${SEDBINARY} 's/__space__/ /g') + if ContainsString "luks," "${LINE}"; then + PARTITION=$(echo ${LINE} | ${AWKBINARY} '{print $1}' | ${AWKBINARY} -F_ '{print $1}') + LogText "Result: Found LUKS encryption on partition ${PARTITION}" + Report "encryption[]=luks,partition,${PARTITION}" + fi + done + unset DATA LINE PARTITION + else + LogText "Result: crypttab file (${FILE}) does not exist" + fi + fi +# +################################################################################# # WaitForKeyPress diff --git a/include/tests_file_permissions b/include/tests_file_permissions index 20136488..2c43fc0e 100644 --- a/include/tests_file_permissions +++ b/include/tests_file_permissions @@ -34,10 +34,10 @@ LogText "Test: Checking file permissions" for PROFILE in ${PROFILES}; do LogText "Using profile ${PROFILE} for baseline." - FIND=$(${EGREPBINARY} '^permfile:|^permdir:' ${PROFILE} | ${CUTBINARY} -d: -f2) + FIND=$(${EGREPBINARY} '^permfile=|^permdir=' ${PROFILE} | ${CUTBINARY} -d= -f2) for I in ${FIND}; do LogText "Checking ${I}" - CheckFilePermissions ${I} + CheckFilePermissions "${I}" LogText " Expected permissions: ${PROFILEVALUE}" LogText " Actual permissions: ${FILEVALUE}" LogText " Result: $PERMS" diff --git a/include/tests_filesystems b/include/tests_filesystems index a52bb66b..4e52ea5e 100644 --- a/include/tests_filesystems +++ b/include/tests_filesystems @@ -48,7 +48,11 @@ Display --indent 4 --text "- Checking ${I} mount point" --result SYMLINK --color WHITE elif [ -d ${I} ]; then LogText "Result: directory ${I} exists" - FIND=$(${MOUNTBINARY} | ${AWKBINARY} -v MP=${I} '{ if ($3==MP) { print $3 }}') + case "${OS}" in + "AIX") FIND=$(${MOUNTBINARY} | ${AWKBINARY} -v MP=${I} '{ if ($2==MP) { print $2 }}') ;; + *) FIND=$(${MOUNTBINARY} | ${AWKBINARY} -v MP=${I} '{ if ($3==MP) { print $3 }}') ;; + esac + if IsEmpty "${FIND}"; then LogText "Result: ${I} not found in mount list. Directory most likely stored on / file system" Display --indent 4 --text "- Checking ${I} mount point" --result "${STATUS_SUGGESTION}" --color YELLOW @@ -158,7 +162,27 @@ done else LogText "Result: no EXT file systems found" - Report "file_systems_ext[]=none" + fi + fi +# +################################################################################# +# + # Test : FILE-6324 + # Description : Checking Linux XFS file systems + Register --test-no FILE-6324 --os Linux --weight L --network NO --category security --description "Checking XFS file systems" + if [ ${SKIPTEST} -eq 0 ]; then + LogText "Test: Checking for Linux XFS file systems" + FIND=$(${MOUNTBINARY} -t xfs | ${AWKBINARY} '{ print $3","$5 }') + if [ ! -z "${FIND}" ]; then + LogText "Result: found one or more XFS file systems" + for I in ${FIND}; do + FILESYSTEM=$(echo ${I} | ${CUTBINARY} -d ',' -f1) + FILETYPE=$(echo ${I} | ${CUTBINARY} -d ',' -f2) + LogText "File system: ${FILESYSTEM} (type: ${FILETYPE})" + Report "file_systems_xfs[]=${FILESYSTEM}|${FILETYPE}|" + done + else + LogText "Result: no XFS file systems found" fi fi # @@ -540,13 +564,13 @@ # --------------------------------------------------------- FILESYSTEMS_TO_CHECK="/boot:nodev,noexec,nosuid /dev/shm:nosuid,nodev,noexec /home:nodev,nosuid /tmp:nodev,noexec,nosuid /var:nosuid /var/log:nodev,noexec,nosuid /var/log/audit:nodev,noexec,nosuid /var/tmp:nodev,noexec,nosuid" - Register --test-no FILE-6374 --os Linux --weight L --network NO --category security --description "Checking /boot mount options" + Register --test-no FILE-6374 --os Linux --weight L --network NO --category security --description "Checking partitions mount options" if [ ${SKIPTEST} -eq 0 ]; then if [ -f /etc/fstab ]; then for I in ${FILESYSTEMS_TO_CHECK}; do FILESYSTEM=$(echo ${I} | ${CUTBINARY} -d: -f1) EXPECTED_FLAGS=$(echo ${I} | ${CUTBINARY} -d: -f2 | ${SEDBINARY} 's/,/ /g') - FS_FSTAB=$(${AWKBINARY} -v fs=${FILESYSTEM} '{ if ($2==fs) { print $3 } }' /etc/fstab) + FS_FSTAB=$(${AWKBINARY} -v fs=${FILESYSTEM} '{ if ($2==fs) { print $3 } }' ${ROOTDIR}etc/fstab) if [ "${FS_FSTAB}" = "glusterfs" ]; then EXPECTED_FLAGS=$(echo ${EXPECTED_FLAGS} | ${SEDBINARY} 's/\<\(nodev\|nosuid\)\> *//g') if [ -z "${EXPECTED_FLAGS}" ]; then @@ -554,7 +578,7 @@ fi fi if [ ! -z "${FS_FSTAB}" ]; then - FOUND_FLAGS=$(${AWKBINARY} -v fs=${FILESYSTEM} '{ if ($2==fs) { print $4 } }' /etc/fstab | ${SEDBINARY} 's/,/ /g' | ${TRBINARY} '\n' ' ') + FOUND_FLAGS=$(${AWKBINARY} -v fs=${FILESYSTEM} '{ if ($2==fs) { print $4 } }' ${ROOTDIR}etc/fstab | ${SEDBINARY} 's/,/ /g' | ${TRBINARY} '\n' ' ') LogText "File system: ${FILESYSTEM}" LogText "Expected flags: ${EXPECTED_FLAGS}" LogText "Found flags: ${FOUND_FLAGS}" @@ -562,7 +586,7 @@ FULLY_HARDENED=1 for FLAG in ${EXPECTED_FLAGS}; do FLAG_AVAILABLE=$(echo ${FOUND_FLAGS} | ${GREPBINARY} ${FLAG}) - if [ "${FLAG_AVAILABLE}" = "" ]; then + if [ -z "${FLAG_AVAILABLE}" ]; then LogText "Result: Could not find mount option ${FLAG} on file system ${FILESYSTEM}" FULLY_HARDENED=0 else diff --git a/include/tests_firewalls b/include/tests_firewalls index 85f2b150..735059fe 100644 --- a/include/tests_firewalls +++ b/include/tests_firewalls @@ -596,11 +596,6 @@ ################################################################################# # -# Report firewall installed for now, if we found one active. Next step would be determining binaries first and apply additional checks. -Report "firewall_active=${FIREWALL_ACTIVE}" -Report "firewall_empty_ruleset=${FIREWALL_EMPTY_RULESET}" -Report "firewall_installed=${FIREWALL_ACTIVE}" - WaitForKeyPress # diff --git a/include/tests_insecure_services b/include/tests_insecure_services index 277791d9..841189d8 100644 --- a/include/tests_insecure_services +++ b/include/tests_insecure_services @@ -18,7 +18,7 @@ # ################################################################################# # -# Unsecure services +# Insecure services # ################################################################################# # @@ -28,32 +28,55 @@ # INETD_ACTIVE=0 INETD_CONFIG_FILE="${ROOTDIR}etc/inetd.conf" + INETD_PACKAGE_INSTALLED=0 + XINETD_ACTIVE=0 + XINETD_CONFIG_FILE="${ROOTDIR}etc/xinetd.conf" + XINETD_CONFIG_DIR="${ROOTDIR}etc/xinetd.d" +# +################################################################################# +# + # Test : INSE-8000 + # Description : Check for installed inetd package + Register --test-no INSE-8000 --weight L --network NO --category security --description "Installed inetd package" + if [ ${SKIPTEST} -eq 0 ]; then + # Check for installed inetd daemon + LogText "Test: Checking if inetd is installed" + if PackageIsInstalled "inetd"; then + INETD_PACKAGE_INSTALLED=1 + LogText "Result: inetd is installed" + Display --indent 2 --text "- Installed inetd package" --result "${STATUS_FOUND}" --color YELLOW + #ReportSuggestion ${TEST_NO} "If there are no inetd services required, it is recommended that the daemon be removed" + else + LogText "Result: inetd is NOT installed" + Display --indent 2 --text "- Installed inetd package" --result "${STATUS_NOT_FOUND}" --color GREEN + fi + fi # ################################################################################# # # Test : INSE-8002 # Description : Check for inetd status - Register --test-no INSE-8002 --weight L --network NO --category security --description "Check for enabled inet daemon" + if [ ${INETD_PACKAGE_INSTALLED} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + Register --test-no INSE-8002 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check for enabled inet daemon" if [ ${SKIPTEST} -eq 0 ]; then # Check running processes LogText "Test: Searching for active inet daemon" - IsRunning inetd - if [ ${RUNNING} -eq 1 ]; then + if IsRunning "inetd"; then LogText "Result: inetd is running" - Display --indent 2 --text "- Checking inetd status" --result "ACTIVE" --color GREEN + Display --indent 4 --text "- inetd status" --result "ACTIVE" --color GREEN INETD_ACTIVE=1 else LogText "Result: inetd is NOT running" - Display --indent 2 --text "- Checking inetd status" --result "NOT ACTIVE" --color GREEN + Display --indent 4 --text "- inetd status" --result "NOT ACTIVE" --color GREEN fi fi # ################################################################################# # # Test : INSE-8004 - # Description : Check for inetd configuration file + # Description : Check for inetd configuration file (inetd) if [ ${INETD_ACTIVE} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi - Register --test-no INSE-8004 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check for enabled inet daemon" + Register --test-no INSE-8004 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Presence of inetd configuration file" if [ ${SKIPTEST} -eq 0 ]; then # Check configuration file LogText "Test: Searching for file ${INETD_CONFIG_FILE}" @@ -73,15 +96,15 @@ if [ ${INETD_ACTIVE} -eq 0 -a -f ${INETD_CONFIG_FILE} ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no INSE-8006 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check configuration of inetd when disabled" if [ ${SKIPTEST} -eq 0 ]; then - # Check if any service is enabled in /etc/inetd.conf (inetd is not active, see test 8002) - LogText "Test: check if all services are disabled if inetd is disabled" + # Check if any service is enabled in /etc/inetd.conf (inetd is not active, see test INSE-8002) + LogText "Test: check if all services are disabled when inetd is disabled" FIND=$(${GREPBINARY} -v "^#" ${INETD_CONFIG_FILE} | ${GREPBINARY} -v "^$") if [ -z "${FIND}" ]; then LogText "Result: no services found in ${INETD_CONFIG_FILE}" - Display --indent 4 --text "- Checking inetd.conf services" --result "${STATUS_OK}" --color GREEN + Display --indent 4 --text "- Checking enabled inetd services" --result "${STATUS_OK}" --color GREEN else LogText "Result: found services in inetd, even though inetd is not running" - Display --indent 4 --text "- Checking inetd.conf services" --result "${STATUS_SUGGESTION}" --color YELLOW + Display --indent 4 --text "- Checking enabled inetd services" --result "${STATUS_SUGGESTION}" --color YELLOW ReportSuggestion ${TEST_NO} "Although inetd is not running, make sure no services are enabled in ${INETD_CONFIG_FILE}, or remove inetd service" fi fi @@ -95,7 +118,7 @@ if [ ${SKIPTEST} -eq 0 ]; then LogText "Test: checking telnet presence in inetd configuration" FIND=$(${GREPBINARY} "^telnet" ${INETD_CONFIG_FILE}) - if [ "${FIND}" = "" ]; then + if [ -z "${FIND}" ]; then LogText "Result: telnet not enabled in ${INETD_CONFIG_FILE}" Display --indent 2 --text "- Checking inetd (telnet)" --result "${STATUS_NOT_FOUND}" --color GREEN AddHP 3 3 @@ -108,6 +131,289 @@ fi # ################################################################################# +# + # Test : INSE-8100 + # Description : Check for installed xinetd daemon + Register --test-no INSE-8100 --weight L --network NO --category security --description "Check for installed xinetd daemon" + if [ ${SKIPTEST} -eq 0 ]; then + # Check for installed xinetd daemon + LogText "Test: Checking for installed xinetd daemon" + if PackageIsInstalled "xinetd"; then + LogText "Result: xinetd is installed" + Display --indent 2 --text "- Installed xinetd package" --result "${STATUS_FOUND}" --color YELLOW + ReportSuggestion ${TEST_NO} "If there are no xinetd services required, it is recommended that the daemon be removed" + else + LogText "Result: xinetd is NOT installed" + Display --indent 2 --text "- Installed xinetd package" --result "${STATUS_OK}" --color GREEN + fi + fi +# +################################################################################# +# + # Test : INSE-8102 + # Description : Check for xinetd status + Register --test-no INSE-8102 --weight L --network NO --category security --description "Check for active xinet daemon" + if [ ${SKIPTEST} -eq 0 ]; then + # Check running processes + LogText "Test: Searching for active extended internet services daemon (xinetd)" + if IsRunning "xinetd"; then + LogText "Result: xinetd is running" + Display --indent 4 --text "- xinetd status" --result "ACTIVE" --color GREEN + XINETD_ACTIVE=1 + else + LogText "Result: xinetd is NOT running" + Display --indent 4 --text "- xinetd status" --result "NOT ACTIVE" --color GREEN + fi + fi +# +################################################################################# +# + # Test : INSE-8104 + # Description : Check for xinetd configuration file + if [ ${XINETD_ACTIVE} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + Register --test-no INSE-8104 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check for enabled xinet daemon" + if [ ${SKIPTEST} -eq 0 ]; then + # Check configuration file + LogText "Test: Searching for file ${XINETD_CONFIG_FILE}" + if [ -f "${XINETD_CONFIG_FILE}" ]; then + LogText "Result: ${XINETD_CONFIG_FILE} exists" + Display --indent 6 --text "- Configuration file (xinetd.conf)" --result "${STATUS_FOUND}" --color WHITE + else + LogText "Result: ${XINETD_CONFIG_FILE} does not exist" + Display --indent 6 --text "- Configuration file (xinetd.conf)" --result "${STATUS_NOT_FOUND}" --color WHITE + fi + fi +# +################################################################################# +# + # Test : INSE-8106 + # Description : Check for xinetd configuration file contents if xinetd is NOT active + if [ ${XINETD_ACTIVE} -eq 0 -a -f ${XINETD_CONFIG_FILE} ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + Register --test-no INSE-8106 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check configuration of xinetd when disabled" + if [ ${SKIPTEST} -eq 0 ]; then + # Check if any service is enabled in /etc/xinetd.d (xinetd is not active, see test INSE-8102) + LogText "Test: check if all services are disabled if xinetd is disabled" + FIND=$(${GREPBINARY} -r "disable\s*=\s*no" ${XINETD_CONFIG_DIR}) + if [ -z "${FIND}" ]; then + LogText "Result: no services found in ${XINETD_CONFIG_DIR}" + Display --indent 6 --text "- Enabled xinetd.d services" --result "${STATUS_NOT_FOUND}" --color GREEN + else + LogText "Result: found services in ${XINETD_CONFIG_DIR}, even though xinetd is not running" + Display --indent 6 --text "- Enabled xinetd.d services" --result "${STATUS_FOUND}" --color YELLOW + ReportSuggestion ${TEST_NO} "Although xinetd is not running, make sure no services are enabled in ${XINETD_CONFIG_DIR}, or remove xinetd service" + fi + fi +# +################################################################################# +# + # Test : INSE-8116 + # Description : Check for insecure services enabled via xinetd + if [ ${XINETD_ACTIVE} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + Register --test-no INSE-8116 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Insecure services enabled via xinetd" + if [ ${SKIPTEST} -eq 0 ]; then + XINETD_INSECURE_SERVICE_FOUND=0 + + ITEMS="chargen chargen-dgram chargen-stream daytime daytime-dgram daytime-stream discard discard-dgram discard-stream echo echo-dgram echo-stream time time-dgram time-stream ntalk rexec rlogin rsh talk telnet tftp" + + for SERVICE in ${ITEMS}; do + LogText "Test: checking service ${SERVICE}" + if ! SkipAtomicTest "${TEST_NO}:${SERVICE}"; then + FILE="${XINETD_CONFIG_DIR}/${SERVICE}" + if [ -f "${FILE}" ]; then + LogText "Test: checking status in xinetd configuration file (${FILE})" + FIND=$(${GREPBINARY} "disable\s*=\s*no" ${FILE}) + if [ ! -z "${FIND}" ]; then + LogText "Result: found insecure service enabled: ${SERVICE}" + XINETD_INSECURE_SERVICE_FOUND=1 + ReportSuggestion "${TEST_NO}" "Disable or remove any insecure services in the xinetd configuration" "${SERVICE}" "text:See log file for more details" + Report "insecure_service[]=${SERVICE}" + fi + fi + else + LogText "Result: skipped, as this item is excluded using the profile" + fi + done + + if [ ${XINETD_INSECURE_SERVICE_FOUND} -eq 0 ]; then + LogText "Result: no insecure services found in xinetd configuration" + Display --indent 6 --text "- Checking xinetd (insecure services)" --result "${STATUS_OK}" --color GREEN + AddHP 3 3 + else + LogText "Result: one ore more insecure services discovered in xinetd configuration" + Display --indent 6 --text "- Checking xinetd (insecure services)" --result "${STATUS_WARNING}" --color RED + AddHP 0 3 + fi + fi +# +################################################################################# +# + # Test : INSE-8150 + # Description : Check for rsync enabled via xinetd + #RSYNC_XINETD_CONFIG_FILE="${XINETD_CONFIG_DIR}/rsync" + #if [ ${XINETD_ACTIVE} -eq 1 -a -f ${RSYNC_XINETD_CONFIG_FILE} ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + #Register --test-no INSE-8150 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check for rsync via xinetd" + #if [ ${SKIPTEST} -eq 0 ]; then + # LogText "Test: checking rsync presence in xinetd configuration" + # FIND=$(${GREPBINARY} "disable\s*=\s*no" ${RSYNC_XINETD_CONFIG_FILE}) + # if [ "${FIND}" = "" ]; then + # LogText "Result: rsync not enabled in ${RSYNC_XINETD_CONFIG_FILE}" + # Display --indent 6 --text "- Checking xinetd (rsync)" --result "${STATUS_DISABLED}" --color GREEN + # else + # LogText "Result: rsync enabled in ${RSYNC_XINETD_CONFIG_FILE}" + # Display --indent 6 --text "- Checking xinetd (rsync)" --result "${STATUS_ENABLED}" --color RED + # ReportSuggestion "${TEST_NO}" "Disable rsync in xinetd configuration" + # fi + #fi +# +################################################################################# +# + # Test : INSE-8200 + # Description : Check if tcp_wrappers is installed when inetd/xinetd is active + if [ ${INETD_ACTIVE} -eq 1 -o ${XINETD_ACTIVE} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + Register --test-no INSE-8200 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check if tcp_wrappers is installed when inetd/xinetd is active" + if [ ${SKIPTEST} -eq 0 ]; then + LogText "Test: Checking if tcp_wrappers is installed" + FOUND=0 + PACKAGES="tcp_wrappers tcpd" + for PACKAGE in ${PACKAGES}; do + if PackageIsInstalled "${PACKAGE}"; then LogText "Package '${PACKAGE}' is installed"; FOUND=1; fi + done + if [ ${FOUND} -eq 1 ]; then + LogText "Result: tcp_wrappers is installed" + Display --indent 2 --text "- Checking tcp_wrappers installation" --result "${STATUS_OK}" --color GREEN + else + LogText "Result: tcp_wrappers is NOT installed" + Display --indent 2 --text "- Checking tcp_wrappers installation" --result "${STATUS_SUGGESTION}" --color YELLOW + #ReportSuggestion ${TEST_NO} "When network services are using the inetd/xinetd service, the tcp_wrappers package should be installed" + fi + fi +# +################################################################################# +# + # Test : INSE-8300 + # Description : Check if rsh client is installed + Register --test-no INSE-8300 --weight L --network NO --category security --description "Check if rsh client is installed" + if [ ${SKIPTEST} -eq 0 ]; then + LogText "Test: Checking if rsh client is installed" + FOUND=0 + PACKAGES="rsh rsh-client rsh-redone-client" + for PACKAGE in ${PACKAGES}; do + if PackageIsInstalled "${PACKAGE}"; then LogText "Package '${PACKAGE}' is installed"; FOUND=1; fi + done + if [ ${FOUND} -eq 1 ]; then + LogText "Result: rsh client is installed" + Display --indent 2 --text "- Installed rsh client package" --result "${STATUS_SUGGESTION}" --color YELLOW + ReportSuggestion ${TEST_NO} "Remove rsh client when it is not in use or replace with the more secure SSH package" + else + LogText "Result: rsh client is NOT installed" + Display --indent 2 --text "- Installed rsh client package" --result "${STATUS_OK}" --color GREEN + fi + fi +# +################################################################################# +# + # Test : INSE-8302 + # Description : Check presence of rsh Trust Files + #Register --test-no INSE-8302 --weight L --network NO --category security --description "Check presence of rsh Trust Files" + #if [ ${SKIPTEST} -eq 0 ]; then + # # Check presence of Rsh Trust Files + # FOUND=0 + # for LINE in $(${CAT_BINARY} /etc/passwd | ${EGREPBINARY} -v '^(root|halt|sync|shutdown)' | ${AWKBINARY} -F: '($7 !="/sbin/nologin" && $7 != "/bin/false") { print }'); do + # USER=$(echo ${LINE} | ${CUTBINARY} -d: -f1) + # DIR=$(echo ${LINE} | ${CUTBINARY} -d: -f6) + # if [ -d ${DIR} ]; then + # for RHOSTS in ${DIR}/.rhosts; do + # if [ ! -h ${RHOSTS} -a -f ${RHOSTS} ]; then + # LogText "FOUND .rhosts file in home directory ${DIR} of ${USER}" + # FOUND=1 + # fi + # done + # fi + # done + # if [ -f /etc/hosts.equiv ];then + # LogText "FOUND /etc/hosts.equiv" + # FOUND=1 + # fi + # if [ ${FOUND} -eq 1 ]; then + # LogText "Result: found one or more Rsh Trust Files" + # Display --indent 4 --text "- Checking presence of Rsh Trust Files" --result "${STATUS_SUGGESTION}" --color YELLOW + # ReportSuggestion ${TEST_NO} "Remove every Rsh Trust Files as they can allow unauthenticated access to a system" + # else + # LogText "Result: no Rsh Trust Files found" + # Display --indent 4 --text "- Checking presence of Rsh Trust Files" --result "${STATUS_OK}" --color GREEN + # fi + #fi +# +################################################################################# +# + # Test : INSE-8304 + # Description : Check if rsh server is installed + Register --test-no INSE-8342 --weight L --network NO --category security --description "Check if rsh server is installed" + if [ ${SKIPTEST} -eq 0 ]; then + # Check if rsh server is installed + LogText "Test: Checking if rsh server is installed" + FOUND=0 + PACKAGES="rsh-server rsh-redone-server" + for PACKAGE in ${PACKAGES}; do + if PackageIsInstalled "${PACKAGE}"; then LogText "Package '${PACKAGE}' is installed"; FOUND=1; fi + done + if [ ${FOUND} -eq 1 ]; then + LogText "Result: rsh server is installed" + Display --indent 2 --text "- Installed rsh server package" --result "${STATUS_SUGGESTION}" --color YELLOW + ReportSuggestion ${TEST_NO} "Remove the rsh-server package and replace with a more secure alternative like SSH" + Report "insecure_service[]=rsh-server" + else + LogText "Result: rsh server is NOT installed" + Display --indent 2 --text "- Installed rsh server package" --result "${STATUS_OK}" --color GREEN + fi + fi +# +################################################################################# +# + # Test : INSE-8310 + # Description : Check if telnet client is installed + Register --test-no INSE-8310 --weight L --network NO --category security --description "Check if telnet client is installed" + if [ ${SKIPTEST} -eq 0 ]; then + # Check if telnet client is installed + LogText "Test: Checking if telnet client is installed" + if PackageIsInstalled "${PACKAGE}"; then LogText "Package '${PACKAGE}' is installed"; FOUND=1; fi + + if [ ${FOUND} -eq 1 ]; then + LogText "Result: telnet client is installed" + Display --indent 2 --text "- Installed telnet client package" --result "${STATUS_FOUND}" --color YELLOW + # Telnet client usage might be used for troubleshooting instead of system administration + #ReportSuggestion ${TEST_NO} "telnet client contain numerous security exposures and have been replaced with the more secure SSH package" + else + LogText "Result: telnet client is NOT installed" + Display --indent 2 --text "- Installed telnet client package" --result "${STATUS_OK}" --color GREEN + fi + fi +# +################################################################################# +# + # Test : INSE-8312 + # Description : Check if telnet server is installed + Register --test-no INSE-8322 --weight L --network NO --category security --description "Check if telnet server is installed" + if [ ${SKIPTEST} -eq 0 ]; then + # Check if TFTP server is installed + LogText "Test: Checking if telnet server is installed" + FOUND=0 + PACKAGES="telnetd telnet-server" + for PACKAGE in ${PACKAGES}; do + if PackageIsInstalled "${PACKAGE}"; then LogText "Package '${PACKAGE}' is installed"; FOUND=1; fi + done + if [ ${FOUND} -eq 1 ]; then + LogText "Result: telnet server is installed" + Display --indent 2 --text "- Installed telnet server package" --result "${STATUS_FOUND}" --color YELLOW + ReportSuggestion ${TEST_NO} "Removing the ${FOUND} package and replace with SSH when possible" + Report "insecure_service[]=telnet-server" + else + LogText "Result: telnet server is NOT installed" + Display --indent 2 --text "- Installed telnet server package" --result "${STATUS_NOT_FOUND}" --color GREEN + fi + fi +# +################################################################################# # if [ ! -z "${LAUNCHCTL_BINARY}" ]; then PREQS_MET="YES"; SKIPREASON=""; else PREQS_MET="NO"; SKIPREASON="No launchctl binary on this system"; fi Register --test-no INSE-8050 --os "macOS" --preqs-met ${PREQS_MET} --skip-reason "${SKIPREASON}" --weight M --network NO --category security --description "Check for insecure services on macOS" diff --git a/include/tests_memory_processes b/include/tests_memory_processes index 71c0fc42..fc1789dc 100644 --- a/include/tests_memory_processes +++ b/include/tests_memory_processes @@ -30,17 +30,17 @@ # Description : Query /proc/meminfo Register --test-no PROC-3602 --os Linux --weight L --network NO --category security --description "Checking /proc/meminfo for memory details" if [ ${SKIPTEST} -eq 0 ]; then - if [ -f /proc/meminfo ]; then - LogText "Result: found /proc/meminfo" - Display --indent 2 --text "- Checking /proc/meminfo" --result "${STATUS_FOUND}" --color GREEN - FIND=$(${AWKBINARY} '/^MemTotal/ { print $2, $3 }' /proc/meminfo) + if [ -f ${ROOTDIR}proc/meminfo ]; then + LogText "Result: found ${ROOTDIR}proc/meminfo" + Display --indent 2 --text "- Checking ${ROOTDIR}proc/meminfo" --result "${STATUS_FOUND}" --color GREEN + FIND=$(${AWKBINARY} '/^MemTotal/ { print $2, $3 }' ${ROOTDIR}proc/meminfo) MEMORY_SIZE=$(echo ${FIND} | ${AWKBINARY} '{ print $1 }') MEMORY_UNITS=$(echo ${FIND} | ${AWKBINARY} '{ print $2 }') LogText "Result: Found ${MEMORY_SIZE} ${MEMORY_UNITS} memory" Report "memory_size=${MEMORY_SIZE}" Report "memory_units=${MEMORY_UNITS}" else - LogText "Result: /proc/meminfo file not found on this system" + LogText "Result: ${ROOTDIR}proc/meminfo file not found on this system" fi fi # @@ -80,7 +80,7 @@ fi if [ -z "${FIND}" ]; then LogText "Result: no zombie processes found" - Display --indent 2 --text "- Searching for dead/zombie processes" --result "${STATUS_OK}" --color GREEN + Display --indent 2 --text "- Searching for dead/zombie processes" --result "${STATUS_NOT_FOUND}" --color GREEN else LogText "Result: found one or more dead or zombie processes" LogText "Output: PIDs ${FIND}" @@ -104,7 +104,7 @@ fi if [ -z "${FIND}" ]; then LogText "Result: No processes were waiting for IO requests to be handled first" - Display --indent 2 --text "- Searching for IO waiting processes" --result "${STATUS_OK}" --color GREEN + Display --indent 2 --text "- Searching for IO waiting processes" --result "${STATUS_NOT_FOUND}" --color GREEN else LogText "Result: found one or more processes which were waiting to get IO requests handled first" LogText "More info: processes which show up with the status flag 'D' are often stuck, until a disk IO event finished. This can happen for example with network storage, where the connection or protocol settings are not logtext well configured." @@ -116,6 +116,27 @@ # ################################################################################# # + # Test : PROC-3802 + # Description : Check presence of prelink tooling + Register --test-no PROC-3802 --weight L --network NO --category security --description "Check presence of prelink tooling" + if [ ${SKIPTEST} -eq 0 ]; then + if PackageIsInstalled "prelink"; then + LogText "Result: prelink packages is installed" + # TODO + # - Add item to website with rationale + #ReportSuggestion "${TEST_NO}" "Disable and remove prelinking of binaries" + AddHP 1 3 + Display --indent 2 --text "- Search prelink tooling" --result "${STATUS_FOUND}" --color YELLOW + else + Display --indent 2 --text "- Search prelink tooling" --result "${STATUS_NOT_FOUND}" --color GREEN + LogText "Result: prelink package is NOT installed" + AddHP 3 3 + fi + fi +# +################################################################################# +# + WaitForKeyPress diff --git a/include/tests_nameservices b/include/tests_nameservices index 19189898..2ae503e3 100644 --- a/include/tests_nameservices +++ b/include/tests_nameservices @@ -644,15 +644,15 @@ if [ "${FIND}" = "127.0.0.1" ]; then LogText "Result: localhost mapped to 127.0.0.1" Display --indent 4 --text "- Checking /etc/hosts (localhost to IP)" --result "${STATUS_OK}" --color GREEN - report "localhost-mapped-to=${FIND}" + Report "localhost-mapped-to=${FIND}" elif [ "${FIND}" = "::1" ]; then LogText "Result: localhost mapped to ::1" Display --indent 4 --text "- Checking /etc/hosts (localhost to IP)" --result "${STATUS_OK}" --color GREEN - report "localhost-mapped-to=${FIND}" + Report "localhost-mapped-to=${FIND}" elif [ "${FIND}" = "127.0.0.1::1" ]; then LogText "Result: localhost mapped to 127.0.0.1 and ::1" Display --indent 4 --text "- Checking /etc/hosts (localhost to IP)" --result "${STATUS_OK}" --color GREEN - report "localhost-mapped-to=${FIND}" + Report "localhost-mapped-to=${FIND}" else LogText "Output: ${FIND}" LogText "Result: this server hostname is not mapped to a local address" diff --git a/include/tests_networking b/include/tests_networking index 3986220b..8c895c4e 100644 --- a/include/tests_networking +++ b/include/tests_networking @@ -370,6 +370,7 @@ # Description : Check listening ports Register --test-no NETW-3012 --weight L --network NO --category security --description "Check listening ports" if [ ${SKIPTEST} -eq 0 ]; then + DATA="" FIND=""; FIND2="" COUNT=0 case ${OS} in @@ -381,24 +382,19 @@ FIND="" fi FIND2="" - ;; + ;; Linux) - if [ ! -z "${NETSTATBINARY}" ]; then + if [ -n "${SSBINARY}" ]; then + DATA=$(${SSBINARY} --query=udp,tcp -plnt | awk '{ if ($1!="Netid") { print "raw,ss,v1|"$1"|"$5"|"$7"|" }}' | sed 's/pid=[0-9]\{1,\},fd=[0-9]\{1,\}//g' | sed 's/users://' | sed 's/,)//g' | tr -d '()"') + elif [ -n "${NETSTATBINARY}" ]; then # UDP FIND=$(${NETSTATBINARY} -nlp 2> /dev/null | ${GREPBINARY} "^udp" | ${AWKBINARY} '{ print $4"|"$1"|"$6"|" }' | ${SEDBINARY} 's:|[0-9]*/:|:') # TCP FIND2=$(${NETSTATBINARY} -nlp 2> /dev/null | ${GREPBINARY} "^tcp" | ${AWKBINARY} '{ if($6=="LISTEN") { print $4"|"$1"|"$7"|" }}' | ${SEDBINARY} 's:|[0-9]*/:|:') else - if [ ! "${SSBINARY}" = "" ]; then - # UDP - FIND=$(${SSBINARY} -u -a -n 2> /dev/null | ${AWKBINARY} '{ print $4 }' | ${GREPBINARY} -v Local) - # TCP - FIND2=$(${SSBINARY} -t -a -n 2> /dev/null | ${AWKBINARY} '{ print $4 }' | ${GREPBINARY} -v Local) - else - ReportException "${TEST_NO}:1" "netstat and ss binary missing to gather listening ports" - fi + ReportException "${TEST_NO}:1" "netstat and ss binary missing to gather listening ports" fi - ;; + ;; macOS) if [ ! "${LSOFBINARY}" = "" ]; then @@ -409,9 +405,7 @@ fi # Not needed as we have a combined test FIND2="" - ;; - - + ;; NetBSD) if [ ! "${SOCKSTATBINARY}" = "" ]; then FIND=$(${SOCKSTATBINARY} 2> /dev/null | ${AWKBINARY} '{ if ($7 ~ /\*.\*/) print $5"|"$6"|"$2"|" }' | ${SORTBINARY} -u) @@ -419,7 +413,7 @@ FIND="" fi FIND2="" - ;; + ;; OpenBSD) if [ ! "${NETSTATBINARY}" = "" ]; then # UDP @@ -429,13 +423,20 @@ else ReportException "${TEST_NO}:3" "netstat missing to gather listening ports" fi - ;; + ;; *) # Got this exception? Provide your details and output of netstat or any other tool to determine this information. ReportException "${TEST_NO}:2" "Unclear what method to use, to determine listening port information" - ;; + ;; esac + if HasData "${DATA}"; then + for ITEM in ${DATA}; do + COUNT=$((COUNT + 1)) + Report "network_listen[]=${ITEM}" + done + fi + # Retrieve information from sockstat, when available LogText "Test: Retrieving sockstat information to find listening ports" if HasData "${FIND}"; then @@ -453,11 +454,10 @@ Report "network_listen_port[]=${ITEM}" done fi - if [ "${FIND}" = "" -a "${FIND2}" = "" ]; then + if [ -z "${DATA}" -a -z "${FIND}" ]; then Display --indent 2 --text "- Getting listening ports (TCP/UDP)" --result "${STATUS_SKIPPED}" --color YELLOW else Display --indent 2 --text "- Getting listening ports (TCP/UDP)" --result "${STATUS_DONE}" --color GREEN - Display --indent 6 --text "* Found ${COUNT} ports" fi fi # @@ -610,33 +610,34 @@ if [ ${SKIPTEST} -eq 0 ]; then FOUND=0 + # addrwatch + if IsRunning "addrwatch"; then + FOUND=1 + fi + # arpwatch - IsRunning arpwatch - if [ ${RUNNING} -eq 1 ]; then + if IsRunning "arpwatch"; then FOUND=1 ARPWATCH_RUNNING=1 - Display --indent 2 --text "- Checking for ARP monitoring software" --result "${STATUS_RUNNING}" --color GREEN fi # arpon - IsRunning arpon - if [ ${RUNNING} -eq 1 ]; then + if IsRunning "arpon"; then FOUND=1 ARPON_RUNNING=1 - Display --indent 2 --text "- Checking for ARP monitoring software" --result "${STATUS_RUNNING}" --color GREEN fi - if [ ${FOUND} -eq 0 ]; then + + if [ ${FOUND} -eq 1 ]; then + Display --indent 2 --text "- Checking for ARP monitoring software" --result "${STATUS_RUNNING}" --color GREEN + else Display --indent 2 --text "- Checking for ARP monitoring software" --result "${STATUS_NOT_FOUND}" --color YELLOW - ReportSuggestion ${TEST_NO} "Consider running ARP monitoring software (arpwatch,arpon)" + ReportSuggestion "${TEST_NO}" "Consider running ARP monitoring software (addrwatch,arpwatch,arpon)" fi fi # ################################################################################# # -Report "dhcp_client_running=${DHCP_CLIENT_RUNNING}" -Report "arpwatch_running=${ARPWATCH_RUNNING}" - WaitForKeyPress # diff --git a/include/tests_php b/include/tests_php index 363321cf..76606c64 100644 --- a/include/tests_php +++ b/include/tests_php @@ -42,6 +42,9 @@ ${ROOTDIR}etc/php5/apache2/php.ini \ ${ROOTDIR}etc/php5/fpm/php.ini \ ${ROOTDIR}private/etc/php.ini \ + ${ROOTDIR}etc/php/7.2/apache2/php.ini \ + ${ROOTDIR}etc/php/7.1/apache2/php.ini \ + ${ROOTDIR}etc/php/7.0/apache2/php.ini \ ${ROOTDIR}etc/php/7.2/cli/php.ini ${ROOTDIR}etc/php/7.2/fpm/php.ini \ ${ROOTDIR}etc/php/7.1/cli/php.ini ${ROOTDIR}etc/php/7.1/fpm/php.ini \ ${ROOTDIR}etc/php/7.0/cli/php.ini ${ROOTDIR}etc/php/7.0/fpm/php.ini \ diff --git a/include/tests_ports_packages b/include/tests_ports_packages index 1426a068..6b6b2ed9 100644 --- a/include/tests_ports_packages +++ b/include/tests_ports_packages @@ -344,7 +344,7 @@ COUNT=0 PACKAGE_AUDIT_TOOL_FOUND=1 PACKAGE_AUDIT_TOOL="zypper" - FIND=$(${ZYPPERBINARY} -n se -t package -i | ${AWKBINARY} '{ if ($1=="i") { print $3 } }') + FIND=$(${ZYPPERBINARY} --non-interactive -n se -t package -i | ${AWKBINARY} '{ if ($1=="i") { print $3 } }') if [ ! -z "${FIND}" ]; then for PKG in ${FIND}; do COUNT=$((COUNT + 1)) @@ -365,7 +365,7 @@ if [ ! -z "${ZYPPERBINARY}" ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no PKGS-7330 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Querying Zypper for vulnerable packages" if [ ${SKIPTEST} -eq 0 ]; then - FIND=$(${ZYPPERBINARY} -n pchk | ${GREPBINARY} "(0 security patches)") + FIND=$(${ZYPPERBINARY} --non-interactive pchk | ${GREPBINARY} "(0 security patches)") if [ ! -z "${FIND}" ]; then LogText "Result: No security updates found with Zypper" Display --indent 2 --text "- Using Zypper to find vulnerable packages" --result "${STATUS_NONE}" --color GREEN @@ -374,7 +374,7 @@ LogText "Result: Zypper found one or more installed packages which are vulnerable." ReportWarning ${TEST_NO} "Found one or more vulnerable packages installed" # Unfortunately zypper does not properly give back which package it is. Usually best guess is last word on the line - FIND=$(${ZYPPERBINARY} -n lp | ${AWKBINARY} '{ if ($5=="security" || $7=="security") { print $NF }}' | ${SEDBINARY} 's/:$//' | ${GREPBINARY} -v "^$" | ${SORTBINARY} -u) + FIND=$(${ZYPPERBINARY} --non-interactive lp | ${AWKBINARY} '{ if ($5=="security" || $7=="security") { print $NF }}' | ${SEDBINARY} 's/:$//' | ${GREPBINARY} -v "^$" | ${SORTBINARY} -u) LogText "List of vulnerable packages/version:" for PKG in ${FIND}; do VULNERABLE_PACKAGES_FOUND=1 @@ -879,8 +879,7 @@ if [ ${DO_TEST} -eq 0 ]; then FileExists ${ROOTDIR}usr/share/yum-cli/cli.py if [ ${FILE_FOUND} -eq 1 ]; then - SearchItem "\-\-security" "${ROOTDIR}usr/share/yum-cli/cli.py" - if [ ${ITEM_FOUND} -eq 1 ]; then + if SearchItem "\-\-security" "${ROOTDIR}usr/share/yum-cli/cli.py"; then DO_TEST=1 LogText "Result: found built-in security in yum" else @@ -892,8 +891,7 @@ if [ ${DO_TEST} -eq 0 ]; then FileExists ${ROOTDIR}etc/yum/pluginconf.d/security.conf if [ ${FILE_FOUND} -eq 1 ]; then - SearchItem "^enabled=1$" "${ROOTDIR}etc/yum/pluginconf.d/security.conf" - if [ ${ITEM_FOUND} -eq 1 ]; then + if SearchItem "^enabled=1$" "${ROOTDIR}etc/yum/pluginconf.d/security.conf"; then DO_TEST=1 LogText "Result: found enabled plugin" else @@ -930,7 +928,6 @@ AddHP 1 2 done ReportWarning ${TEST_NO} "Found one or more vulnerable packages." - ReportSuggestion ${TEST_NO} "Use 'yum --security update' to update your system" fi else LogText "Result: yum-security package not found" @@ -968,8 +965,8 @@ FOUND=0 FileExists ${ROOTDIR}etc/yum.conf if [ ${FILE_FOUND} -eq 1 ]; then - SearchItem "^gpgenabled\s*=\s*1$" "${ROOTDIR}etc/yum.conf"; if [ ${ITEM_FOUND} -eq 1 ]; then FOUND=1; fi - SearchItem "^gpgcheck\s*=\s*1$" "${ROOTDIR}etc/yum.conf"; if [ ${ITEM_FOUND} -eq 1 ]; then FOUND=1; fi + if SearchItem "^gpgenabled\s*=\s*1$" "${ROOTDIR}etc/yum.conf"; then FOUND=1; fi + if SearchItem "^gpgcheck\s*=\s*1$" "${ROOTDIR}etc/yum.conf"; then FOUND=1; fi if [ ${FOUND} -eq 1 ]; then LogText "Result: GPG check is enabled" Display --indent 2 --text "- Checking GPG checks (yum.conf)" --result "${STATUS_OK}" --color GREEN @@ -1055,7 +1052,8 @@ # # Test : PKGS-7392 # Description : Check Debian/Ubuntu vulnerable packages - if [ -x ${ROOTDIR}usr/bin/apt-get ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + # Note : Skip for zypper-based systems + if [ -x ${ROOTDIR}usr/bin/apt-get -a -z "${ZYPPERBINARY}" ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no PKGS-7392 --os Linux --preqs-met ${PREQS_MET} --root-only YES --weight L --network YES --category security --description "Check for Debian/Ubuntu security updates" if [ ${SKIPTEST} -eq 0 ]; then VULNERABLE_PACKAGES_FOUND=0 @@ -1247,8 +1245,20 @@ Register --test-no PKGS-7410 --weight L --network NO --category security --description "Count installed kernel packages" if [ ${SKIPTEST} -eq 0 ]; then KERNELS=0 - if [ ! -z "${RPMBINARY}" ]; then - LogText "Test: Checking how many kernel packages are installed" + LogText "Test: Checking how many kernel packages are installed" + + if [ ! -z "${DPKGBINARY}" ]; then + KERNELS=$(${DPKGBINARY} -l 2> /dev/null | ${GREPBINARY} "linux-image-[0-9]" | ${WCBINARY} -l) + if [ ${KERNELS} -eq 0 ]; then + LogText "Result: found no kernels from dpkg -l output, which is unexpected" + ReportException "KRNL-5840:2" "Could not find any kernel packages from DPKG output" + elif [ ${KERNELS} -gt 5 ]; then + LogText "Result: found more than 5 kernel packages on the system, which might indicate lack of regular cleanups" + ReportSuggestion "${TEST_NO}" "Remove any unneeded kernel packages" "${KERNELS} kernels" "text:validate dpkg -l output and perform cleanup with apt autoremove" + else + LogText "Result: found ${KERNELS} kernel packages on the system, which is fine" + fi + elif [ ! -z "${RPMBINARY}" ]; then KERNELS=$(${RPMBINARY} -q kernel 2> /dev/null | ${WCBINARY} -l) if [ ${KERNELS} -eq 0 ]; then LogText "Result: found no kernels from rpm -q kernel output, which is unexpected" @@ -1256,22 +1266,77 @@ elif [ ${KERNELS} -gt 5 ]; then LogText "Result: found more than 5 kernel packages on the system, which might indicate lack of regular cleanups" ReportSuggestion "${TEST_NO}" "Remove any unneeded kernel packages with package-cleanup utility (--old-kernels)" - AddHP 4 5 else - LogText "Result: found ${KERNELS} on the system, which is fine" - AddHP 1 1 + LogText "Result: found ${KERNELS} kernel packages on the system, which is fine" fi fi + + Report "installed_kernel_packages=${KERNELS}" fi # ################################################################################# # + # Test : PKGS-7420 + # Description : Detect toolkit to automatically download and apply upgrades + Register --test-no PKGS-7420 --weight L --network NO --category security --description "Detect toolkit to automatically download and apply upgrades" + if [ ${SKIPTEST} -eq 0 ]; then + UNATTENDED_UPGRADES_TOOLKIT=0 + UNATTENDED_UPGRADES_TOOL="" + UNATTENDED_UPGRADES_OPTION_AVAILABLE=0 -if [ ! -z "${INSTALLED_PACKAGES}" ]; then Report "installed_packages_array=${INSTALLED_PACKAGES}"; fi + case "${OS}" in + "Linux") + case "${LINUX_VERSION}" in + "CentOS" | "Debian" | "Fedora" | "RHEL" | "Ubuntu") -Report "package_audit_tool=${PACKAGE_AUDIT_TOOL}" -Report "package_audit_tool_found=${PACKAGE_AUDIT_TOOL_FOUND}" -Report "vulnerable_packages_found=${VULNERABLE_PACKAGES_FOUND}" + UNATTENDED_UPGRADES_OPTION_AVAILABLE=1 + # Test available tools for Linux + if [ -f "${ROOTDIR}bin/auter" ]; then + UNATTENDED_UPGRADES_TOOL="auter" + UNATTENDED_UPGRADES_TOOLKIT=1 + LogText "Result: found ${UNATTENDED_UPGRADES_TOOL}" + Report "unattended_upgrade_tool[]=${UNATTENDED_UPGRADES_TOOL}" + fi + if [ -f "${ROOTDIR}sbin/yum-cron" ]; then + UNATTENDED_UPGRADES_TOOL="yum-cron" + UNATTENDED_UPGRADES_TOOLKIT=1 + LogText "Result: found ${UNATTENDED_UPGRADES_TOOL}" + Report "unattended_upgrade_tool[]=${UNATTENDED_UPGRADES_TOOL}" + fi + if [ -f "${ROOTDIR}usr/bin/dnf-automatic" ]; then + UNATTENDED_UPGRADES_TOOL="dnf-automatic" + UNATTENDED_UPGRADES_TOOLKIT=1 + LogText "Result: found ${UNATTENDED_UPGRADES_TOOL}" + Report "unattended_upgrade_tool[]=${UNATTENDED_UPGRADES_TOOL}" + fi + if [ -f "${ROOTDIR}usr/bin/unattended-upgrade" ]; then + UNATTENDED_UPGRADES_TOOL="unattended-upgrade" + UNATTENDED_UPGRADES_TOOLKIT=1 + LogText "Result: found ${UNATTENDED_UPGRADES_TOOL}" + Report "unattended_upgrade_tool[]=${UNATTENDED_UPGRADES_TOOL}" + fi + ;; + esac + ;; + esac + + if [ ${UNATTENDED_UPGRADES_OPTION_AVAILABLE} -eq 1 ]; then + if [ ${UNATTENDED_UPGRADES_TOOLKIT} -eq 1 ]; then + AddHP 5 5 + Display --indent 2 --text "- Toolkit for automatic upgrades (${UNATTENDED_UPGRADES_TOOL})" --result "${STATUS_FOUND}" --color GREEN + else + AddHP 1 5 + Display --indent 2 --text "- Toolkit for automatic upgrades" --result "${STATUS_NOTFOUND}" --color YELLOW + LogText "Result: no toolkit for automatic updates discovered" + ReportSuggestion "${TEST_NO}" "Consider using a tool to automatically apply upgrades" + fi + fi + + Report "unattended_upgrade_option_available=${UNATTENDED_UPGRADES_OPTION_AVAILABLE}" + fi +# +################################################################################# +# WaitForKeyPress diff --git a/include/tests_shells b/include/tests_shells index 8f9763b4..3a094ad8 100644 --- a/include/tests_shells +++ b/include/tests_shells @@ -31,9 +31,10 @@ # Files (interactive login shells): /etc/profile $HOME/.bash_profile # $HOME/.bash_login $HOME/.profile # Files (interactive non-login shells): $HOME/.bash_rc - + # # csh/tcsh # Files: /etc/csh.cshrc /etc/csh.login + # # zsh # Files: /etc/zshenv /etc/zsh/zshenv $HOME/.zshenv /etc/zprofile # /etc/zsh/zprofile $HOME/.zprofile /etc/zshrc /etc/zsh/zshrc @@ -68,8 +69,8 @@ ################################################################################# # # Test : SHLL-6211 - # Description : which shells are available according /etc/shells - Register --test-no SHLL-6211 --weight L --network NO --category security --description "Checking available and valid shells" + # Description : Determine available shell according /etc/shells + Register --test-no SHLL-6211 --weight L --network NO --category security --description "Available and valid shells" if [ ${SKIPTEST} -eq 0 ]; then LogText "Test: Searching for ${ROOTDIR}etc/shells" if [ -f ${ROOTDIR}etc/shells ]; then @@ -98,8 +99,8 @@ ################################################################################# # # Test : SHLL-6220 - # Description : check for idle session killing tools or settings - Register --test-no SHLL-6220 --weight L --network NO --category security --description "Checking available and valid shells" + # Description : Check for idle session killing tools or settings + Register --test-no SHLL-6220 --weight L --network NO --category security --description "Idle session killing tools or settings" if [ ${SKIPTEST} -eq 0 ]; then IDLE_TIMEOUT_METHOD="" diff --git a/include/tests_ssh b/include/tests_ssh index e811e069..852e2db5 100644 --- a/include/tests_ssh +++ b/include/tests_ssh @@ -27,6 +27,7 @@ SSH_DAEMON_PORT="" SSH_DAEMON_RUNNING=0 SSH_DAEMON_OPTIONS_FILE="" + OPENSSHD_RUNNING=0 OPENSSHD_VERSION=0 OPENSSHD_VERSION_MAJOR=0 OPENSSHD_VERSION_MINOR=0 @@ -42,8 +43,8 @@ Register --test-no SSH-7402 --weight L --network NO --category security --description "Check for running SSH daemon" if [ ${SKIPTEST} -eq 0 ]; then LogText "Test: Searching for a SSH daemon" - IsRunning sshd - if [ ${RUNNING} -eq 1 ] || PortIsListening "TCP" 22; then + if IsRunning "sshd"; then + OPENSSHD_RUNNING=1 SSH_DAEMON_RUNNING=1 Display --indent 2 --text "- Checking running SSH daemon" --result "${STATUS_FOUND}" --color GREEN # Store settings in a temporary file @@ -51,6 +52,9 @@ SSH_DAEMON_OPTIONS_FILE="${TEMP_FILE}" # Use a non-existing user, to ensure that systems that have a Match block configured, will be evaluated as well ${SSHDBINARY} -T -C user=doesnotexist,host=none,addr=none 2> /dev/null > ${SSH_DAEMON_OPTIONS_FILE} + elif PortIsListening "TCP" 22; then + Display --indent 2 --text "- Checking running SSH daemon" --result "${STATUS_FOUND}" --color GREEN + SSH_DAEMON_RUNNING=1 else Display --indent 2 --text "- Checking running SSH daemon" --result "${STATUS_NOT_FOUND}" --color WHITE fi @@ -60,7 +64,7 @@ # # Test : SSH-7404 # Description : Determine SSH daemon configuration file location - if [ ${SSH_DAEMON_RUNNING} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + if [ ${OPENSSHD_RUNNING} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no SSH-7404 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check SSH daemon file location" if [ ${SKIPTEST} -eq 0 ]; then FOUND=0 @@ -95,7 +99,7 @@ # # Test : SSH-7406 # Description : Check OpenSSH version - if [ ${SSH_DAEMON_RUNNING} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + if [ ${OPENSSHD_RUNNING} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no SSH-7406 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Determine OpenSSH version" if [ ${SKIPTEST} -eq 0 ]; then OPENSSHD_VERSION=$(${SSHDBINARY} -t -d 2>&1 | ${GREPBINARY} 'sshd version' | ${AWKBINARY} '{if($4~OpenSSH_){print $4}}' | ${AWKBINARY} -F_ '{print $2}' | ${TRBINARY} -d ',' | ${TRBINARY} -d '\r') @@ -113,7 +117,7 @@ # Test : SSH-7408 # Description : Check SSH specific defined options # Notes : Instead of parsing the configuration file, we query the SSH daemon itself - if [ ${SSH_DAEMON_RUNNING} -eq 1 -a ! -z "${SSH_DAEMON_OPTIONS_FILE}" -a ${OPENSSHD_VERSION_MAJOR} -ge 5 -a ${OPENSSHD_VERSION_MINOR} -ge 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + if [ ${OPENSSHD_RUNNING} -eq 1 -a ! -z "${SSH_DAEMON_OPTIONS_FILE}" -a ${OPENSSHD_VERSION_MAJOR} -ge 5 -a ${OPENSSHD_VERSION_MINOR} -ge 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no SSH-7408 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check SSH specific defined options" if [ ${SKIPTEST} -eq 0 ]; then LogText "Test: Checking specific defined options in ${SSH_DAEMON_OPTIONS_FILE}" @@ -258,31 +262,31 @@ fi if [ "${RESULT}" = "GOOD" ]; then - LogText "Result: SSH option ${OPTIONNAME} is configured very well" - Display --indent 4 --text "- SSH option: ${OPTIONNAME}" --result "${STATUS_OK}" --color GREEN + LogText "Result: OpenSSH option ${OPTIONNAME} is configured very well" + Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result "${STATUS_OK}" --color GREEN AddHP 3 3 elif [ "${RESULT}" = "MIDSCORED" ]; then - LogText "Result: SSH option ${OPTIONNAME} is configured reasonably" + LogText "Result: OpenSSH option ${OPTIONNAME} is configured reasonably" ReportSuggestion ${TEST_NO} "Consider hardening SSH configuration" "${OPTIONNAME} (${FOUNDVALUE} --> ${EXPECTEDVALUE})" "-" ReportDetails --test "${TEST_NO}" --service "sshd" --field "${OPTIONNAME}" --value "${FOUNDVALUE}" --preferredvalue "${EXPECTEDVALUE}" --description "sshd option ${OPTIONNAME}" - Display --indent 4 --text "- SSH option: ${OPTIONNAME}" --result "${STATUS_SUGGESTION}" --color YELLOW + Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result "${STATUS_SUGGESTION}" --color YELLOW AddHP 1 3 elif [ "${RESULT}" = "WEAK" ]; then - LogText "Result: SSH option ${OPTIONNAME} is in a weak configuration state and should be fixed" + LogText "Result: OpenSSH option ${OPTIONNAME} is in a weak configuration state and should be fixed" ReportSuggestion ${TEST_NO} "Consider hardening SSH configuration" "${OPTIONNAME} (${FOUNDVALUE} --> ${EXPECTEDVALUE})" "-" ReportDetails --test "${TEST_NO}" --service "sshd" --field "${OPTIONNAME}" --value "${FOUNDVALUE}" --preferredvalue "${EXPECTEDVALUE}" --description "sshd option ${OPTIONNAME}" - Display --indent 4 --text "- SSH option: ${OPTIONNAME}" --result "${STATUS_SUGGESTION}" --color YELLOW + Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result "${STATUS_SUGGESTION}" --color YELLOW AddHP 0 3 elif [ "${RESULT}" = "UNKNOWN" ]; then - LogText "Result: Value of SSH option ${OPTIONNAME} is unknown (not defined)" - Display --indent 4 --text "- SSH option: ${OPTIONNAME}" --result DEFAULT --color WHITE + LogText "Result: Value of OpenSSH option ${OPTIONNAME} is unknown (not defined)" + Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result DEFAULT --color WHITE Report "unknown_config_option[]=ssh|$SSH_DAEMON_CONFIG}|${OPTIONNAME}|" else LogText "Result: Option ${OPTIONNAME} not found in output" - Display --indent 4 --text "- SSH option: ${OPTIONNAME}" --result "${STATUS_NOT_FOUND}" --color WHITE + Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result "${STATUS_NOT_FOUND}" --color WHITE fi else - if IsVerbose; then Display --indent 4 --text "- SSH option: ${OPTIONNAME}" --result "SKIPPED (via config)" --color WHITE; fi + if IsVerbose; then Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result "SKIPPED (via config)" --color WHITE; fi fi done fi @@ -290,32 +294,32 @@ ################################################################################# # # Test : SSH-7440 - # Description : AllowUsers / AllowGroups + # Description : OpenSSH - AllowUsers / AllowGroups # Goal : Check if only a specific amount of users/groups can log in to the system - if [ ${SSH_DAEMON_RUNNING} -eq 1 -a ! -z "${SSH_DAEMON_OPTIONS_FILE}" ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi - Register --test-no SSH-7440 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check SSH option: AllowUsers and AllowGroups" + if [ ${OPENSSHD_RUNNING} -eq 1 -a ! -z "${SSH_DAEMON_OPTIONS_FILE}" ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi + Register --test-no SSH-7440 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check OpenSSH option: AllowUsers and AllowGroups" if [ ${SKIPTEST} -eq 0 ]; then FOUND=0 # AllowUsers FIND=$(${EGREPBINARY} -i "^AllowUsers" ${SSH_DAEMON_OPTIONS_FILE} | ${AWKBINARY} '{ print $2 }') if [ ! -z "${FIND}" ]; then LogText "Result: AllowUsers set, with value ${FIND}" - Display --indent 4 --text "- SSH option: AllowUsers" --result "${STATUS_FOUND}" --color GREEN + Display --indent 4 --text "- OpenSSH option: AllowUsers" --result "${STATUS_FOUND}" --color GREEN FOUND=1 else LogText "Result: AllowUsers is not set" - Display --indent 4 --text "- SSH option: AllowUsers" --result "${STATUS_NOT_FOUND}" --color WHITE + Display --indent 4 --text "- OpenSSH option: AllowUsers" --result "${STATUS_NOT_FOUND}" --color WHITE fi # AllowGroups FIND=$(${EGREPBINARY} -i "^AllowGroups" ${SSH_DAEMON_OPTIONS_FILE} | ${AWKBINARY} '{ print $2 }') if [ ! -z "${FIND}" ]; then LogText "Result: AllowUsers set ${FIND}" - Display --indent 4 --text "- SSH option: AllowGroups" --result "${STATUS_FOUND}" --color GREEN + Display --indent 4 --text "- OpenSSH option: AllowGroups" --result "${STATUS_FOUND}" --color GREEN FOUND=1 else LogText "Result: AllowGroups is not set" - Display --indent 4 --text "- SSH option: AllowGroups" --result "${STATUS_NOT_FOUND}" --color WHITE + Display --indent 4 --text "- OpenSSH option: AllowGroups" --result "${STATUS_NOT_FOUND}" --color WHITE fi if [ ${FOUND} -eq 1 ]; then @@ -331,6 +335,7 @@ # Report "ssh_daemon_running=${SSH_DAEMON_RUNNING}" +Report "openssh_daemon_running=${OPENSSHD_RUNNING}" WaitForKeyPress diff --git a/include/tests_webservers b/include/tests_webservers index 5d0907b9..dfe8aabc 100644 --- a/include/tests_webservers +++ b/include/tests_webservers @@ -251,8 +251,7 @@ Register --test-no HTTP-6640 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Determining existence of specific Apache modules" if [ ${SKIPTEST} -eq 0 ]; then # Check modules, module - CheckItem "apache_module" "/mod_evasive([0-9][0-9])?.so" - if [ ${ITEM_FOUND} -eq 1 ]; then + if CheckItem "apache_module" "/mod_evasive([0-9][0-9])?.so"; then Display --indent 10 --text "mod_evasive: anti-DoS/brute force" --result "${STATUS_FOUND}" --color GREEN AddHP 3 3 else @@ -271,8 +270,7 @@ Register --test-no HTTP-6641 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Determining existence of specific Apache modules" if [ ${SKIPTEST} -eq 0 ]; then # Check modules, module - CheckItem "apache_module" "/mod_(reqtimeout|qos).so" - if [ ${ITEM_FOUND} -eq 1 ]; then + if CheckItem "apache_module" "/mod_(reqtimeout|qos).so"; then Display --indent 10 --text "mod_reqtimeout/mod_qos" --result "${STATUS_FOUND}" --color GREEN AddHP 3 3 else @@ -290,8 +288,7 @@ Register --test-no HTTP-6643 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Determining existence of specific Apache modules" if [ ${SKIPTEST} -eq 0 ]; then # Check modules, module - CheckItem "apache_module" "/mod_security2.so" - if [ ${ITEM_FOUND} -eq 1 ]; then + if CheckItem "apache_module" "/mod_security2.so"; then Display --indent 10 --text "ModSecurity: web application firewall" --result "${STATUS_FOUND}" --color GREEN AddHP 3 3 else diff --git a/lynis b/lynis index 15d509ea..0914f9c4 100755 --- a/lynis +++ b/lynis @@ -21,6 +21,11 @@ # Lynis is an automated auditing tool for Unix based operating systems. # ################################################################################# +# + # Code quality: don't allow using undefined variables + set -o nounset +# +################################################################################# # # In Solaris /bin/sh is not POSIX, but /usr/xpg4/bin/sh is. # Switch to /usr/xpg4/bin/sh if it exists and we are not already running it. @@ -35,10 +40,10 @@ PROGRAM_AUTHOR_CONTACT="lynis-dev@cisofy.com" # Version details - PROGRAM_RELEASE_DATE="2019-03-21" - PROGRAM_RELEASE_TIMESTAMP=1553157295 - PROGRAM_RELEASE_TYPE="final" # dev or final - PROGRAM_VERSION="2.7.3" + PROGRAM_RELEASE_DATE="2019-06-29" + PROGRAM_RELEASE_TIMESTAMP=1561383761 + PROGRAM_RELEASE_TYPE="dev" # dev or final + PROGRAM_VERSION="3.0.0" # Source, documentation and license PROGRAM_SOURCE="https://github.com/CISOfy/lynis" @@ -55,11 +60,6 @@ DISPLAY_LANG="${LANG}" # required by function Display to deal with multi-bytes characters. - # Code quality: - # Set strict checking for development version for first part of code. After - # initialization this is checked with strict profile option. - if [ ${PROGRAM_RELEASE_TYPE} = "dev" ]; then set -u; fi - # ################################################################################# # @@ -67,6 +67,9 @@ # ################################################################################# # + # Check setuid bit + if [ -u "$0" ]; then echo "The called binary has the set-user-id bit - As this is unusual, execution will be stopped."; exit 1; fi + # Work directory WORKDIR=$(pwd) @@ -139,8 +142,7 @@ Make sure to execute ${PROGRAM_NAME} from untarred directory or check your insta ################################################################################# # # Perform a basic check for permissions. After including functions, using SafePerms() - WARN_ON_FILE_ISSUES=1 - WARN_ON_FILE_ISSUES_ASKED=0 + IGNORE_FILE_PERMISSION_ISSUES=0 FILES_TO_CHECK="consts functions" @@ -192,14 +194,10 @@ Make sure to execute ${PROGRAM_NAME} from untarred directory or check your insta printf "\n Option 2) Change ownership of the related files (or full directory).\n\n Commands (full directory):\n # cd ..\n # chown -R 0:0 lynis\n # cd lynis\n # ./lynis audit system" fi printf "\n\n[ Press ENTER to continue, or CTRL+C to cancel ]" - WARN_ON_FILE_ISSUES_ASKED=1 + IGNORE_FILE_PERMISSION_ISSUES=1 read DUMMY fi - if [ ${WARN_ON_FILE_ISSUES_ASKED} -eq 1 ]; then - WARN_ON_FILE_ISSUES=0 - fi - # Now include files if permissions are correct, or user decided to continue . ${INCLUDEDIR}/consts . ${INCLUDEDIR}/functions @@ -613,6 +611,9 @@ ${NORMAL} if [ ${EOL} -eq 1 ]; then echo " End-of-life: ${WARNING}YES${NORMAL}" ReportWarning "GEN-0010" "This version ${OS_VERSION} is marked end-of-life as of ${EOL_DATE}" + elif [ ${EOL} -eq 255 ]; then + # TODO - mark as item where community can provide help + LogText "Note: the end-of-life of '${OS_FULLNAME}' could not be checked. Entry missing in software-eol.db?" fi if [ ! -z "${OS_MODE}" ]; then echo " Operating system mode: ${OS_MODE}"; fi @@ -853,8 +854,17 @@ ${NORMAL} LogText "Action: checking plugin status in profile: ${PROFILE}" FIND3=$(grep "^plugin=${FIND2}" ${PROFILE}) if [ ! -z "${FIND3}" ]; then - LogText "Result: plugin enabled in profile (${PROFILE})" - PLUGIN_ENABLED_STATE=1 + FOUND=0 + for I in ${DISABLED_PLUGINS}; do + if [ "${I}" = "${FIND2}" ]; then + FOUND=1 + LogText "Result: plugin ${FIND2} is specifically disabled" + fi + done + if [ ${FOUND} -eq 0 ]; then + LogText "Result: plugin enabled in profile (${PROFILE})" + PLUGIN_ENABLED_STATE=1 + fi fi done if [ ${PLUGIN_ENABLED_STATE} -eq 1 ]; then @@ -948,7 +958,7 @@ ${NORMAL} for INCLUDE_TEST in ${INCLUDE_TESTS}; do INCLUDE_FILE="${INCLUDEDIR}/tests_${INCLUDE_TEST}" if [ -f ${INCLUDE_FILE} ]; then - if SafePerms ${INCLUDE_FILE}; then + if SafeFile ${INCLUDE_FILE}; then . ${INCLUDE_FILE} else LogText "Exception: skipping test category ${INCLUDE_TEST}, file ${INCLUDE_FILE} has bad permissions (should be 640, 600 or 400)"