diff --git a/.gitignore b/.gitignore index 3bb39987..913be28e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ playbooks/test.yml roles/test context/ particle/.vagrant +stig/stig.db diff --git a/stig/README.rst b/stig/README.rst new file mode 100644 index 00000000..d14dcc13 --- /dev/null +++ b/stig/README.rst @@ -0,0 +1,335 @@ +Secure Technical Implementation Guide - Audit and Remediations +=============================================================== + +Overview +-------- + +Round about 300 unofficial audit and remediation tasks for different Secure Technical Implementation Guides (STIG). + +Compiled STIGs: + +.. csv-table:: + :header-rows: 1 + + Type, Name, Version, ``stig_profile_name:``, ``stig_profile_version:``, Tested Platforms + CIS, Rocky Linux 9 Benchmark, v2.0.0, ``CIS Rocky Linux 9``, ``v2.0.0``, Rocky Linux 9 + +`Drop us a line `_ if you have successfully used any of the STIG profiles on other platforms. + +To audit before and after applying the remediations, you might use tools like `OpenVAS `_, `Lynis `_, `Nessus `_ or our Python script `files/audit.py `_. Remediations are done using the `tasks/main.yml `_. + +.. attention:: + + Do not attempt to use any of the Ansible tasks without first testing them in a non-operational environment. Linuxfabrik assume no responsibility whatsoever for its use by other parties, and makes no guarantees, expressed or implied, about its quality, reliability, or any other characteristic. + +.. note:: + + * This role cannot be run with the ``--check`` parameter. + * Password-less SSH access is required for the audits and remediations. + * The Rocky Linux 9 profile is intended for Rocky Linux 9 systems. + + +Before you begin +---------------- + +While remediating, this role breaks things on productive machines when applied completely. + This role **will make changes to the system that could or will break things**. Please take the time to familarise yourself with the STIG profile of your choice before applying this role to a system. + +Compile a list of non-applicable remediations for each server. + For example: If you are running an outbound proxy with Squid and would like to apply the "CIS CentOS Linux 7 v3.1.2" profile, you should exclude "2.2.12 Ensure HTTP Proxy Server is not installed". + +This role does not create or change host firewalls. + Because there are far more firewall tools on earth than just firewalld, nftables and iptables, and maybe you create your firewall rules using other techniques or roles. This topic is too complex to be configured automatically. + + +Installation +------------ + +Place this Ansible role in an appropriate directory: + +.. code-block:: text + + stig + ├── audit.csv: Audit script definitions + ├── audit.py: Python audit script + ├── audits: Contains all Audit Snippets (Bash) + ├── create-db: Script to create stig.db from CSV files + ├── dump-db: Script to dump stig.db to CSV files + ├── lib: Home of Linuxfabrik libraries + ├── profile.csv: STIG profile definitions + ├── remediation.csv: Remediation variable definitions + └── stig.db: SQLite database file + +If you want to use ``audit.py``: + +* Install Python modules: + + .. code-block:: + + dnf -y install python3-termcolor + +* Copy all Python files from https://git.linuxfabrik.ch/linuxfabrik/lib into ``stig/lib``. + + +Auditing a Machine (audit.py) +----------------------------- + +If using our Python script `files/audit.py `_, ensure that you are able to access the machine using SSH with root privileges and password-less authentication. The script checks SSH connectivity and password-less ``sudo`` before running audits. + +For example, start an audit only with controls whose name starts with "1", but at the same time exclude all controls whose name starts with "1.3" and "1.4": + +.. code-block:: bash + + ./audit.py --profile-name="CIS Rocky Linux 9" --profile-version="v2.0.0" --hostname=192.0.2.249 --username=root --lengthy --control-name-include='^1' --control-name-exclude='^1\.3|^1\.4' + +Example output (parts ommitted):: + + Audit Result + ============ + + ... + + Summary Table + ------------- + + Control ! Script ! Scoring ! Lvl ! Result + ------------------------------------------------------------------------+-------------------+---------+-----+------- + 1.1.1.1 Ensure mounting of cramfs filesystems is disabled (Automated) ! cramfs_off.sh ! Scored ! 1 ! Failed + 1.1.1.2 Ensure mounting of squashfs filesystems is disabled (Automated) ! squashfs_off.sh ! Scored ! 2 ! Failed + 1.1.1.3 Ensure mounting of udf filesystems is disabled (Automated) ! udf_off.sh ! Scored ! 1 ! Failed + 1.1.2 Ensure /tmp is configured (Automated) ! tmp_separate_partition.sh ! Scored ! 1 ! Passed + 1.1.3 Ensure noexec option set on /tmp partition (Automated) ! tmp_noexec.sh ! Scored ! 1 ! Passed + 1.1.4 Ensure nodev option set on /tmp partition (Automated) ! tmp_nodev.sh ! Scored ! 1 ! Passed + 1.1.5 Ensure nosuid option set on /tmp partition (Automated) ! tmp_nosuid.sh ! Scored ! 1 ! Passed + ... + + Profile + ------- + + * Benchmark: CIS Rocky Linux 9 (v2.0.0) + * Host: ``192.0.2.194`` + * Datetime: 2021-09-28 14:22:45 + * Score: 128/236 points (54.2%) + * Grade: F + +For each control: + +* If you get "Passed", the configuration is CIS-compliant for that control. +* If you get "Failed", the CIS requirements are not met. +* If you get "Skipped", the control is not applicable on that system. +* If you get "TODO", no audit script is implemented for that control yet. +* If you get "Review", we cannot detect compliance automatically; check the configuration manually. + +The overall grade is calculated as follows: + +.. code-block:: python + + def get_grade(percentage): + if percentage >= 97: + return 'A+' + if percentage >= 93: + return 'A' + if percentage >= 90: + return 'A-' + if percentage >= 87: + return 'B+' + if percentage >= 83: + return 'B' + if percentage >= 80: + return 'B-' + if percentage >= 77: + return 'C+' + if percentage >= 73: + return 'C' + if percentage >= 70: + return 'C-' + if percentage >= 67: + return 'D+' + if percentage >= 63: + return 'D' + if percentage >= 60: + return 'D-' + return 'F' + + +Remediating a Machine +--------------------- + +We have implemented more audits than remediation measures, especially in the area of application servers (for example Apache). The reason: Audits are not only easier to implement, but the configuration of an existing application server is far too specialized and complex to be done by a small, general role. Better, specialized or custom Ansible roles must be used here to deploy and maintain the server. + +After applying remediations: + +* Reboot. Always reboot a remediated machine to be sure for all settings to take effect. +* Keep an eye on your monitoring software. +* Run a second audit. +* Fix further findings using other roles. + +Variables (have a look at `defaults/main.yml `_ for a complete list of available variables): + +.. code-block:: yml + + stig: + - profile_name: 'CIS Rocky Linux 9' # mandatory + profile_version: 'v2.0.0' # default: "latest" + also_use_controls_disabled_by_default: True # default: false + control_name_include: # use regular expressions here + - '^1' + - '^2' + control_name_exclude: # use regular expressions here + - '^2\.1' + - '^2\.3' + + +Ansible Role Variables +---------------------- + +Have a look at `defaults/main.yml `_ for a complete list of available variables. + + +STIG "CIS Rocky Linux 9 Benchmark" - Details +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Mandatory: + +* You have to set the ``stig__grub2_password`` variable. + +Some remediations are disabled by default for various reasons - enable them only if needed: + +* | Audit system file permissions (or similar) + | Reason: File permissions can be reset by the package manager or even a reboot at any time, which means that auditing tends to fail. For this reason, our audit task ignores some of the known files in ``var/log``. +* | Ensure audit logs are not automatically deleted (or similar) + | Reason: No customer likes to have his machine stopped simply because the audit partition runs out of space, and the mass of cryptic audit logs cannot be checked anyway. +* | Ensure password expiration is 365 days or less (or similar) + | Reason: May lock you and Ansible out. +* | Ensure rsyslog is configured to send logs to a remote log host (or similar) + | Reason: This is more complex in reality than the CIS mediation suggests. +* | Ensure SSH root login is disabled (or similar) + | Reason: May lock you and Ansible out. +* | Ensure updates, patches, and additional security software are installed (or similar) + | Reason: Skipping this saves quite some time during the run. Also, there are other possible update strategies. + + +stig.db +------- + +This is a SQLite file and can be viewed and edited with *DB Browser for SQLite*, for example. + +The ``profile`` table contains STIG control definitions (currently some CIS benchmarks). The meaning of some of the columns: + +* ``enabled``: Specifies whether a *remediation* should be applied automatically or not. Set to "0" if this causes problems or is unnecessary. + +The ``audit`` table maps controls to audit scripts (Bash). The meaning of some of the columns: + +* ``exec_order``: Execution order within the specific STIG profile. +* ``audit_name``: Filename of the audit script in the ``audits/`` directory. + +The ``remediation`` table maps controls to Ansible remediation variables. + +Use NULL to unset any value. + +To get a complete list of disabled remediations, execute this SQL statement on ``stig.db``: + +.. code-block:: text + + SELECT * + FROM profile + WHERE + enabled = 0 + +Some audits and remediations in some STIG profiles might not be implemented for various reasons. As an example, to get a list, execute this SQL statement on ``stig.db``: + +.. code-block:: text + + SELECT p.* + FROM profile p + LEFT JOIN audit a ON p.id = a.id + WHERE + p.profile_name = "CIS Rocky Linux 9" + AND a.id IS NULL + + +Naming Scheme for Controls +-------------------------- + +From a remediation action point of view: ``[-
][-
]-`` + +* package or device: for example "httpd" or "tmp" +* section: for example "vhosts" +* action: a remediation action that should be done. One of + + * get: fetch some information - for audit tasks that will never have a remediation counterpart + * compare: compare two or more items - for audit tasks that will never have a remediation counterpart + + * off: disabling or configuring something to "off" + * on: enabling or configuring something to "on" + * disable: disabling a service + * enable: enabling a service + + * install: install a package + * update: update a package or packages + * remove: uninstalling a package, deleting files and directories + + * chmod: changing permissions using chmod + * chown: changing owner using chown + + * cron: configuring cronjobs + * timer: configuring systemd timer + * select: set something from a list of choices + * configure: more or less complex configuration tasks + * setup: both installation and configuration + + +Examples +-------- + +Auditing a Rocky Linux 9 VM, excluding some controls: + +.. code-block:: bash + + ./audit.py --lengthy --profile-name='CIS Rocky Linux 9' --profile-version='v2.0.0' --hostname=192.0.2.194 --control-name-exclude='^1\.9|^3\.4\.|^4\.1\.2\.2|^4\.2\.1\.5|^5\.2\.10|^5\.3\.1|^5\.3\.2|^5\.3\.3|^5\.4\.2|^5\.5\.1\.1' + +Apply the remedies of "CIS Rocky Linux 9" (use this as a starting point): + +.. code-block:: text + + # hosts.yml + cis_hosts: + vars: + ansible_become: True + hosts: + 192.0.2.194: + +.. code-block:: text + + # host_vars/192.0.2.194.yml + stig__crypto_policy: 'FIPS' + stig__grub2_password: 'BlueLake23' + +.. code-block:: text + + # group_vars/cis_hosts.yml + stig: + - profile_name: 'CIS Rocky Linux 9' + profile_version: 'v2.0.0' # or "latest" + also_use_controls_disabled_by_default: False + control_name_include: # use regular expressions here + - '^1' + - '^2' + - '^3' + - '^4' + - '^5' + - '^6' + control_name_exclude: + - '^1\.9' + +.. code-block:: text + + # playbook.yml + - hosts: + - 'cis_hosts' + roles: + - role: 'stig' + +.. code-block:: bash + + ansible-playbook --inventory=path/to/hosts.yml path/to/playbook.yml --extra-vars="ansible_ssh_user=root" diff --git a/stig/audit.csv b/stig/audit.csv new file mode 100644 index 00000000..eb1990f1 --- /dev/null +++ b/stig/audit.csv @@ -0,0 +1,346 @@ +id,exec_order,audit_name + +cramfs_off,1,cramfs_off.sh +freevxfs_off,2,freevxfs_off.sh +hfs_off,3,hfs_off.sh +hfsplus_off,4,hfsplus_off.sh +jffs2_off,5,jffs2_off.sh +squashfs_off,6,squashfs_off.sh +udf_off,7,udf_off.sh +usb_storage_off,8,usb_storage_off.sh +unused_filesystems_modules_off,9,unused_filesystems_modules_off.sh + +tmp_separate_partition,10,tmp_separate_partition.sh +tmp_nodev,11,tmp_nodev.sh +tmp_nosuid,12,tmp_nosuid.sh +tmp_noexec,13,tmp_noexec.sh + +dev_shm_separate_partition,14,dev_shm_separate_partition.sh +dev_shm_nodev,15,dev_shm_nodev.sh +dev_shm_nosuid,16,dev_shm_nosuid.sh +dev_shm_noexec,17,dev_shm_noexec.sh + +home_separate_partition,18,home_separate_partition.sh +home_nodev,19,home_nodev.sh +home_nosuid,20,home_nosuid.sh + +var_separate_partition,21,var_separate_partition.sh +var_nodev,22,var_nodev.sh +var_nosuid,23,var_nosuid.sh + +var_tmp_separate_partition,24,var_tmp_separate_partition.sh +var_tmp_nodev,25,var_tmp_nodev.sh +var_tmp_nosuid,26,var_tmp_nosuid.sh +var_tmp_noexec,27,var_tmp_noexec.sh + +var_log_separate_partition,28,var_log_separate_partition.sh +var_log_nodev,29,var_log_nodev.sh +var_log_nosuid,30,var_log_nosuid.sh +var_log_noexec,31,var_log_noexec.sh + +var_log_audit_separate_partition,32,var_log_audit_separate_partition.sh +var_log_audit_nodev,33,var_log_audit_nodev.sh +var_log_audit_nosuid,34,var_log_audit_nosuid.sh +var_log_audit_noexec,35,var_log_audit_noexec.sh + +gpg_keys_configured,36,gpg_keys_configured.sh +gpgcheck_globally_activated,37,gpgcheck_globally_activated.sh +repo_gpgcheck_globally_activated,38,repo_gpgcheck_globally_activated.sh +package_manager_repos_configured,39,package_manager_repos_configured.sh + +updates_patches_installed,40,updates_patches_installed.sh + +selinux_installed,41,selinux_installed.sh +selinux_not_disabled_bootloader,42,selinux_not_disabled_bootloader.sh +selinux_policy_configured,43,selinux_policy_configured.sh +selinux_mode_not_disabled,44,selinux_mode_not_disabled.sh +selinux_mode_enforcing,45,selinux_mode_enforcing.sh +no_unconfined_services,46,no_unconfined_services.sh +mcstrans_not_installed,47,mcstrans_not_installed.sh +setroubleshoot_not_installed,48,setroubleshoot_not_installed.sh + +bootloader_password_set,49,bootloader_password_set.sh +bootloader_config_access,50,bootloader_config_access.sh + +aslr_enabled,51,aslr_enabled.sh +ptrace_scope_restricted,52,ptrace_scope_restricted.sh +core_dump_backtraces_disabled,53,core_dump_backtraces_disabled.sh +core_dump_storage_disabled,54,core_dump_storage_disabled.sh + +crypto_policy_not_legacy,55,crypto_policy_not_legacy.sh +crypto_policy_not_sshd,56,crypto_policy_not_sshd.sh +crypto_policy_disable_sha1,57,crypto_policy_disable_sha1.sh +crypto_policy_disable_mac_128_bits,58,crypto_policy_disable_mac_128_bits.sh +crypto_policy_disable_ssh_cbc,59,crypto_policy_disable_ssh_cbc.sh +crypto_policy_disable_ssh_chacha20_poly1305,60,crypto_policy_disable_ssh_chacha20_poly1305.sh +crypto_policy_disable_ssh_etm,61,crypto_policy_disable_ssh_etm.sh + +motd_configured,62,motd_configured.sh +local_login_banner_configured,63,local_login_banner_configured.sh +remote_login_banner_configured,64,remote_login_banner_configured.sh +motd_permissions_configured,65,motd_permissions_configured.sh +issue_permissions_configured,66,issue_permissions_configured.sh +issue_net_permissions_configured,67,issue_net_permissions_configured.sh + +gdm_removed,68,gdm_removed.sh +gdm_login_banner_configured,69,gdm_login_banner_configured.sh +gdm_disable_user_list_enabled,70,gdm_disable_user_list_enabled.sh +gdm_screen_locks_when_idle,71,gdm_screen_locks_when_idle.sh +gdm_screen_locks_cannot_be_overridden,72,gdm_screen_locks_cannot_be_overridden.sh +gdm_automount_removable_media_disabled,73,gdm_automount_removable_media_disabled.sh +gdm_automount_not_overridden,74,gdm_automount_not_overridden.sh +gdm_autorun_never_enabled,75,gdm_autorun_never_enabled.sh +gdm_autorun_never_not_overridden,76,gdm_autorun_never_not_overridden.sh +xdmcp_not_enabled,77,xdmcp_not_enabled.sh + +autofs_unused,78,autofs_unused.sh +avahi_daemon_unused,79,avahi_daemon_unused.sh +dhcp_server_unused,80,dhcp_server_unused.sh +dns_server_unused,81,dns_server_unused.sh +dnsmasq_unused,82,dnsmasq_unused.sh +samba_unused,83,samba_unused.sh +ftp_server_unused,84,ftp_server_unused.sh +message_access_server_unused,85,message_access_server_unused.sh +nfs_server_unused,86,nfs_server_unused.sh +nis_server_unused,87,nis_server_unused.sh +print_server_unused,88,print_server_unused.sh +rpcbind_unused,89,rpcbind_unused.sh +rsync_unused,90,rsync_unused.sh +snmp_unused,91,snmp_unused.sh +telnet_unused,92,telnet_unused.sh +tftp_unused,93,tftp_unused.sh +web_proxy_unused,94,web_proxy_unused.sh +web_server_unused,95,web_server_unused.sh +xinetd_unused,96,xinetd_unused.sh +x_window_unused,97,x_window_unused.sh +mta_local_only_mode,98,mta_local_only_mode.sh +approved_services_listening,99,approved_services_listening.sh + +ftp_client_not_installed,100,ftp_client_not_installed.sh +ldap_client_not_installed,101,ldap_client_not_installed.sh +nis_client_not_installed,102,nis_client_not_installed.sh +telnet_client_not_installed,103,telnet_client_not_installed.sh +tftp_client_not_installed,104,tftp_client_not_installed.sh + +time_synchronization,105,time_synchronization.sh +chrony_configured,106,chrony_configured.sh +chrony_not_root,107,chrony_not_root.sh + +cron_enabled_n_active,108,cron_enabled_n_active.sh +cron_permissions_configured,109,cron_permissions_configured.sh +cron_hourly_configured,110,cron_hourly_configured.sh +cron_daily_configured,111,cron_daily_configured.sh +cron_weekly_configured,112,cron_weekly_configured.sh +cron_monthly_configured,113,cron_monthly_configured.sh +crond_permissions_configured,114,crond_permissions_configured.sh +crontab_auth_restricted,115,crontab_auth_restricted.sh + +at_auth_restricted,116,at_auth_restricted.sh + +ipv6_status_identified,117,ipv6_status_identified.sh +wireless_interfaces_disabled,118,wireless_interfaces_disabled.sh +bluetooth_services_unused,119,bluetooth_services_unused.sh + +dccp_kernel_module_unavailable,120,dccp_kernel_module_unavailable.sh +tipc_kernel_module_unavailable,121,tipc_kernel_module_unavailable.sh +rds_kernel_module_unavailable,122,rds_kernel_module_unavailable.sh +sctp_kernel_module_unavailable,123,sctp_kernel_module_unavailable.sh + +ip_forwarding_disabled,124,ip_forwarding_disabled.sh +packet_redirect_sending_disabled,125,packet_redirect_sending_disabled.sh +bogus_icmp_responses_ignored,126,bogus_icmp_responses_ignored.sh +icmp_broadcasted_requests_ignored,127,icmp_broadcasted_requests_ignored.sh +icmp_redirects_not_accepted,128,icmp_redirects_not_accepted.sh +icmp_secure_redirects_not_accepted,129,icmp_secure_redirects_not_accepted.sh +reverse_path_filtering_enabled,130,reverse_path_filtering_enabled.sh +source_routed_packets_not_accepted,131,source_routed_packets_not_accepted.sh +suspicious_packets_logged,132,suspicious_packets_logged.sh +tcp_syn_cookies_enabled,133,tcp_syn_cookies_enabled.sh +ipv6_router_advertisements_not_accepted,134,ipv6_router_advertisements_not_accepted.sh + +nftables_installed,135,nftables_installed.sh +single_firewall_config_utility,136,single_firewall_config_utility.sh +firewalld_drops_services_n_ports,137,firewalld_drops_services_n_ports.sh +firewalld_loopback_traffic,138,firewalld_loopback_traffic.sh + +nftables_base_chains_exist,139,nftables_base_chains_exist.sh +nftables_established_connections_configured,140,nftables_established_connections_configured.sh +nftables_deny_firewall_policy,141,nftables_deny_firewall_policy.sh +nftables_loopback_traffic,142,nftables_loopback_traffic.sh + +sshd_config_permissions,143,sshd_config_permissions.sh +sshd_private_host_key_permissions,144,sshd_private_host_key_permissions.sh +sshd_public_host_key_permissions,145,sshd_public_host_key_permissions.sh +sshd_ciphers,146,sshd_ciphers.sh +sshd_kex_algorithms,147,sshd_kex_algorithms.sh +sshd_macs,148,sshd_macs.sh +sshd_access,149,sshd_access.sh +sshd_banner,150,sshd_banner.sh +sshd_client_alive,151,sshd_client_alive.sh +sshd_disable_forwarding,152,sshd_disable_forwarding.sh +sshd_gssapi_authentication,153,sshd_gssapi_authentication.sh +sshd_host_based_authentication,154,sshd_host_based_authentication.sh +sshd_ignore_rhosts,155,sshd_ignore_rhosts.sh +sshd_login_grace_time,156,sshd_login_grace_time.sh +sshd_log_level,157,sshd_log_level.sh +sshd_max_auth_tries,158,sshd_max_auth_tries.sh +sshd_max_startups,159,sshd_max_startups.sh +sshd_max_sessions,160,sshd_max_sessions.sh +sshd_permit_empty_passwords,161,sshd_permit_empty_passwords.sh +sshd_permit_root_login,162,sshd_permit_root_login.sh +sshd_permit_user_environment,163,sshd_permit_user_environment.sh +sshd_use_pam,164,sshd_use_pam.sh + +sudo_installed,165,sudo_installed.sh +sudo_use_pty,166,sudo_use_pty.sh +sudo_logfile_exists,167,sudo_logfile_exists.sh +sudo_password_required,168,sudo_password_required.sh +sudo_reauth_not_disabled_globally,169,sudo_reauth_not_disabled_globally.sh +sudo_auth_timeout_configured,170,sudo_auth_timeout_configured.sh +su_access_restricted,171,su_access_restricted.sh + +latest_pam_installed,172,latest_pam_installed.sh +latest_authselect_installed,173,latest_authselect_installed.sh +latest_libpwquality_installed,174,latest_libpwquality_installed.sh + +authselect_profile_includes_pam_modules,175,authselect_profile_includes_pam_modules.sh +pam_faillock_enabled,176,pam_faillock_enabled.sh +pam_pwquality_enabled,177,pam_pwquality_enabled.sh +pam_pwhistory_enabled,178,pam_pwhistory_enabled.sh +pam_unix_enabled,179,pam_unix_enabled.sh + +password_failed_lockout_configured,180,password_failed_lockout_configured.sh +password_unlock_time_configured,181,password_unlock_time_configured.sh +password_lockout_includes_root,182,password_lockout_includes_root.sh + +password_changed_characters_configured,183,password_changed_characters_configured.sh +password_length_configured,184,password_length_configured.sh +password_complexity_configured,185,password_complexity_configured.sh +password_same_consecutive_chars_configured,186,password_same_consecutive_chars_configured.sh +password_max_sequential_chars_configured,187,password_max_sequential_chars_configured.sh +password_dictionary_check_enabled,188,password_dictionary_check_enabled.sh +password_quality_enforced_for_root,189,password_quality_enforced_for_root.sh + +password_history_remember_configured,190,password_history_remember_configured.sh +password_history_enforced_for_root,191,password_history_enforced_for_root.sh +pam_pwhistory_includes_use_authtok,192,pam_pwhistory_includes_use_authtok.sh + +pam_unix_no_nullok,193,pam_unix_no_nullok.sh +pam_unix_no_remember,194,pam_unix_no_remember.sh +pam_unix_strong_hashing_algorithm,195,pam_unix_strong_hashing_algorithm.sh +pam_unix_includes_use_authtok,196,pam_unix_includes_use_authtok.sh + +password_expiration_configured,197,password_expiration_configured.sh +minimum_password_days_configured,198,minimum_password_days_configured.sh +password_expiration_warning_days,199,password_expiration_warning_days_configured.sh +strong_password_hashing_algorithm,200,strong_password_hashing_algorithm.sh +inactive_password_lock_configured,201,inactive_password_lock_configured.sh +users_last_password_change_in_past,202,users_last_password_change_in_past.sh + +root_only_uid_0,203,root_only_uid_0.sh +root_only_gid_0,204,root_only_gid_0.sh +group_root_only_gid_0,205,group_root_only_gid_0.sh +root_account_access_controlled,206,root_account_access_controlled.sh +root_path_integrity,207,root_path_integrity.sh +root_umask_configured,208,root_umask_configured.sh +system_accounts_no_valid_shell,209,system_accounts_no_valid_shell.sh +accounts_without_valid_shell_locked,210,accounts_without_valid_shell_locked.sh + +nologin_not_listed_in_shells,211,nologin_not_listed_in_shells.sh +default_user_shell_timeout_configured,212,default_user_shell_timeout_configured.sh +default_user_umask_configured,213,default_user_umask_configured.sh + +aide_installed,214,aide_installed.sh +aide_integrity_checked,215,aide_integrity_checked.sh +audit_tools_protected,216,audit_tools_protected.sh + +journald_enabled,217,journald_enabled.sh +journald_log_access,218,journald_log_access.sh +journald_log_rotation,219,journald_log_rotation.sh +single_logging,220,single_logging.sh + +journal_remote_pkg,221,journal_remote_pkg.sh +journal_upload_auth,222,journal_upload_auth.sh +journal_upload_enabled,223,journal_upload_enabled.sh +journal_remote_unused,224,journal_remote_unused.sh + +journald_no_syslog,225,journald_no_syslog.sh +journald_compress,226,journald_compress.sh +journald_storage,227,journald_storage.sh + +rsyslog_installed,228,rsyslog_installed.sh +rsyslog_enabled,229,rsyslog_enabled.sh +journald_to_rsyslog,230,journald_to_rsyslog.sh +rsyslog_mode,231,rsyslog_mode.sh +rsyslog_logging,232,rsyslog_logging.sh +rsyslog_remote,233,rsyslog_remote.sh +rsyslog_no_receive,234,rsyslog_no_receive.sh +rsyslog_logrotate,235,rsyslog_logrotate.sh + +logfiles_access,236,logfiles_access.sh + +auditd_installed,237,auditd_installed.sh +auditd_prior_processes,238,auditd_prior_processes.sh +auditd_backlog_limit,239,auditd_backlog_limit.sh +auditd_active,240,auditd_active.sh +audit_maxsize,241,audit_maxsize.sh +audit_keep_logs,242,audit_keep_logs.sh +audit_full_act,243,audit_full_act.sh +audit_lowspace,244,audit_lowspace.sh + +audit_scope,245,audit_scope.sh +audit_su_exec,246,audit_su_exec.sh +audit_sudo_log,247,audit_sudo_log.sh +audit_timechg,248,audit_timechg.sh +audit_netenv,249,audit_netenv.sh +audit_privcmd,250,audit_privcmd.sh +audit_filefail,251,audit_filefail.sh +audit_identity,252,audit_identity.sh +audit_permchg,253,audit_permchg.sh +audit_mount,254,audit_mount.sh +audit_session,255,audit_session.sh +audit_login,256,audit_login.sh +audit_delete,257,audit_delete.sh +audit_mac,258,audit_mac.sh +audit_chcon,259,audit_chcon.sh +audit_setfacl,260,audit_setfacl.sh +audit_chacl,261,audit_chacl.sh +audit_usermod,262,audit_usermod.sh +audit_kmod,263,audit_kmod.sh +audit_config_immutable,264,audit_config_immutable.sh +audit_config_consistent,265,audit_config_consistent.sh + +audit_log_dir_mode,266,audit_log_dir_mode.sh +audit_log_mode,267,audit_log_mode.sh +audit_log_owner,268,audit_log_owner.sh +audit_log_group,269,audit_log_group.sh +audit_conf_mode,270,audit_conf_mode.sh +audit_conf_owner,271,audit_conf_owner.sh +audit_conf_group,272,audit_conf_group.sh +audit_tools_mode,273,audit_tools_mode.sh +audit_tools_owner,274,audit_tools_owner.sh +audit_tools_group,275,audit_tools_group.sh + +etc_passwd_permissions,276,etc_passwd_permissions.sh +etc_passwd_dash_permissions,277,etc_passwd_dash_permissions.sh +etc_group_permissions,278,etc_group_permissions.sh +etc_group_dash_permissions,279,etc_group_dash_permissions.sh +etc_shadow_permissions,280,etc_shadow_permissions.sh +etc_shadow_dash_permissions,281,etc_shadow_dash_permissions.sh +etc_gshadow_permissions,282,etc_gshadow_permissions.sh +etc_gshadow_dash_permissions,283,etc_gshadow_dash_permissions.sh +etc_shells_permissions,284,etc_shells_permissions.sh +etc_security_opasswd_permissions,285,etc_security_opasswd_permissions.sh +world_writable_files_and_directories_secured,286,world_writable_files_and_directories_secured.sh +no_unowned_or_ungrouped_files_exist,287,no_unowned_or_ungrouped_files_exist.sh +suid_sgid_files_reviewed,288,suid_sgid_files_reviewed.sh + +passwd_use_shadowed_passwords,289,passwd_use_shadowed_passwords.sh +shadow_password_fields_not_empty,290,shadow_password_fields_not_empty.sh +passwd_groups_exist_in_group,291,passwd_groups_exist_in_group.sh +no_duplicate_uids_exist,292,no_duplicate_uids_exist.sh +no_duplicate_gids_exist,293,no_duplicate_gids_exist.sh +no_duplicate_user_names_exist,294,no_duplicate_user_names_exist.sh +no_duplicate_group_names_exist,295,no_duplicate_group_names_exist.sh +local_interactive_user_home_directories_configured,296,local_interactive_user_home_directories_configured.sh +local_interactive_user_dot_files_access_configured,297,local_interactive_user_dot_files_access_configured.sh diff --git a/stig/audit.py b/stig/audit.py new file mode 100644 index 00000000..94cc9666 --- /dev/null +++ b/stig/audit.py @@ -0,0 +1,457 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8; py-indent-offset: 4 -*- +# +# Author: Linuxfabrik GmbH, Zurich, Switzerland +# Contact: info (at) linuxfabrik (dot) ch +# https://www.linuxfabrik.ch/ +# License: The Unlicense, see LICENSE file. + +# https://git.linuxfabrik.ch/linuxfabrik-icinga-plugins/checks-linux/-/blob/master/CONTRIBUTING.md +# 2025060301 + +"""Have a look at the check's README for further details. +""" + +import os + +# considering a virtual environment +ACTIVATE_THIS = False +venv_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'linuxfabrik-venv3') +if os.path.exists(venv_path): + ACTIVATE_THIS = os.path.join(venv_path, 'bin/activate_this.py') + +if os.getenv('LINUXFABRIK_VENV3'): + ACTIVATE_THIS = os.path.join(os.getenv('LINUXFABRIK_VENV3') + 'bin/activate_this.py') + +if ACTIVATE_THIS and os.path.isfile(ACTIVATE_THIS): + exec(open(ACTIVATE_THIS).read(), {'__file__': ACTIVATE_THIS}) # pylint: disable=W0122 + + +import argparse # pylint: disable=C0413 +import sys # pylint: disable=C0413 +from termcolor import colored + +import lib.base # pylint: disable=C0413 +import lib.db_sqlite # pylint: disable=C0413 +import lib.shell # pylint: disable=C0413 +import lib.time # pylint: disable=C0413 +import lib.txt # pylint: disable=C0413 +from lib.globals import STATE_OK, STATE_UNKNOWN # pylint: disable=C0413 + + +__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland' +__version__ = '2022051101' + +DESCRIPTION = """A working Linuxfabrik monitoring plugin, written in Python 3, as a basis for + further development, and much more text to help admins get this check up and + running.""" + +DEFAULT_HOSTNAME = 'localhost' +DEFAULT_LENGTHY = False +DEFAULT_PATH = '.' +DEFAULT_USERNAME = 'root' + +#NOTE: check what profiles are still needed +STIG_PROFILES = [ + 'CIS Rocky Linux 9', +] + +# list of result codes +PASS = 0 +FAIL = 1 +SKIP = 2 # not applicable +TODO = 4 # needs to be implemented +REV = 8 # review manually + +# profile name -> (os_name, version_prefix) +PROFILE_OS_MAP = { + 'CIS Rocky Linux 9': ('Rocky Linux', '9'), + 'CIS CentOS Linux 8': ('CentOS Linux', '8'), + 'CIS CentOS Linux 7': ('CentOS Linux', '7'), +} + + +def parse_args(): + """Parse command line arguments using argparse. + """ + parser = argparse.ArgumentParser(description=DESCRIPTION) + + parser.add_argument( + '-V', '--version', + action='version', + version='%(prog)s: v{} by {}'.format(__version__, __author__) + ) + + parser.add_argument( + '--control-name-exclude', + help='STIG control names to exclude, using a regular expression. Example: "^1\\.1\\.2|^1\\.1\\.4". Default: %(default)s', + dest='CONTROL_NAME_EXCLUDE', + default=None, + ) + + parser.add_argument( + '--control-name-include', + help='STIG control names to use, using a regular expression. Include comes before exclude. Example: "^1\\.1|^3". Default: %(default)s', + dest='CONTROL_NAME_INCLUDE', + default=None, + ) + + parser.add_argument( + '-H', '--hostname', + help='Host to be audited, can be IP address or hostname. Default: %(default)s', + dest='HOSTNAME', + default=DEFAULT_HOSTNAME, + ) + + parser.add_argument( + '--lengthy', + help='Extended reporting.', + dest='LENGTHY', + action='store_true', + default=DEFAULT_LENGTHY, + ) + + parser.add_argument( + '--path', + help='Local path to stig.db. Default: %(default)s', + dest='PATH', + default=DEFAULT_PATH, + ) + + parser.add_argument( + '--profile-name', + help='STIG profile to audit.', + dest='PROFILE_NAME', + choices=STIG_PROFILES, + required=True, + ) + + parser.add_argument( + '--profile-version', + help='STIG profile version. To be specific, use something like "v3.1.2". Default: %(default)s', + dest='PROFILE_VERSION', + default='latest', + ) + + parser.add_argument( + '--username', + help='Remote SSH Username. Default: %(default)s', + dest='USERNAME', + default=DEFAULT_USERNAME, + ) + + return parser.parse_args() + + +def get_latest(args): + """Get latest audit from local STIG database (SQLite). + """ + success, conn = lib.db_sqlite.connect(path=args.PATH, filename='stig.db') + if not success: + return False + + # Get a list of matching profile remediations from the STIG database + if args.PROFILE_VERSION == 'latest': + success, result = lib.db_sqlite.select( + conn, + """SELECT profile_version + FROM profile + WHERE + profile_name = :profile_name + ORDER BY profile_version DESC + LIMIT 1""", + data={ + 'profile_name': args.PROFILE_NAME, + }, + delete_db_on_operational_error=False, + ) + lib.db_sqlite.close(conn) + if not success or not result: + return False + return result[0]['profile_version'] + + lib.db_sqlite.close(conn) + return args.PROFILE_VERSION + + +def get_audits(args): + """Get audits from local STIG database (SQLite). + """ + success, conn = lib.db_sqlite.connect(path=args.PATH, filename='stig.db') + if not success: + return False + + data = {} + sql = """SELECT p.*, a.exec_order, a.audit_name + FROM profile AS p + LEFT JOIN audit AS a ON p.id = a.id + WHERE + profile_name = :profile_name + AND profile_version = :profile_version + """ + if args.CONTROL_NAME_INCLUDE: + sql += 'and control_name REGEXP :control_name_include\n' + data['control_name_include'] = args.CONTROL_NAME_INCLUDE + if args.CONTROL_NAME_EXCLUDE: + sql += 'and control_name NOT REGEXP :control_name_exclude\n' + data['control_name_exclude'] = args.CONTROL_NAME_EXCLUDE + sql += 'ORDER BY a.exec_order ASC' + data['profile_name'] = args.PROFILE_NAME + data['profile_version'] = get_latest(args) + success, result = lib.db_sqlite.select(conn, sql, data) + if not success: + # error accessing or querying the cache + lib.db_sqlite.close(conn) + return False + + if not result or result is None: + # key not found + lib.db_sqlite.close(conn) + return False + + # return the value + lib.db_sqlite.close(conn) + return result + + +def retc2str(retc): + if retc == PASS: + return colored('Passed', 'green') + if retc == FAIL: + return colored('Failed', 'red') + if retc == SKIP: + return colored('Skipped', 'yellow') + if retc == TODO: + return colored('TODO', 'magenta') + if retc == REV: + return colored('Review', 'blue') + + +def get_grade(percentage): + if percentage >= 97: + return colored('A+', 'green', attrs=['bold']) + if percentage >= 93: + return colored('A', 'green', attrs=['bold']) + if percentage >= 90: + return colored('A-', 'green', attrs=['bold']) + if percentage >= 87: + return colored('B+', 'yellow', attrs=['bold']) + if percentage >= 83: + return colored('B', 'yellow', attrs=['bold']) + if percentage >= 80: + return colored('B-', 'yellow', attrs=['bold']) + if percentage >= 77: + return colored('C+', 'yellow', attrs=['bold']) + if percentage >= 73: + return colored('C', 'yellow', attrs=['bold']) + if percentage >= 70: + return colored('C-', 'yellow', attrs=['bold']) + if percentage >= 67: + return colored('D+', 'red', attrs=['bold']) + if percentage >= 63: + return colored('D', 'red', attrs=['bold']) + if percentage >= 60: + return colored('D-', 'red', attrs=['bold']) + return colored('F', 'red', attrs=['bold']) + + +def get_remote_os(username, hostname): + """get os name and version from remote host via /etc/os-release.""" + cmd = 'ssh {}@{} "cat /etc/os-release"'.format(username, hostname) + stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(cmd, shell=True)) + if retc != 0: + return None, None + os_name = None + os_version = None + for line in stdout.splitlines(): + if line.startswith('NAME='): + os_name = line.split('=', 1)[1].strip('"\'') + elif line.startswith('VERSION_ID='): + os_version = line.split('=', 1)[1].strip('"\'') + return os_name, os_version + + + +def check_prerequisites(username, hostname): + """Validate SSH connectivity and passwordless sudo access. + + Args: + username: SSH username + hostname: Remote hostname or IP + + Returns: + tuple: (success: bool, error_message: str) + """ + # Test 1: Basic SSH connectivity + cmd = 'ssh -o ConnectTimeout=5 -o BatchMode=yes {}@{} "echo test" 2>&1'.format(username, hostname) + stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(cmd, shell=True)) + + if retc != 0: + if 'Permission denied' in stderr or 'No route to host' in stderr or 'Connection refused' in stderr: + return False, f'SSH connection failed: Cannot connect to {username}@{hostname}. Check SSH keys and network connectivity.' + return False, f'SSH connection failed: {stderr.strip()}' + + # Test 2: Passwordless sudo + cmd = 'ssh {}@{} "sudo -n true" 2>&1'.format(username, hostname) + stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(cmd, shell=True)) + + if retc != 0: + if 'a password is required' in stderr or 'a terminal is required' in stderr: + return False, ( + f'Passwordless sudo is not configured for {username}@{hostname}.\n' + f'Fix: Add "{username} ALL=(ALL) NOPASSWD: ALL" to /etc/sudoers on the remote host.' + ) + return False, f'Sudo check failed: {stderr.strip()}' + + return True, '' + +def main(): + """The main function. Hier spielt die Musik. + """ + + # parse the command line, exit with UNKNOWN if it fails + try: + args = parse_args() + except SystemExit: + sys.exit(STATE_UNKNOWN) + + # get all audits that has to be done + audits = get_audits(args) + if not audits: + lib.base.oao('No audit tasks found.') + + # init some vars + total_score = 0 + host_score = 0 + percentage = 0 + prolog = '' + + msg = 'Audit Result\n============\n\n' + + # verify remote os matches profile + if args.PROFILE_NAME in PROFILE_OS_MAP: + expected_os, expected_ver = PROFILE_OS_MAP[args.PROFILE_NAME] + detected_os, detected_ver = get_remote_os(args.USERNAME, args.HOSTNAME) + if detected_os and detected_ver: + if detected_os != expected_os or not detected_ver.startswith(expected_ver): + print(colored( + f'Warning: OS mismatch - detected "{detected_os} {detected_ver}", ' + f'expected "{expected_os} {expected_ver}*"', + 'yellow', + )) + + prereq_ok, prereq_error = check_prerequisites(args.USERNAME, args.HOSTNAME) + if not prereq_ok: + print(colored('FAILED', 'red')) + print(colored('\nError: ' + prereq_error, 'red')) + sys.exit(STATE_UNKNOWN) + print(colored('OK', 'green')) + # prepare the host, copy shell libraries + cmd = 'scp audits/lib.sh {}@{}:/tmp/'.format( + args.USERNAME, + args.HOSTNAME, + ) + stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(cmd)) + # The scp utility exits 0 on success, and >0 if an error occurs. + if retc != 0: + print(f'The command "{cmd}" failed with:\n{stderr}') + sys.exit(STATE_UNKNOWN) + + # progress bar + count = len(audits) + if count == 0: + increase = 100 + else: + increase = 100 / count + progress = 0 + + # run the audit on the specified host via ssh + # this code section is ok for the moment, but could be improved in the future + table_values = [] + for audit in audits: + # print the progress bar + print('{}% ({}){}'.format(round(progress), audit['control_name'], ' '*40) , end='\r') + progress += increase + + audit_path = 'audits/' + str(audit['audit_name']) if audit['audit_name'] else '' + if audit_path and os.path.isfile(audit_path) and os.stat(audit_path).st_size > 0: + cmd = 'ssh {}@{} "sudo bash -s --" < audits/{}'.format( + args.USERNAME, + args.HOSTNAME, + audit['audit_name'], + ) + stdout, stderr, retc = lib.base.coe(lib.shell.shell_exec(cmd, shell=True)) + # ssh exits with the exit status of the remote command or with 255 if an error occurred. + if retc == 255: + print(f'The command "{cmd}" failed with:\n{stderr}') + sys.exit(STATE_UNKNOWN) + + # todo do error handling + if stdout: + prolog += stdout.strip() + '\n\n\n' + else: + # there is no script for auditing provided, seems to be not implemented + retc = TODO + table_values.append({ + 'control_name': audit['control_name'], + 'scored': 'Scored' if audit['automated'] == 1 else 'Not Scored', + 'level': audit['server_level'], + 'result': retc2str(retc), + 'audit_name': audit['audit_name'], + }) + # calculate the score based on the plugin return code (but only if this is a scored test) + if audit['automated'] == 1: + if retc == PASS: + total_score += audit['server_level'] + host_score += audit['server_level'] + if retc == FAIL: + total_score += audit['server_level'] + print(' '*85, end='\r') # clear the progress bar + + if total_score: + percentage = round(host_score / total_score * 100, 1) + + # build the message + if prolog: + msg += '{}\n\n\n'.format(prolog.strip()) + msg += 'Summary Table\n-------------\n\n' + if args.LENGTHY: + msg += lib.base.get_table( + table_values, + ['control_name', 'audit_name', 'scored', 'level', 'result'], + header=['Control', 'Script', 'Scoring', 'Lvl', 'Result'], + ) + else: + msg += lib.base.get_table( + table_values, + ['control_name', 'result'], + header=['Control', 'Result'], + ) + msg += '\n\n' + + msg += 'Profile\n-------\n\n' + msg += '* Benchmark: {} ({})\n* Host: ``{}``\n* Datetime: {}\n'.format( + args.PROFILE_NAME, + get_latest(args), + args.HOSTNAME, + lib.time.now(as_type='iso'), + ) + + if total_score: + msg += '* Score: {}/{} {} ({}%)\n* Grade: {}'.format( + host_score, + total_score, + lib.txt.pluralize('point', total_score), + percentage, + get_grade(percentage), + ) + + # over and out + lib.base.oao(msg) + + +if __name__ == '__main__': + try: + main() + except Exception: # pylint: disable=W0703 + lib.base.cu() \ No newline at end of file diff --git a/stig/audits/ default_user_shell_timeout_configured.sh b/stig/audits/ default_user_shell_timeout_configured.sh new file mode 100644 index 00000000..d6a3c9ff --- /dev/null +++ b/stig/audits/ default_user_shell_timeout_configured.sh @@ -0,0 +1,40 @@ +source /tmp/lib.sh + +l_tmout_set="900" +files="$(grep -Pls -- '^([^#\n\r]+)?\bTMOUT\b' /etc/*bashrc /etc/profile /etc/profile.d/*.sh 2>/dev/null)" + +[ -n "$files" ] || exit $FAIL + +found_ok="" + +while IFS= read -r f; do + [ -f "$f" ] || continue + + tmout_vals="$(grep -Po -- '^([^#\n\r]+)?\bTMOUT=\d+\b' "$f" 2>/dev/null | awk -F= '{print $2}')" + tmout_readonly="$(grep -P -- '^\h*(typeset\h\-xr\hTMOUT=\d+|([^#\n\r]+)?\breadonly\h+TMOUT\b)' "$f" 2>/dev/null)" + tmout_export="$(grep -P -- '^\h*(typeset\h\-xr\hTMOUT=\d+|([^#\n\r]+)?\bexport\b([^#\n\r]+\b)?TMOUT\b)' "$f" 2>/dev/null)" + + if [ -n "$tmout_vals" ]; then + while IFS= read -r v; do + [ -n "$v" ] || continue + + if [ "$v" -le 0 ] || [ "$v" -gt "$l_tmout_set" ]; then + exit $FAIL + fi + + if [ -z "$tmout_readonly" ] || [ -z "$tmout_export" ]; then + exit $FAIL + fi + + found_ok="1" + done <<< "$tmout_vals" + else + if [ -n "$tmout_readonly" ] || [ -n "$tmout_export" ]; then + exit $FAIL + fi + fi +done <<< "$files" + +[ -n "$found_ok" ] || exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/accounts_without_valid_shell_locked.sh b/stig/audits/accounts_without_valid_shell_locked.sh new file mode 100644 index 00000000..40c920ee --- /dev/null +++ b/stig/audits/accounts_without_valid_shell_locked.sh @@ -0,0 +1,15 @@ +source /tmp/lib.sh + +l_valid_shells="^($(awk -F\/ '$NF != "nologin" {print}' /etc/shells | sed -rn '/^\//{s,/,\\\\/,g;p}' | paste -s -d '|' - ))$" + +noncompliant="$( +while IFS= read -r l_user; do + passwd -S "$l_user" 2>/dev/null | awk '$2 !~ /^L/ {print $1}' +done < <(awk -v pat="$l_valid_shells" -F: '($1 != "root" && $(NF) !~ pat) {print $1}' /etc/passwd) +)" + +if [ -n "$noncompliant" ]; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/aide_installed.sh b/stig/audits/aide_installed.sh new file mode 100644 index 00000000..6bcff67b --- /dev/null +++ b/stig/audits/aide_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_installed 'aide'; then + exit $PASS +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/aide_integrity_checked.sh b/stig/audits/aide_integrity_checked.sh new file mode 100644 index 00000000..7fa18302 --- /dev/null +++ b/stig/audits/aide_integrity_checked.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +cron_hits="$(grep -Ers '^([^#]+\s+)?(\/usr\/s?bin\/|^\s*)aide(\.wrapper)?\s(--?\S+\s)*(--(check|update)|\$AIDEARGS)\b' /etc/cron.* /etc/crontab /var/spool/cron/ 2>/dev/null)" + +if [ -z "$cron_hits" ]; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/aslr_enabled.sh b/stig/audits/aslr_enabled.sh new file mode 100644 index 00000000..af02bfc5 --- /dev/null +++ b/stig/audits/aslr_enabled.sh @@ -0,0 +1,57 @@ +source /tmp/lib.sh + +kpname="kernel.randomize_va_space" +expected="2" + +runval="$(sysctl -n "$kpname" 2>/dev/null | xargs)" +if [ "$runval" != "$expected" ]; then + echo "FAIL: $kpname is '$runval' in the running configuration (expected '$expected')" + exit $FAIL +fi + +durable_val="" +durable_file="" + +curfile="" + +while IFS= read -r line; do + if echo "$line" | grep -Pq '^\s*#\s*/[^#\s]+\.conf\b'; then + curfile="${line#\# }" + curfile="$(echo "$curfile" | xargs)" + continue + fi + + echo "$line" | grep -Pq '^\s*#' && continue + [ -z "$(echo "$line" | xargs)" ] && continue + + if echo "$line" | grep -Pq "^\s*${kpname//./\\.}\s*="; then + durable_val="$(echo "$line" | awk -F= '{print $2}' | xargs)" + durable_file="$curfile" + fi +done < <(/usr/lib/systemd/systemd-sysctl --cat-config 2>/dev/null) + +ufwscf="" +if [ -f /etc/default/ufw ]; then + ufwscf="$(awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw | xargs)" +fi + +if [ -n "$ufwscf" ] && [ -f "$ufwscf" ]; then + ufw_last="$(grep -P "^\s*${kpname//./\\.}\s*=" "$ufwscf" 2>/dev/null | tail -n 1)" + if [ -n "$ufw_last" ]; then + durable_val="$(echo "$ufw_last" | awk -F= '{print $2}' | xargs)" + durable_file="$ufwscf" + fi +fi + +if [ -z "$durable_val" ]; then + echo "FAIL: $kpname is not set in an included sysctl configuration file" + exit $FAIL +fi + +if [ "$durable_val" != "$expected" ]; then + echo "FAIL: $kpname is '$durable_val' in '$durable_file' (expected '$expected')" + exit $FAIL +fi + +echo "PASS: $kpname is '$expected' in running config and durable config ($durable_file)" +exit $PASS \ No newline at end of file diff --git a/stig/audits/at_auth_restricted.sh b/stig/audits/at_auth_restricted.sh new file mode 100644 index 00000000..f1a713c9 --- /dev/null +++ b/stig/audits/at_auth_restricted.sh @@ -0,0 +1,21 @@ +source /tmp/lib.sh + +# not applicable if at not installed +is_not_installed at && exit $PASS + +# /etc/at.allow must exist, mode <=640, owner root, group root or daemon +[ -f /etc/at.allow ] || exit $FAIL +mode="$(stat -Lc '%a' /etc/at.allow 2>/dev/null)" +uid="$(stat -Lc '%u' /etc/at.allow 2>/dev/null)" +grp="$(stat -Lc '%G' /etc/at.allow 2>/dev/null)" +[ "$mode" -le 640 ] && [ "$uid" -eq 0 ] && [[ "$grp" == "root" || "$grp" == "daemon" ]] || exit $FAIL + +# /etc/at.deny: must not exist, or be mode <=640, owner root, group root or daemon +if [ -e /etc/at.deny ]; then + mode="$(stat -Lc '%a' /etc/at.deny 2>/dev/null)" + uid="$(stat -Lc '%u' /etc/at.deny 2>/dev/null)" + grp="$(stat -Lc '%G' /etc/at.deny 2>/dev/null)" + [ "$mode" -le 640 ] && [ "$uid" -eq 0 ] && [[ "$grp" == "root" || "$grp" == "daemon" ]] || exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/audit_chacl.sh b/stig/audits/audit_chacl.sh new file mode 100644 index 00000000..fc8e20d1 --- /dev/null +++ b/stig/audits/audit_chacl.sh @@ -0,0 +1,26 @@ +source /tmp/lib.sh + +fail=0 + +UID_MIN=$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs) +[ -n "${UID_MIN}" ] || exit $FAIL + +awk "/^ *-a *always,exit/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -F *perm=x/ \ +&&/ -F *path=\/usr\/bin\/chacl/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk "/^ *-a *always,exit/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -F *perm=x/ \ +&&/ -F *path=\/usr\/bin\/chacl/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_chcon.sh b/stig/audits/audit_chcon.sh new file mode 100644 index 00000000..8270de18 --- /dev/null +++ b/stig/audits/audit_chcon.sh @@ -0,0 +1,26 @@ +source /tmp/lib.sh + +fail=0 + +UID_MIN=$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs) +[ -n "${UID_MIN}" ] || exit $FAIL + +awk "/^ *-a *always,exit/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -F *perm=x/ \ +&&/ -F *path=\/usr\/bin\/chcon/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk "/^ *-a *always,exit/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -F *perm=x/ \ +&&/ -F *path=\/usr\/bin\/chcon/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_conf_group.sh b/stig/audits/audit_conf_group.sh new file mode 100644 index 00000000..ee8e5683 --- /dev/null +++ b/stig/audits/audit_conf_group.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if find /etc/audit/ -type f \( -name '*.conf' -o -name '*.rules' \) ! -group root | grep -q '.'; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/audit_conf_mode.sh b/stig/audits/audit_conf_mode.sh new file mode 100644 index 00000000..0e8afc98 --- /dev/null +++ b/stig/audits/audit_conf_mode.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +l_perm_mask="0137" + +if find /etc/audit/ -type f \( -name "*.conf" -o -name '*.rules' \) -perm /"$l_perm_mask" | grep -q '.'; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/audit_conf_owner.sh b/stig/audits/audit_conf_owner.sh new file mode 100644 index 00000000..c6106552 --- /dev/null +++ b/stig/audits/audit_conf_owner.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if find /etc/audit/ -type f \( -name '*.conf' -o -name '*.rules' \) ! -user root | grep -q '.'; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/audit_config_consistent.sh b/stig/audits/audit_config_consistent.sh new file mode 100644 index 00000000..8e3dbb8f --- /dev/null +++ b/stig/audits/audit_config_consistent.sh @@ -0,0 +1,5 @@ +source /tmp/lib.sh + +augenrules --check 2>&1 | grep -q 'No change' || exit $FAIL + +exit $PASS diff --git a/stig/audits/audit_config_immutable.sh b/stig/audits/audit_config_immutable.sh new file mode 100644 index 00000000..65208573 --- /dev/null +++ b/stig/audits/audit_config_immutable.sh @@ -0,0 +1,5 @@ +source /tmp/lib.sh + +grep -Ph -- '^\h*-e\h+2\b' /etc/audit/rules.d/*.rules | tail -1 | grep -q '.' || exit $FAIL + +exit $PASS diff --git a/stig/audits/audit_delete.sh b/stig/audits/audit_delete.sh new file mode 100644 index 00000000..e843e782 --- /dev/null +++ b/stig/audits/audit_delete.sh @@ -0,0 +1,28 @@ +source /tmp/lib.sh + +fail=0 + +UID_MIN=$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs) +[ -n "${UID_MIN}" ] || exit $FAIL + +awk "/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -S/ \ +&&(/unlink/||/rename/||/unlinkat/||/renameat/) \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk "/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -S/ \ +&&(/unlink/||/rename/||/unlinkat/||/renameat/) \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_filefail.sh b/stig/audits/audit_filefail.sh new file mode 100644 index 00000000..f1819000 --- /dev/null +++ b/stig/audits/audit_filefail.sh @@ -0,0 +1,34 @@ +source /tmp/lib.sh + +fail=0 + +UID_MIN=$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs) +[ -n "${UID_MIN}" ] || exit $FAIL + +awk "/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&(/ -F *exit=-EACCES/||/ -F *exit=-EPERM/) \ +&&/ -S/ \ +&&/creat/ \ +&&/open/ \ +&&/truncate/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk "/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&(/ -F *exit=-EACCES/||/ -F *exit=-EPERM/) \ +&&/ -S/ \ +&&/creat/ \ +&&/open/ \ +&&/truncate/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/audit_full_act.sh b/stig/audits/audit_full_act.sh new file mode 100644 index 00000000..23ab47dd --- /dev/null +++ b/stig/audits/audit_full_act.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +grep -Pq -- '^\h*disk_full_action\h*=\h*(halt|single)\b' /etc/audit/auditd.conf || exit $FAIL +grep -Pq -- '^\h*disk_error_action\h*=\h*(syslog|single|halt)\b' /etc/audit/auditd.conf || exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/audit_identity.sh b/stig/audits/audit_identity.sh new file mode 100644 index 00000000..4544dee4 --- /dev/null +++ b/stig/audits/audit_identity.sh @@ -0,0 +1,33 @@ +source /tmp/lib.sh + +fail=0 + +awk '/^ *-w/ \ +&&(/\/etc\/group/ \ +||/\/etc\/passwd/ \ +||/\/etc\/gshadow/ \ +||/\/etc\/shadow/ \ +||/\/etc\/security\/opasswd/ \ +||/\/etc\/nsswitch.conf/ \ +||/\/etc\/pam.conf/ \ +||/\/etc\/pam.d/) \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk '/^ *-w/ \ +&&(/\/etc\/group/ \ +||/\/etc\/passwd/ \ +||/\/etc\/gshadow/ \ +||/\/etc\/shadow/ \ +||/\/etc\/security\/opasswd/ \ +||/\/etc\/nsswitch.conf/ \ +||/\/etc\/pam.conf/ \ +||/\/etc\/pam.d/) \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/audit_keep_logs.sh b/stig/audits/audit_keep_logs.sh new file mode 100644 index 00000000..b1b6009d --- /dev/null +++ b/stig/audits/audit_keep_logs.sh @@ -0,0 +1,12 @@ +source /tmp/lib.sh + +fail=0 + +grep -P -- '^\h*disk_full_action\h*=\h*(halt|single)\b' /etc/audit/auditd.conf >/dev/null 2>&1 || fail=1 +grep -P -- '^\h*disk_error_action\h*=\h*(syslog|single|halt)\b' /etc/audit/auditd.conf >/dev/null 2>&1 || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_kmod.sh b/stig/audits/audit_kmod.sh new file mode 100644 index 00000000..5e86cb73 --- /dev/null +++ b/stig/audits/audit_kmod.sh @@ -0,0 +1,50 @@ +source /tmp/lib.sh + +fail=0 + +UID_MIN=$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs) +[ -n "${UID_MIN}" ] || exit $FAIL + +# syscall rules +awk '/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F auid!=unset/||/ -F auid!=-1/||/ -F auid!=4294967295/) \ +&&/ -S/ \ +&&(/init_module/ \ + ||/finit_module/ \ + ||/delete_module/ \ + ||/create_module/ \ + ||/query_module/) \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +awk "/^ *-a *always,exit/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -F *perm=x/ \ +&&/ -F *path=\/usr\/bin\/kmod/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +# running config +auditctl -l | awk '/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F auid!=unset/||/ -F auid!=-1/||/ -F auid!=4294967295/) \ +&&/ -S/ \ +&&(/init_module/ \ + ||/finit_module/ \ + ||/delete_module/ \ + ||/create_module/ \ + ||/query_module/) \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +auditctl -l | awk "/^ *-a *always,exit/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -F *perm=x/ \ +&&/ -F *path=\/usr\/bin\/kmod/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_log_dir_mode.sh b/stig/audits/audit_log_dir_mode.sh new file mode 100644 index 00000000..e31c6df2 --- /dev/null +++ b/stig/audits/audit_log_dir_mode.sh @@ -0,0 +1,14 @@ +source /tmp/lib.sh + +[ -e "/etc/audit/auditd.conf" ] || exit $FAIL + +l_audit_log_directory="$(dirname "$(awk -F= '/^\s*log_file\s*/{print $2}' /etc/audit/auditd.conf | xargs)")" +[ -d "$l_audit_log_directory" ] || exit $FAIL + +l_perm_mask="0027" +l_directory_mode="$(stat -Lc '%#a' "$l_audit_log_directory")" +if [ $(( $l_directory_mode & $l_perm_mask )) -gt 0 ]; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/audit_log_group.sh b/stig/audits/audit_log_group.sh new file mode 100644 index 00000000..2866492c --- /dev/null +++ b/stig/audits/audit_log_group.sh @@ -0,0 +1,15 @@ +source /tmp/lib.sh + +[ -e "/etc/audit/auditd.conf" ] || exit $FAIL + +l_audit_log_directory="$(dirname "$(awk -F= '/^\s*log_file\s*/{print $2}' /etc/audit/auditd.conf | xargs)")" +l_audit_log_group="$(awk -F= '/^\s*log_group\s*/{print $2}' /etc/audit/auditd.conf | xargs)" + +grep -Pq -- '^\h*(root|adm)\h*$' <<< "$l_audit_log_group" || exit $FAIL +[ -d "$l_audit_log_directory" ] || exit $FAIL + +if find "$l_audit_log_directory" -maxdepth 1 -type f \( ! -group root -a ! -group adm \) | grep -q '.'; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/audit_log_mode.sh b/stig/audits/audit_log_mode.sh new file mode 100644 index 00000000..fe173fef --- /dev/null +++ b/stig/audits/audit_log_mode.sh @@ -0,0 +1,13 @@ +source /tmp/lib.sh + +[ -e "/etc/audit/auditd.conf" ] || exit $FAIL + +l_audit_log_directory="$(dirname "$(awk -F= '/^\s*log_file\s*/{print $2}' /etc/audit/auditd.conf | xargs)")" +[ -d "$l_audit_log_directory" ] || exit $FAIL + +l_perm_mask="0177" +if find "$l_audit_log_directory" -maxdepth 1 -type f -perm /"$l_perm_mask" | grep -q '.'; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/audit_log_owner.sh b/stig/audits/audit_log_owner.sh new file mode 100644 index 00000000..5e80a999 --- /dev/null +++ b/stig/audits/audit_log_owner.sh @@ -0,0 +1,12 @@ +source /tmp/lib.sh + +[ -e "/etc/audit/auditd.conf" ] || exit $FAIL + +l_audit_log_directory="$(dirname "$(awk -F= '/^\s*log_file\s*/{print $2}' /etc/audit/auditd.conf | xargs)")" +[ -d "$l_audit_log_directory" ] || exit $FAIL + +if find "$l_audit_log_directory" -maxdepth 1 -type f ! -user root | grep -q '.'; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/audit_login.sh b/stig/audits/audit_login.sh new file mode 100644 index 00000000..9f3de420 --- /dev/null +++ b/stig/audits/audit_login.sh @@ -0,0 +1,21 @@ +source /tmp/lib.sh + +fail=0 + +awk '/^ *-w/ \ +&&(/\/var\/log\/lastlog/ \ + ||/\/var\/run\/faillock/) \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk '/^ *-w/ \ +&&(/\/var\/log\/lastlog/ \ + ||/\/var\/run\/faillock/) \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_lowspace.sh b/stig/audits/audit_lowspace.sh new file mode 100644 index 00000000..afabd183 --- /dev/null +++ b/stig/audits/audit_lowspace.sh @@ -0,0 +1,12 @@ +source /tmp/lib.sh + +fail=0 + +grep -P -- '^\h*space_left_action\h*=\h*(email|exec|single|halt)\b' /etc/audit/auditd.conf >/dev/null 2>&1 || fail=1 +grep -P -- '^\h*admin_space_left_action\h*=\h*(single|halt)\b' /etc/audit/auditd.conf >/dev/null 2>&1 || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/audit_mac.sh b/stig/audits/audit_mac.sh new file mode 100644 index 00000000..15efb770 --- /dev/null +++ b/stig/audits/audit_mac.sh @@ -0,0 +1,21 @@ +source /tmp/lib.sh + +fail=0 + +awk '/^ *-w/ \ +&&(/\/etc\/selinux/ \ + ||/\/usr\/share\/selinux/) \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk '/^ *-w/ \ +&&(/\/etc\/selinux/ \ + ||/\/usr\/share\/selinux/) \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_maxsize.sh b/stig/audits/audit_maxsize.sh new file mode 100644 index 00000000..43f5ee1e --- /dev/null +++ b/stig/audits/audit_maxsize.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if grep -Po -- '^\h*max_log_file\h*=\h*\d+\b' /etc/audit/auditd.conf > /dev/null; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_mount.sh b/stig/audits/audit_mount.sh new file mode 100644 index 00000000..4a507e7f --- /dev/null +++ b/stig/audits/audit_mount.sh @@ -0,0 +1,28 @@ +source /tmp/lib.sh + +fail=0 + +UID_MIN=$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs) +[ -n "${UID_MIN}" ] || exit $FAIL + +awk "/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -S/ \ +&&/mount/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk "/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -S/ \ +&&/mount/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_netenv.sh b/stig/audits/audit_netenv.sh new file mode 100644 index 00000000..76900b16 --- /dev/null +++ b/stig/audits/audit_netenv.sh @@ -0,0 +1,43 @@ +source /tmp/lib.sh + +fail=0 + +awk '/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&/ -S/ \ +&&(/sethostname/ \ +||/setdomainname/) \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +awk '/^ *-w/ \ +&&(/\/etc\/issue/ \ +||/\/etc\/issue.net/ \ +||/\/etc\/hosts/ \ +||/\/etc\/sysconfig\/network/ \ +||/\/etc\/hostname/ \ +||/\/etc\/NetworkManager/) \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk '/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&/ -S/ \ +&&(/sethostname/ \ +||/setdomainname/) \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +auditctl -l | awk '/^ *-w/ \ +&&(/\/etc\/issue/ \ +||/\/etc\/issue.net/ \ +||/\/etc\/hosts/ \ +||/\/etc\/sysconfig\/network/ \ +||/\/etc\/hostname/ \ +||/\/etc\/NetworkManager/) \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/audit_permchg.sh b/stig/audits/audit_permchg.sh new file mode 100644 index 00000000..aba76b90 --- /dev/null +++ b/stig/audits/audit_permchg.sh @@ -0,0 +1,34 @@ +source /tmp/lib.sh + +fail=0 + +UID_MIN=$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs) +[ -n "${UID_MIN}" ] || exit $FAIL + +awk "/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -S/ \ +&&/ -F *auid>=${UID_MIN}/ \ +&&(/chmod/||/fchmod/||/fchmodat/ \ +||/chown/||/fchown/||/fchownat/||/lchown/ \ +||/setxattr/||/lsetxattr/||/fsetxattr/ \ +||/removexattr/||/lremovexattr/||/fremovexattr/) \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk "/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -S/ \ +&&/ -F *auid>=${UID_MIN}/ \ +&&(/chmod/||/fchmod/||/fchmodat/ \ +||/chown/||/fchown/||/fchownat/||/lchown/ \ +||/setxattr/||/lsetxattr/||/fsetxattr/ \ +||/removexattr/||/lremovexattr/||/fremovexattr/) \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/audit_privcmd.sh b/stig/audits/audit_privcmd.sh new file mode 100644 index 00000000..50c16f48 --- /dev/null +++ b/stig/audits/audit_privcmd.sh @@ -0,0 +1,19 @@ +source /tmp/lib.sh + +fail=0 + +RUNNING=$(auditctl -l) +[ -n "${RUNNING}" ] || exit $FAIL + +for PARTITION in $(findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,) | grep -Pv "noexec|nosuid" | awk '{print $1}'); do + for PRIVILEGED in $(find "${PARTITION}" -xdev -perm /6000 -type f); do + grep -qr "${PRIVILEGED}" /etc/audit/rules.d && printf '' || fail=1 + printf -- "${RUNNING}" | grep -q "${PRIVILEGED}" || fail=1 + done +done + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/audit_scope.sh b/stig/audits/audit_scope.sh new file mode 100644 index 00000000..f4684098 --- /dev/null +++ b/stig/audits/audit_scope.sh @@ -0,0 +1,19 @@ +source /tmp/lib.sh + +fail=0 + +awk '/^ *-w/ \ +&&/\/etc\/sudoers/ \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk '/^ *-w/ \ +&&/\/etc\/sudoers/ \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/audit_session.sh b/stig/audits/audit_session.sh new file mode 100644 index 00000000..6e1c3ffb --- /dev/null +++ b/stig/audits/audit_session.sh @@ -0,0 +1,23 @@ +source /tmp/lib.sh + +fail=0 + +awk '/^ *-w/ \ +&&(/\/var\/run\/utmp/ \ + ||/\/var\/log\/wtmp/ \ + ||/\/var\/log\/btmp/) \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk '/^ *-w/ \ +&&(/\/var\/run\/utmp/ \ + ||/\/var\/log\/wtmp/ \ + ||/\/var\/log\/btmp/) \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_setfacl.sh b/stig/audits/audit_setfacl.sh new file mode 100644 index 00000000..c951be72 --- /dev/null +++ b/stig/audits/audit_setfacl.sh @@ -0,0 +1,26 @@ +source /tmp/lib.sh + +fail=0 + +UID_MIN=$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs) +[ -n "${UID_MIN}" ] || exit $FAIL + +awk "/^ *-a *always,exit/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -F *perm=x/ \ +&&/ -F *path=\/usr\/bin\/setfacl/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk "/^ *-a *always,exit/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -F *perm=x/ \ +&&/ -F *path=\/usr\/bin\/setfacl/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_su_exec.sh b/stig/audits/audit_su_exec.sh new file mode 100644 index 00000000..2f2609bc --- /dev/null +++ b/stig/audits/audit_su_exec.sh @@ -0,0 +1,23 @@ +source /tmp/lib.sh + +fail=0 + +awk '/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&(/ -C *euid!=uid/||/ -C *uid!=euid/) \ +&&/ -S *execve/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk '/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&(/ -C *euid!=uid/||/ -C *uid!=euid/) \ +&&/ -S *execve/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/audit_sudo_log.sh b/stig/audits/audit_sudo_log.sh new file mode 100644 index 00000000..eabe1f7a --- /dev/null +++ b/stig/audits/audit_sudo_log.sh @@ -0,0 +1,22 @@ +source /tmp/lib.sh + +fail=0 + +SUDO_LOG_FILE=$(grep -r logfile /etc/sudoers* | sed -e 's/.*logfile=//;s/,? .*//' -e 's/"//g' -e 's|/|\\/|g') +[ -n "${SUDO_LOG_FILE}" ] || exit $FAIL + +awk "/^ *-w/ \ +&&/${SUDO_LOG_FILE}/ \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk "/^ *-w/ \ +&&/${SUDO_LOG_FILE}/ \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/audit_timechg.sh b/stig/audits/audit_timechg.sh new file mode 100644 index 00000000..c95e9575 --- /dev/null +++ b/stig/audits/audit_timechg.sh @@ -0,0 +1,35 @@ +source /tmp/lib.sh + +fail=0 + +awk '/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&/ -S/ \ +&&(/adjtimex/ \ +||/settimeofday/ \ +||/clock_settime/ ) \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +awk '/^ *-w/ \ +&&/\/etc\/localtime/ \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk '/^ *-a *always,exit/ \ +&&/ -F *arch=b(32|64)/ \ +&&/ -S/ \ +&&(/adjtimex/ \ +||/settimeofday/ \ +||/clock_settime/ ) \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +auditctl -l | awk '/^ *-w/ \ +&&/\/etc\/localtime/ \ +&&/ +-p *wa/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)' | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/audit_tools_group.sh b/stig/audits/audit_tools_group.sh new file mode 100644 index 00000000..559555ad --- /dev/null +++ b/stig/audits/audit_tools_group.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if stat -Lc "%n %G" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/augenrules 2>/dev/null | awk '$2 != "root" {print}' | grep -q '.'; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/audit_tools_mode.sh b/stig/audits/audit_tools_mode.sh new file mode 100644 index 00000000..42fb4dce --- /dev/null +++ b/stig/audits/audit_tools_mode.sh @@ -0,0 +1,14 @@ +source /tmp/lib.sh + +l_perm_mask="0022" +a_audit_tools=("/sbin/auditctl" "/sbin/aureport" "/sbin/ausearch" "/sbin/autrace" "/sbin/auditd" "/sbin/augenrules") + +for l_audit_tool in "${a_audit_tools[@]}"; do + [ -e "$l_audit_tool" ] || continue + l_mode="$(stat -Lc '%#a' "$l_audit_tool")" + if [ $(( "$l_mode" & "$l_perm_mask" )) -gt 0 ]; then + exit $FAIL + fi +done + +exit $PASS diff --git a/stig/audits/audit_tools_owner.sh b/stig/audits/audit_tools_owner.sh new file mode 100644 index 00000000..e4640053 --- /dev/null +++ b/stig/audits/audit_tools_owner.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if stat -Lc "%n %U" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/augenrules 2>/dev/null | awk '$2 != "root" {print}' | grep -q '.'; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/audit_tools_protected.sh b/stig/audits/audit_tools_protected.sh new file mode 100644 index 00000000..061687a5 --- /dev/null +++ b/stig/audits/audit_tools_protected.sh @@ -0,0 +1,26 @@ +source /tmp/lib.sh + +l_config_file="$(whereis aide.conf | awk '{print $2}')" +[ -f "$l_config_file" ] || exit $FAIL + +l_systemd_analyze="$(whereis systemd-analyze | awk '{print $2}')" +a_audit_files=("auditctl" "auditd" "ausearch" "aureport" "autrace" "augenrules") +a_items=("p" "i" "n" "u" "g" "s" "b" "acl" "xattrs" "sha512") + +fail=0 +for l_audit_file in "${a_audit_files[@]}"; do + [ -f "$(readlink -f "/sbin/$l_audit_file")" ] || continue + l_match="$("$l_systemd_analyze" cat-config "$l_config_file" 2>/dev/null | grep -Po "^\h*(\/usr)?\/sbin\/$l_audit_file\b.*")" + if [ -z "$l_match" ]; then + fail=1; continue + fi + for l_var in "${a_items[@]}"; do + grep -Pq "\b$l_var\b" <<< "$l_match" || { fail=1; break; } + done +done + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/audit_usermod.sh b/stig/audits/audit_usermod.sh new file mode 100644 index 00000000..9650d766 --- /dev/null +++ b/stig/audits/audit_usermod.sh @@ -0,0 +1,26 @@ +source /tmp/lib.sh + +fail=0 + +UID_MIN=$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs) +[ -n "${UID_MIN}" ] || exit $FAIL + +awk "/^ *-a *always,exit/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -F *perm=x/ \ +&&/ -F *path=\/usr\/sbin\/usermod/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" /etc/audit/rules.d/*.rules | grep -q '.' || fail=1 + +auditctl -l | awk "/^ *-a *always,exit/ \ +&&(/ -F *auid!=unset/||/ -F *auid!=-1/||/ -F *auid!=4294967295/) \ +&&/ -F *auid>=${UID_MIN}/ \ +&&/ -F *perm=x/ \ +&&/ -F *path=\/usr\/sbin\/usermod/ \ +&&(/ key= *[!-~]* *$/||/ -k *[!-~]* *$/)" | grep -q '.' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/auditd_active.sh b/stig/audits/auditd_active.sh new file mode 100644 index 00000000..e33f4074 --- /dev/null +++ b/stig/audits/auditd_active.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_enabled 'auditd' && is_active 'auditd'; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/auditd_backlog_limit.sh b/stig/audits/auditd_backlog_limit.sh new file mode 100644 index 00000000..d8b4546d --- /dev/null +++ b/stig/audits/auditd_backlog_limit.sh @@ -0,0 +1,8 @@ +source /tmp/lib.sh + +if grubby --info=ALL | grep -Po '\baudit_backlog_limit=\d+\b' | awk -F= '$2 >= 8192 {found=1} END {exit !found}' \ + && grep -Pso -- '^\h*GRUB_CMDLINE_LINUX="([^#\n\r]+\h+)?\baudit_backlog_limit=\d+\b' /etc/default/grub | awk -F= '$2 >= 8192 {found=1} END {exit !found}'; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/auditd_installed.sh b/stig/audits/auditd_installed.sh new file mode 100644 index 00000000..b54da003 --- /dev/null +++ b/stig/audits/auditd_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_installed 'audit' && is_installed 'audit-libs'; then + exit $PASS +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/auditd_prior_processes.sh b/stig/audits/auditd_prior_processes.sh new file mode 100644 index 00000000..d2cb9688 --- /dev/null +++ b/stig/audits/auditd_prior_processes.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +grubby --info=ALL 2>/dev/null | grep -Poq '\baudit=1\b' || exit $FAIL +grep -Psoi -- '^\h*GRUB_CMDLINE_LINUX="([^#\n\r]+\h+)?audit=1\b' /etc/default/grub >/dev/null 2>&1 || exit $FAIL + +exit $PASS diff --git a/stig/audits/authselect_profile_includes_pam_modules.sh b/stig/audits/authselect_profile_includes_pam_modules.sh new file mode 100644 index 00000000..1339caed --- /dev/null +++ b/stig/audits/authselect_profile_includes_pam_modules.sh @@ -0,0 +1,20 @@ +source /tmp/lib.sh + +l_profile="$(head -1 /etc/authselect/authselect.conf 2>/dev/null)" +if [ -z "$l_profile" ]; then + exit $FAIL +fi + +l_base="/etc/authselect/$l_profile" +if [ ! -r "$l_base/system-auth" ] || [ ! -r "$l_base/password-auth" ]; then + exit $FAIL +fi + +if grep -Pq -- '\bpam_pwquality\.so\b' "$l_base"/{system,password}-auth 2>/dev/null \ + && grep -Pq -- '\bpam_pwhistory\.so\b' "$l_base"/{system,password}-auth 2>/dev/null \ + && grep -Pq -- '\bpam_faillock\.so\b' "$l_base"/{system,password}-auth 2>/dev/null \ + && grep -Pq -- '\bpam_unix\.so\b' "$l_base"/{system,password}-auth 2>/dev/null; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/autofs_unused.sh b/stig/audits/autofs_unused.sh new file mode 100644 index 00000000..1799576c --- /dev/null +++ b/stig/audits/autofs_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'autofs' autofs.service && exit $PASS || exit $FAIL diff --git a/stig/audits/avahi_daemon_unused.sh b/stig/audits/avahi_daemon_unused.sh new file mode 100644 index 00000000..72ed4232 --- /dev/null +++ b/stig/audits/avahi_daemon_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'avahi' avahi-daemon.socket avahi-daemon.service && exit $PASS || exit $FAIL diff --git a/stig/audits/bluetooth_services_unused.sh b/stig/audits/bluetooth_services_unused.sh new file mode 100644 index 00000000..f2c6f907 --- /dev/null +++ b/stig/audits/bluetooth_services_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'bluez' bluetooth.service && exit $PASS || exit $FAIL diff --git a/stig/audits/bogus_icmp_responses_ignored.sh b/stig/audits/bogus_icmp_responses_ignored.sh new file mode 100644 index 00000000..51d60feb --- /dev/null +++ b/stig/audits/bogus_icmp_responses_ignored.sh @@ -0,0 +1,76 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_ipv6_disabled="" +a_parlist=("net.ipv4.icmp_ignore_bogus_error_responses=1") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +f_ipv6_chk() +{ + l_ipv6_disabled="" + ! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_ipv6_disabled="yes" + if sysctl net.ipv6.conf.all.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_ipv6_disabled="yes" + fi + [ -z "$l_ipv6_disabled" ] && l_ipv6_disabled="no" +} + +f_kernel_parameter_chk() +{ + l_krp="$(sysctl "$l_kpname" | awk -F= '{print $2}' | xargs)" + if [ "$l_krp" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_krp\" in the running configuration" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_krp\" in the running configuration and should have a value of: \"$l_kpvalue\"" + fi + unset A_out; declare -A A_out + while read -r l_out; do + if [ -n "$l_out" ]; then + if [[ $l_out =~ ^\s*# ]]; then + l_file="${l_out//# /}" + else + l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") + fi + fi + done < <(/usr/lib/systemd/systemd-sysctl --cat-config | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + if [ -n "$l_ufwscf" ]; then + l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" | xargs)" + l_kpar="${l_kpar//\//.}" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") + fi + if (( ${#A_out[@]} > 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/bootloader_config_access.sh b/stig/audits/bootloader_config_access.sh new file mode 100644 index 00000000..3eafad1a --- /dev/null +++ b/stig/audits/bootloader_config_access.sh @@ -0,0 +1,48 @@ +source /tmp/lib.sh + +{ +l_output="" l_output2="" +file_mug_chk() +{ +l_out="" l_out2="" +[[ "$(dirname "$l_file")" =~ ^\/boot\/efi\/EFI ]] && l_pmask="0077" || l_pmask="0177" +l_maxperm="$( printf '%o' $(( 0777 & ~$l_pmask )) )" +if [ $(( $l_mode & $l_pmask )) -gt 0 ]; then +l_out2="$l_out2\n +- Is mode \"$l_mode\" and should be mode: \"$l_maxperm\" or more restrictive" +else +l_out="$l_out\n +- Is correctly mode: \"$l_mode\" which is mode: \"$l_maxperm\" or more restrictive" +fi +if [ "$l_user" = "root" ]; then +l_out="$l_out\n +- Is correctly owned by user: \"$l_user\"" +else +l_out2="$l_out2\n +- Is owned by user: \"$l_user\" and should be owned by user: \"root\"" +fi +if [ "$l_group" = "root" ]; then +l_out="$l_out\n +- Is correctly group-owned by group: \"$l_user\"" +else +l_out2="$l_out2\n +- Is group-owned by group: \"$l_user\" and should be group-owned by group: +\"root\"" +fi +[ -n "$l_out" ] && l_output="$l_output\n - File: \"$l_file\"$l_out\n" +[ -n "$l_out2" ] && l_output2="$l_output2\n - File: \"$l_file\"$l_out2\n" +} +while IFS= read -r -d $'\0' l_gfile; do +while read -r l_file l_mode l_user l_group; do +file_mug_chk +done <<< "$(stat -Lc '%n %#a %U %G' "$l_gfile")" +done < <(find /boot -type f \( -name 'grub*' -o -name 'user.cfg' \) -print0) +if [ -z "$l_output2" ]; then +echo -e "\n- Audit Result:\n *** PASS ***\n- * Correctly set * :\n$l_output\n" +exit $PASS +else +echo -e "\n- Audit Result:\n ** FAIL **\n - * Reasons for audit failure * :\n$l_output2\n" +[ -n "$l_output" ] && echo -e " - * Correctly set * :\n$l_output\n" +exit $FAIL +fi +} diff --git a/stig/audits/bootloader_password_set.sh b/stig/audits/bootloader_password_set.sh new file mode 100644 index 00000000..833c93e1 --- /dev/null +++ b/stig/audits/bootloader_password_set.sh @@ -0,0 +1,11 @@ +source /tmp/lib.sh + +l_grub_password_file="$(find /boot -type f -name 'user.cfg' ! -empty)" + +if [ -f "$l_grub_password_file" ] && \ + awk -F. '/^\s*GRUB2_PASSWORD=\S+/ {print $1"."$2"."$3}' "$l_grub_password_file" | \ + grep -q '^GRUB2_PASSWORD=grub\.pbkdf2\.sha512'; then + exit $PASS +fi + +exit $FAIL diff --git a/stig/audits/chrony_configured.sh b/stig/audits/chrony_configured.sh new file mode 100644 index 00000000..5adce846 --- /dev/null +++ b/stig/audits/chrony_configured.sh @@ -0,0 +1,8 @@ +source /tmp/lib.sh + +if is_installed 'chrony'; then + if grep -Prs -- '^\h*(server|pool)\h+[^#\n\r]+' /etc/chrony.conf /etc/chrony.d/ &>/dev/null; then + exit $PASS + fi +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/chrony_not_root.sh b/stig/audits/chrony_not_root.sh new file mode 100644 index 00000000..fa707d4d --- /dev/null +++ b/stig/audits/chrony_not_root.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +if is_installed 'chrony'; then + # grep -Psi -- '^\h*OPTIONS=\"?\h*([^#\n\r]+\h+)?-u\h+root\b' /etc/sysconfig/chronyd + if ! grep -Psi -- '^\h*OPTIONS=\"?\h*([^#\n\r]+\h+)?-u\h+root\b' /etc/sysconfig/chronyd &>/dev/null; then + exit $PASS + fi +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/core_dump_backtraces_disabled.sh b/stig/audits/core_dump_backtraces_disabled.sh new file mode 100644 index 00000000..19b28c3a --- /dev/null +++ b/stig/audits/core_dump_backtraces_disabled.sh @@ -0,0 +1,40 @@ +source /tmp/lib.sh + +l_output="" l_output2="" +a_parlist=("ProcessSizeMax=0") +l_systemd_config_file="/etc/systemd/coredump.conf" # Main systemd configuration file + +config_file_parameter_chk() +{ +unset A_out; declare -A A_out # Check config file(s) setting +while read -r l_out; do +if [ -n "$l_out" ]; then +if [[ $l_out =~ ^\s*# ]]; then +l_file="${l_out//# /}" +else +l_systemd_parameter="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" +grep -Piq -- "^\h*$l_systemd_parameter_name\b" <<< "$l_systemd_parameter" && A_out+=(["$l_systemd_parameter"]="$l_file") +fi +fi +done < <(/usr/bin/systemd-analyze cat-config "$l_systemd_config_file" 2>/dev/null | grep -Pio '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + +if (( ${#A_out[@]} > 0 )); then # Assess output from files and generate output +while IFS="=" read -r l_systemd_file_parameter_name l_systemd_file_parameter_value; do +l_systemd_file_parameter_name="${l_systemd_file_parameter_name// /}" +l_systemd_file_parameter_value="${l_systemd_file_parameter_value// /}" +if ! grep -Piq "^\h*$l_systemd_parameter_value\b" <<< "$l_systemd_file_parameter_value"; then + exit $FAIL +fi +done < <(grep -Pio -- "^\h*$l_systemd_parameter_name\h*=\h*\H+" "${A_out[@]}") +else +exit $FAIL +fi +} + +while IFS="=" read -r l_systemd_parameter_name l_systemd_parameter_value; do # Assess and check parameters +l_systemd_parameter_name="${l_systemd_parameter_name// /}" +l_systemd_parameter_value="${l_systemd_parameter_value// /}" +config_file_parameter_chk +done < <(printf '%s\n' "${a_parlist[@]}") + +exit $PASS \ No newline at end of file diff --git a/stig/audits/core_dump_storage_disabled.sh b/stig/audits/core_dump_storage_disabled.sh new file mode 100644 index 00000000..ef4f4379 --- /dev/null +++ b/stig/audits/core_dump_storage_disabled.sh @@ -0,0 +1,40 @@ +source /tmp/lib.sh + +l_output="" l_output2="" +a_parlist=("Storage=none") +l_systemd_config_file="/etc/systemd/coredump.conf" # Main systemd configuration file + +config_file_parameter_chk() +{ +unset A_out; declare -A A_out # Check config file(s) setting +while read -r l_out; do +if [ -n "$l_out" ]; then +if [[ $l_out =~ ^\s*# ]]; then +l_file="${l_out//# /}" +else +l_systemd_parameter="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" +grep -Piq -- "^\h*$l_systemd_parameter_name\b" <<< "$l_systemd_parameter" && A_out+=(["$l_systemd_parameter"]="$l_file") +fi +fi +done < <(/usr/bin/systemd-analyze cat-config "$l_systemd_config_file" 2>/dev/null | grep -Pio '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + +if (( ${#A_out[@]} > 0 )); then # Assess output from files and generate output +while IFS="=" read -r l_systemd_file_parameter_name l_systemd_file_parameter_value; do +l_systemd_file_parameter_name="${l_systemd_file_parameter_name// /}" +l_systemd_file_parameter_value="${l_systemd_file_parameter_value// /}" +if ! grep -Piq "^\h*$l_systemd_parameter_value\b" <<< "$l_systemd_file_parameter_value"; then + exit $FAIL +fi +done < <(grep -Pio -- "^\h*$l_systemd_parameter_name\h*=\h*\H+" "${A_out[@]}") +else +exit $FAIL +fi +} + +while IFS="=" read -r l_systemd_parameter_name l_systemd_parameter_value; do # Assess and check parameters +l_systemd_parameter_name="${l_systemd_parameter_name// /}" +l_systemd_parameter_value="${l_systemd_parameter_value// /}" +config_file_parameter_chk +done < <(printf '%s\n' "${a_parlist[@]}") + +exit $PASS \ No newline at end of file diff --git a/stig/audits/cramfs_off.sh b/stig/audits/cramfs_off.sh new file mode 100644 index 00000000..1d519b4a --- /dev/null +++ b/stig/audits/cramfs_off.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "cramfs" "fs" && exit $PASS || exit $FAIL diff --git a/stig/audits/cron_daily_configured.sh b/stig/audits/cron_daily_configured.sh new file mode 100644 index 00000000..742f7ace --- /dev/null +++ b/stig/audits/cron_daily_configured.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/cron.daily 700 && exit $PASS || exit $FAIL diff --git a/stig/audits/cron_enabled_n_active.sh b/stig/audits/cron_enabled_n_active.sh new file mode 100644 index 00000000..ea9a21d9 --- /dev/null +++ b/stig/audits/cron_enabled_n_active.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if { is_enabled 'crond' || is_enabled 'cron'; } && \ + { is_active 'crond' || is_active 'cron'; }; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/cron_hourly_configured.sh b/stig/audits/cron_hourly_configured.sh new file mode 100644 index 00000000..1cfc37ad --- /dev/null +++ b/stig/audits/cron_hourly_configured.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/cron.hourly 700 && exit $PASS || exit $FAIL diff --git a/stig/audits/cron_monthly_configured.sh b/stig/audits/cron_monthly_configured.sh new file mode 100644 index 00000000..8f5dd021 --- /dev/null +++ b/stig/audits/cron_monthly_configured.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/cron.monthly 700 && exit $PASS || exit $FAIL diff --git a/stig/audits/cron_permissions_configured.sh b/stig/audits/cron_permissions_configured.sh new file mode 100644 index 00000000..8b9e3377 --- /dev/null +++ b/stig/audits/cron_permissions_configured.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/crontab 600 && exit $PASS || exit $FAIL diff --git a/stig/audits/cron_weekly_configured.sh b/stig/audits/cron_weekly_configured.sh new file mode 100644 index 00000000..8b60f044 --- /dev/null +++ b/stig/audits/cron_weekly_configured.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/cron.weekly 700 && exit $PASS || exit $FAIL diff --git a/stig/audits/crond_permissions_configured.sh b/stig/audits/crond_permissions_configured.sh new file mode 100644 index 00000000..5a5f3965 --- /dev/null +++ b/stig/audits/crond_permissions_configured.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/cron.d 700 && exit $PASS || exit $FAIL diff --git a/stig/audits/crontab_auth_restricted.sh b/stig/audits/crontab_auth_restricted.sh new file mode 100644 index 00000000..5ec8f4dc --- /dev/null +++ b/stig/audits/crontab_auth_restricted.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/cron.allow 640 && exit $PASS || exit $FAIL diff --git a/stig/audits/crypto_policy_disable_mac_128_bits.sh b/stig/audits/crypto_policy_disable_mac_128_bits.sh new file mode 100644 index 00000000..2974f46e --- /dev/null +++ b/stig/audits/crypto_policy_disable_mac_128_bits.sh @@ -0,0 +1,5 @@ +source /tmp/lib.sh + +grep -Pi -- '^\h*mac\h*=\h*([^#\n\r]+)?-64\b' /etc/crypto-policies/state/CURRENT.pol \ + >/dev/null 2>&1 && exit $FAIL +exit $PASS \ No newline at end of file diff --git a/stig/audits/crypto_policy_disable_sha1.sh b/stig/audits/crypto_policy_disable_sha1.sh new file mode 100644 index 00000000..5692b94b --- /dev/null +++ b/stig/audits/crypto_policy_disable_sha1.sh @@ -0,0 +1,15 @@ +source /tmp/lib.sh + +fail=0 + +awk -F= '($1~/(hash|sign)/ && $2~/SHA1/ && $2!~/^\s*\-\s*([^#\n\r]+)?SHA1/){print}' \ + /etc/crypto-policies/state/CURRENT.pol | grep -q '.' && fail=1 + +grep -Psi -- '^\h*sha1_in_certs\h*=\h*' /etc/crypto-policies/state/CURRENT.pol \ + | grep -q '0' || fail=1 + +if [ "$fail" -eq 0 ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/crypto_policy_disable_ssh_cbc.sh b/stig/audits/crypto_policy_disable_ssh_cbc.sh new file mode 100644 index 00000000..15936b68 --- /dev/null +++ b/stig/audits/crypto_policy_disable_ssh_cbc.sh @@ -0,0 +1,23 @@ +source /tmp/lib.sh + +l_output="" l_output2="" + +if grep -Piq -- '^\h*cipher\h*=\h*([^#\n\r]+)?-CBC\b' /etc/crypto-policies/state/CURRENT.pol; then + if grep -Piq -- '^\h*cipher@(lib|open)ssh(-server|-client)?\h*=\h*' /etc/crypto-policies/state/CURRENT.pol; then + if ! grep -Piq -- '^\h*cipher@(lib|open)ssh(-server|-client)?\h*=\h*([^#\n\r]+)?-CBC\b' /etc/crypto-policies/state/CURRENT.pol; then + l_output="$l_output\n - Cipher Block Chaining (CBC) is disabled for SSH" + else + l_output2="$l_output2\n - Cipher Block Chaining (CBC) is enabled for SSH" + fi + else + l_output2="$l_output2\n - Cipher Block Chaining (CBC) is enabled for SSH" + fi +else + l_output=" - Cipher Block Chaining (CBC) is disabled" +fi + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/crypto_policy_disable_ssh_chacha20_poly1305.sh b/stig/audits/crypto_policy_disable_ssh_chacha20_poly1305.sh new file mode 100644 index 00000000..0f13be2c --- /dev/null +++ b/stig/audits/crypto_policy_disable_ssh_chacha20_poly1305.sh @@ -0,0 +1,23 @@ +source /tmp/lib.sh + +l_output="" l_output2="" + +if grep -Piq -- '^\h*cipher\h*=\h*([^#\n\r]+)?-CBC\b' /etc/crypto-policies/state/CURRENT.pol; then + if grep -Piq -- '^\h*cipher@(lib|open)ssh(-server|-client)?\h*=\h*' /etc/crypto-policies/state/CURRENT.pol; then + if ! grep -Piq -- '^\h*cipher@(lib|open)ssh(-server|-client)?\h*=\h*([^#\n\r]+)?\bchacha20-poly1305\b' /etc/crypto-policies/state/CURRENT.pol; then + l_output="$l_output\n - chacha20-poly1305 is disabled for SSH" + else + l_output2="$l_output2\n - chacha20-poly1305 is enabled for SSH" + fi + else + l_output2="$l_output2\n - chacha20-poly1305 is enabled for SSH" + fi +else + l_output=" - chacha20-poly1305 is disabled" +fi + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/crypto_policy_disable_ssh_etm.sh b/stig/audits/crypto_policy_disable_ssh_etm.sh new file mode 100644 index 00000000..733813bf --- /dev/null +++ b/stig/audits/crypto_policy_disable_ssh_etm.sh @@ -0,0 +1,5 @@ +source /tmp/lib.sh + +grep -Psi -- '^\h*etm\b' /etc/crypto-policies/state/CURRENT.pol \ + | grep -qiE '(etm@(libssh|openssh(-server|-client)?|SSH)|^etm)\s*=\s*DISABLE_ETM' && exit $PASS +exit $FAIL \ No newline at end of file diff --git a/stig/audits/crypto_policy_not_legacy.sh b/stig/audits/crypto_policy_not_legacy.sh new file mode 100644 index 00000000..0abcbf9f --- /dev/null +++ b/stig/audits/crypto_policy_not_legacy.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +grep -Pi '^\h*LEGACY\b' /etc/crypto-policies/config >/dev/null 2>&1 && exit $FAIL +exit $PASS \ No newline at end of file diff --git a/stig/audits/crypto_policy_not_sshd.sh b/stig/audits/crypto_policy_not_sshd.sh new file mode 100644 index 00000000..e379711d --- /dev/null +++ b/stig/audits/crypto_policy_not_sshd.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +grep -Pi '^\h*CRYPTO_POLICY\h*=' /etc/sysconfig/sshd >/dev/null 2>&1 && exit $FAIL +exit $PASS \ No newline at end of file diff --git a/stig/audits/dccp_kernel_module_unavailable.sh b/stig/audits/dccp_kernel_module_unavailable.sh new file mode 100644 index 00000000..bf86c7b9 --- /dev/null +++ b/stig/audits/dccp_kernel_module_unavailable.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "dccp" "net" && exit $PASS || exit $FAIL diff --git a/stig/audits/default_user_shell_timeout_configured.sh b/stig/audits/default_user_shell_timeout_configured.sh new file mode 100644 index 00000000..5f9d03ea --- /dev/null +++ b/stig/audits/default_user_shell_timeout_configured.sh @@ -0,0 +1,21 @@ +source /tmp/lib.sh + +l_tmout_set="900" +l_found=0 + +while IFS= read -r l_file; do + l_tmout_value="$(grep -Po -- '^([^#\n\r]+)?\bTMOUT=\d+\b' "$l_file" | awk -F= '{print $2}')" + [ -z "$l_tmout_value" ] && continue + if [[ "$l_tmout_value" -gt "$l_tmout_set" || "$l_tmout_value" -le "0" ]]; then + exit $FAIL + fi + grep -Pq -- '^\h*(typeset\h\-xr\hTMOUT=\d+|([^#\n\r]+)?\breadonly\h+TMOUT\b)' "$l_file" || exit $FAIL + grep -Pq -- '^\h*(typeset\h\-xr\hTMOUT=\d+|([^#\n\r]+)?\bexport\b([^#\n\r]+\b)?TMOUT\b)' "$l_file" || exit $FAIL + l_found=1 +done < <(grep -Pls -- '^([^#\n\r]+)?\bTMOUT\b' /etc/*bashrc /etc/profile /etc/profile.d/*.sh 2>/dev/null) + +if [ "$l_found" -eq 0 ]; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/default_user_umask_configured.sh b/stig/audits/default_user_umask_configured.sh new file mode 100644 index 00000000..8c9f0cfe --- /dev/null +++ b/stig/audits/default_user_umask_configured.sh @@ -0,0 +1,31 @@ +source /tmp/lib.sh + +umask_ok_re='^\h*umask\h+(0?[0-7][2-7]7|u(=[rwx]{0,3}),g=([rx]{0,2}),o=)(\h*#.*)?$' +umask_bad_re='^\h*umask\h+(([0-7][0-7][01][0-7]\b|[0-7][0-7][0-7][0-6]\b)|([0-7][01][0-7]\b|[0-7][0-7][0-6]\b)|(u=[rwx]{1,3},)?(((g=[rx]?[rx]?w[rx]?[rx]?\b)(,o=[rwx]{1,3})?)|((g=[wrx]{1,3},)?o=[wrx]{1,3}\b)))' + +pam_ok_re='^\h*session\h+[^#\n\r]+\h+pam_umask\.so\h+([^#\n\r]+\h+)?umask=(0?[0-7][2-7]7)\b' +pam_bad_re='^\h*session\h+[^#\n\r]+\h+pam_umask\.so\h+([^#\n\r]+\h+)?umask=(([0-7][0-7][01][0-7]\b|[0-7][0-7][0-7][0-6]\b)|([0-7][01][0-7]\b))' + +files=() +while IFS= read -r -d $'\0' f; do files+=("$f"); done < <(find /etc/profile.d/ -type f -name '*.sh' -print0 2>/dev/null) +files+=(/etc/profile /etc/bashrc /etc/bash.bashrc /etc/login.defs /etc/default/login) + +for f in "${files[@]}"; do + [ -f "$f" ] || continue + if grep -Psiq -- "$umask_bad_re" "$f"; then exit $FAIL; fi +done + +if [ -f /etc/pam.d/postlogin ]; then + if grep -Psiq -- "$pam_bad_re" /etc/pam.d/postlogin; then exit $FAIL; fi +fi + +for f in "${files[@]}"; do + [ -f "$f" ] || continue + if grep -Psiq -- "$umask_ok_re" "$f"; then exit $PASS; fi +done + +if [ -f /etc/pam.d/postlogin ]; then + if grep -Psiq -- "$pam_ok_re" /etc/pam.d/postlogin; then exit $PASS; fi +fi + +exit $FAIL \ No newline at end of file diff --git a/stig/audits/dev_shm_nodev.sh b/stig/audits/dev_shm_nodev.sh new file mode 100644 index 00000000..c7ca1701 --- /dev/null +++ b/stig/audits/dev_shm_nodev.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /dev/shm nodev && exit $PASS || exit $FAIL diff --git a/stig/audits/dev_shm_noexec.sh b/stig/audits/dev_shm_noexec.sh new file mode 100644 index 00000000..7ab4427b --- /dev/null +++ b/stig/audits/dev_shm_noexec.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /dev/shm noexec && exit $PASS || exit $FAIL diff --git a/stig/audits/dev_shm_nosuid.sh b/stig/audits/dev_shm_nosuid.sh new file mode 100644 index 00000000..a4b248fe --- /dev/null +++ b/stig/audits/dev_shm_nosuid.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /dev/shm nosuid && exit $PASS || exit $FAIL diff --git a/stig/audits/dev_shm_separate_partition.sh b/stig/audits/dev_shm_separate_partition.sh new file mode 100644 index 00000000..b48f57a0 --- /dev/null +++ b/stig/audits/dev_shm_separate_partition.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_separate_partition /dev/shm && exit $PASS || exit $FAIL diff --git a/stig/audits/dhcp_server_unused.sh b/stig/audits/dhcp_server_unused.sh new file mode 100644 index 00000000..5b43fda5 --- /dev/null +++ b/stig/audits/dhcp_server_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'dhcp-server' dhcpd.service dhcpd6.service && exit $PASS || exit $FAIL diff --git a/stig/audits/dns_server_unused.sh b/stig/audits/dns_server_unused.sh new file mode 100644 index 00000000..7fe96937 --- /dev/null +++ b/stig/audits/dns_server_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'bind' named.service && exit $PASS || exit $FAIL diff --git a/stig/audits/dnsmasq_unused.sh b/stig/audits/dnsmasq_unused.sh new file mode 100644 index 00000000..a85c6900 --- /dev/null +++ b/stig/audits/dnsmasq_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'dnsmasq' dnsmasq.service && exit $PASS || exit $FAIL diff --git a/stig/audits/etc_group_dash_permissions.sh b/stig/audits/etc_group_dash_permissions.sh new file mode 100644 index 00000000..8033d430 --- /dev/null +++ b/stig/audits/etc_group_dash_permissions.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/group- 644 && exit $PASS || exit $FAIL diff --git a/stig/audits/etc_group_permissions.sh b/stig/audits/etc_group_permissions.sh new file mode 100644 index 00000000..d03f1f44 --- /dev/null +++ b/stig/audits/etc_group_permissions.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/group 644 && exit $PASS || exit $FAIL diff --git a/stig/audits/etc_gshadow_dash_permissions.sh b/stig/audits/etc_gshadow_dash_permissions.sh new file mode 100644 index 00000000..e6cbc03c --- /dev/null +++ b/stig/audits/etc_gshadow_dash_permissions.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/gshadow- 0 && exit $PASS || exit $FAIL diff --git a/stig/audits/etc_gshadow_permissions.sh b/stig/audits/etc_gshadow_permissions.sh new file mode 100644 index 00000000..8ac05814 --- /dev/null +++ b/stig/audits/etc_gshadow_permissions.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/gshadow 0 && exit $PASS || exit $FAIL diff --git a/stig/audits/etc_passwd_dash_permissions.sh b/stig/audits/etc_passwd_dash_permissions.sh new file mode 100644 index 00000000..5f7252df --- /dev/null +++ b/stig/audits/etc_passwd_dash_permissions.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/passwd- 644 && exit $PASS || exit $FAIL diff --git a/stig/audits/etc_passwd_permissions.sh b/stig/audits/etc_passwd_permissions.sh new file mode 100644 index 00000000..66418eaa --- /dev/null +++ b/stig/audits/etc_passwd_permissions.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/passwd 644 && exit $PASS || exit $FAIL diff --git a/stig/audits/etc_security_opasswd_permissions.sh b/stig/audits/etc_security_opasswd_permissions.sh new file mode 100644 index 00000000..8ceec606 --- /dev/null +++ b/stig/audits/etc_security_opasswd_permissions.sh @@ -0,0 +1,5 @@ +source /tmp/lib.sh + +{ [ ! -e /etc/security/opasswd ] || check_file_root_perms /etc/security/opasswd 600; } && \ +{ [ ! -e /etc/security/opasswd.old ] || check_file_root_perms /etc/security/opasswd.old 600; } && \ +exit $PASS || exit $FAIL diff --git a/stig/audits/etc_shadow_dash_permissions.sh b/stig/audits/etc_shadow_dash_permissions.sh new file mode 100644 index 00000000..6ac69228 --- /dev/null +++ b/stig/audits/etc_shadow_dash_permissions.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/shadow- 0 && exit $PASS || exit $FAIL diff --git a/stig/audits/etc_shadow_permissions.sh b/stig/audits/etc_shadow_permissions.sh new file mode 100644 index 00000000..00a22969 --- /dev/null +++ b/stig/audits/etc_shadow_permissions.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/shadow 0 && exit $PASS || exit $FAIL diff --git a/stig/audits/etc_shells_permissions.sh b/stig/audits/etc_shells_permissions.sh new file mode 100644 index 00000000..44bf5363 --- /dev/null +++ b/stig/audits/etc_shells_permissions.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/shells 644 && exit $PASS || exit $FAIL diff --git a/stig/audits/firewalld_drops_services_n_ports.sh b/stig/audits/firewalld_drops_services_n_ports.sh new file mode 100644 index 00000000..be0d1cea --- /dev/null +++ b/stig/audits/firewalld_drops_services_n_ports.sh @@ -0,0 +1,13 @@ +source /tmp/lib.sh + +if systemctl is-enabled --quiet firewalld.service; then + active_zone=$(firewall-cmd --get-active-zones | awk 'NR==1{print $1}') + + echo "Active zone: $active_zone" + echo "--------------------------------------------------" + firewall-cmd --list-all --zone="$active_zone" | grep -E '^(services:|ports:)' +else + echo "firewalld.service is not enabled." +fi + +#NOTE: needs to run under sudo/root to be authorized to access firewalld diff --git a/stig/audits/firewalld_loopback_traffic.sh b/stig/audits/firewalld_loopback_traffic.sh new file mode 100644 index 00000000..3cbfd11a --- /dev/null +++ b/stig/audits/firewalld_loopback_traffic.sh @@ -0,0 +1,47 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_hbfw="" + +if systemctl is-enabled firewalld.service 2>/dev/null | grep -q 'enabled'; then + echo -e "\n - FirewallD is in use on the system" && l_hbfw="fwd" +elif systemctl is-enabled nftables.service 2>/dev/null | grep -q 'enabled'; then + echo -e "\n - nftables is in use on the system \n - Recommendation is NA" && l_hbfw="nft" +else + echo -e "\n - Error - Neither FirewallD or NFTables is enabled\n - Please follow recommendation: \"Ensure a single firewall configuration utility is in use\"" +fi + +if [ "$l_hbfw" = "fwd" ]; then + if nft list ruleset | awk '/hook\s+input\s+/,/\}\s*(#.*)?$/' | grep -Pq -- '\H+\h+"lo"\h+accept'; then + l_output="$l_output\n - Network traffic to the loopback address is correctly set to accept" + else + l_output2="$l_output2\n - Network traffic to the loopback address is not set to accept" + fi + + l_ipsaddr="$(nft list ruleset | awk '/filter_IN_public_deny|hook\s+input\s+/,/\}\s*(#.*)?$/' | grep -P -- 'ip\h+saddr')" + + if grep -Pq -- 'ip\h+saddr\h+127\.0\.0\.0\/8\h+(counter\h+packets\h+\d+\h+bytes\h+\d+\h+)?drop' <<< "$l_ipsaddr" \ + || grep -Pq -- 'ip\h+daddr\h+\!\=\h+127\.0\.0\.1\h+ip\h+saddr\h+127\.0\.0\.1\h+drop' <<< "$l_ipsaddr"; then + l_output="$l_output\n - IPv4 network traffic from loopback address correctly set to drop" + else + l_output2="$l_output2\n - IPv4 network traffic from loopback address not set to drop" + fi + + if grep -Pq -- '^\h*0\h*$' /sys/module/ipv6/parameters/disable; then + l_ip6saddr="$(nft list ruleset | awk '/filter_IN_public_deny|hook input/,/}/' | grep 'ip6 saddr')" + + if grep -Pq -- 'ip6\h+saddr\h+::1\h+(counter\h+packets\h+\d+\h+bytes\h+\d+\h+)?drop' <<< "$l_ip6saddr" \ + || grep -Pq -- 'ip6\h+daddr\h+\!=\h+::1\h+ip6\h+saddr\h+::1\h+drop' <<< "$l_ip6saddr"; then + l_output="$l_output\n - IPv6 network traffic from loopback address correctly set to drop" + else + l_output2="$l_output2\n - IPv6 network traffic from loopback address not set to drop" + fi + fi +fi + +if [ "$l_hbfw" = "nft" ] || [ -z "$l_output2" ]; then + echo -e "\n- Audit Result:\n *** PASS ***\n$l_output" + exit "$PASS" +else + echo -e "\n- Audit Result:\n *** FAIL ***\n$l_output2\n\n - Correctly set:\n$l_output" + exit "$FAIL" +fi diff --git a/stig/audits/freevxfs_off.sh b/stig/audits/freevxfs_off.sh new file mode 100644 index 00000000..99ae6efa --- /dev/null +++ b/stig/audits/freevxfs_off.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "freevxfs" "fs" && exit $PASS || exit $FAIL diff --git a/stig/audits/ftp_client_not_installed.sh b/stig/audits/ftp_client_not_installed.sh new file mode 100644 index 00000000..c9909a6f --- /dev/null +++ b/stig/audits/ftp_client_not_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_not_installed 'ftp'; then + exit $PASS +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/ftp_server_unused.sh b/stig/audits/ftp_server_unused.sh new file mode 100644 index 00000000..29c11048 --- /dev/null +++ b/stig/audits/ftp_server_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'vsftpd' vsftpd.service && exit $PASS || exit $FAIL diff --git a/stig/audits/gdm_automount_not_overridden.sh b/stig/audits/gdm_automount_not_overridden.sh new file mode 100644 index 00000000..d0336d10 --- /dev/null +++ b/stig/audits/gdm_automount_not_overridden.sh @@ -0,0 +1,34 @@ +source /tmp/lib.sh + +any_pkg_installed "gdm gdm3" || exit $PASS + +l_output2="" + +l_kfd="/etc/dconf/db/$(grep -Psril '^\h*automount\b' /etc/dconf/db/*/ 2>/dev/null | awk -F'/' '{split($(NF-1),a,".");print a[1]}' | head -n1).d" +l_kfd2="/etc/dconf/db/$(grep -Psril '^\h*automount-open\b' /etc/dconf/db/*/ 2>/dev/null | awk -F'/' '{split($(NF-1),a,".");print a[1]}' | head -n1).d" + +if [ -d "$l_kfd" ]; then + if grep -Priq '^\h*\/org\/gnome\/desktop\/media-handling\/automount\b' "$l_kfd" 2>/dev/null; then + : + else + l_output2="$l_output2\n - \"automount\" is not locked" + fi +else + l_output2="$l_output2\n - \"automount\" is not set so it can not be locked" +fi + +if [ -d "$l_kfd2" ]; then + if grep -Priq '^\h*\/org\/gnome\/desktop\/media-handling\/automount-open\b' "$l_kfd2" 2>/dev/null; then + : + else + l_output2="$l_output2\n - \"automount-open\" is not locked" + fi +else + l_output2="$l_output2\n - \"automount-open\" is not set so it can not be locked" +fi + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/gdm_automount_removable_media_disabled.sh b/stig/audits/gdm_automount_removable_media_disabled.sh new file mode 100644 index 00000000..922438ac --- /dev/null +++ b/stig/audits/gdm_automount_removable_media_disabled.sh @@ -0,0 +1,45 @@ +source /tmp/lib.sh + +any_pkg_installed "gdm gdm3" || exit $PASS + +l_output2="" + +# Look for existing settings and set variables if they exist +l_kfile="$(grep -Prils -- '^\h*automount\b' /etc/dconf/db/*.d 2>/dev/null | head -n1)" +l_kfile2="$(grep -Prils -- '^\h*automount-open\b' /etc/dconf/db/*.d 2>/dev/null | head -n1)" + +# Set profile name based on dconf db directory ({PROFILE_NAME}.d) +l_gpname="" +if [ -n "$l_kfile" ] && [ -f "$l_kfile" ]; then + l_gpname="$(awk -F\/ '{split($(NF-1),a,".");print a[1]}' <<< "$l_kfile")" +elif [ -n "$l_kfile2" ] && [ -f "$l_kfile2" ]; then + l_gpname="$(awk -F\/ '{split($(NF-1),a,".");print a[1]}' <<< "$l_kfile2")" +fi + +if [ -n "$l_gpname" ]; then + l_gpdir="/etc/dconf/db/$l_gpname.d" + + grep -Pq -- "^\h*system-db:$l_gpname\b" /etc/dconf/profile/* 2>/dev/null || l_output2="$l_output2\n - dconf database profile isn't set" + [ -f "/etc/dconf/db/$l_gpname" ] || l_output2="$l_output2\n - The dconf database \"$l_gpname\" doesn't exist" + [ -d "$l_gpdir" ] || l_output2="$l_output2\n - The dconf directory \"$l_gpdir\" doesn't exist" + + if [ -n "$l_kfile" ] && grep -Pqrs -- '^\h*automount\h*=\h*false\b' "$l_kfile" 2>/dev/null; then + : + else + l_output2="$l_output2\n - \"automount\" is not set correctly" + fi + + if [ -n "$l_kfile2" ] && grep -Pqs -- '^\h*automount-open\h*=\h*false\b' "$l_kfile2" 2>/dev/null; then + : + else + l_output2="$l_output2\n - \"automount-open\" is not set correctly" + fi +else + l_output2="$l_output2\n - neither \"automount\" or \"automount-open\" is set" +fi + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/gdm_autorun_never_enabled.sh b/stig/audits/gdm_autorun_never_enabled.sh new file mode 100644 index 00000000..b05f4a85 --- /dev/null +++ b/stig/audits/gdm_autorun_never_enabled.sh @@ -0,0 +1,32 @@ +source /tmp/lib.sh + +any_pkg_installed "gdm gdm3" || exit $PASS + +l_output2="" + +# Look for existing settings and set variables if they exist +l_kfile="$(grep -Prils -- '^\h*autorun-never\b' /etc/dconf/db/*.d 2>/dev/null | head -n1)" + +# Set profile name based on dconf db directory ({PROFILE_NAME}.d) +l_gpname="" +if [ -n "$l_kfile" ] && [ -f "$l_kfile" ]; then + l_gpname="$(awk -F\/ '{split($(NF-1),a,".");print a[1]}' <<< "$l_kfile")" +fi + +if [ -n "$l_gpname" ]; then + l_gpdir="/etc/dconf/db/$l_gpname.d" + + grep -Pq -- "^\h*system-db:$l_gpname\b" /etc/dconf/profile/* 2>/dev/null || l_output2="$l_output2\n - dconf database profile isn't set" + [ -f "/etc/dconf/db/$l_gpname" ] || l_output2="$l_output2\n - The dconf database \"$l_gpname\" doesn't exist" + [ -d "$l_gpdir" ] || l_output2="$l_output2\n - The dconf directory \"$l_gpdir\" doesn't exist" + + grep -Pqrs -- '^\h*autorun-never\h*=\h*true\b' "$l_kfile" 2>/dev/null || l_output2="$l_output2\n - \"autorun-never\" is not set correctly" +else + l_output2="$l_output2\n - \"autorun-never\" is not set" +fi + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/gdm_autorun_never_not_overridden.sh b/stig/audits/gdm_autorun_never_not_overridden.sh new file mode 100644 index 00000000..a5e122d8 --- /dev/null +++ b/stig/audits/gdm_autorun_never_not_overridden.sh @@ -0,0 +1,19 @@ +source /tmp/lib.sh + +any_pkg_installed "gdm gdm3" || exit $PASS + +l_output2="" + +l_kfd="/etc/dconf/db/$(grep -Psril '^\h*autorun-never\b' /etc/dconf/db/*/ 2>/dev/null | awk -F'/' '{split($(NF-1),a,".");print a[1]}' | head -n1).d" + +if [ -d "$l_kfd" ]; then + grep -Priq '^\h*\/org\/gnome\/desktop\/media-handling\/autorun-never\b' "$l_kfd" 2>/dev/null || l_output2="$l_output2\n - \"autorun-never\" is not locked" +else + l_output2="$l_output2\n - \"autorun-never\" is not set so it can not be locked" +fi + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/gdm_disable_user_list_enabled.sh b/stig/audits/gdm_disable_user_list_enabled.sh new file mode 100644 index 00000000..c4947f8b --- /dev/null +++ b/stig/audits/gdm_disable_user_list_enabled.sh @@ -0,0 +1,21 @@ +source /tmp/lib.sh + +any_pkg_installed "gdm gdm3" || exit $PASS + +output2="" + +l_gdmfile="$(grep -Pril '^\h*disable-user-list\h*=\h*true\b' /etc/dconf/db 2>/dev/null | head -n1)" +if [ -n "$l_gdmfile" ]; then + l_gdmprofile="$(awk -F\/ '{split($(NF-1),a,".");print a[1]}' <<< "$l_gdmfile")" + + grep -Pq "^\h*system-db:$l_gdmprofile\b" "/etc/dconf/profile/$l_gdmprofile" 2>/dev/null || output2="$output2\n - The \"$l_gdmprofile\" doesn't exist" + [ -f "/etc/dconf/db/$l_gdmprofile" ] || output2="$output2\n - The \"$l_gdmprofile\" profile doesn't exist in the dconf database" +else + output2="$output2\n - The \"disable-user-list\" option is not enabled" +fi + +if [ -z "$output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/gdm_login_banner_configured.sh b/stig/audits/gdm_login_banner_configured.sh new file mode 100644 index 00000000..660d9b40 --- /dev/null +++ b/stig/audits/gdm_login_banner_configured.sh @@ -0,0 +1,27 @@ +source /tmp/lib.sh + +any_pkg_installed "gdm gdm3" || exit $PASS + +l_output2="" + +l_gdmfile="$(grep -Prils '^\h*banner-message-enable\b' /etc/dconf/db/*.d 2>/dev/null | head -n1)" +if [ -n "$l_gdmfile" ]; then + l_gdmprofile="$(awk -F\/ '{split($(NF-1),a,".");print a[1]}' <<< "$l_gdmfile")" + + grep -Pisq '^\h*banner-message-enable=true\b' "$l_gdmfile" || l_output2="$l_output2\n - The \"banner-message-enable\" option is not enabled" + + l_lsbt="$(grep -Pios '^\h*banner-message-text=.*$' "$l_gdmfile" 2>/dev/null)" + [ -n "$l_lsbt" ] || l_output2="$l_output2\n - The \"banner-message-text\" option is not set" + + grep -Pq "^\h*system-db:$l_gdmprofile\b" "/etc/dconf/profile/$l_gdmprofile" 2>/dev/null || l_output2="$l_output2\n - The \"$l_gdmprofile\" doesn't exist" + + [ -f "/etc/dconf/db/$l_gdmprofile" ] || l_output2="$l_output2\n - The \"$l_gdmprofile\" profile doesn't exist in the dconf database" +else + l_output2="$l_output2\n - The \"banner-message-enable\" option isn't configured" +fi + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/gdm_removed.sh b/stig/audits/gdm_removed.sh new file mode 100644 index 00000000..55cb010b --- /dev/null +++ b/stig/audits/gdm_removed.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if is_installed 'gdm'; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/gdm_screen_locks_cannot_be_overridden.sh b/stig/audits/gdm_screen_locks_cannot_be_overridden.sh new file mode 100644 index 00000000..3aeb0088 --- /dev/null +++ b/stig/audits/gdm_screen_locks_cannot_be_overridden.sh @@ -0,0 +1,27 @@ +source /tmp/lib.sh + +any_pkg_installed "gdm gdm3" || exit $PASS + +l_output2="" + +# set directory of key file to be locked +l_kfd="/etc/dconf/db/$(grep -Psril '^\h*idle-delay\h*=\h*uint32\h+\d+\b' /etc/dconf/db/*/ 2>/dev/null | awk -F'/' '{split($(NF-1),a,".");print a[1]}' | head -n1).d" +l_kfd2="/etc/dconf/db/$(grep -Psril '^\h*lock-delay\h*=\h*uint32\h+\d+\b' /etc/dconf/db/*/ 2>/dev/null | awk -F'/' '{split($(NF-1),a,".");print a[1]}' | head -n1).d" + +if [ -d "$l_kfd" ]; then + grep -Prilq '\/org\/gnome\/desktop\/session\/idle-delay\b' "$l_kfd" 2>/dev/null || l_output2="$l_output2\n - \"idle-delay\" is not locked" +else + l_output2="$l_output2\n - \"idle-delay\" is not set so it can not be locked" +fi + +if [ -d "$l_kfd2" ]; then + grep -Prilq '\/org\/gnome\/desktop\/screensaver\/lock-delay\b' "$l_kfd2" 2>/dev/null || l_output2="$l_output2\n - \"lock-delay\" is not locked" +else + l_output2="$l_output2\n - \"lock-delay\" is not set so it can not be locked" +fi + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/gdm_screen_locks_when_idle.sh b/stig/audits/gdm_screen_locks_when_idle.sh new file mode 100644 index 00000000..812e3fdd --- /dev/null +++ b/stig/audits/gdm_screen_locks_when_idle.sh @@ -0,0 +1,45 @@ +source /tmp/lib.sh + +any_pkg_installed "gdm gdm3" || exit $PASS + +l_output2="" +l_idmv="900" # Set for max value for idle-delay in seconds +l_ldmv="5" # Set for max value for lock-delay in seconds + +# Determine file containing idle-delay key +l_kfile="$(grep -Psril '^\h*idle-delay\h*=\h*uint32\h+\d+\b' /etc/dconf/db/*/ 2>/dev/null | head -n1)" + +if [ -n "$l_kfile" ]; then + # set profile name (This is the name of a dconf database) + l_profile="$(awk -F'/' '{split($(NF-1),a,".");print a[1]}' <<< "$l_kfile")" + + # Confirm that idle-delay exists, includes uint32, and value is between 1 and max + l_idv="$(awk -F 'uint32' '/idle-delay/{print $2}' "$l_kfile" 2>/dev/null | xargs)" + if [ -n "$l_idv" ]; then + [ "$l_idv" -gt "0" -a "$l_idv" -le "$l_idmv" ] || l_output2="$l_output2\n - The \"idle-delay\" option is not set correctly" + else + l_output2="$l_output2\n - The \"idle-delay\" option is not set in \"$l_kfile\"" + fi + + # Confirm that lock-delay exists, includes uint32, and value is between 0 and max + l_ldv="$(awk -F 'uint32' '/lock-delay/{print $2}' "$l_kfile" 2>/dev/null | xargs)" + if [ -n "$l_ldv" ]; then + [ "$l_ldv" -ge "0" -a "$l_ldv" -le "$l_ldmv" ] || l_output2="$l_output2\n - The \"lock-delay\" option is not set correctly" + else + l_output2="$l_output2\n - The \"lock-delay\" option is not set in \"$l_kfile\"" + fi + + # Confirm that dconf profile exists + grep -Psq "^\h*system-db:$l_profile\b" /etc/dconf/profile/* 2>/dev/null || l_output2="$l_output2\n - The \"$l_profile\" doesn't exist" + + # Confirm that dconf profile database file exists + [ -f "/etc/dconf/db/$l_profile" ] || l_output2="$l_output2\n - The \"$l_profile\" profile doesn't exist in the dconf database" +else + l_output2="$l_output2\n - The \"idle-delay\" option doesn't exist, remaining tests skipped" +fi + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/gpg_keys_configured.sh b/stig/audits/gpg_keys_configured.sh new file mode 100644 index 00000000..b741a73c --- /dev/null +++ b/stig/audits/gpg_keys_configured.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if grep -r gpgkey /etc/yum.repos.d/* /etc/dnf/dnf.conf >/dev/null 2>&1 && is_installed 'gpg-pubkey'; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/gpgcheck_globally_activated.sh b/stig/audits/gpgcheck_globally_activated.sh new file mode 100644 index 00000000..0848390b --- /dev/null +++ b/stig/audits/gpgcheck_globally_activated.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if grep -Piq -- '^\h*gpgcheck\h*=\h*(1|true|yes)\b' /etc/dnf/dnf.conf && \ + ! grep -Pris -- '^\h*gpgcheck\h*=\h*(0|[^01]|[^Tt][^Rr][^Uu][^Ee]|[^Yy][^Ee][^Ss])' /etc/yum.repos.d/ >/dev/null 2>&1; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/group_root_only_gid_0.sh b/stig/audits/group_root_only_gid_0.sh new file mode 100644 index 00000000..635d30c5 --- /dev/null +++ b/stig/audits/group_root_only_gid_0.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +invalid_gid0_groups="$(awk -F: '$3=="0"{print $1}' /etc/group | grep -v '^root$')" + +if [ -n "$invalid_gid0_groups" ]; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/hfs_off.sh b/stig/audits/hfs_off.sh new file mode 100644 index 00000000..895c83e5 --- /dev/null +++ b/stig/audits/hfs_off.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "hfs" "fs" && exit $PASS || exit $FAIL diff --git a/stig/audits/hfsplus_off.sh b/stig/audits/hfsplus_off.sh new file mode 100644 index 00000000..2e55a5f4 --- /dev/null +++ b/stig/audits/hfsplus_off.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "hfsplus" "fs" && exit $PASS || exit $FAIL diff --git a/stig/audits/home_nodev.sh b/stig/audits/home_nodev.sh new file mode 100644 index 00000000..9c8f9b9c --- /dev/null +++ b/stig/audits/home_nodev.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /home nodev && exit $PASS || exit $FAIL diff --git a/stig/audits/home_nosuid.sh b/stig/audits/home_nosuid.sh new file mode 100644 index 00000000..017525e6 --- /dev/null +++ b/stig/audits/home_nosuid.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /home nosuid && exit $PASS || exit $FAIL diff --git a/stig/audits/home_separate_partition.sh b/stig/audits/home_separate_partition.sh new file mode 100644 index 00000000..789b486f --- /dev/null +++ b/stig/audits/home_separate_partition.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_separate_partition /home && exit $PASS || exit $FAIL diff --git a/stig/audits/icmp_broadcasted_requests_ignored.sh b/stig/audits/icmp_broadcasted_requests_ignored.sh new file mode 100644 index 00000000..81a65d52 --- /dev/null +++ b/stig/audits/icmp_broadcasted_requests_ignored.sh @@ -0,0 +1,76 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_ipv6_disabled="" +a_parlist=("net.ipv4.icmp_echo_ignore_broadcasts=1") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +f_ipv6_chk() +{ + l_ipv6_disabled="" + ! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_ipv6_disabled="yes" + if sysctl net.ipv6.conf.all.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_ipv6_disabled="yes" + fi + [ -z "$l_ipv6_disabled" ] && l_ipv6_disabled="no" +} + +f_kernel_parameter_chk() +{ + l_krp="$(sysctl "$l_kpname" | awk -F= '{print $2}' | xargs)" + if [ "$l_krp" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_krp\" in the running configuration" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_krp\" in the running configuration and should have a value of: \"$l_kpvalue\"" + fi + unset A_out; declare -A A_out + while read -r l_out; do + if [ -n "$l_out" ]; then + if [[ $l_out =~ ^\s*# ]]; then + l_file="${l_out//# /}" + else + l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") + fi + fi + done < <(/usr/lib/systemd/systemd-sysctl --cat-config | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + if [ -n "$l_ufwscf" ]; then + l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" | xargs)" + l_kpar="${l_kpar//\//.}" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") + fi + if (( ${#A_out[@]} > 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/icmp_redirects_not_accepted.sh b/stig/audits/icmp_redirects_not_accepted.sh new file mode 100644 index 00000000..e54d285f --- /dev/null +++ b/stig/audits/icmp_redirects_not_accepted.sh @@ -0,0 +1,77 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_ipv6_disabled="" +a_parlist=("net.ipv4.conf.all.accept_redirects=0" "net.ipv4.conf.default.accept_redirects=0" + "net.ipv6.conf.all.accept_redirects=0" "net.ipv6.conf.default.accept_redirects=0") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +f_ipv6_chk() +{ + l_ipv6_disabled="" + ! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_ipv6_disabled="yes" + if sysctl net.ipv6.conf.all.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_ipv6_disabled="yes" + fi + [ -z "$l_ipv6_disabled" ] && l_ipv6_disabled="no" +} + +f_kernel_parameter_chk() +{ + l_krp="$(sysctl "$l_kpname" | awk -F= '{print $2}' | xargs)" + if [ "$l_krp" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_krp\" in the running configuration" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_krp\" in the running configuration and should have a value of: \"$l_kpvalue\"" + fi + unset A_out; declare -A A_out + while read -r l_out; do + if [ -n "$l_out" ]; then + if [[ $l_out =~ ^\s*# ]]; then + l_file="${l_out//# /}" + else + l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") + fi + fi + done < <(/usr/lib/systemd/systemd-sysctl --cat-config | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + if [ -n "$l_ufwscf" ]; then + l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" | xargs)" + l_kpar="${l_kpar//\//.}" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") + fi + if (( ${#A_out[@]} > 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/icmp_secure_redirects_not_accepted.sh b/stig/audits/icmp_secure_redirects_not_accepted.sh new file mode 100644 index 00000000..1a9401da --- /dev/null +++ b/stig/audits/icmp_secure_redirects_not_accepted.sh @@ -0,0 +1,76 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_ipv6_disabled="" +a_parlist=("net.ipv4.conf.all.secure_redirects=0" "net.ipv4.conf.default.secure_redirects=0") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +f_ipv6_chk() +{ + l_ipv6_disabled="" + ! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_ipv6_disabled="yes" + if sysctl net.ipv6.conf.all.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_ipv6_disabled="yes" + fi + [ -z "$l_ipv6_disabled" ] && l_ipv6_disabled="no" +} + +f_kernel_parameter_chk() +{ + l_krp="$(sysctl "$l_kpname" | awk -F= '{print $2}' | xargs)" + if [ "$l_krp" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_krp\" in the running configuration" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_krp\" in the running configuration and should have a value of: \"$l_kpvalue\"" + fi + unset A_out; declare -A A_out + while read -r l_out; do + if [ -n "$l_out" ]; then + if [[ $l_out =~ ^\s*# ]]; then + l_file="${l_out//# /}" + else + l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") + fi + fi + done < <(/usr/lib/systemd/systemd-sysctl --cat-config | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + if [ -n "$l_ufwscf" ]; then + l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" | xargs)" + l_kpar="${l_kpar//\//.}" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") + fi + if (( ${#A_out[@]} > 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/inactive_password_lock_configured.sh b/stig/audits/inactive_password_lock_configured.sh new file mode 100644 index 00000000..e5d24f82 --- /dev/null +++ b/stig/audits/inactive_password_lock_configured.sh @@ -0,0 +1,15 @@ +source /tmp/lib.sh + +inactive_default="$(useradd -D | grep -E '^INACTIVE=' | awk -F= '{print $2}')" + +if [ -z "$inactive_default" ] || [ "$inactive_default" -gt 45 ] || [ "$inactive_default" -lt 0 ]; then + exit $FAIL +fi + +non_compliant_users="$(awk -F: '($2~/^\$.+\$/) { if ($7 > 45 || $7 < 0) print $1 }' /etc/shadow)" + +if [ -n "$non_compliant_users" ]; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/ip_forwarding_disabled.sh b/stig/audits/ip_forwarding_disabled.sh new file mode 100644 index 00000000..8379e995 --- /dev/null +++ b/stig/audits/ip_forwarding_disabled.sh @@ -0,0 +1,76 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_ipv6_disabled="" +a_parlist=("net.ipv4.ip_forward=0" "net.ipv6.conf.all.forwarding=0") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +f_ipv6_chk() +{ + l_ipv6_disabled="" + ! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_ipv6_disabled="yes" + if sysctl net.ipv6.conf.all.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_ipv6_disabled="yes" + fi + [ -z "$l_ipv6_disabled" ] && l_ipv6_disabled="no" +} + +f_kernel_parameter_chk() +{ + l_krp="$(sysctl "$l_kpname" | awk -F= '{print $2}' | xargs)" + if [ "$l_krp" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_krp\" in the running configuration" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_krp\" in the running configuration and should have a value of: \"$l_kpvalue\"" + fi + unset A_out; declare -A A_out + while read -r l_out; do + if [ -n "$l_out" ]; then + if [[ $l_out =~ ^\s*# ]]; then + l_file="${l_out//# /}" + else + l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") + fi + fi + done < <(/usr/lib/systemd/systemd-sysctl --cat-config | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + if [ -n "$l_ufwscf" ]; then + l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" | xargs)" + l_kpar="${l_kpar//\//.}" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") + fi + if (( ${#A_out[@]} > 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/ipv6_router_advertisements_not_accepted.sh b/stig/audits/ipv6_router_advertisements_not_accepted.sh new file mode 100644 index 00000000..89a4c666 --- /dev/null +++ b/stig/audits/ipv6_router_advertisements_not_accepted.sh @@ -0,0 +1,76 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_ipv6_disabled="" +a_parlist=("net.ipv6.conf.all.accept_ra=0" "net.ipv6.conf.default.accept_ra=0") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +f_ipv6_chk() +{ + l_ipv6_disabled="" + ! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_ipv6_disabled="yes" + if sysctl net.ipv6.conf.all.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_ipv6_disabled="yes" + fi + [ -z "$l_ipv6_disabled" ] && l_ipv6_disabled="no" +} + +f_kernel_parameter_chk() +{ + l_krp="$(sysctl "$l_kpname" | awk -F= '{print $2}' | xargs)" + if [ "$l_krp" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_krp\" in the running configuration" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_krp\" in the running configuration and should have a value of: \"$l_kpvalue\"" + fi + unset A_out; declare -A A_out + while read -r l_out; do + if [ -n "$l_out" ]; then + if [[ $l_out =~ ^\s*# ]]; then + l_file="${l_out//# /}" + else + l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") + fi + fi + done < <(/usr/lib/systemd/systemd-sysctl --cat-config | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + if [ -n "$l_ufwscf" ]; then + l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" | xargs)" + l_kpar="${l_kpar//\//.}" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") + fi + if (( ${#A_out[@]} > 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi diff --git a/stig/audits/ipv6_status_identified.sh b/stig/audits/ipv6_status_identified.sh new file mode 100644 index 00000000..f4b83c42 --- /dev/null +++ b/stig/audits/ipv6_status_identified.sh @@ -0,0 +1,17 @@ +source /tmp/lib.sh + +l_output="" +! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_output="- IPv6 is not enabled" +if sysctl net.ipv6.conf.all.disable_ipv6 2>/dev/null | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 2>/dev/null | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_output="- IPv6 is not enabled" +fi + +if [ -z "$l_output" ]; then + l_output="- IPv6 is enabled" + echo -e "\n$l_output\n" + exit $PASS +else + echo -e "\n$l_output\n" + exit $FAIL +fi diff --git a/stig/audits/issue_net_permissions_configured.sh b/stig/audits/issue_net_permissions_configured.sh new file mode 100644 index 00000000..2d016344 --- /dev/null +++ b/stig/audits/issue_net_permissions_configured.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/issue.net 644 && exit $PASS || exit $FAIL diff --git a/stig/audits/issue_permissions_configured.sh b/stig/audits/issue_permissions_configured.sh new file mode 100644 index 00000000..20cd6160 --- /dev/null +++ b/stig/audits/issue_permissions_configured.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_file_root_perms /etc/issue 644 && exit $PASS || exit $FAIL diff --git a/stig/audits/jffs2_off.sh b/stig/audits/jffs2_off.sh new file mode 100644 index 00000000..0b85ee82 --- /dev/null +++ b/stig/audits/jffs2_off.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "jffs2" "fs" && exit $PASS || exit $FAIL diff --git a/stig/audits/journal_remote_pkg.sh b/stig/audits/journal_remote_pkg.sh new file mode 100644 index 00000000..111c2355 --- /dev/null +++ b/stig/audits/journal_remote_pkg.sh @@ -0,0 +1,13 @@ +source /tmp/lib.sh + +# systemd-journal-remote installed (only if journald is chosen) +if systemctl is-active --quiet rsyslog; then + exit $PASS +fi + +if systemctl is-active --quiet systemd-journald; then + if is_installed 'systemd-journal-remote'; then exit $PASS; fi + exit $FAIL +fi + +exit $FAIL diff --git a/stig/audits/journal_remote_unused.sh b/stig/audits/journal_remote_unused.sh new file mode 100644 index 00000000..238b9aff --- /dev/null +++ b/stig/audits/journal_remote_unused.sh @@ -0,0 +1,17 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet rsyslog; then + exit $PASS +fi + +if systemctl is-active --quiet systemd-journald; then + if systemctl is-enabled systemd-journal-remote.socket systemd-journal-remote.service 2>/dev/null | grep -Pq '^enabled'; then + exit $FAIL + fi + if systemctl is-active systemd-journal-remote.socket systemd-journal-remote.service 2>/dev/null | grep -Pq '^active'; then + exit $FAIL + fi + exit $PASS +fi + +exit $FAIL diff --git a/stig/audits/journal_upload_auth.sh b/stig/audits/journal_upload_auth.sh new file mode 100644 index 00000000..86bb6c38 --- /dev/null +++ b/stig/audits/journal_upload_auth.sh @@ -0,0 +1,13 @@ +source /tmp/lib.sh + +cfg="$(systemd-analyze cat-config systemd/journal-upload.conf /etc/systemd/journal-upload.conf.d/*.conf 2>/dev/null)" + +for k in URL ServerKeyFile ServerCertificateFile TrustedCertificateFile; do + if ! grep -Eq "^[[:space:]]*${k}=[^[:space:]]+" <<< "$cfg"; then + exit $FAIL + fi +done + +exit $PASS + + #NOTE: the verification of the output is based on example values this here MUST be manually reviewed \ No newline at end of file diff --git a/stig/audits/journal_upload_enabled.sh b/stig/audits/journal_upload_enabled.sh new file mode 100644 index 00000000..b79c929a --- /dev/null +++ b/stig/audits/journal_upload_enabled.sh @@ -0,0 +1,15 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet rsyslog; then + exit $PASS +fi + +if systemctl is-active --quiet systemd-journald; then + if systemctl is-enabled systemd-journal-upload.service 2>/dev/null | grep -qx 'enabled' && \ + systemctl is-active systemd-journal-upload.service 2>/dev/null | grep -qx 'active'; then + exit $PASS + fi + exit $FAIL +fi + +exit $FAIL diff --git a/stig/audits/journald_compress.sh b/stig/audits/journald_compress.sh new file mode 100644 index 00000000..c9eb6629 --- /dev/null +++ b/stig/audits/journald_compress.sh @@ -0,0 +1,14 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet rsyslog; then + exit $PASS +fi + +if systemctl is-active --quiet systemd-journald; then + if systemd-analyze cat-config systemd/journald.conf systemd/journald.conf.d/* 2>/dev/null | grep -Eq '^Compress=yes\b'; then + exit $PASS + fi + exit $FAIL +fi + +exit $FAIL diff --git a/stig/audits/journald_enabled.sh b/stig/audits/journald_enabled.sh new file mode 100644 index 00000000..c6848dc2 --- /dev/null +++ b/stig/audits/journald_enabled.sh @@ -0,0 +1,17 @@ +source /tmp/lib.sh + +file_path="" +if [ -f /etc/tmpfiles.d/systemd.conf ]; then + file_path="/etc/tmpfiles.d/systemd.conf" +elif [ -f /usr/lib/tmpfiles.d/systemd.conf ]; then + file_path="/usr/lib/tmpfiles.d/systemd.conf" +fi + +if [ -n "$file_path" ]; then + if ! stat -Lc '%a' "$file_path" | grep -Eq '^(0|2|4|6)40$'; then + exit $FAIL + fi + exit $PASS +fi + +exit $PASS diff --git a/stig/audits/journald_log_access.sh b/stig/audits/journald_log_access.sh new file mode 100644 index 00000000..bec20ae1 --- /dev/null +++ b/stig/audits/journald_log_access.sh @@ -0,0 +1,35 @@ +source /tmp/lib.sh + +# tmpfiles systemd.conf: check override/default and perms in file entries are <=0640 +l_output="" file_path="" + +if [ -f /etc/tmpfiles.d/systemd.conf ]; then + file_path="/etc/tmpfiles.d/systemd.conf" +elif [ -f /usr/lib/tmpfiles.d/systemd.conf ]; then + file_path="/usr/lib/tmpfiles.d/systemd.conf" +fi + +if [ -z "$file_path" ]; then + exit $PASS +fi + +higher_permissions_found=false + +while IFS= read -r line; do + # skip empty lines and comments + [[ "$line" =~ ^[[:space:]]*$ ]] && continue + [[ "$line" =~ ^[[:space:]]*# ]] && continue + + # tmpfiles format: type path mode uid gid age argument... + # flag any explicit mode that is more permissive than 0640 (e.g. 0641+, 0650+, 0700+, etc) + if echo "$line" | grep -Piq '^\s*[A-Za-z]\S*\s+\S+\s+0*([6-7][4-7][1-7]|7[0-7][0-7])\s+'; then + higher_permissions_found=true + break + fi +done < "$file_path" + +if $higher_permissions_found; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/journald_log_rotation.sh b/stig/audits/journald_log_rotation.sh new file mode 100644 index 00000000..f4d98335 --- /dev/null +++ b/stig/audits/journald_log_rotation.sh @@ -0,0 +1,12 @@ +source /tmp/lib.sh + +# journald rotation set per site policy (requires non-empty values) +cfg="$(systemd-analyze cat-config systemd/journald.conf /etc/systemd/journald.conf.d/*.conf 2>/dev/null)" + +for k in SystemMaxUse SystemKeepFree RuntimeMaxUse RuntimeKeepFree MaxFileSec; do + if ! grep -Eq "^[[:space:]]*${k}=[^[:space:]]+" <<< "$cfg"; then + exit $FAIL + fi +done + +exit $PASS diff --git a/stig/audits/journald_no_syslog.sh b/stig/audits/journald_no_syslog.sh new file mode 100644 index 00000000..a208ef97 --- /dev/null +++ b/stig/audits/journald_no_syslog.sh @@ -0,0 +1,14 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet rsyslog; then + exit $PASS +fi + +if systemctl is-active --quiet systemd-journald; then + if systemd-analyze cat-config systemd/journald.conf systemd/journald.conf.d/* 2>/dev/null | grep -Eq '^ForwardToSyslog=no\b'; then + exit $PASS + fi + exit $FAIL +fi + +exit $FAIL diff --git a/stig/audits/journald_storage.sh b/stig/audits/journald_storage.sh new file mode 100644 index 00000000..49a98960 --- /dev/null +++ b/stig/audits/journald_storage.sh @@ -0,0 +1,14 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet rsyslog; then + exit $PASS +fi + +if systemctl is-active --quiet systemd-journald; then + if systemd-analyze cat-config systemd/journald.conf systemd/journald.conf.d/* 2>/dev/null | grep -Eq '^Storage=persistent\b'; then + exit $PASS + fi + exit $FAIL +fi + +exit $FAIL diff --git a/stig/audits/journald_to_rsyslog.sh b/stig/audits/journald_to_rsyslog.sh new file mode 100644 index 00000000..5a56e4ff --- /dev/null +++ b/stig/audits/journald_to_rsyslog.sh @@ -0,0 +1,14 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet systemd-journald; then + exit $PASS +fi + +if systemctl is-active --quiet rsyslog; then + if systemd-analyze cat-config systemd/journald.conf systemd/journald.conf.d/* 2>/dev/null | grep -Eq '^ForwardToSyslog=yes\b'; then + exit $PASS + fi + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/latest_authselect_installed.sh b/stig/audits/latest_authselect_installed.sh new file mode 100644 index 00000000..21c53094 --- /dev/null +++ b/stig/audits/latest_authselect_installed.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +rpm_version_gte "authselect" "0" "1.2.6" "2" && exit $PASS || exit $FAIL diff --git a/stig/audits/latest_libpwquality_installed.sh b/stig/audits/latest_libpwquality_installed.sh new file mode 100644 index 00000000..abd910cb --- /dev/null +++ b/stig/audits/latest_libpwquality_installed.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +rpm_version_gte "libpwquality" "0" "1.4.4" "8" && exit $PASS || exit $FAIL diff --git a/stig/audits/latest_pam_installed.sh b/stig/audits/latest_pam_installed.sh new file mode 100644 index 00000000..c60c4fc4 --- /dev/null +++ b/stig/audits/latest_pam_installed.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +rpm_version_gte "pam" "0" "1.5.1" "19" && exit $PASS || exit $FAIL diff --git a/stig/audits/ldap_client_not_installed.sh b/stig/audits/ldap_client_not_installed.sh new file mode 100644 index 00000000..3008f87a --- /dev/null +++ b/stig/audits/ldap_client_not_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_not_installed 'openldap-clients'; then + exit $PASS +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/lib.sh b/stig/audits/lib.sh new file mode 100644 index 00000000..b84aac27 --- /dev/null +++ b/stig/audits/lib.sh @@ -0,0 +1,244 @@ +# list of result codes +PASS=0 +FAIL=1 +SKIP=2 # not applicable +TODO=4 # needs to be implemented +REV=8 # review manually + +# Remove locale settings to eliminate localized outputs where possible +export LC_ALL=C +unset LANG + + +extract() { + local result=$(echo "$1" | awk -F "$2|$3" '{print tolower($2)}') + echo "$result" +} + + +has_fsopt_on_usb() { + local fsopt=$1 + ## Note: Only usb media is supported at the moment. Need to investigate what + ## difference a CDROM, etc. can make, but I've set it up ready to add + ## another search term. You're welcome :) + local filesystems=$(for device in "$devices"; do lsblk --paths --noheadings --list $device | grep -E --invert-match '^$device|[SWAP]' | awk '{print $1}'; done) + for filesystem in $filesystems; do + fs_without_opt=$(mount | grep -E "$filesystem\s" | grep --invert-match "$fsopt" &>/dev/null | wc -l) + if [ $fs_without_opt -ne 0 ]; then return 1; fi + done + return 0 +} + + +has_usb_devices() { + if [ -z "$(lsblk --paths --noheadings --list --scsi | awk '/usb/ {print $1}')" ]; then return 1; fi + return 0 +} + + +is_active() { + if systemctl is-active $1 >/dev/null 2>&1; then return 0; fi + return 1 +} + + +is_active_kernelmod() { + if [ $(diff -qsZ <(modprobe --dry-run --verbose $1 2>/dev/null | tail -n1) <(echo "install /bin/true") &>/dev/null; echo $?) -ne 0 ]; then return 0; fi + if [ -n "$(lsmod | grep $1)" ]; then return 0; fi + return 1 +} + + +is_disabled() { + if ! systemctl is-enabled $1 >/dev/null 2>&1; then return 0; fi + return 1 +} + + +is_enabled() { + if systemctl is-enabled $1 &>/dev/null; then return 0; fi + return 1 +} + + +is_installed() { + if rpm -q $1 &> /dev/null; then return 0; fi + return 1 +} + + +is_not_installed() { + if ! rpm -q $1 &> /dev/null; then return 0; fi + return 1 +} + + +ipv6_is_enabled() { + if [ "$(cat /sys/module/ipv6/parameters/disable)" = "1" ]; then return 1; fi + return 0 +} + + +# returns 0 if any pkg from a space-separated list is installed; auto-detects dpkg/rpm +any_pkg_installed() { + local pkg_list="$1" + local pq="" + if command -v dpkg-query > /dev/null 2>&1; then + pq="dpkg-query -W" + elif command -v rpm > /dev/null 2>&1; then + pq="rpm -q" + fi + for pkg in $pkg_list; do + $pq "$pkg" > /dev/null 2>&1 && return 0 + done + return 1 +} + + +# returns 0 if installed rpm pkg version >= given minimum epoch:version-release +rpm_version_gte() { + local pkg="$1" min_epoch="$2" min_ver="$3" min_rel="$4" + local evr + evr="$(rpm -q --qf '%{EPOCHNUM}:%{VERSION}-%{RELEASE}\n' "$pkg" 2>/dev/null)" + [ -z "$evr" ] && return 1 + python3 - <= 0 else 1) +PY +} + + +# pass if pkg not installed or all services neither enabled nor active +service_unused() { + local pkg="$1"; shift + is_not_installed "$pkg" && return 0 + systemctl is-enabled "$@" 2>/dev/null | grep -q 'enabled' && return 1 + systemctl is-active "$@" 2>/dev/null | grep -q '^active' && return 1 + return 0 +} + + +# pass if mountpoint exists and has given option +has_mount_option() { + local mnt="$1" opt="$2" + findmnt -kn "$mnt" >/dev/null 2>&1 || return 1 + findmnt -kn "$mnt" | grep -v "$opt" | grep -q '.' && return 1 + return 0 +} + + +# pass if mountpoint exists as separate partition +has_separate_partition() { + findmnt -kn "$1" >/dev/null 2>&1 && return 0 + return 1 +} + + +# pass if file owned by root:root and mode <= max_mode +check_file_root_perms() { + local file="$1" max_mode="$2" + [ -e "$file" ] || return 1 + local mode uid gid + mode="$(stat -Lc '%a' "$file" 2>/dev/null)" || return 1 + uid="$(stat -Lc '%u' "$file" 2>/dev/null)" || return 1 + gid="$(stat -Lc '%g' "$file" 2>/dev/null)" || return 1 + [ "$mode" -le "$max_mode" ] && [ "$uid" -eq 0 ] && [ "$gid" -eq 0 ] && return 0 + return 1 +} + + +# pass if login banner file has no OS/version info leaks +check_login_banner() { + local file="$1" + [ -f "$file" ] || return 1 + local os_id re + os_id="$(grep -E '^ID=' /etc/os-release 2>/dev/null | head -n1 | cut -d= -f2 | sed -e 's/"//g')" + re='(\\v|\\r|\\m|\\s)' + [ -n "$os_id" ] && re="(\\v|\\r|\\m|\\s|${os_id})" + grep -Eiq -- "$re" "$file" && return 1 + return 0 +} + + +# pass if kernel module is not loaded, not loadable, and deny listed +check_kernel_module_disabled() { + local l_mod_name="$1" l_mod_type="$2" + local l_output3="" l_dl="" + local a_output=() a_output2=() + local l_mod_path + l_mod_path="$(readlink -f /lib/modules/**/kernel/$l_mod_type 2>/dev/null | sort -u)" + + _f_module_chk() { + l_dl="y" + local a_showconfig=() + while IFS= read -r l_showconfig; do + a_showconfig+=("$l_showconfig") + done < <(modprobe --showconfig 2>/dev/null | grep -P -- '\b(install|blacklist)\h+'"${l_mod_name//-/_}"'\b') + + if ! lsmod | grep "$l_mod_name" &>/dev/null; then + a_output+=(" - kernel module: \"$l_mod_name\" is not loaded") + else + a_output2+=(" - kernel module: \"$l_mod_name\" is loaded") + fi + + if grep -Pq -- '\binstall\h+'"${l_mod_name//-/_}"'\h+\/bin\/(true|false)\b' <<< "${a_showconfig[*]}"; then + a_output+=(" - kernel module: \"$l_mod_name\" is not loadable") + else + a_output2+=(" - kernel module: \"$l_mod_name\" is loadable") + fi + + if grep -Pq -- '\bblacklist\h+'"${l_mod_name//-/_}"'\b' <<< "${a_showconfig[*]}"; then + a_output+=(" - kernel module: \"$l_mod_name\" is deny listed") + else + a_output2+=(" - kernel module: \"$l_mod_name\" is not deny listed") + fi + } + + for l_mod_base_directory in $l_mod_path; do + if [ -d "$l_mod_base_directory/${l_mod_name/-/\/}" ] && [ -n "$(ls -A "$l_mod_base_directory/${l_mod_name/-/\/}" 2>/dev/null)" ]; then + l_output3="$l_output3\n - \"$l_mod_base_directory\"" + [[ "$l_mod_name" =~ overlay ]] && l_mod_name="${l_mod_name::-2}" + [ "$l_dl" != "y" ] && _f_module_chk + else + a_output+=(" - kernel module: \"$l_mod_name\" doesn't exist in \"$l_mod_base_directory\"") + fi + done + + [ -n "$l_output3" ] && echo -e "\n\n -- INFO --\n - module: \"$l_mod_name\" exists in:$l_output3" + + if [ "${#a_output2[@]}" -le 0 ]; then + printf '%s\n' "" "- Audit Result:" " ** PASS **" "${a_output[@]}" + return 0 + else + printf '%s\n' "" "- Audit Result:" " ** FAIL **" " - Reason(s) for audit failure:" "${a_output2[@]}" + [ "${#a_output[@]}" -gt 0 ] && printf '%s\n' "- Correctly set:" "${a_output[@]}" + return 1 + fi +} + + +test_perms() { + local perms=$1 + local file=$2 + + # if the file does not exist, we are tolerant: then the file permissions are not wrong + if [ ! -e $file ]; then return $PASS; fi + + local u=$(echo $perms | cut -c1) + local g=$(echo $perms | cut -c2) + local o=$(echo $perms | cut -c3) + local file_perms="$(stat -L $file | awk '/Access: \(/ {print $2}')" + local file_u=$(echo $file_perms | cut -c3) + local file_g=$(echo $file_perms | cut -c4) + local file_o=$(echo $file_perms | cut -c5) + + if [ "$(ls -ld $file | awk '{ print $3" "$4 }')" != "root root" ]; then return $FAIL; fi + if [ $file_u -gt $u ]; then return $FAIL; fi + if [ $file_g -gt $g ]; then return $FAIL; fi + if [ $file_o -gt $o ]; then return $FAIL; fi + + return $PASS +} diff --git a/stig/audits/local_interactive_user_dot_files_access_configured.sh b/stig/audits/local_interactive_user_dot_files_access_configured.sh new file mode 100644 index 00000000..46c0e5f3 --- /dev/null +++ b/stig/audits/local_interactive_user_dot_files_access_configured.sh @@ -0,0 +1,33 @@ +source /tmp/lib.sh + +if awk -F: 'BEGIN{ + while ((getline < "/etc/shells") > 0) if ($0 ~ /^\// && $0 !~ /nologin/) s=s "|" $0 + sub(/^\|/,"",s); re="^(" s ")$" +} +$7 ~ re { print $1 ":" $6 }' /etc/passwd | while IFS=: read -r u h; do + [ -d "$h" ] || continue + g="$(id -gn "$u" | xargs)" + find "$h" -xdev -type f -name '.*' -print0 2>/dev/null | while IFS= read -r -d $'\0' f; do + b="$(basename "$f")" + read -r m o go < <(stat -Lc '%a %U %G' "$f") + case "$b" in + .forward|.rhost) exit 1 ;; + .netrc) + [ "$o" = "$u" ] || exit 1 + [ "$go" = "$g" ] || exit 1 + (( (8#$m & 0177) == 0 )) || exit 1 + ;; + .bash_history) + [ "$o" = "$u" ] || exit 1 + [ "$go" = "$g" ] || exit 1 + (( (8#$m & 0177) == 0 )) || exit 1 + ;; + *) + [ "$o" = "$u" ] || exit 1 + [ "$go" = "$g" ] || exit 1 + (( (8#$m & 0133) == 0 )) || exit 1 + ;; + esac + done || exit 1 +done; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/local_interactive_user_home_directories_configured.sh b/stig/audits/local_interactive_user_home_directories_configured.sh new file mode 100644 index 00000000..f9b210ed --- /dev/null +++ b/stig/audits/local_interactive_user_home_directories_configured.sh @@ -0,0 +1,13 @@ +source /tmp/lib.sh + +if awk -F: 'BEGIN{ + while ((getline < "/etc/shells") > 0) if ($0 ~ /^\// && $0 !~ /nologin/) s=s "|" $0 + sub(/^\|/,"",s); re="^(" s ")$" +} +$7 ~ re { print $1 ":" $6 }' /etc/passwd | while IFS=: read -r u h; do + [ -d "$h" ] || exit 1 + [ "$(stat -Lc '%U' "$h")" = "$u" ] || exit 1 + m="$(stat -Lc '%a' "$h")" + (( (8#$m & 0027) == 0 )) || exit 1 +done; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/local_login_banner_configured.sh b/stig/audits/local_login_banner_configured.sh new file mode 100644 index 00000000..779f0039 --- /dev/null +++ b/stig/audits/local_login_banner_configured.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_login_banner /etc/issue && exit $PASS || exit $FAIL diff --git a/stig/audits/logfiles_access.sh b/stig/audits/logfiles_access.sh new file mode 100644 index 00000000..5046d476 --- /dev/null +++ b/stig/audits/logfiles_access.sh @@ -0,0 +1,94 @@ +source /tmp/lib.sh + +l_op2="" l_output2="" +l_uidmin="$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs)" +file_test_chk() +{ +l_op2="" +if [ $(( $l_mode & $perm_mask )) -gt 0 ]; then +l_op2="$l_op2\n - Mode: \"$l_mode\" should be \"$maxperm\" or more restrictive" +fi +if [[ ! "$l_user" =~ $l_auser ]]; then +l_op2="$l_op2\n - Owned by: \"$l_user\" and should be owned by \"${l_auser//|/ or }\"" +fi +if [[ ! "$l_group" =~ $l_agroup ]]; then +l_op2="$l_op2\n - Group owned by: \"$l_group\" and should be group owned by \"${l_agroup//|/ or }\"" +fi +[ -n "$l_op2" ] && l_output2="$l_output2\n - File: \"$l_fname\" is:$l_op2\n" +} +unset a_file && a_file=() # clear and initialize array +# loop to create array with stat of files that could possibly fail one of the audits +while IFS= read -r -d $'\0' l_file; do +[ -e "$l_file" ] && a_file+=("$(stat -Lc '%n^%#a^%U^%u^%G^%g' "$l_file")") +done < <(find -L /var/log -type f \( -perm /0137 -o ! -user root -o ! -group root \) -print0) +while IFS="^" read -r l_fname l_mode l_user l_uid l_group l_gid; do +l_bname="$(basename "$l_fname")" +case "$l_bname" in +lastlog | lastlog.* | wtmp | wtmp.* | wtmp-* | btmp | btmp.* | btmp-* | README) +perm_mask='0113' +maxperm="$( printf '%o' $(( 0777 & ~$perm_mask)) )" +l_auser="root" +l_agroup="(root|utmp)" +file_test_chk +;; +secure | auth.log | syslog | messages) +perm_mask='0137' +maxperm="$( printf '%o' $(( 0777 & ~$perm_mask)) )" +l_auser="(root|syslog)" +l_agroup="(root|adm)" +file_test_chk +;; +SSSD | sssd) +perm_mask='0117' +maxperm="$( printf '%o' $(( 0777 & ~$perm_mask)) )" +l_auser="(root|SSSD)" +l_agroup="(root|SSSD)" +file_test_chk +;; +gdm | gdm3) +perm_mask='0117' +maxperm="$( printf '%o' $(( 0777 & ~$perm_mask)) )" +l_auser="root" +l_agroup="(root|gdm|gdm3)" +file_test_chk +;; +*.journal | *.journal~) +perm_mask='0137' +maxperm="$( printf '%o' $(( 0777 & ~$perm_mask)) )" +l_auser="root" +l_agroup="(root|systemd-journal)" +file_test_chk +;; +*) +perm_mask='0137' +maxperm="$( printf '%o' $(( 0777 & ~$perm_mask)) )" +l_auser="(root|syslog)" +l_agroup="(root|adm)" +if [ "$l_uid" -lt "$l_uidmin" ] && [ -z "$(awk -v grp="$l_group" -F: '$1==grp {print $4}' +/etc/group)" ]; then +if [[ ! "$l_user" =~ $l_auser ]]; then +l_auser="(root|syslog|$l_user)" +fi +if [[ ! "$l_group" =~ $l_agroup ]]; then +l_tst="" +while l_out3="" read -r l_duid; do +[ "$l_duid" -ge "$l_uidmin" ] && l_tst=failed +done <<< "$(awk -F: '$4=='"$l_gid"' {print $3}' /etc/passwd)" +[ "$l_tst" != "failed" ] && l_agroup="(root|adm|$l_group)" +fi +fi +file_test_chk +;; +esac +done <<< "$(printf '%s\n' "${a_file[@]}")" +unset a_file # clear array +# if all files passed, then we pass +if [ -z "$l_output2" ]; then +echo -e "\n- Audit Results:\n ** Pass **\n- All files in \"/var/log/\" have appropriate permissions and +ownership\n" +exit $PASS +else +# print the reason why we are failing +echo -e "\n- Audit Results:\n ** Fail **\n$l_output2" +exit $FAIL +fi diff --git a/stig/audits/mcstrans_not_installed.sh b/stig/audits/mcstrans_not_installed.sh new file mode 100644 index 00000000..89c086f9 --- /dev/null +++ b/stig/audits/mcstrans_not_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_not_installed 'mcstrans'; then + exit $PASS +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/message_access_server_unused.sh b/stig/audits/message_access_server_unused.sh new file mode 100644 index 00000000..edf7a243 --- /dev/null +++ b/stig/audits/message_access_server_unused.sh @@ -0,0 +1,5 @@ +source /tmp/lib.sh + +service_unused 'dovecot' dovecot.socket dovecot.service || exit $FAIL +service_unused 'cyrus-imapd' cyrus-imapd.service || exit $FAIL +exit $PASS diff --git a/stig/audits/minimum_password_days_configured.sh b/stig/audits/minimum_password_days_configured.sh new file mode 100644 index 00000000..4d07fe4a --- /dev/null +++ b/stig/audits/minimum_password_days_configured.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +pass_min_days="$(grep -Pi -- '^\h*PASS_MIN_DAYS\h+\d+\b' /etc/login.defs 2>/dev/null | awk '{print $2}' | tail -n 1)" +[ -n "$pass_min_days" ] || exit $FAIL +[ "$pass_min_days" -ge 1 ] 2>/dev/null || exit $FAIL + +awk -F: '($2~/^\$.+\$/) {if($4 < 1) exit 1}' /etc/shadow 2>/dev/null || exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/motd_configured.sh b/stig/audits/motd_configured.sh new file mode 100644 index 00000000..31ee7164 --- /dev/null +++ b/stig/audits/motd_configured.sh @@ -0,0 +1,22 @@ +source /tmp/lib.sh + +l_output2="" +a_files=() + +os_id="$(grep '^ID=' /etc/os-release 2>/dev/null | cut -d= -f2 | sed -e 's/"//g')" + +for l_file in /etc/motd /etc/motd.d/*; do + [ -e "$l_file" ] || continue + + if grep -Psqi -- "(\\\v|\\\r|\\\m|\\\s|\b${os_id}\b)" "$l_file"; then + l_output2="$l_output2\n - File: \"$l_file\" includes system information" + else + a_files+=("$l_file") + fi +done + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/motd_permissions_configured.sh b/stig/audits/motd_permissions_configured.sh new file mode 100644 index 00000000..8bbeae0b --- /dev/null +++ b/stig/audits/motd_permissions_configured.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +[ ! -e /etc/motd ] && exit $PASS +check_file_root_perms /etc/motd 644 && exit $PASS || exit $FAIL diff --git a/stig/audits/mta_local_only_mode.sh b/stig/audits/mta_local_only_mode.sh new file mode 100644 index 00000000..5bb6dbbd --- /dev/null +++ b/stig/audits/mta_local_only_mode.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if [ "$(postconf -n inet_interfaces 2>/dev/null)" != "inet_interfaces = all" ] && ! ss -plntu | grep -P -- ':(25|465|587)\b' | grep -Pvq -- '\h+(127\.0\.0\.1|\[?::1\]?):(25|465|587)\b'; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/nfs_server_unused.sh b/stig/audits/nfs_server_unused.sh new file mode 100644 index 00000000..af57c761 --- /dev/null +++ b/stig/audits/nfs_server_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'nfs-utils' nfs-server.service && exit $PASS || exit $FAIL diff --git a/stig/audits/nftables_base_chains_exist.sh b/stig/audits/nftables_base_chains_exist.sh new file mode 100644 index 00000000..f256a001 --- /dev/null +++ b/stig/audits/nftables_base_chains_exist.sh @@ -0,0 +1,51 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_hbfw="" + +# Determine which firewall utility is in use (same convention as prior CIS scripts) +if systemctl is-enabled firewalld.service 2>/dev/null | grep -q 'enabled'; then + l_hbfw="fwd" +elif systemctl is-enabled nftables.service 2>/dev/null | grep -q 'enabled'; then + l_hbfw="nft" +fi + +# Firewalld note: base chains are installed by default +if [ "$l_hbfw" = "fwd" ]; then + echo -e "\n- Audit Result:\n *** PASS ***\n - FirewallD is in use (base chains installed by default)\n" + exit "$PASS" +fi + +# If nftables isn't in use, this audit can't be satisfied as written +if [ "$l_hbfw" != "nft" ]; then + echo -e "\n- Audit Result:\n *** FAIL ***\n - Neither FirewallD nor NFTables appears to be enabled\n - Ensure a single firewall configuration utility is in use\n" + exit "$FAIL" +fi + +# NFTables audit: verify base chains exist for INPUT/FORWARD/OUTPUT hooks +l_ruleset="$(nft list ruleset 2>/dev/null)" + +if grep -q 'hook input' <<< "$l_ruleset" && grep -q 'type filter hook input' <<< "$l_ruleset"; then + l_output="$l_output\n - INPUT base chain exists (type filter hook input)" +else + l_output2="$l_output2\n - Missing INPUT base chain for filter hook input" +fi + +if grep -q 'hook forward' <<< "$l_ruleset" && grep -q 'type filter hook forward' <<< "$l_ruleset"; then + l_output="$l_output\n - FORWARD base chain exists (type filter hook forward)" +else + l_output2="$l_output2\n - Missing FORWARD base chain for filter hook forward" +fi + +if grep -q 'hook output' <<< "$l_ruleset" && grep -q 'type filter hook output' <<< "$l_ruleset"; then + l_output="$l_output\n - OUTPUT base chain exists (type filter hook output)" +else + l_output2="$l_output2\n - Missing OUTPUT base chain for filter hook output" +fi + +if [ -z "$l_output2" ]; then + echo -e "\n- Audit Result:\n *** PASS ***\n$l_output\n" + exit "$PASS" +else + echo -e "\n- Audit Result:\n *** FAIL ***\n$l_output2\n\n - Correctly set:\n$l_output\n" + exit "$FAIL" +fi diff --git a/stig/audits/nftables_deny_firewall_policy.sh b/stig/audits/nftables_deny_firewall_policy.sh new file mode 100644 index 00000000..b39a545b --- /dev/null +++ b/stig/audits/nftables_deny_firewall_policy.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if systemctl --quiet is-enabled nftables.service && ! nft list ruleset | grep 'hook input' | grep -v 'policy drop' | grep -q . && ! nft list ruleset | grep 'hook forward' | grep -v 'policy drop' | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/nftables_established_connections_configured.sh b/stig/audits/nftables_established_connections_configured.sh new file mode 100644 index 00000000..21cf353e --- /dev/null +++ b/stig/audits/nftables_established_connections_configured.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if nft list ruleset | grep -Pq 'ct state (established|related).*(accept|counter accept)'; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/nftables_installed.sh b/stig/audits/nftables_installed.sh new file mode 100644 index 00000000..dfcb07dd --- /dev/null +++ b/stig/audits/nftables_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_installed 'nftables'; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/nftables_loopback_traffic.sh b/stig/audits/nftables_loopback_traffic.sh new file mode 100644 index 00000000..fa18858d --- /dev/null +++ b/stig/audits/nftables_loopback_traffic.sh @@ -0,0 +1,16 @@ +source /tmp/lib.sh + +if systemctl is-enabled firewalld.service 2>/dev/null | grep -q 'enabled'; then exit $PASS; fi +if ! systemctl is-enabled nftables.service 2>/dev/null | grep -q 'enabled'; then exit $FAIL; fi + +if ! nft list ruleset 2>/dev/null | awk '/hook\s+input\s+/,/\}\s*(#.*)?$/' | grep -Pq -- '\H+\h+"lo"\h+accept'; then exit $FAIL; fi + +l_ipsaddr="$(nft list ruleset 2>/dev/null | awk '/filter_IN_public_deny|hook\s+input\s+/,/\}\s*(#.*)?$/' | grep -P -- 'ip\h+saddr')" +if ! ( grep -Pq -- 'ip\h+saddr\h+127\.0\.0\.0\/8\h+(counter\h+packets\h+\d+\h+bytes\h+\d+\h+)?drop' <<< "$l_ipsaddr" || grep -Pq -- 'ip\h+daddr\h+\!\=\h+127\.0\.0\.1\h+ip\h+saddr\h+127\.0\.0\.1\h+drop' <<< "$l_ipsaddr" ); then exit $FAIL; fi + +if grep -Pq -- '^\h*0\h*$' /sys/module/ipv6/parameters/disable; then + l_ip6saddr="$(nft list ruleset 2>/dev/null | awk '/filter_IN_public_deny|hook input/,/}/' | grep 'ip6 saddr')" + if ! ( grep -Pq 'ip6\h+saddr\h+::1\h+(counter\h+packets\h+\d+\h+bytes\h+\d+\h+)?drop' <<< "$l_ip6saddr" || grep -Pq -- 'ip6\h+daddr\h+\!=\h+::1\h+ip6\h+saddr\h+::1\h+drop' <<< "$l_ip6saddr" ); then exit $FAIL; fi +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/nis_client_not_installed.sh b/stig/audits/nis_client_not_installed.sh new file mode 100644 index 00000000..36f1efd2 --- /dev/null +++ b/stig/audits/nis_client_not_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_not_installed 'ypbind'; then + exit $PASS +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/nis_server_unused.sh b/stig/audits/nis_server_unused.sh new file mode 100644 index 00000000..a5ed6497 --- /dev/null +++ b/stig/audits/nis_server_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'ypserv' ypserv.service && exit $PASS || exit $FAIL diff --git a/stig/audits/no_duplicate_gids_exist.sh b/stig/audits/no_duplicate_gids_exist.sh new file mode 100644 index 00000000..d08e56dc --- /dev/null +++ b/stig/audits/no_duplicate_gids_exist.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! cut -f3 -d: /etc/group | sort -n | uniq -c | awk '($1 > 1) {print}' | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/no_duplicate_group_names_exist.sh b/stig/audits/no_duplicate_group_names_exist.sh new file mode 100644 index 00000000..5ddba66b --- /dev/null +++ b/stig/audits/no_duplicate_group_names_exist.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! cut -d: -f3 /etc/group | sort -n | uniq -d | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/no_duplicate_uids_exist.sh b/stig/audits/no_duplicate_uids_exist.sh new file mode 100644 index 00000000..66d34620 --- /dev/null +++ b/stig/audits/no_duplicate_uids_exist.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! cut -f3 -d: /etc/passwd | sort -n | uniq -c | awk '($1 > 1) {print}' | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/no_duplicate_user_names_exist.sh b/stig/audits/no_duplicate_user_names_exist.sh new file mode 100644 index 00000000..3a7c3730 --- /dev/null +++ b/stig/audits/no_duplicate_user_names_exist.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! cut -f1 -d: /etc/passwd | sort | uniq -d | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/no_unconfined_services.sh b/stig/audits/no_unconfined_services.sh new file mode 100644 index 00000000..a958394d --- /dev/null +++ b/stig/audits/no_unconfined_services.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if ! ps -eZ | grep -q 'unconfined_service_t'; then + exit $FAIL +fi +exit $PASS \ No newline at end of file diff --git a/stig/audits/no_unowned_or_ungrouped_files_exist.sh b/stig/audits/no_unowned_or_ungrouped_files_exist.sh new file mode 100644 index 00000000..4c9affa4 --- /dev/null +++ b/stig/audits/no_unowned_or_ungrouped_files_exist.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! find / -xdev \( -path /proc -o -path /proc/\* -o -path /sys -o -path /sys/\* -o -path /run/user -o -path /run/user/\* \) -prune -o \( -nouser -o -nogroup \) -print -quit 2>/dev/null | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/nologin_not_listed_in_shells.sh b/stig/audits/nologin_not_listed_in_shells.sh new file mode 100644 index 00000000..0011e3e2 --- /dev/null +++ b/stig/audits/nologin_not_listed_in_shells.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if grep -Psq '^\h*([^#\n\r]+)?\/nologin\b' /etc/shells; then exit $FAIL; fi +exit $PASS \ No newline at end of file diff --git a/stig/audits/package_manager_repos_configured.sh b/stig/audits/package_manager_repos_configured.sh new file mode 100644 index 00000000..9c07b55e --- /dev/null +++ b/stig/audits/package_manager_repos_configured.sh @@ -0,0 +1,13 @@ +source /tmp/lib.sh + +# global config must set repo_gpgcheck=1 +if ! grep -Piq -- '^\h*repo_gpgcheck\h*=\h*1\b' /etc/dnf/dnf.conf; then + exit $FAIL +fi + +# ensure no repo_gpgcheck=0 in repos +if grep -Prs -- '^\h*repo_gpgcheck\h*=\h*0\b' /etc/yum.repos.d/ >/dev/null 2>&1; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/packet_redirect_sending_disabled.sh b/stig/audits/packet_redirect_sending_disabled.sh new file mode 100644 index 00000000..05a4b123 --- /dev/null +++ b/stig/audits/packet_redirect_sending_disabled.sh @@ -0,0 +1,76 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_ipv6_disabled="" +a_parlist=("net.ipv4.conf.all.send_redirects=0" "net.ipv4.conf.default.send_redirects=0") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +f_ipv6_chk() +{ + l_ipv6_disabled="" + ! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_ipv6_disabled="yes" + if sysctl net.ipv6.conf.all.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_ipv6_disabled="yes" + fi + [ -z "$l_ipv6_disabled" ] && l_ipv6_disabled="no" +} + +f_kernel_parameter_chk() +{ + l_krp="$(sysctl "$l_kpname" | awk -F= '{print $2}' | xargs)" + if [ "$l_krp" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_krp\" in the running configuration" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_krp\" in the running configuration and should have a value of: \"$l_kpvalue\"" + fi + unset A_out; declare -A A_out + while read -r l_out; do + if [ -n "$l_out" ]; then + if [[ $l_out =~ ^\s*# ]]; then + l_file="${l_out//# /}" + else + l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") + fi + fi + done < <(/usr/lib/systemd/systemd-sysctl --cat-config | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + if [ -n "$l_ufwscf" ]; then + l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" | xargs)" + l_kpar="${l_kpar//\//.}" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") + fi + if (( ${#A_out[@]} > 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/pam_faillock_enabled.sh b/stig/audits/pam_faillock_enabled.sh new file mode 100644 index 00000000..b4240bd3 --- /dev/null +++ b/stig/audits/pam_faillock_enabled.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +for f in /etc/pam.d/system-auth /etc/pam.d/password-auth; do + grep -Pq '\bpam_faillock\.so\b' "$f" 2>/dev/null || exit $FAIL +done + +exit $PASS \ No newline at end of file diff --git a/stig/audits/pam_pwhistory_enabled.sh b/stig/audits/pam_pwhistory_enabled.sh new file mode 100644 index 00000000..0d195b94 --- /dev/null +++ b/stig/audits/pam_pwhistory_enabled.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +re='^\h*password\h+required\h+pam_pwhistory\.so\b([^#\n\r]+\h+)?use_authtok\b' + +for f in /etc/pam.d/password-auth /etc/pam.d/system-auth; do + grep -Pq -- "$re" "$f" 2>/dev/null || exit $FAIL +done + +exit $PASS \ No newline at end of file diff --git a/stig/audits/pam_pwhistory_includes_use_authtok.sh b/stig/audits/pam_pwhistory_includes_use_authtok.sh new file mode 100644 index 00000000..de81d83c --- /dev/null +++ b/stig/audits/pam_pwhistory_includes_use_authtok.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +grep -P -- '^\h*password\h+([^#\n\r]+)\h+pam_pwhistory\.so\h+([^#\n\r]+\h+)?use_authtok\b' \ + /etc/pam.d/password-auth /etc/pam.d/system-auth >/dev/null 2>&1 || exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/pam_pwquality_enabled.sh b/stig/audits/pam_pwquality_enabled.sh new file mode 100644 index 00000000..35dc6132 --- /dev/null +++ b/stig/audits/pam_pwquality_enabled.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +for f in /etc/pam.d/system-auth /etc/pam.d/password-auth; do + grep -Pq '\bpam_pwquality\.so\b' "$f" 2>/dev/null || exit $FAIL +done + +exit $PASS diff --git a/stig/audits/pam_unix_enabled.sh b/stig/audits/pam_unix_enabled.sh new file mode 100644 index 00000000..c1880745 --- /dev/null +++ b/stig/audits/pam_unix_enabled.sh @@ -0,0 +1,22 @@ +source /tmp/lib.sh + +# we expect pam_unix.so to appear at least in these stacks: +# - auth (typically sufficient) +# - account (typically required) +# - password (typically sufficient + sha512 shadow) +# - session (typically required) + + +re_auth='^\h*auth\h+\H+\h+pam_unix\.so\b' +re_acct='^\h*account\h+\H+\h+pam_unix\.so\b' +re_pass='^\h*password\h+\H+\h+pam_unix\.so\b.*\bsha512\b.*\bshadow\b' +re_sess='^\h*session\h+\H+\h+pam_unix\.so\b' + +for f in /etc/pam.d/password-auth /etc/pam.d/system-auth; do + grep -Pq -- "$re_auth" "$f" 2>/dev/null || exit $FAIL + grep -Pq -- "$re_acct" "$f" 2>/dev/null || exit $FAIL + grep -Pq -- "$re_pass" "$f" 2>/dev/null || exit $FAIL + grep -Pq -- "$re_sess" "$f" 2>/dev/null || exit $FAIL +done + +exit $PASS \ No newline at end of file diff --git a/stig/audits/pam_unix_includes_use_authtok.sh b/stig/audits/pam_unix_includes_use_authtok.sh new file mode 100644 index 00000000..bce0414f --- /dev/null +++ b/stig/audits/pam_unix_includes_use_authtok.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +grep -Pq -- '^\h*password\h+([^#\n\r]+)\h+pam_unix\.so\h+([^#\n\r]+\h+)?use_authtok\b' /etc/pam.d/password-auth 2>/dev/null || exit $FAIL +grep -Pq -- '^\h*password\h+([^#\n\r]+)\h+pam_unix\.so\h+([^#\n\r]+\h+)?use_authtok\b' /etc/pam.d/system-auth 2>/dev/null || exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/pam_unix_no_nullok.sh b/stig/audits/pam_unix_no_nullok.sh new file mode 100644 index 00000000..7ddfdaa3 --- /dev/null +++ b/stig/audits/pam_unix_no_nullok.sh @@ -0,0 +1,17 @@ +source /tmp/lib.sh + +files=(/etc/pam.d/password-auth /etc/pam.d/system-auth) + +re='^\h*(auth|account|password|session)\h+(requisite|required|sufficient)\h+pam_unix\.so\b' + +for f in "${files[@]}"; do + [ -f "$f" ] || continue + + while IFS= read -r line; do + if grep -Pq '\bnullok\b' <<<"$line"; then + exit $FAIL + fi + done < <(grep -P -- "$re" "$f" 2>/dev/null) +done + +exit $PASS \ No newline at end of file diff --git a/stig/audits/pam_unix_no_remember.sh b/stig/audits/pam_unix_no_remember.sh new file mode 100644 index 00000000..d2eba40f --- /dev/null +++ b/stig/audits/pam_unix_no_remember.sh @@ -0,0 +1,17 @@ +source /tmp/lib.sh + +files=(/etc/pam.d/password-auth /etc/pam.d/system-auth) + +re='^\h*password\h+([^#\n\r]+\h+)?pam_unix\.so\b' + +for f in "${files[@]}"; do + [ -f "$f" ] || continue + + while IFS= read -r line; do + if grep -Pq '\bremember=[0-9]+\b' <<<"$line"; then + exit $FAIL + fi + done < <(grep -Pi -- "$re" "$f" 2>/dev/null) +done + +exit $PASS \ No newline at end of file diff --git a/stig/audits/pam_unix_strong_hashing_algorithm.sh b/stig/audits/pam_unix_strong_hashing_algorithm.sh new file mode 100644 index 00000000..98aef75b --- /dev/null +++ b/stig/audits/pam_unix_strong_hashing_algorithm.sh @@ -0,0 +1,12 @@ +source /tmp/lib.sh + +files=(/etc/pam.d/password-auth /etc/pam.d/system-auth) + +re='^\h*password\h+([^#\n\r]+)\h+pam_unix\.so\h+([^#\n\r]+\h+)?(sha512|yescrypt)\b' + +for f in "${files[@]}"; do + [ -f "$f" ] || exit $FAIL + grep -Pq -- "$re" "$f" 2>/dev/null || exit $FAIL +done + +exit $PASS \ No newline at end of file diff --git a/stig/audits/passwd_groups_exist_in_group.sh b/stig/audits/passwd_groups_exist_in_group.sh new file mode 100644 index 00000000..3fef2a38 --- /dev/null +++ b/stig/audits/passwd_groups_exist_in_group.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! comm -23 <(awk -F: '{print $4}' /etc/passwd | sort -u) <(awk -F: '{print $3}' /etc/group | sort -u) | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/passwd_use_shadowed_passwords.sh b/stig/audits/passwd_use_shadowed_passwords.sh new file mode 100644 index 00000000..bce31de4 --- /dev/null +++ b/stig/audits/passwd_use_shadowed_passwords.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! awk -F: '($2 != "x" ) { print "User: \"" $1 "\" is not set to shadowed passwords "}' /etc/passwd | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/password_changed_characters_configured.sh b/stig/audits/password_changed_characters_configured.sh new file mode 100644 index 00000000..2091d3a0 --- /dev/null +++ b/stig/audits/password_changed_characters_configured.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +grep -Psi -- '^\h*difok\h*=\h*([2-9]|[1-9][0-9]+)\b' \ + /etc/security/pwquality.conf /etc/security/pwquality.conf.d/*.conf >/dev/null 2>&1 || exit $FAIL + +grep -Psi -- '^\h*password\h+(requisite|required|sufficient)\h+pam_pwquality\.so\h+([^#\n\r]+\h+)?difok\h*=\h*([0-1])\b' \ + /etc/pam.d/system-auth /etc/pam.d/password-auth >/dev/null 2>&1 && exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_complexity_configured.sh b/stig/audits/password_complexity_configured.sh new file mode 100644 index 00000000..cfff7622 --- /dev/null +++ b/stig/audits/password_complexity_configured.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +grep -Psi -- '^\h*(minclass|[dulo]credit)\b' \ + /etc/security/pwquality.conf /etc/security/pwquality.conf.d/*.conf >/dev/null 2>&1 || exit $FAIL + +grep -Psi -- '^\h*password\h+(requisite|required|sufficient)\h+pam_pwquality\.so\h+([^#\n\r]+\h+)?(minclass=[0-3]|[dulo]credit=[^-]\d*)\b' \ + /etc/pam.d/system-auth /etc/pam.d/password-auth >/dev/null 2>&1 && exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_dictionary_check_enabled.sh b/stig/audits/password_dictionary_check_enabled.sh new file mode 100644 index 00000000..6f1f6618 --- /dev/null +++ b/stig/audits/password_dictionary_check_enabled.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +grep -Psi -- '^\h*dictcheck\h*=\h*0\b' \ + /etc/security/pwquality.conf /etc/security/pwquality.conf.d/*.conf >/dev/null 2>&1 && exit $FAIL + +grep -Psi -- '^\h*password\h+(requisite|required|sufficient)\h+pam_pwquality\.so\h+([^#\n\r]+\h+)?dictcheck\h*=\h*0\b' \ + /etc/pam.d/system-auth /etc/pam.d/password-auth >/dev/null 2>&1 && exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_expiration_configured.sh b/stig/audits/password_expiration_configured.sh new file mode 100644 index 00000000..d879e731 --- /dev/null +++ b/stig/audits/password_expiration_configured.sh @@ -0,0 +1,10 @@ +source /tmp/lib.sh + +pass_max_days="$(grep -Pi -- '^\h*PASS_MAX_DAYS\h+\d+\b' /etc/login.defs 2>/dev/null | awk '{print $2}' | tail -n 1)" +[ -n "$pass_max_days" ] || exit $FAIL +[ "$pass_max_days" -ge 1 ] 2>/dev/null || exit $FAIL +[ "$pass_max_days" -le 365 ] 2>/dev/null || exit $FAIL + +awk -F: '($2~/^\$.+\$/) {if($5 > 365 || $5 < 1) exit 1}' /etc/shadow 2>/dev/null || exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_expiration_warning_days.sh b/stig/audits/password_expiration_warning_days.sh new file mode 100644 index 00000000..65ce64cf --- /dev/null +++ b/stig/audits/password_expiration_warning_days.sh @@ -0,0 +1,15 @@ +source /tmp/lib.sh + +pass_warn="$(grep -Pi -- '^\h*PASS_WARN_AGE\h+\d+\b' /etc/login.defs | awk '{print $2}')" + +if [ -z "$pass_warn" ] || [ "$pass_warn" -lt 7 ]; then + exit $FAIL +fi + +non_compliant_users="$(awk -F: '($2~/^\$.+\$/) { if ($6 < 7) print $1 }' /etc/shadow)" + +if [ -n "$non_compliant_users" ]; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_expiration_warning_days_configured.sh b/stig/audits/password_expiration_warning_days_configured.sh new file mode 100644 index 00000000..65ce64cf --- /dev/null +++ b/stig/audits/password_expiration_warning_days_configured.sh @@ -0,0 +1,15 @@ +source /tmp/lib.sh + +pass_warn="$(grep -Pi -- '^\h*PASS_WARN_AGE\h+\d+\b' /etc/login.defs | awk '{print $2}')" + +if [ -z "$pass_warn" ] || [ "$pass_warn" -lt 7 ]; then + exit $FAIL +fi + +non_compliant_users="$(awk -F: '($2~/^\$.+\$/) { if ($6 < 7) print $1 }' /etc/shadow)" + +if [ -n "$non_compliant_users" ]; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_failed_lockout_configured.sh b/stig/audits/password_failed_lockout_configured.sh new file mode 100644 index 00000000..0c402403 --- /dev/null +++ b/stig/audits/password_failed_lockout_configured.sh @@ -0,0 +1,8 @@ +source /tmp/lib.sh + +grep -Pi -- '^\h*deny\h*=\h*[1-5]\b' /etc/security/faillock.conf >/dev/null 2>&1 || exit $FAIL + +grep -Pi -- '^\h*auth\h+(requisite|required|sufficient)\h+pam_faillock\.so\h+([^#\n\r]+\h+)?deny\h*=\h*(0|[6-9]|[1-9][0-9]+)\b' \ + /etc/pam.d/system-auth /etc/pam.d/password-auth >/dev/null 2>&1 && exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_history_enforced_for_root.sh b/stig/audits/password_history_enforced_for_root.sh new file mode 100644 index 00000000..3c028ee6 --- /dev/null +++ b/stig/audits/password_history_enforced_for_root.sh @@ -0,0 +1,5 @@ +source /tmp/lib.sh + +grep -Pi -- '^\h*enforce_for_root\b' /etc/security/pwhistory.conf >/dev/null 2>&1 || exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_history_remember_configured.sh b/stig/audits/password_history_remember_configured.sh new file mode 100644 index 00000000..e0ccc08e --- /dev/null +++ b/stig/audits/password_history_remember_configured.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +grep -Pi -- '^\h*remember\h*=\h*(2[4-9]|[3-9][0-9]|[1-9][0-9]{2,})\b' \ + /etc/security/pwhistory.conf >/dev/null 2>&1 || exit $FAIL + +grep -Pi -- '^\h*password\h+(requisite|required|sufficient)\h+pam_pwhistory\.so\h+([^#\n\r]+\h+)?remember=(2[0-3]|1[0-9]|[0-9])\b' \ + /etc/pam.d/system-auth /etc/pam.d/password-auth >/dev/null 2>&1 && exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_length_configured.sh b/stig/audits/password_length_configured.sh new file mode 100644 index 00000000..290de9ca --- /dev/null +++ b/stig/audits/password_length_configured.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +grep -Psi -- '^\h*minlen\h*=\h*(1[4-9]|[2-9][0-9]|[1-9][0-9]{2,})\b' \ + /etc/security/pwquality.conf /etc/security/pwquality.conf.d/*.conf >/dev/null 2>&1 || exit $FAIL + +grep -Psi -- '^\h*password\h+(requisite|required|sufficient)\h+pam_pwquality\.so\h+([^#\n\r]+\h+)?minlen\h*=\h*([0-9]|1[0-3])\b' \ + /etc/pam.d/system-auth /etc/pam.d/password-auth >/dev/null 2>&1 && exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_lockout_includes_root.sh b/stig/audits/password_lockout_includes_root.sh new file mode 100644 index 00000000..c299e02c --- /dev/null +++ b/stig/audits/password_lockout_includes_root.sh @@ -0,0 +1,19 @@ +source /tmp/lib.sh + +conf="/etc/security/faillock.conf" + +if ! grep -Pi -- '^\h*(even_deny_root|root_unlock_time\h*=\h*\d+)\b' "$conf" >/dev/null 2>&1; then + exit $FAIL +fi + +rut="$(grep -Pi -- '^\h*root_unlock_time\h*=\h*\d+\b' "$conf" 2>/dev/null | head -n1 | grep -oP '\d+')" +if [ -n "$rut" ]; then + [ "$rut" -ge 60 ] || exit $FAIL +fi + +grep -Pi -- '^\h*root_unlock_time\h*=\h*([1-9]|[1-5][0-9])\b' "$conf" >/dev/null 2>&1 && exit $FAIL + +grep -Pi -- '^\h*auth\h+([^#\n\r]+\h+)pam_faillock\.so\h+([^#\n\r]+\h+)?root_unlock_time\h*=\h*([1-9]|[1-5][0-9])\b' \ + /etc/pam.d/system-auth /etc/pam.d/password-auth >/dev/null 2>&1 && exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_max_sequential_chars_configured.sh b/stig/audits/password_max_sequential_chars_configured.sh new file mode 100644 index 00000000..eb616bd1 --- /dev/null +++ b/stig/audits/password_max_sequential_chars_configured.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +grep -Psi -- '^\h*maxsequence\h*=\h*[1-3]\b' \ + /etc/security/pwquality.conf /etc/security/pwquality.conf.d/*.conf >/dev/null 2>&1 || exit $FAIL + +grep -Psi -- '^\h*password\h+(requisite|required|sufficient)\h+pam_pwquality\.so\h+([^#\n\r]+\h+)?maxsequence\h*=\h*(0|[4-9]|[1-9][0-9]+)\b' \ + /etc/pam.d/system-auth /etc/pam.d/password-auth >/dev/null 2>&1 && exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_quality_enforced_for_root.sh b/stig/audits/password_quality_enforced_for_root.sh new file mode 100644 index 00000000..e51fbdc6 --- /dev/null +++ b/stig/audits/password_quality_enforced_for_root.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +grep -Psi -- '^\h*enforce_for_root\b' \ + /etc/security/pwquality.conf /etc/security/pwquality.conf.d/*.conf >/dev/null 2>&1 || exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_same_consecutive_chars_configured.sh b/stig/audits/password_same_consecutive_chars_configured.sh new file mode 100644 index 00000000..e7e914a5 --- /dev/null +++ b/stig/audits/password_same_consecutive_chars_configured.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +grep -Psi -- '^\h*maxrepeat\h*=\h*[1-3]\b' \ + /etc/security/pwquality.conf /etc/security/pwquality.conf.d/*.conf >/dev/null 2>&1 || exit $FAIL + +grep -Psi -- '^\h*password\h+(requisite|required|sufficient)\h+pam_pwquality\.so\h+([^#\n\r]+\h+)?maxrepeat\h*=\h*(0|[4-9]|[1-9][0-9]+)\b' \ + /etc/pam.d/system-auth /etc/pam.d/password-auth >/dev/null 2>&1 && exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/password_unlock_time_configured.sh b/stig/audits/password_unlock_time_configured.sh new file mode 100644 index 00000000..ab0a73af --- /dev/null +++ b/stig/audits/password_unlock_time_configured.sh @@ -0,0 +1,8 @@ +source /tmp/lib.sh + +grep -Pi -- '^\h*unlock_time\h*=\h*(0|9[0-9][0-9]|[1-9][0-9]{3,})\b' /etc/security/faillock.conf >/dev/null 2>&1 || exit $FAIL + +grep -Pi -- '^\h*auth\h+(requisite|required|sufficient)\h+pam_faillock\.so\h+([^#\n\r]+\h+)?unlock_time\h*=\h*([1-9]|[1-9][0-9]|[1-8][0-9][0-9])\b' \ + /etc/pam.d/system-auth /etc/pam.d/password-auth >/dev/null 2>&1 && exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/print_server_unused.sh b/stig/audits/print_server_unused.sh new file mode 100644 index 00000000..3ad3f713 --- /dev/null +++ b/stig/audits/print_server_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'cups' cups.socket cups.service && exit $PASS || exit $FAIL diff --git a/stig/audits/ptrace_scope_restricted.sh b/stig/audits/ptrace_scope_restricted.sh new file mode 100644 index 00000000..44ae0c77 --- /dev/null +++ b/stig/audits/ptrace_scope_restricted.sh @@ -0,0 +1,49 @@ +source /tmp/lib.sh + +l_output="" l_output2="" +a_parlist=("kernel.yama.ptrace_scope=1") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +kernel_parameter_chk() +{ +l_krp="$(sysctl "$l_kpname" 2>/dev/null | awk -F= '{print $2}' | xargs)" # Check running configuration +if [ "$l_krp" != "$l_kpvalue" ]; then + exit $FAIL +fi + +unset A_out; declare -A A_out # Check durable setting (files) +while read -r l_out; do +if [ -n "$l_out" ]; then +if [[ $l_out =~ ^\s*# ]]; then +l_file="${l_out//# /}" +else +l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" +[ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") +fi +fi +done < <(/usr/lib/systemd/systemd-sysctl --cat-config 2>/dev/null | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + +if [ -n "$l_ufwscf" ] && [ -f "$l_ufwscf" ]; then # Account for systems with UFW (Not covered by systemd-sysctl --cat-config) +l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" 2>/dev/null | xargs)" +l_kpar="${l_kpar//\//.}" +[ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") +fi + +if (( ${#A_out[@]} > 0 )); then +while IFS="=" read -r l_fkpname l_fkpvalue; do +l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" +if [ "$l_fkpvalue" != "$l_kpvalue" ]; then + exit $FAIL +fi +done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") +else +exit $FAIL +fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do +l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" +kernel_parameter_chk +done < <(printf '%s\n' "${a_parlist[@]}") + +exit $PASS \ No newline at end of file diff --git a/stig/audits/rds_kernel_module_unavailable.sh b/stig/audits/rds_kernel_module_unavailable.sh new file mode 100644 index 00000000..78b53196 --- /dev/null +++ b/stig/audits/rds_kernel_module_unavailable.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "rds" "net" && exit $PASS || exit $FAIL diff --git a/stig/audits/remote_login_banner_configured.sh b/stig/audits/remote_login_banner_configured.sh new file mode 100644 index 00000000..a8bed1b9 --- /dev/null +++ b/stig/audits/remote_login_banner_configured.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_login_banner /etc/issue.net && exit $PASS || exit $FAIL diff --git a/stig/audits/repo_gpgcheck_globally_activated.sh b/stig/audits/repo_gpgcheck_globally_activated.sh new file mode 100644 index 00000000..1f217421 --- /dev/null +++ b/stig/audits/repo_gpgcheck_globally_activated.sh @@ -0,0 +1,12 @@ +source /tmp/lib.sh + +if ! grep -Piq -- '^\h*repo_gpgcheck\h*=\h*1\b' /etc/dnf/dnf.conf; then + exit $FAIL +fi + +# no repo_gpgcheck=0 overrides in repos +for repo in $(grep -l "repo_gpgcheck=0" /etc/yum.repos.d/* 2>/dev/null); do + exit $FAIL +done + +exit $PASS diff --git a/stig/audits/reverse_path_filtering_enabled.sh b/stig/audits/reverse_path_filtering_enabled.sh new file mode 100644 index 00000000..a422b442 --- /dev/null +++ b/stig/audits/reverse_path_filtering_enabled.sh @@ -0,0 +1,76 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_ipv6_disabled="" +a_parlist=("net.ipv4.conf.all.rp_filter=1" "net.ipv4.conf.default.rp_filter=1") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +f_ipv6_chk() +{ + l_ipv6_disabled="" + ! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_ipv6_disabled="yes" + if sysctl net.ipv6.conf.all.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_ipv6_disabled="yes" + fi + [ -z "$l_ipv6_disabled" ] && l_ipv6_disabled="no" +} + +f_kernel_parameter_chk() +{ + l_krp="$(sysctl "$l_kpname" | awk -F= '{print $2}' | xargs)" + if [ "$l_krp" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_krp\" in the running configuration" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_krp\" in the running configuration and should have a value of: \"$l_kpvalue\"" + fi + unset A_out; declare -A A_out + while read -r l_out; do + if [ -n "$l_out" ]; then + if [[ $l_out =~ ^\s*# ]]; then + l_file="${l_out//# /}" + else + l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") + fi + fi + done < <(/usr/lib/systemd/systemd-sysctl --cat-config | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + if [ -n "$l_ufwscf" ]; then + l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" | xargs)" + l_kpar="${l_kpar//\//.}" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") + fi + if (( ${#A_out[@]} > 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/root_account_access_controlled.sh b/stig/audits/root_account_access_controlled.sh new file mode 100644 index 00000000..0f8c4453 --- /dev/null +++ b/stig/audits/root_account_access_controlled.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +root_status="$(passwd -S root 2>/dev/null | awk '{print $2}')" + +if [ "$root_status" = "P" ] || [ "$root_status" = "L" ]; then + exit $PASS +fi + +exit $FAIL \ No newline at end of file diff --git a/stig/audits/root_only_gid_0.sh b/stig/audits/root_only_gid_0.sh new file mode 100644 index 00000000..fe1d3a49 --- /dev/null +++ b/stig/audits/root_only_gid_0.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +invalid_gid0_users="$(awk -F: '($1 !~ /^(sync|shutdown|halt|operator)$/ && $4=="0") {print $1}' /etc/passwd)" + +if [ "$invalid_gid0_users" != "root" ]; then + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/root_only_uid_0.sh b/stig/audits/root_only_uid_0.sh new file mode 100644 index 00000000..d17cd843 --- /dev/null +++ b/stig/audits/root_only_uid_0.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +non_root_uid0="$(awk -F: '($3 == 0 && $1 != "root") { print $1 }' /etc/passwd)" + +if [ -n "$non_root_uid0" ]; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/root_path_integrity.sh b/stig/audits/root_path_integrity.sh new file mode 100644 index 00000000..5ac3c902 --- /dev/null +++ b/stig/audits/root_path_integrity.sh @@ -0,0 +1,29 @@ +source /tmp/lib.sh + +l_output2="" +l_pmask="0022" +l_maxperm="$( printf '%o' $(( 0777 & ~$l_pmask )) )" +l_root_path="$(sudo -Hiu root env | grep '^PATH' | cut -d= -f2)" + +unset a_path_loc && IFS=":" read -ra a_path_loc <<< "$l_root_path" + +grep -q "::" <<< "$l_root_path" && l_output2="fail" +grep -Pq ":\h*$" <<< "$l_root_path" && l_output2="fail" +grep -Pq '(\h+|:)\.(:|\h*$)' <<< "$l_root_path" && l_output2="fail" + +while read -r l_path; do + if [ -d "$l_path" ]; then + while read -r l_fmode l_fown; do + [ "$l_fown" != "root" ] && l_output2="fail" + [ $(( l_fmode & l_pmask )) -gt 0 ] && l_output2="fail" + done <<< "$(stat -Lc '%#a %U' "$l_path" 2>/dev/null)" + else + l_output2="fail" + fi +done <<< "$(printf "%s\n" "${a_path_loc[@]}")" + +if [ -z "$l_output2" ]; then + exit $PASS +fi + +exit $FAIL \ No newline at end of file diff --git a/stig/audits/root_umask_configured.sh b/stig/audits/root_umask_configured.sh new file mode 100644 index 00000000..db6c61ae --- /dev/null +++ b/stig/audits/root_umask_configured.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +umask_hits="$(grep -Psi -- '^\h*umask\h+(([0-7][0-7][01][0-7]\b|[0-7][0-7][0-7][0-6]\b)|([0-7][01][0-7]\b|[0-7][0-7][0-6]\b)|(u=[rwx]{1,3},)?(((g=[rx]?[rx]?w[rx]?[rx]?\b)(,o=[rwx]{1,3})?)|((g=[wrx]{1,3},)?o=[wrx]{1,3}\b)))' /root/.bash_profile /root/.bashrc 2>/dev/null)" + +if [ -n "$umask_hits" ]; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/rpcbind_unused.sh b/stig/audits/rpcbind_unused.sh new file mode 100644 index 00000000..1f9a6eb3 --- /dev/null +++ b/stig/audits/rpcbind_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'rpcbind' rpcbind.socket rpcbind.service && exit $PASS || exit $FAIL diff --git a/stig/audits/rsync_unused.sh b/stig/audits/rsync_unused.sh new file mode 100644 index 00000000..b1181725 --- /dev/null +++ b/stig/audits/rsync_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'rsync-daemon' rsyncd.socket rsyncd.service && exit $PASS || exit $FAIL diff --git a/stig/audits/rsyslog_enabled.sh b/stig/audits/rsyslog_enabled.sh new file mode 100644 index 00000000..4934cfd8 --- /dev/null +++ b/stig/audits/rsyslog_enabled.sh @@ -0,0 +1,14 @@ +source /tmp/lib.sh + +if is_active 'systemd-journald'; then + exit $PASS +fi + +if is_active 'rsyslog'; then + if is_enabled 'rsyslog'; then + exit $PASS + fi + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/rsyslog_installed.sh b/stig/audits/rsyslog_installed.sh new file mode 100644 index 00000000..9388a112 --- /dev/null +++ b/stig/audits/rsyslog_installed.sh @@ -0,0 +1,12 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet systemd-journald; then + exit $PASS +fi + +if systemctl is-active --quiet rsyslog; then + if is_installed 'rsyslog'; then exit $PASS; fi + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/rsyslog_logging.sh b/stig/audits/rsyslog_logging.sh new file mode 100644 index 00000000..8e539945 --- /dev/null +++ b/stig/audits/rsyslog_logging.sh @@ -0,0 +1,14 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet systemd-journald; then + exit $PASS +fi + +if systemctl is-active --quiet rsyslog; then + if [ -e /var/log/maillog ]; then + exit $PASS + fi + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/rsyslog_logrotate.sh b/stig/audits/rsyslog_logrotate.sh new file mode 100644 index 00000000..f0fb485c --- /dev/null +++ b/stig/audits/rsyslog_logrotate.sh @@ -0,0 +1,25 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet systemd-journald; then + exit $PASS +fi + +if systemctl is-active --quiet rsyslog; then + l_output="" l_rotate_conf="" + if [ -f /etc/logrotate.conf ]; then + l_rotate_conf="/etc/logrotate.conf" + elif compgen -G "/etc/logrotate.d/*.conf" >/dev/null 2>&1; then + for file in /etc/logrotate.d/*.conf; do + l_rotate_conf="$file" + done + else + l_output="$l_output\n- rsyslog is in use and logrotate is not configured" + fi + + if [ -z "$l_output" ]; then + exit $PASS + fi + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/rsyslog_mode.sh b/stig/audits/rsyslog_mode.sh new file mode 100644 index 00000000..5c484e43 --- /dev/null +++ b/stig/audits/rsyslog_mode.sh @@ -0,0 +1,14 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet systemd-journald; then + exit $PASS +fi + +if systemctl is-active --quiet rsyslog; then + if grep -Psq '^\h*\$FileCreateMode\h+0[0,2,4,6][0,2,4]0\b' /etc/rsyslog.conf /etc/rsyslog.d/*.conf 2>/dev/null; then + exit $PASS + fi + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/rsyslog_no_receive.sh b/stig/audits/rsyslog_no_receive.sh new file mode 100644 index 00000000..f74e7ce6 --- /dev/null +++ b/stig/audits/rsyslog_no_receive.sh @@ -0,0 +1,25 @@ +source /tmp/lib.sh + +# 6.2.3.7 rsyslog not configured to receive logs (no imtcp) (only if rsyslog is used) +if systemctl is-active --quiet systemd-journald; then + exit $PASS +fi + +if systemctl is-active --quiet rsyslog; then + if grep -Psi -- '^\h*module\(load=\"?imtcp\"?\)' /etc/rsyslog.conf /etc/rsyslog.d/*.conf 2>/dev/null | grep -q .; then + exit $FAIL + fi + if grep -Psi -- '^\h*input\(type=\"?imtcp\"?\b' /etc/rsyslog.conf /etc/rsyslog.d/*.conf 2>/dev/null | grep -q .; then + exit $FAIL + fi + #obsolete legacy format: + if grep -Psi -- '^\h*\$ModLoad\h+imtcp\b' /etc/rsyslog.conf /etc/rsyslog.d/*.conf 2>/dev/null | grep -q .; then + exit $FAIL + fi + if grep -Psi -- '^\h*\$InputTCPServerRun\b' /etc/rsyslog.conf /etc/rsyslog.d/*.conf 2>/dev/null | grep -q .; then + exit $FAIL + fi + exit $PASS +fi + +exit $PASS diff --git a/stig/audits/rsyslog_remote.sh b/stig/audits/rsyslog_remote.sh new file mode 100644 index 00000000..b16b86e0 --- /dev/null +++ b/stig/audits/rsyslog_remote.sh @@ -0,0 +1,15 @@ +source /tmp/lib.sh + +if systemctl is-active --quiet systemd-journald; then + exit $PASS +fi + +if systemctl is-active --quiet rsyslog; then + if grep -Psq "^*.*[^I][^I]*@" /etc/rsyslog.conf /etc/rsyslog.d/*.conf 2>/dev/null || \ + grep -Psiq -- '^\s*([^#]+\s+)?action\(([^#]+\s+)?\btarget=\"?[^#"]+\"?\b' /etc/rsyslog.conf /etc/rsyslog.d/*.conf 2>/dev/null; then + exit $PASS + fi + exit $FAIL +fi + +exit $PASS diff --git a/stig/audits/samba_unused.sh b/stig/audits/samba_unused.sh new file mode 100644 index 00000000..7d634c61 --- /dev/null +++ b/stig/audits/samba_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'samba' smb.service && exit $PASS || exit $FAIL diff --git a/stig/audits/sctp_kernel_module_unavailable.sh b/stig/audits/sctp_kernel_module_unavailable.sh new file mode 100644 index 00000000..91074eb9 --- /dev/null +++ b/stig/audits/sctp_kernel_module_unavailable.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "sctp" "net" && exit $PASS || exit $FAIL diff --git a/stig/audits/selinux_installed.sh b/stig/audits/selinux_installed.sh new file mode 100644 index 00000000..079272cd --- /dev/null +++ b/stig/audits/selinux_installed.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if is_installed 'libselinux'; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/selinux_mode_enforcing.sh b/stig/audits/selinux_mode_enforcing.sh new file mode 100644 index 00000000..b5e69510 --- /dev/null +++ b/stig/audits/selinux_mode_enforcing.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if grep -Eq '^\s*SELINUX=enforcing\b' /etc/selinux/config && \ + [ "$(getenforce)" = "Enforcing" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/selinux_mode_not_disabled.sh b/stig/audits/selinux_mode_not_disabled.sh new file mode 100644 index 00000000..6d1df9c8 --- /dev/null +++ b/stig/audits/selinux_mode_not_disabled.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if getenforce | grep -Eq '^(Enforcing|Permissive)$' && \ + grep -Eqi '^\s*SELINUX=(enforcing|permissive)\b' /etc/selinux/config; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/selinux_not_disabled_bootloader.sh b/stig/audits/selinux_not_disabled_bootloader.sh new file mode 100644 index 00000000..0a19080e --- /dev/null +++ b/stig/audits/selinux_not_disabled_bootloader.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! grubby --info=ALL | grep -Pq '(selinux|enforcing)=0\b'; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/selinux_policy_configured.sh b/stig/audits/selinux_policy_configured.sh new file mode 100644 index 00000000..624841f9 --- /dev/null +++ b/stig/audits/selinux_policy_configured.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if grep -Eq '^\s*SELINUXTYPE=(targeted|mls)\b' /etc/selinux/config && \ + [ "$(sestatus | grep 'Loaded policy name' | awk -F': ' '{print $2}')" = "$(grep -E '^\s*SELINUXTYPE=' /etc/selinux/config | awk -F'=' '{print $2}')" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/setroubleshoot_not_installed.sh b/stig/audits/setroubleshoot_not_installed.sh new file mode 100644 index 00000000..15604ba9 --- /dev/null +++ b/stig/audits/setroubleshoot_not_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_not_installed 'setroubleshoot'; then + exit $PASS +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/shadow_password_fields_not_empty.sh b/stig/audits/shadow_password_fields_not_empty.sh new file mode 100644 index 00000000..4742d29b --- /dev/null +++ b/stig/audits/shadow_password_fields_not_empty.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! awk -F: '($2 == "" ) { print $1 " does not have a password "}' /etc/shadow | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/single_firewall_config_utility.sh b/stig/audits/single_firewall_config_utility.sh new file mode 100644 index 00000000..72feab49 --- /dev/null +++ b/stig/audits/single_firewall_config_utility.sh @@ -0,0 +1,27 @@ +source /tmp/lib.sh + +l_fwd_status="" +l_nft_status="" +l_fwutil_status="" + +is_installed 'firewalld' && l_fwd_status="$(systemctl is-enabled firewalld.service):$(systemctl is-active firewalld.service)" +is_installed 'nftables' && l_nft_status="$(systemctl is-enabled nftables.service):$(systemctl is-active nftables.service)" + +l_fwutil_status="$l_fwd_status:$l_nft_status" + +case $l_fwutil_status in + enabled:active:masked:inactive|enabled:active:disabled:inactive) + exit $PASS ;; + masked:inactive:enabled:active|disabled:inactive:enabled:active) + exit $PASS ;; + enabled:active:enabled:active) + exit $FAIL ;; + enabled:*:enabled:*) + exit $FAIL ;; + *:active:*:active) + exit $FAIL ;; + :enabled:active) + exit $PASS ;; + *) + exit $FAIL ;; +esac diff --git a/stig/audits/single_logging.sh b/stig/audits/single_logging.sh new file mode 100644 index 00000000..b09e23a9 --- /dev/null +++ b/stig/audits/single_logging.sh @@ -0,0 +1,16 @@ +source /tmp/lib.sh + +# only one logging system in use (rsyslog xor journald) +rs=0 jl=0 +systemctl is-active --quiet rsyslog && rs=1 +systemctl is-active --quiet systemd-journald && jl=1 + +if [ "$rs" -eq 1 ] && [ "$jl" -eq 1 ]; then + exit $FAIL +fi + +if [ "$rs" -eq 1 ] || [ "$jl" -eq 1 ]; then + exit $PASS +fi + +exit $FAIL diff --git a/stig/audits/snmp_unused.sh b/stig/audits/snmp_unused.sh new file mode 100644 index 00000000..3ce188a0 --- /dev/null +++ b/stig/audits/snmp_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'net-snmp' snmpd.service && exit $PASS || exit $FAIL diff --git a/stig/audits/source_routed_packets_not_accepted.sh b/stig/audits/source_routed_packets_not_accepted.sh new file mode 100644 index 00000000..4b35ab47 --- /dev/null +++ b/stig/audits/source_routed_packets_not_accepted.sh @@ -0,0 +1,77 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_ipv6_disabled="" +a_parlist=("net.ipv4.conf.all.accept_source_route=0" "net.ipv4.conf.default.accept_source_route=0" + "net.ipv6.conf.all.accept_source_route=0" "net.ipv6.conf.default.accept_source_route=0") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +f_ipv6_chk() +{ + l_ipv6_disabled="" + ! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_ipv6_disabled="yes" + if sysctl net.ipv6.conf.all.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_ipv6_disabled="yes" + fi + [ -z "$l_ipv6_disabled" ] && l_ipv6_disabled="no" +} + +f_kernel_parameter_chk() +{ + l_krp="$(sysctl "$l_kpname" | awk -F= '{print $2}' | xargs)" + if [ "$l_krp" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_krp\" in the running configuration" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_krp\" in the running configuration and should have a value of: \"$l_kpvalue\"" + fi + unset A_out; declare -A A_out + while read -r l_out; do + if [ -n "$l_out" ]; then + if [[ $l_out =~ ^\s*# ]]; then + l_file="${l_out//# /}" + else + l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") + fi + fi + done < <(/usr/lib/systemd/systemd-sysctl --cat-config | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + if [ -n "$l_ufwscf" ]; then + l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" | xargs)" + l_kpar="${l_kpar//\//.}" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") + fi + if (( ${#A_out[@]} > 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/squashfs_off.sh b/stig/audits/squashfs_off.sh new file mode 100644 index 00000000..3d13710c --- /dev/null +++ b/stig/audits/squashfs_off.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "squashfs" "fs" && exit $PASS || exit $FAIL diff --git a/stig/audits/sshd_access.sh b/stig/audits/sshd_access.sh new file mode 100644 index 00000000..bdee5292 --- /dev/null +++ b/stig/audits/sshd_access.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if grep -Pi '^\h*(AllowUsers|AllowGroups|DenyUsers|DenyGroups)\h+\S+' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/sshd_banner.sh b/stig/audits/sshd_banner.sh new file mode 100644 index 00000000..1cc6f338 --- /dev/null +++ b/stig/audits/sshd_banner.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ "$(sshd -T | awk '/^banner/{print $2}')" != "none" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/sshd_ciphers.sh b/stig/audits/sshd_ciphers.sh new file mode 100644 index 00000000..cd5f8906 --- /dev/null +++ b/stig/audits/sshd_ciphers.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if [ -z "$(sshd -T | grep -Pi -- '^ciphers\h+\"?([^#\n\r]+,)?((3des|blowfish|cast128|aes(128|192|256))-cbc|arcfour(128|256)?|rijndael-cbc@lysator\.liu\.se|chacha20-poly1305@openssh\.com)\b')" ]; then exit $PASS; fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/sshd_client_alive.sh b/stig/audits/sshd_client_alive.sh new file mode 100644 index 00000000..608cd2f9 --- /dev/null +++ b/stig/audits/sshd_client_alive.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ -n "$(sshd -T | grep -Pi -- '(clientaliveinterval|clientalivecountmax)')" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/sshd_config_permissions.sh b/stig/audits/sshd_config_permissions.sh new file mode 100644 index 00000000..f13f5c7a --- /dev/null +++ b/stig/audits/sshd_config_permissions.sh @@ -0,0 +1,55 @@ +source /tmp/lib.sh + +l_output="" l_output2="" + +perm_mask="0177" +maxperm="$(printf '%o' $(( 0777 & ~${perm_mask#0} )))" + +SSHD_FILES_CHK() { +while IFS=: read -r l_mode l_user l_group; do + l_out2="" + + # too permissive (treat as octal) + if [ $(( 8#$l_mode & 8#$perm_mask )) -gt 0 ]; then + l_out2="$l_out2\n - Is mode: \"$l_mode\" should be: \"$maxperm\" or more restrictive" + fi + + if [ "$l_user" != "root" ]; then + l_out2="$l_out2\n - Is owned by: \"$l_user\" should be owned by \"root\"" + fi + + if [ "$l_group" != "root" ]; then + l_out2="$l_out2\n - Is group owned by: \"$l_group\" should be group owned by \"root\"" + fi + + if [ -n "$l_out2" ]; then + l_output2="$l_output2\n - File: \"$l_file\":$l_out2" + else + l_output="$l_output\n - File: \"$l_file\":\n - Correct: mode ($l_mode), owner ($l_user), and group owner ($l_group) configured" + fi +done < <(stat -Lc '%a:%U:%G' "$l_file" 2>/dev/null) +} + +# Check main config +if [ -e "/etc/ssh/sshd_config" ]; then +l_file="/etc/ssh/sshd_config" +SSHD_FILES_CHK +fi + +# Check drop-in configs (only files that are potentially non-compliant to reduce noise) +while IFS= read -r -d $'\0' l_file; do +[ -e "$l_file" ] && SSHD_FILES_CHK +done < <( +find -L /etc/ssh/sshd_config.d -type f \ + \( -perm /077 -o ! -user root -o ! -group root \) \ + -print0 2>/dev/null +) + +if [ -z "$l_output2" ]; then +echo -e "\n- Audit Result:\n ** PASS **\n - * Correctly set * :\n$l_output\n" +exit $PASS +else +echo -e "\n- Audit Result:\n ** FAIL **\n - * Reasons for audit failure * :\n$l_output2\n" +[ -n "$l_output" ] && echo -e " - * Correctly set * :\n$l_output\n" +exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/sshd_disable_forwarding.sh b/stig/audits/sshd_disable_forwarding.sh new file mode 100644 index 00000000..4d3e7b61 --- /dev/null +++ b/stig/audits/sshd_disable_forwarding.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ "$(sshd -T -C user=root -C host="$(hostname)" -C addr="$(grep "$(hostname)" /etc/hosts | awk '{print $1}')" | awk '/^disableforwarding/{print $2}')" = "yes" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/sshd_gssapi_authentication.sh b/stig/audits/sshd_gssapi_authentication.sh new file mode 100644 index 00000000..f60d719c --- /dev/null +++ b/stig/audits/sshd_gssapi_authentication.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if [ "$(sshd -T | awk '/^gssapiauthentication/{print $2}')" = "no" ]; then + exit $PASS +fi + +exit $FAIL diff --git a/stig/audits/sshd_host_based_authentication.sh b/stig/audits/sshd_host_based_authentication.sh new file mode 100644 index 00000000..31e612ad --- /dev/null +++ b/stig/audits/sshd_host_based_authentication.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if sshd -T | grep -Pi '^\h*hostbasedauthentication\h+no\b' >/dev/null; then + exit $PASS +fi + +exit $FAIL diff --git a/stig/audits/sshd_ignore_rhosts.sh b/stig/audits/sshd_ignore_rhosts.sh new file mode 100644 index 00000000..e921812b --- /dev/null +++ b/stig/audits/sshd_ignore_rhosts.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if sshd -T | grep -Pi '^\h*ignoreRhosts\h+yes\b' >/dev/null; then + exit $PASS +fi + +exit $FAIL diff --git a/stig/audits/sshd_kex_algorithms.sh b/stig/audits/sshd_kex_algorithms.sh new file mode 100644 index 00000000..2ed64e5f --- /dev/null +++ b/stig/audits/sshd_kex_algorithms.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if [ -z "$(sshd -T | grep -Pi -- 'kexalgorithms\h+([^#\n\r]+,)?(diffie-hellman-group1-sha1|diffie-hellman-group14-sha1|diffie-hellman-group-exchange-sha1)\b')" ]; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/sshd_log_level.sh b/stig/audits/sshd_log_level.sh new file mode 100644 index 00000000..100bd3a4 --- /dev/null +++ b/stig/audits/sshd_log_level.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if sshd -T | grep -Pi '^\h*loglevel\h+(VERBOSE|INFO)\b' >/dev/null; then + exit $PASS +fi + +exit $FAIL diff --git a/stig/audits/sshd_login_grace_time.sh b/stig/audits/sshd_login_grace_time.sh new file mode 100644 index 00000000..9c9f09e9 --- /dev/null +++ b/stig/audits/sshd_login_grace_time.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ -z "$(sshd -T | awk '$1 ~ /^\s*logingracetime/{if($2 > 60) print $0}')" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/sshd_macs.sh b/stig/audits/sshd_macs.sh new file mode 100644 index 00000000..39008c82 --- /dev/null +++ b/stig/audits/sshd_macs.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if [ -z "$(sshd -T | grep -Pi -- 'macs\h+([^#\n\r]+,)?(hmac-md5|hmac-md5-96|hmac-ripemd160|hmac-sha1-96|umac-64@openssh\.com|hmac-md5-etm@openssh\.com|hmac-md5-96-etm@openssh\.com|hmac-ripemd160-etm@openssh\.com|hmac-sha1-96-etm@openssh\.com|umac-64-etm@openssh\.com|umac-128-etm@openssh\.com)\b')" ]; then exit $PASS; fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/sshd_max_auth_tries.sh b/stig/audits/sshd_max_auth_tries.sh new file mode 100644 index 00000000..797c4a0f --- /dev/null +++ b/stig/audits/sshd_max_auth_tries.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ "$(sshd -T | awk '/^maxauthtries/{print $2}')" -le 4 ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/sshd_max_sessions.sh b/stig/audits/sshd_max_sessions.sh new file mode 100644 index 00000000..9b9cd3b2 --- /dev/null +++ b/stig/audits/sshd_max_sessions.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ "$(sshd -T | awk '/^maxsessions/{print $2}')" -le 10 ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/sshd_max_startups.sh b/stig/audits/sshd_max_startups.sh new file mode 100644 index 00000000..def642c3 --- /dev/null +++ b/stig/audits/sshd_max_startups.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ -z "$(sshd -T | awk '$1 ~ /^\s*maxstartups/{split($2, a, ":");{if(a[1] > 10 || a[2] > 30 || a[3] > 60) print $0}}')" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/sshd_permit_empty_passwords.sh b/stig/audits/sshd_permit_empty_passwords.sh new file mode 100644 index 00000000..0dca3014 --- /dev/null +++ b/stig/audits/sshd_permit_empty_passwords.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ "$(sshd -T | awk '/^permitemptypasswords/{print $2}')" = "no" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/sshd_permit_root_login.sh b/stig/audits/sshd_permit_root_login.sh new file mode 100644 index 00000000..d5caed5f --- /dev/null +++ b/stig/audits/sshd_permit_root_login.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ "$(sshd -T -C user=root -C host="$(hostname)" -C addr="$(grep "$(hostname)" /etc/hosts | awk '{print $1}')" | awk '/^permitrootlogin/{print $2}')" = "no" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/sshd_permit_user_environment.sh b/stig/audits/sshd_permit_user_environment.sh new file mode 100644 index 00000000..005a079b --- /dev/null +++ b/stig/audits/sshd_permit_user_environment.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ "$(sshd -T | awk '/^permituserenvironment/{print $2}')" = "no" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/sshd_private_host_key_permissions.sh b/stig/audits/sshd_private_host_key_permissions.sh new file mode 100644 index 00000000..1fbdcc1c --- /dev/null +++ b/stig/audits/sshd_private_host_key_permissions.sh @@ -0,0 +1,66 @@ +source /tmp/lib.sh + +l_output="" l_output2="" + +# Determine ssh key group name if present (ssh_keys, ssh, _ssh) +l_ssh_group_name="$(awk -F: '($1 ~ /^(ssh_keys|_?ssh)$/) {print $1; exit}' /etc/group)" + +f_file_chk() { +while IFS=: read -r l_file_mode l_file_owner l_file_group; do + l_out2="" + + # If group is the SSH key group allow group-read; else stricter (no group perms) + if [ -n "$l_ssh_group_name" ] && [ "$l_file_group" = "$l_ssh_group_name" ]; then + l_pmask="0137" + else + l_pmask="0177" + fi + + l_maxperm="$(printf '%o' $(( 0777 & ~${l_pmask#0} )))" + + # mode too permissive (treat as octal) + if [ $(( 8#$l_file_mode & 8#$l_pmask )) -gt 0 ]; then + l_out2="$l_out2\n - Mode: \"$l_file_mode\" should be mode: \"$l_maxperm\" or more restrictive" + fi + + # wrong owner + if [ "$l_file_owner" != "root" ]; then + l_out2="$l_out2\n - Owned by: \"$l_file_owner\" should be owned by \"root\"" + fi + + # wrong group (root or ssh group) + if [ -n "$l_ssh_group_name" ]; then + if [[ ! "$l_file_group" =~ ^($l_ssh_group_name|root)$ ]]; then + l_out2="$l_out2\n - Owned by group: \"$l_file_group\" should be group owned by: \"$l_ssh_group_name\" or \"root\"" + fi + else + if [ "$l_file_group" != "root" ]; then + l_out2="$l_out2\n - Owned by group: \"$l_file_group\" should be group owned by: \"root\"" + fi + fi + + if [ -n "$l_out2" ]; then + l_output2="$l_output2\n - File: \"$l_file\"$l_out2" + else + l_output="$l_output\n - File: \"$l_file\"\n - Correct: mode: \"$l_file_mode\", owner: \"$l_file_owner\", and group owner: \"$l_file_group\" configured" + fi +done < <(stat -Lc '%a:%U:%G' "$l_file" 2>/dev/null) +} + +while IFS= read -r -d $'\0' l_file; do +if ssh-keygen -lf "$l_file" &>/dev/null; then + if file "$l_file" | grep -Piq -- '\bopenssh\h+([^#\n\r]+\h+)?private\h+key\b'; then + f_file_chk + fi +fi +done < <(find -L /etc/ssh -xdev -type f -print0 2>/dev/null) + +if [ -z "$l_output2" ]; then +[ -z "$l_output" ] && l_output="\n - No OpenSSH private keys found" +echo -e "\n- Audit Result:\n ** PASS **\n - * Correctly configured * :$l_output" +exit $PASS +else +echo -e "\n- Audit Result:\n ** FAIL **\n - * Reasons for audit failure * :$l_output2\n" +[ -n "$l_output" ] && echo -e "\n - * Correctly configured * :\n$l_output\n" +exit $FAIL +fi diff --git a/stig/audits/sshd_public_host_key_permissions.sh b/stig/audits/sshd_public_host_key_permissions.sh new file mode 100644 index 00000000..6212ad2f --- /dev/null +++ b/stig/audits/sshd_public_host_key_permissions.sh @@ -0,0 +1,51 @@ +source /tmp/lib.sh + +l_output="" l_output2="" +l_pmask="0133" +l_maxperm="$(printf '%o' $(( 0777 & ~${l_pmask#0} )))" + +FILE_CHK() { +while IFS=: read -r l_file_mode l_file_owner l_file_group; do + l_out2="" + + # mode too permissive + if [ $(( 8#$l_file_mode & 8#$l_pmask )) -gt 0 ]; then + l_out2="$l_out2\n - Mode: \"$l_file_mode\" should be mode: \"$l_maxperm\" or more restrictive" + fi + + # wrong owner + if [ "$l_file_owner" != "root" ]; then + l_out2="$l_out2\n - Owned by: \"$l_file_owner\" should be owned by \"root\"" + fi + + # wrong group + if [ "$l_file_group" != "root" ]; then + l_out2="$l_out2\n - Owned by group: \"$l_file_group\" should be group owned by \"root\"" + fi + + if [ -n "$l_out2" ]; then + l_output2="$l_output2\n - File: \"$l_file\"$l_out2" + else + l_output="$l_output\n - File: \"$l_file\"\n - Correct: mode: \"$l_file_mode\", owner: \"$l_file_owner\", and group owner: \"$l_file_group\" configured" + fi +done < <(stat -Lc '%a:%U:%G' "$l_file" 2>/dev/null) +} + +while IFS= read -r -d $'\0' l_file; do +# only process actual SSH public keys +if ssh-keygen -lf "$l_file" &>/dev/null; then + if file "$l_file" | grep -Piq -- '\bopenssh\h+([^#\n\r]+\h+)?public\h+key\b'; then + FILE_CHK + fi +fi +done < <(find -L /etc/ssh -xdev -type f -print0 2>/dev/null) + +if [ -z "$l_output2" ]; then +[ -z "$l_output" ] && l_output="\n - No OpenSSH public keys found" +echo -e "\n- Audit Result:\n ** PASS **\n - * Correctly configured * :$l_output" +exit $PASS +else +echo -e "\n- Audit Result:\n ** FAIL **\n - * Reasons for audit failure * :$l_output2\n" +[ -n "$l_output" ] && echo -e "\n - * Correctly configured * :\n$l_output\n" +exit $FAIL +fi diff --git a/stig/audits/sshd_use_pam.sh b/stig/audits/sshd_use_pam.sh new file mode 100644 index 00000000..61842735 --- /dev/null +++ b/stig/audits/sshd_use_pam.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if [ "$(sshd -T | awk '/^usepam/{print $2}')" = "yes" ]; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/strong_password_hashing_algorithm.sh b/stig/audits/strong_password_hashing_algorithm.sh new file mode 100644 index 00000000..c35b5c0c --- /dev/null +++ b/stig/audits/strong_password_hashing_algorithm.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +encrypt_method="$(grep -Pi -- '^\h*ENCRYPT_METHOD\h+(SHA512|yescrypt)\b' /etc/login.defs | awk '{print $2}')" + +if [ -z "$encrypt_method" ]; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/su_access_restricted.sh b/stig/audits/su_access_restricted.sh new file mode 100644 index 00000000..825409ab --- /dev/null +++ b/stig/audits/su_access_restricted.sh @@ -0,0 +1,16 @@ +source /tmp/lib.sh + +pam_re='^\h*auth\h+(?:required|requisite)\h+pam_wheel\.so\h+(?:[^#\n\r]+\h+)?((?!\2)(use_uid\b|group=\H+\b))\h+(?:[^#\n\r]+\h+)?((?!\1)(use_uid\b|group=\H+\b))(\h+.*)?$' + +# Must have the pam_wheel line (with use_uid and group=...) +line="$(grep -Pi -- "$pam_re" /etc/pam.d/su 2>/dev/null | head -n1)" +[ -n "$line" ] || exit $FAIL + +#extract group name from group= +grp="$(grep -oP 'group=\K\S+' <<< "$line" 2>/dev/null | head -n1)" +[ -n "$grp" ] || exit $FAIL + +#group must exist and contain no users (4th field empty) +awk -F: -v g="$grp" '($1==g){ if($4=="") exit 0; else exit 1 } END{ exit 1 }' /etc/group 2>/dev/null || exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/sudo_auth_timeout_configured.sh b/stig/audits/sudo_auth_timeout_configured.sh new file mode 100644 index 00000000..078b5f63 --- /dev/null +++ b/stig/audits/sudo_auth_timeout_configured.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if grep -roP 'timestamp_timeout=\K[0-9]+' /etc/sudoers* 2>/dev/null | awk '$1 > 15 { exit 1 }'; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/sudo_installed.sh b/stig/audits/sudo_installed.sh new file mode 100644 index 00000000..32f60cd4 --- /dev/null +++ b/stig/audits/sudo_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_installed 'sudo'; then + exit $PASS +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/sudo_logfile_exists.sh b/stig/audits/sudo_logfile_exists.sh new file mode 100644 index 00000000..f916b27d --- /dev/null +++ b/stig/audits/sudo_logfile_exists.sh @@ -0,0 +1,5 @@ +source /tmp/lib.sh + +grep -rPsiq -- '^\h*Defaults\h+([^#]+,\h*)?logfile\h*=\h*(\"|\')?\H+(\"|\')?(,\h*\H+\h*)*\h*(#.*)?$' /etc/sudoers* 2>/dev/null || exit $FAIL + +exit $PASS \ No newline at end of file diff --git a/stig/audits/sudo_password_required.sh b/stig/audits/sudo_password_required.sh new file mode 100644 index 00000000..66f5bcf9 --- /dev/null +++ b/stig/audits/sudo_password_required.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if grep -rPq -- '^[^#].*NOPASSWD' /etc/sudoers* 2>/dev/null; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/sudo_reauth_not_disabled_globally.sh b/stig/audits/sudo_reauth_not_disabled_globally.sh new file mode 100644 index 00000000..95c0b6e4 --- /dev/null +++ b/stig/audits/sudo_reauth_not_disabled_globally.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if grep -rPq -- '^[^#].*!authenticate\b' /etc/sudoers* 2>/dev/null; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/sudo_use_pty.sh b/stig/audits/sudo_use_pty.sh new file mode 100644 index 00000000..a7d9754e --- /dev/null +++ b/stig/audits/sudo_use_pty.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +grep -rPiq -- '^\h*Defaults\h+([^#\n\r]+,\h*)?use_pty\b' /etc/sudoers* 2>/dev/null || exit $FAIL + +if grep -rPiq -- '^\h*Defaults\h+([^#\n\r]+,\h*)?!use_pty\b' /etc/sudoers* 2>/dev/null; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/suid_sgid_files_reviewed.sh b/stig/audits/suid_sgid_files_reviewed.sh new file mode 100644 index 00000000..19403f62 --- /dev/null +++ b/stig/audits/suid_sgid_files_reviewed.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! while IFS= read -r l_mount; do find "$l_mount" -xdev -type f \( -perm -2000 -o -perm -4000 \) -print 2>/dev/null; done < <(findmnt -Dkerno fstype,target,options | awk '($1 !~ /^\s*(nfs|proc|smb|vfat|iso9660|efivarfs|selinuxfs)/ && $2 !~ /^\/run\/user\// && $3 !~/noexec/ && $3 !~/nosuid/) {print $2}'); do :; done | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/suspicious_packets_logged.sh b/stig/audits/suspicious_packets_logged.sh new file mode 100644 index 00000000..d3f4f75b --- /dev/null +++ b/stig/audits/suspicious_packets_logged.sh @@ -0,0 +1,76 @@ +source /tmp/lib.sh + +l_output="" l_output2="" l_ipv6_disabled="" +a_parlist=("net.ipv4.conf.all.log_martians=1" "net.ipv4.conf.default.log_martians=1") +l_ufwscf="$([ -f /etc/default/ufw ] && awk -F= '/^\s*IPT_SYSCTL=/ {print $2}' /etc/default/ufw)" + +f_ipv6_chk() +{ + l_ipv6_disabled="" + ! grep -Pqs -- '^\h*0\b' /sys/module/ipv6/parameters/disable && l_ipv6_disabled="yes" + if sysctl net.ipv6.conf.all.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.all\.disable_ipv6\h*=\h*1\b" && \ + sysctl net.ipv6.conf.default.disable_ipv6 | grep -Pqs -- "^\h*net\.ipv6\.conf\.default\.disable_ipv6\h*=\h*1\b"; then + l_ipv6_disabled="yes" + fi + [ -z "$l_ipv6_disabled" ] && l_ipv6_disabled="no" +} + +f_kernel_parameter_chk() +{ + l_krp="$(sysctl "$l_kpname" | awk -F= '{print $2}' | xargs)" + if [ "$l_krp" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_krp\" in the running configuration" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_krp\" in the running configuration and should have a value of: \"$l_kpvalue\"" + fi + unset A_out; declare -A A_out + while read -r l_out; do + if [ -n "$l_out" ]; then + if [[ $l_out =~ ^\s*# ]]; then + l_file="${l_out//# /}" + else + l_kpar="$(awk -F= '{print $1}' <<< "$l_out" | xargs)" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_file") + fi + fi + done < <(/usr/lib/systemd/systemd-sysctl --cat-config | grep -Po '^\h*([^#\n\r]+|#\h*\/[^#\n\r\h]+\.conf\b)') + if [ -n "$l_ufwscf" ]; then + l_kpar="$(grep -Po "^\h*$l_kpname\b" "$l_ufwscf" | xargs)" + l_kpar="${l_kpar//\//.}" + [ "$l_kpar" = "$l_kpname" ] && A_out+=(["$l_kpar"]="$l_ufwscf") + fi + if (( ${#A_out[@]} > 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/system_accounts_no_shell.sh b/stig/audits/system_accounts_no_shell.sh new file mode 100644 index 00000000..0838c684 --- /dev/null +++ b/stig/audits/system_accounts_no_shell.sh @@ -0,0 +1,13 @@ +source /tmp/lib.sh + +l_valid_shells="^($(awk -F\/ '$NF != "nologin" {print}' /etc/shells | sed -rn '/^\//{s,/,\\\\/,g;p}' | paste -s -d '|' - ))$" + +uid_min="$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs)" + +bad_service_accounts="$(awk -v pat="$l_valid_shells" -v uidmin="$uid_min" -F: '($1!~/^(root|halt|sync|shutdown|nfsnobody)$/ && ($3 0 )); then + while IFS="=" read -r l_fkpname l_fkpvalue; do + l_fkpname="${l_fkpname// /}"; l_fkpvalue="${l_fkpvalue// /}" + if [ "$l_fkpvalue" = "$l_kpvalue" ]; then + l_output="$l_output\n - \"$l_kpname\" is correctly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\"\n" + else + l_output2="$l_output2\n - \"$l_kpname\" is incorrectly set to \"$l_fkpvalue\" in \"$(printf '%s' "${A_out[@]}")\" and should have a value of: \"$l_kpvalue\"\n" + fi + done < <(grep -Po -- "^\h*$l_kpname\h*=\h*\H+" "${A_out[@]}") + else + l_output2="$l_output2\n - \"$l_kpname\" is not set in an included file\n ** Note: \"$l_kpname\" May be set in a file that's ignored by load procedure **\n" + fi +} + +while IFS="=" read -r l_kpname l_kpvalue; do + l_kpname="${l_kpname// /}"; l_kpvalue="${l_kpvalue// /}" + if grep -q '^net.ipv6.' <<< "$l_kpname"; then + [ -z "$l_ipv6_disabled" ] && f_ipv6_chk + if [ "$l_ipv6_disabled" = "yes" ]; then + l_output="$l_output\n - IPv6 is disabled on the system, \"$l_kpname\" is not applicable" + else + f_kernel_parameter_chk + fi + else + f_kernel_parameter_chk + fi +done < <(printf '%s\n' "${a_parlist[@]}") + +unset a_parlist; unset A_out + +if [ -z "$l_output2" ]; then + exit $PASS +else + exit $FAIL +fi \ No newline at end of file diff --git a/stig/audits/telnet_client_not_installed.sh b/stig/audits/telnet_client_not_installed.sh new file mode 100644 index 00000000..3b9ba019 --- /dev/null +++ b/stig/audits/telnet_client_not_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_not_installed 'telnet'; then + exit $PASS +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/telnet_unused.sh b/stig/audits/telnet_unused.sh new file mode 100644 index 00000000..f5a0ad0a --- /dev/null +++ b/stig/audits/telnet_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'telnet-server' telnet.socket && exit $PASS || exit $FAIL diff --git a/stig/audits/tftp_client_not_installed.sh b/stig/audits/tftp_client_not_installed.sh new file mode 100644 index 00000000..af30d28c --- /dev/null +++ b/stig/audits/tftp_client_not_installed.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_not_installed 'tftp'; then + exit $PASS +fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/tftp_unused.sh b/stig/audits/tftp_unused.sh new file mode 100644 index 00000000..69a2aaf0 --- /dev/null +++ b/stig/audits/tftp_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'tftp-server' tftp.socket tftp.service && exit $PASS || exit $FAIL diff --git a/stig/audits/time_synchronization.sh b/stig/audits/time_synchronization.sh new file mode 100644 index 00000000..9130c791 --- /dev/null +++ b/stig/audits/time_synchronization.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if is_installed 'chrony'; then exit $PASS; fi +exit $FAIL \ No newline at end of file diff --git a/stig/audits/tipc_kernel_module_unavailable.sh b/stig/audits/tipc_kernel_module_unavailable.sh new file mode 100644 index 00000000..7bab7fa0 --- /dev/null +++ b/stig/audits/tipc_kernel_module_unavailable.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "tipc" "net" && exit $PASS || exit $FAIL diff --git a/stig/audits/tmp_nodev.sh b/stig/audits/tmp_nodev.sh new file mode 100644 index 00000000..676046ae --- /dev/null +++ b/stig/audits/tmp_nodev.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /tmp nodev && exit $PASS || exit $FAIL diff --git a/stig/audits/tmp_noexec.sh b/stig/audits/tmp_noexec.sh new file mode 100644 index 00000000..184d2d22 --- /dev/null +++ b/stig/audits/tmp_noexec.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /tmp noexec && exit $PASS || exit $FAIL diff --git a/stig/audits/tmp_nosuid.sh b/stig/audits/tmp_nosuid.sh new file mode 100644 index 00000000..14ad173a --- /dev/null +++ b/stig/audits/tmp_nosuid.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /tmp nosuid && exit $PASS || exit $FAIL diff --git a/stig/audits/tmp_separate_partition.sh b/stig/audits/tmp_separate_partition.sh new file mode 100644 index 00000000..fe0742db --- /dev/null +++ b/stig/audits/tmp_separate_partition.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if findmnt -kn /tmp >/dev/null 2>&1 && ! systemctl is-enabled tmp.mount 2>/dev/null | grep -Eq 'masked|disabled'; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/udf_off.sh b/stig/audits/udf_off.sh new file mode 100644 index 00000000..dae14724 --- /dev/null +++ b/stig/audits/udf_off.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "udf" "fs" && exit $PASS || exit $FAIL diff --git a/stig/audits/unused_filesystems_modules_off.sh b/stig/audits/unused_filesystems_modules_off.sh new file mode 100644 index 00000000..d7afa74b --- /dev/null +++ b/stig/audits/unused_filesystems_modules_off.sh @@ -0,0 +1,54 @@ +source /tmp/lib.sh + +a_output=(); a_output2=(); a_modprope_config=(); a_excluded=(); a_available_modules=() +a_ignore=("xfs" "vfat" "ext2" "ext3" "ext4") +a_cve_exists=("afs" "ceph" "cifs" "exfat" "ext" "fat" "fscache" "fuse" "gfs2" "nfs_common" "nfsd" +"smbfs_common") +f_module_chk() +{ +l_out2=""; grep -Pq -- "\b$l_mod_name\b" <<< "${a_cve_exists[*]}" && l_out2=" <- CVE exists!" +if ! grep -Pq -- '\bblacklist\h+'"$l_mod_name"'\b' <<< "${a_modprope_config[*]}"; then +a_output2+=(" - Kernel module: \"$l_mod_name\" is not fully disabled $l_out2") +elif ! grep -Pq -- '\binstall\h+'"$l_mod_name"'\h+\/bin\/(false|true)\b' <<< "${a_modprope_config[*]}"; +then +a_output2+=(" - Kernel module: \"$l_mod_name\" is not fully disabled $l_out2") +fi +if lsmod | grep "$l_mod_name" &> /dev/null; then # Check if the module is currently loaded +l_output2+=(" - Kernel module: \"$l_mod_name\" is loaded" "") +fi +} +while IFS= read -r -d $'\0' l_module_dir; do +a_available_modules+=("$(basename "$l_module_dir")") +done < <(find "$(readlink -f /lib/modules/"$(uname -r)"/kernel/fs)" -mindepth 1 -maxdepth 1 -type d ! -empty +-print0) +while IFS= read -r l_exclude; do +if grep -Pq -- "\b$l_exclude\b" <<< "${a_cve_exists[*]}"; then +a_output2+=(" - ** WARNING: kernel module: \"$l_exclude\" has a CVE and is currently mounted! **") +elif +grep -Pq -- "\b$l_exclude\b" <<< "${a_available_modules[*]}"; then +a_output+=(" - Kernel module: \"$l_exclude\" is currently mounted - do NOT unload or disable") +fi +! grep -Pq -- "\b$l_exclude\b" <<< "${a_ignore[*]}" && a_ignore+=("$l_exclude") +done < <(findmnt -knD | awk '{print $2}' | sort -u) +while IFS= read -r l_config; do +a_modprope_config+=("$l_config") +done < <(modprobe --showconfig | grep -P '^\h*(blacklist|install)') +for l_mod_name in "${a_available_modules[@]}"; do # Iterate over all filesystem modules +[[ "$l_mod_name" =~ overlay ]] && l_mod_name="${l_mod_name::-2}" +if grep -Pq -- "\b$l_mod_name\b" <<< "${a_ignore[*]}"; then +a_excluded+=(" - Kernel module: \"$l_mod_name\"") +else +f_module_chk +fi +done +[ "${#a_excluded[@]}" -gt 0 ] && printf '%s\n' "" " -- INFO --" \ +"The following intentionally skipped" \ +"${a_excluded[@]}" +if [ "${#a_output2[@]}" -le 0 ]; then +printf '%s\n' "" " - No unused filesystem kernel modules are enabled" "${a_output[@]}" "" +exit $PASS +else +printf '%s\n' "" "-- Audit Result: --" " ** REVIEW the following **" "${a_output2[@]}" +[ "${#a_output[@]}" -gt 0 ] && printf '%s\n' "" "-- Correctly set: --" "${a_output[@]}" "" +exit $FAIL +fi diff --git a/stig/audits/updates_patches_installed.sh b/stig/audits/updates_patches_installed.sh new file mode 100644 index 00000000..3ae16530 --- /dev/null +++ b/stig/audits/updates_patches_installed.sh @@ -0,0 +1,9 @@ +source /tmp/lib.sh + +if dnf check-update >/dev/null 2>&1; then + if ! dnf needs-restarting -r >/dev/null 2>&1; then + exit $PASS + fi +fi + +exit $FAIL diff --git a/stig/audits/usb_storage_off.sh b/stig/audits/usb_storage_off.sh new file mode 100644 index 00000000..a911ff4b --- /dev/null +++ b/stig/audits/usb_storage_off.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +check_kernel_module_disabled "usb-storage" "drivers" && exit $PASS || exit $FAIL diff --git a/stig/audits/users_last_password_change_in_past.sh b/stig/audits/users_last_password_change_in_past.sh new file mode 100644 index 00000000..0d7fea7f --- /dev/null +++ b/stig/audits/users_last_password_change_in_past.sh @@ -0,0 +1,19 @@ +source /tmp/lib.sh + +now_epoch="$(date +%s)" + +non_compliant_users="$( +while IFS= read -r l_user; do + l_change="$(date -d "$(chage --list "$l_user" | grep '^Last password change' | cut -d: -f2 | grep -v 'never$')" +%s 2>/dev/null)" + [ -n "$l_change" ] || continue + if [ "$l_change" -gt "$now_epoch" ]; then + echo "$l_user" + fi +done < <(awk -F: '$2~/^\$.+\$/{print $1}' /etc/shadow) +)" + +if [ -n "$non_compliant_users" ]; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/var_log_audit_nodev.sh b/stig/audits/var_log_audit_nodev.sh new file mode 100644 index 00000000..38de5d66 --- /dev/null +++ b/stig/audits/var_log_audit_nodev.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var/log/audit nodev && exit $PASS || exit $FAIL diff --git a/stig/audits/var_log_audit_noexec.sh b/stig/audits/var_log_audit_noexec.sh new file mode 100644 index 00000000..7c2785ea --- /dev/null +++ b/stig/audits/var_log_audit_noexec.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var/log/audit noexec && exit $PASS || exit $FAIL diff --git a/stig/audits/var_log_audit_nosuid.sh b/stig/audits/var_log_audit_nosuid.sh new file mode 100644 index 00000000..c810b5c4 --- /dev/null +++ b/stig/audits/var_log_audit_nosuid.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var/log/audit nosuid && exit $PASS || exit $FAIL diff --git a/stig/audits/var_log_audit_separate_partition.sh b/stig/audits/var_log_audit_separate_partition.sh new file mode 100644 index 00000000..d202c7d3 --- /dev/null +++ b/stig/audits/var_log_audit_separate_partition.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_separate_partition /var/log/audit && exit $PASS || exit $FAIL diff --git a/stig/audits/var_log_nodev.sh b/stig/audits/var_log_nodev.sh new file mode 100644 index 00000000..31d739ef --- /dev/null +++ b/stig/audits/var_log_nodev.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var/log nodev && exit $PASS || exit $FAIL diff --git a/stig/audits/var_log_noexec.sh b/stig/audits/var_log_noexec.sh new file mode 100644 index 00000000..ab097248 --- /dev/null +++ b/stig/audits/var_log_noexec.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var/log noexec && exit $PASS || exit $FAIL diff --git a/stig/audits/var_log_nosuid.sh b/stig/audits/var_log_nosuid.sh new file mode 100644 index 00000000..57a51b56 --- /dev/null +++ b/stig/audits/var_log_nosuid.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var/log nosuid && exit $PASS || exit $FAIL diff --git a/stig/audits/var_log_separate_partition.sh b/stig/audits/var_log_separate_partition.sh new file mode 100644 index 00000000..63ee2f92 --- /dev/null +++ b/stig/audits/var_log_separate_partition.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_separate_partition /var/log && exit $PASS || exit $FAIL diff --git a/stig/audits/var_nodev.sh b/stig/audits/var_nodev.sh new file mode 100644 index 00000000..c9ec691e --- /dev/null +++ b/stig/audits/var_nodev.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var nodev && exit $PASS || exit $FAIL diff --git a/stig/audits/var_nosuid.sh b/stig/audits/var_nosuid.sh new file mode 100644 index 00000000..1c58c94d --- /dev/null +++ b/stig/audits/var_nosuid.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var nosuid && exit $PASS || exit $FAIL diff --git a/stig/audits/var_separate_partition.sh b/stig/audits/var_separate_partition.sh new file mode 100644 index 00000000..c55b59d7 --- /dev/null +++ b/stig/audits/var_separate_partition.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_separate_partition /var && exit $PASS || exit $FAIL diff --git a/stig/audits/var_tmp_nodev.sh b/stig/audits/var_tmp_nodev.sh new file mode 100644 index 00000000..681697e3 --- /dev/null +++ b/stig/audits/var_tmp_nodev.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var/tmp nodev && exit $PASS || exit $FAIL diff --git a/stig/audits/var_tmp_noexec.sh b/stig/audits/var_tmp_noexec.sh new file mode 100644 index 00000000..c67c884f --- /dev/null +++ b/stig/audits/var_tmp_noexec.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var/tmp noexec && exit $PASS || exit $FAIL diff --git a/stig/audits/var_tmp_nosuid.sh b/stig/audits/var_tmp_nosuid.sh new file mode 100644 index 00000000..7f4693de --- /dev/null +++ b/stig/audits/var_tmp_nosuid.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_mount_option /var/tmp nosuid && exit $PASS || exit $FAIL diff --git a/stig/audits/var_tmp_separate_partition.sh b/stig/audits/var_tmp_separate_partition.sh new file mode 100644 index 00000000..867df815 --- /dev/null +++ b/stig/audits/var_tmp_separate_partition.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +has_separate_partition /var/tmp && exit $PASS || exit $FAIL diff --git a/stig/audits/web_proxy_unused.sh b/stig/audits/web_proxy_unused.sh new file mode 100644 index 00000000..8594b749 --- /dev/null +++ b/stig/audits/web_proxy_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'squid' squid.service && exit $PASS || exit $FAIL diff --git a/stig/audits/web_server_unused.sh b/stig/audits/web_server_unused.sh new file mode 100644 index 00000000..d2ca2c1b --- /dev/null +++ b/stig/audits/web_server_unused.sh @@ -0,0 +1,5 @@ +source /tmp/lib.sh + +service_unused 'httpd' httpd.socket httpd.service || exit $FAIL +service_unused 'nginx' nginx.service || exit $FAIL +exit $PASS diff --git a/stig/audits/wireless_interfaces_disabled.sh b/stig/audits/wireless_interfaces_disabled.sh new file mode 100644 index 00000000..50a59cc1 --- /dev/null +++ b/stig/audits/wireless_interfaces_disabled.sh @@ -0,0 +1,46 @@ +source /tmp/lib.sh + +l_output="" l_output2="" +module_chk() +{ +# check how module will be loaded +l_loadable="$(modprobe -n -v "$l_mname")" +if grep -Pq -- '^\h*install \/bin\/(true|false)' <<< "$l_loadable"; then +l_output="$l_output\n - module: \"$l_mname\" is not loadable: \"$l_loadable\"" +else +l_output2="$l_output2\n - module: \"$l_mname\" is loadable: \"$l_loadable\"" +fi +# check if the module is currently loaded +if ! lsmod | grep "$l_mname" > /dev/null 2>&1; then +l_output="$l_output\n - module: \"$l_mname\" is not loaded" +else +l_output2="$l_output2\n - module: \"$l_mname\" is loaded" +fi +# check if the module is deny listed +if modprobe --showconfig | grep -Pq -- "^\h*blacklist\h+$l_mname\b"; then +l_output="$l_output\n - module: \"$l_mname\" is deny listed in: \"$(grep -Pl -- "^\h*blacklist\h+$l_mname\b" /etc/modprobe.d/*)\"" +else +l_output2="$l_output2\n - module: \"$l_mname\" is not deny listed" +fi +} +if [ -n "$(find /sys/class/net/*/ -type d -name wireless)" ]; then +l_dname=$(for driverdir in $(find /sys/class/net/*/ -type d -name wireless | xargs -0 dirname); do +basename "$(readlink -f "$driverdir"/device/driver/module)";done | sort -u) +for l_mname in $l_dname; do +module_chk +done +fi +# report results. If no failures output in l_output2, we pass +if [ -z "$l_output2" ]; then +echo -e "\n- Audit Result:\n ** PASS **" +if [ -z "$l_output" ]; then +echo -e "\n - System has no wireless NICs installed" +else +echo -e "\n$l_output\n" +fi +else +echo -e "\n- Audit Result:\n ** FAIL **\n - Reason(s) for audit failure:\n$l_output2\n" +[ -n "$l_output" ] && echo -e "\n- Correctly set:\n$l_output\n" +fi + + diff --git a/stig/audits/world_writable_files_and_directories_secured.sh b/stig/audits/world_writable_files_and_directories_secured.sh new file mode 100644 index 00000000..95feddea --- /dev/null +++ b/stig/audits/world_writable_files_and_directories_secured.sh @@ -0,0 +1,4 @@ +source /tmp/lib.sh + +if ! find / -xdev \( -path /proc -o -path /proc/\* -o -path /sys -o -path /sys/\* -o -path /run/user -o -path /run/user/\* \) -prune -o -type f -perm -0002 -print -quit 2>/dev/null | grep -q . && ! find / -xdev \( -path /proc -o -path /proc/\* -o -path /sys -o -path /sys/\* -o -path /run/user -o -path /run/user/\* \) -prune -o -type d -perm -0002 ! -perm -1000 -print -quit 2>/dev/null | grep -q .; then exit $PASS; fi +exit $FAIL diff --git a/stig/audits/x_window_unused.sh b/stig/audits/x_window_unused.sh new file mode 100644 index 00000000..b4394992 --- /dev/null +++ b/stig/audits/x_window_unused.sh @@ -0,0 +1,6 @@ +source /tmp/lib.sh + +if is_not_installed 'xorg-x11-server-common'; then + exit $PASS +fi +exit $FAIL diff --git a/stig/audits/xdmcp_not_enabled.sh b/stig/audits/xdmcp_not_enabled.sh new file mode 100644 index 00000000..4fb426fc --- /dev/null +++ b/stig/audits/xdmcp_not_enabled.sh @@ -0,0 +1,7 @@ +source /tmp/lib.sh + +if grep -Eisq '^\s*Enable\s*=\s*true' /etc/gdm/custom.conf 2>/dev/null; then + exit $FAIL +fi + +exit $PASS \ No newline at end of file diff --git a/stig/audits/xinetd_unused.sh b/stig/audits/xinetd_unused.sh new file mode 100644 index 00000000..d006fc64 --- /dev/null +++ b/stig/audits/xinetd_unused.sh @@ -0,0 +1,3 @@ +source /tmp/lib.sh + +service_unused 'xinetd' xinetd.service && exit $PASS || exit $FAIL diff --git a/stig/create-db b/stig/create-db new file mode 100644 index 00000000..fffe1516 --- /dev/null +++ b/stig/create-db @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright: (c) 2026, Linuxfabrik GmbH, Zurich, Switzerland, https://www.linuxfabrik.ch +# The Unlicense (see LICENSE or https://unlicense.org/) + +import argparse +import csv +import os +import sys + +import lib.db_sqlite + + +def main(): + parser = argparse.ArgumentParser(description='Create the STIG database.') + parser.add_argument( + '--force', + action='store_true', + help='Remove existing database and re-create it.', + ) + args = parser.parse_args() + + script_dir = os.path.dirname(os.path.abspath(__file__)) + db_path = os.path.join(script_dir, 'stig.db') + + if os.path.exists(db_path): + if args.force: + os.remove(db_path) + print(f'Removed existing database: {db_path}') + else: + print(f'Database already exists: {db_path}. Use --force to re-create it.') + sys.exit(1) + + success, conn = lib.db_sqlite.connect(path=os.path.dirname(db_path), filename=os.path.basename(db_path)) + if not success: + print(f'Failed to connect to database: {conn}') + sys.exit(1) + + lib.db_sqlite.create_table( + conn, + table='profile', + definition=''' + "id" TEXT, + "profile_name" TEXT NOT NULL, + "profile_version" TEXT NOT NULL, + "control_name" TEXT NOT NULL, + "enabled" INTEGER DEFAULT 1, + "automated" INTEGER DEFAULT 1, + "server_level" INTEGER DEFAULT 1, + "workstation_level" INTEGER DEFAULT 1, + PRIMARY KEY("id") + ''', + ) + + lib.db_sqlite.create_table( + conn, + table='audit', + definition=''' + "id" TEXT, + "exec_order" INTEGER DEFAULT NULL, + "audit_name" TEXT DEFAULT NULL, + FOREIGN KEY("id") REFERENCES "profile"("id") + ''', + ) + + lib.db_sqlite.create_table( + conn, + table='remediation', + definition=''' + "id" TEXT, + "remediation_variable" TEXT DEFAULT NULL, + "mandatory" INTEGER DEFAULT 0, + "comment" TEXT DEFAULT NULL, + FOREIGN KEY("id") REFERENCES "profile"("id") + ''', + ) + + # Import data from CSV files + # Note: We don't use lib.db_sqlite.import_csv() here because it creates the table itself, + # but we need to create the tables with specific constraints (foreign keys, defaults) first. + tables = ['profile', 'audit', 'remediation'] + for table in tables: + csv_path = os.path.join(script_dir, f'{table}.csv') + if os.path.exists(csv_path): + with open(csv_path, newline='') as f: + reader = csv.DictReader(f) + for row in reader: + success, result = lib.db_sqlite.insert(conn, row, table=table) + if not success: + print(f'Failed to insert into {table}: {result}') + sys.exit(1) + print(f'Imported {csv_path}') + + lib.db_sqlite.commit(conn) + lib.db_sqlite.close(conn) + print(f'Created database: {db_path}') + + +if __name__ == '__main__': + main() diff --git a/stig/dump-db b/stig/dump-db new file mode 100644 index 00000000..a8977c49 --- /dev/null +++ b/stig/dump-db @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright: (c) 2026, Linuxfabrik GmbH, Zurich, Switzerland, https://www.linuxfabrik.ch +# The Unlicense (see LICENSE or https://unlicense.org/) + +import csv +import os +import sys + +import lib.db_sqlite + + +def main(): + script_dir = os.path.dirname(os.path.abspath(__file__)) + db_path = os.path.join(script_dir, 'stig.db') + + if not os.path.exists(db_path): + print(f'Database does not exist: {db_path}') + sys.exit(1) + + success, conn = lib.db_sqlite.connect(path=os.path.dirname(db_path), filename=os.path.basename(db_path)) + if not success: + print(f'Failed to connect to database: {conn}') + sys.exit(1) + + tables = ['profile', 'audit', 'remediation'] + + for table in tables: + csv_path = os.path.join(script_dir, f'{table}.csv') + success, rows = lib.db_sqlite.select(conn, f'SELECT * FROM {table}') + if not success: + print(f'Failed to select from {table}: {rows}') + sys.exit(1) + + if not rows: + # Write empty file with just headers + success, cols = lib.db_sqlite.select(conn, f'PRAGMA table_info({table})') + if success and cols: + fieldnames = [col['name'] for col in cols] + with open(csv_path, 'w', newline='') as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + print(f'Dumped {table} (0 rows) to {csv_path}') + continue + + fieldnames = list(rows[0].keys()) + with open(csv_path, 'w', newline='') as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(rows) + print(f'Dumped {table} ({len(rows)} rows) to {csv_path}') + + lib.db_sqlite.close(conn) + + +if __name__ == '__main__': + main() diff --git a/stig/lib b/stig/lib new file mode 100644 index 00000000..dc598c56 --- /dev/null +++ b/stig/lib @@ -0,0 +1 @@ +../lib \ No newline at end of file diff --git a/stig/profile.csv b/stig/profile.csv new file mode 100644 index 00000000..ef42ac89 --- /dev/null +++ b/stig/profile.csv @@ -0,0 +1,346 @@ +id,profile_name,profile_version,control_name,enabled,automated,server_level,workstation_level + +cramfs_off,CIS Rocky Linux 9,v2.0.0,1.1.1.1 Ensure cramfs kernel module is not available,1,1,1,1 +freevxfs_off,CIS Rocky Linux 9,v2.0.0,1.1.1.2 Ensure freevxfs kernel module is not available,1,1,1,1 +hfs_off,CIS Rocky Linux 9,v2.0.0,1.1.1.3 Ensure hfs kernel module is not available,1,1,1,1 +hfsplus_off,CIS Rocky Linux 9,v2.0.0,1.1.1.4 Ensure hfsplus kernel module is not available,1,1,1,1 +jffs2_off,CIS Rocky Linux 9,v2.0.0,1.1.1.5 Ensure jffs2 kernel module is not available,1,1,1,1 +squashfs_off,CIS Rocky Linux 9,v2.0.0,1.1.1.6 Ensure squashfs kernel module is not available,1,1,1,1 +udf_off,CIS Rocky Linux 9,v2.0.0,1.1.1.7 Ensure udf kernel module is not available,1,1,1,1 +usb_storage_off,CIS Rocky Linux 9,v2.0.0,1.1.1.8 Ensure usb-storage kernel module is not available,1,1,1,1 +unused_filesystems_modules_off,CIS Rocky Linux 9,v2.0.0,1.1.1.9 Ensure unused filesystems kernel modules are not available,1,0,1,1 + +tmp_separate_partition,CIS Rocky Linux 9,v2.0.0,1.1.2.1.1 Ensure /tmp is a separate partition,1,1,1,1 +tmp_nodev,CIS Rocky Linux 9,v2.0.0,1.1.2.1.2 Ensure nodev option set on /tmp partition,1,1,1,1 +tmp_nosuid,CIS Rocky Linux 9,v2.0.0,1.1.2.1.3 Ensure nosuid option set on /tmp partition,1,1,1,1 +tmp_noexec,CIS Rocky Linux 9,v2.0.0,1.1.2.1.4 Ensure noexec option set on /tmp partition,1,1,1,1 + +dev_shm_separate_partition,CIS Rocky Linux 9,v2.0.0,1.1.2.2.1 Ensure /dev/shm is a separate partition,1,1,1,1 +dev_shm_nodev,CIS Rocky Linux 9,v2.0.0,1.1.2.2.2 Ensure nodev option set on /dev/shm partition,1,1,1,1 +dev_shm_nosuid,CIS Rocky Linux 9,v2.0.0,1.1.2.2.3 Ensure nosuid option set on /dev/shm partition,1,1,1,1 +dev_shm_noexec,CIS Rocky Linux 9,v2.0.0,1.1.2.2.4 Ensure noexec option set on /dev/shm partition,1,1,1,1 + +home_separate_partition,CIS Rocky Linux 9,v2.0.0,1.1.2.3.1 Ensure separate partition exists for /home,1,1,1,1 +home_nodev,CIS Rocky Linux 9,v2.0.0,1.1.2.3.2 Ensure nodev option set on /home partition,1,1,1,1 +home_nosuid,CIS Rocky Linux 9,v2.0.0,1.1.2.3.3 Ensure nosuid option set on /home partition,1,1,1,1 + +var_separate_partition,CIS Rocky Linux 9,v2.0.0,1.1.2.4.1 Ensure separate partition exists for /var,1,1,1,1 +var_nodev,CIS Rocky Linux 9,v2.0.0,1.1.2.4.2 Ensure nodev option set on /var partition,1,1,1,1 +var_nosuid,CIS Rocky Linux 9,v2.0.0,1.1.2.4.3 Ensure nosuid option set on /var partition,1,1,1,1 + +var_tmp_separate_partition,CIS Rocky Linux 9,v2.0.0,1.1.2.5.1 Ensure separate partition exists for /var/tmp,1,1,1,1 +var_tmp_nodev,CIS Rocky Linux 9,v2.0.0,1.1.2.5.2 Ensure nodev option set on /var/tmp partition,1,1,1,1 +var_tmp_nosuid,CIS Rocky Linux 9,v2.0.0,1.1.2.5.3 Ensure nosuid option set on /var/tmp partition,1,1,1,1 +var_tmp_noexec,CIS Rocky Linux 9,v2.0.0,1.1.2.5.4 Ensure noexec option set on /var/tmp partition,1,1,1,1 + +var_log_separate_partition,CIS Rocky Linux 9,v2.0.0,1.1.2.6.1 Ensure separate partition exists for /var/log,1,1,1,1 +var_log_nodev,CIS Rocky Linux 9,v2.0.0,1.1.2.6.2 Ensure nodev option set on /var/log partition,1,1,1,1 +var_log_nosuid,CIS Rocky Linux 9,v2.0.0,1.1.2.6.3 Ensure nosuid option set on /var/log partition,1,1,1,1 +var_log_noexec,CIS Rocky Linux 9,v2.0.0,1.1.2.6.4 Ensure noexec option set on /var/log partition,1,1,1,1 + +var_log_audit_separate_partition,CIS Rocky Linux 9,v2.0.0,1.1.2.7.1 Ensure separate partition exists for /var/log/audit,1,1,1,1 +var_log_audit_nodev,CIS Rocky Linux 9,v2.0.0,1.1.2.7.2 Ensure nodev option set on /var/log/audit partition,1,1,1,1 +var_log_audit_nosuid,CIS Rocky Linux 9,v2.0.0,1.1.2.7.3 Ensure nosuid option set on /var/log/audit partition,1,1,1,1 +var_log_audit_noexec,CIS Rocky Linux 9,v2.0.0,1.1.2.7.4 Ensure noexec option set on /var/log/audit partition,1,1,1,1 + +gpg_keys_configured,CIS Rocky Linux 9,v2.0.0,1.2.1.1 Ensure GPG keys are configured,1,0,1,1 +gpgcheck_globally_activated,CIS Rocky Linux 9,v2.0.0,1.2.1.2 Ensure gpgcheck is globally activated,1,1,1,1 +repo_gpgcheck_globally_activated,CIS Rocky Linux 9,v2.0.0,1.2.1.3 Ensure repo_gpgcheck is globally activated,1,0,1,1 +package_manager_repos_configured,CIS Rocky Linux 9,v2.0.0,1.2.1.4 Ensure package manager repositories are configured,1,0,1,1 + +updates_patches_installed,CIS Rocky Linux 9,v2.0.0,"1.2.2.1 Ensure updates, patches, and additional security software are installed",1,0,1,1 + +selinux_installed,CIS Rocky Linux 9,v2.0.0,1.3.1.1 Ensure SELinux is installed,1,1,1,1 +selinux_not_disabled_bootloader,CIS Rocky Linux 9,v2.0.0,1.3.1.2 Ensure SELinux is not disabled in bootloader configuration,1,1,1,1 +selinux_policy_configured,CIS Rocky Linux 9,v2.0.0,1.3.1.3 Ensure SELinux policy is configured,1,1,1,1 +selinux_mode_not_disabled,CIS Rocky Linux 9,v2.0.0,1.3.1.4 Ensure the SELinux mode is not disabled,1,1,1,1 +selinux_mode_enforcing,CIS Rocky Linux 9,v2.0.0,1.3.1.5 Ensure the SELinux mode is enforcing,1,1,1,1 +no_unconfined_services,CIS Rocky Linux 9,v2.0.0,1.3.1.6 Ensure no unconfined services exist,1,0,1,1 +mcstrans_not_installed,CIS Rocky Linux 9,v2.0.0,1.3.1.7 Ensure the MCS Translation Service (mcstrans) is not installed,1,1,1,1 +setroubleshoot_not_installed,CIS Rocky Linux 9,v2.0.0,1.3.1.8 Ensure SETroubleshoot is not installed,1,1,1,1 + +bootloader_password_set,CIS Rocky Linux 9,v2.0.0,1.4.1 Ensure bootloader password is set,1,1,1,1 +bootloader_config_access,CIS Rocky Linux 9,v2.0.0,1.4.2 Ensure access to bootloader config is configured,1,1,1,1 + +aslr_enabled,CIS Rocky Linux 9,v2.0.0,1.5.1 Ensure address space layout randomization is enabled,1,1,1,1 +ptrace_scope_restricted,CIS Rocky Linux 9,v2.0.0,1.5.2 Ensure ptrace_scope is restricted,1,1,1,1 +core_dump_backtraces_disabled,CIS Rocky Linux 9,v2.0.0,1.5.3 Ensure core dump backtraces are disabled,1,1,1,1 +core_dump_storage_disabled,CIS Rocky Linux 9,v2.0.0,1.5.4 Ensure core dump storage is disabled,1,1,1,1 + +crypto_policy_not_legacy,CIS Rocky Linux 9,v2.0.0,1.6.1 Ensure system wide crypto policy is not set to legacy,1,1,1,1 +crypto_policy_not_sshd,CIS Rocky Linux 9,v2.0.0,1.6.2 Ensure system wide crypto policy is not set in sshd configuration,1,1,1,1 +crypto_policy_disable_sha1,CIS Rocky Linux 9,v2.0.0,1.6.3 Ensure system wide crypto policy disables sha1 hash and signature support,1,1,1,1 +crypto_policy_disable_mac_128_bits,CIS Rocky Linux 9,v2.0.0,1.6.4 Ensure system wide crypto policy disables macs less than 128 bits,1,1,1,1 +crypto_policy_disable_ssh_cbc,CIS Rocky Linux 9,v2.0.0,1.6.5 Ensure system wide crypto policy disables cbc for ssh,1,1,1,1 +crypto_policy_disable_ssh_chacha20_poly1305,CIS Rocky Linux 9,v2.0.0,1.6.6 Ensure system wide crypto policy disables chacha20-poly1305 for ssh,1,0,1,1 +crypto_policy_disable_ssh_etm,CIS Rocky Linux 9,v2.0.0,1.6.7 Ensure system wide crypto policy disables EtM for ssh,1,0,1,1 + +motd_configured,CIS Rocky Linux 9,v2.0.0,1.7.1 Ensure message of the day is configured properly,1,1,1,1 +local_login_banner_configured,CIS Rocky Linux 9,v2.0.0,1.7.2 Ensure local login warning banner is configured properly,1,1,1,1 +remote_login_banner_configured,CIS Rocky Linux 9,v2.0.0,1.7.3 Ensure remote login warning banner is configured properly,1,1,1,1 +motd_permissions_configured,CIS Rocky Linux 9,v2.0.0,1.7.4 Ensure access to /etc/motd is configured,1,1,1,1 +issue_permissions_configured,CIS Rocky Linux 9,v2.0.0,1.7.5 Ensure access to /etc/issue is configured,1,1,1,1 +issue_net_permissions_configured,CIS Rocky Linux 9,v2.0.0,1.7.6 Ensure access to /etc/issue.net is configured,1,1,1,1 + +gdm_removed,CIS Rocky Linux 9,v2.0.0,1.8.1 Ensure GNOME Display Manager is removed,1,1,1,0 +gdm_login_banner_configured,CIS Rocky Linux 9,v2.0.0,1.8.2 Ensure GDM login banner is configured,1,1,1,1 +gdm_disable_user_list_enabled,CIS Rocky Linux 9,v2.0.0,1.8.3 Ensure GDM disable-user-list option is enabled,1,1,1,1 +gdm_screen_locks_when_idle,CIS Rocky Linux 9,v2.0.0,1.8.4 Ensure GDM screen locks when the user is idle,1,1,1,1 +gdm_screen_locks_cannot_be_overridden,CIS Rocky Linux 9,v2.0.0,1.8.5 Ensure GDM screen locks cannot be overridden,1,1,1,1 +gdm_automount_removable_media_disabled,CIS Rocky Linux 9,v2.0.0,1.8.6 Ensure GDM automatic mounting of removable media is disabled,1,1,1,1 +gdm_automount_not_overridden,CIS Rocky Linux 9,v2.0.0,1.8.7 Ensure GDM disabling automatic mounting of removable media is not overridden,1,1,1,1 +gdm_autorun_never_enabled,CIS Rocky Linux 9,v2.0.0,1.8.8 Ensure GDM autorun-never is enabled,1,1,1,1 +gdm_autorun_never_not_overridden,CIS Rocky Linux 9,v2.0.0,1.8.9 Ensure GDM autorun-never is not overridden,1,1,1,1 +xdmcp_not_enabled,CIS Rocky Linux 9,v2.0.0,1.8.10 Ensure XDMCP is not enabled,1,1,1,1 + +autofs_unused,CIS Rocky Linux 9,v2.0.0,2.1.1 Ensure autofs services are not in use,1,1,1,1 +avahi_daemon_unused,CIS Rocky Linux 9,v2.0.0,2.1.2 Ensure avahi daemon services are not in use,1,1,1,1 +dhcp_server_unused,CIS Rocky Linux 9,v2.0.0,2.1.3 Ensure DHCP server services are not in use,1,1,1,1 +dns_server_unused,CIS Rocky Linux 9,v2.0.0,2.1.4 Ensure DNS server services are not in use,1,1,1,1 +dnsmasq_unused,CIS Rocky Linux 9,v2.0.0,2.1.5 Ensure dnsmasq services are not in use,1,1,1,1 +samba_unused,CIS Rocky Linux 9,v2.0.0,2.1.6 Ensure Samba file server services are not in use,1,1,1,1 +ftp_server_unused,CIS Rocky Linux 9,v2.0.0,2.1.7 Ensure ftp server services are not in use,1,1,1,1 +message_access_server_unused,CIS Rocky Linux 9,v2.0.0,2.1.8 Ensure message access server services are not in use,1,1,1,1 +nfs_server_unused,CIS Rocky Linux 9,v2.0.0,2.1.9 Ensure network file system services are not in use,1,1,1,1 +nis_server_unused,CIS Rocky Linux 9,v2.0.0,2.1.10 Ensure nis server services are not in use,1,1,1,1 +print_server_unused,CIS Rocky Linux 9,v2.0.0,2.1.11 Ensure print server services are not in use,1,1,1,1 +rpcbind_unused,CIS Rocky Linux 9,v2.0.0,2.1.12 Ensure rpcbind services are not in use,1,1,1,1 +rsync_unused,CIS Rocky Linux 9,v2.0.0,2.1.13 Ensure rsync services are not in use,1,1,1,1 +snmp_unused,CIS Rocky Linux 9,v2.0.0,2.1.14 Ensure snmp services are not in use,1,1,1,1 +telnet_unused,CIS Rocky Linux 9,v2.0.0,2.1.15 Ensure telnet server services are not in use,1,1,1,1 +tftp_unused,CIS Rocky Linux 9,v2.0.0,2.1.16 Ensure tftp server services are not in use,1,1,1,1 +web_proxy_unused,CIS Rocky Linux 9,v2.0.0,2.1.17 Ensure web proxy server services are not in use,1,1,1,1 +web_server_unused,CIS Rocky Linux 9,v2.0.0,2.1.18 Ensure web server services are not in use,1,1,1,1 +xinetd_unused,CIS Rocky Linux 9,v2.0.0,2.1.19 Ensure xinetd services are not in use,1,1,1,1 +x_window_unused,CIS Rocky Linux 9,v2.0.0,2.1.20 Ensure X Window server services are not in use,1,1,1,1 +mta_local_only_mode,CIS Rocky Linux 9,v2.0.0,2.1.21 Ensure mail transfer agents are configured for local-only mode,1,1,1,1 +approved_services_listening,CIS Rocky Linux 9,v2.0.0,2.1.22 Ensure only approved services are listening on a network interface,0,0,1,1 + +ftp_client_not_installed,CIS Rocky Linux 9,v2.0.0,2.2.1 Ensure ftp client is not installed,1,1,1,1 +ldap_client_not_installed,CIS Rocky Linux 9,v2.0.0,2.2.2 Ensure ldap client is not installed,1,1,1,1 +nis_client_not_installed,CIS Rocky Linux 9,v2.0.0,2.2.3 Ensure nis client is not installed,1,1,1,1 +telnet_client_not_installed,CIS Rocky Linux 9,v2.0.0,2.2.4 Ensure telnet client is not installed,1,1,1,1 +tftp_client_not_installed,CIS Rocky Linux 9,v2.0.0,2.2.5 Ensure tftp client is not installed,1,1,1,1 + +time_synchronization,CIS Rocky Linux 9,v2.0.0,2.3.1 Ensure time synchronization is in use,1,1,1,1 +chrony_configured,CIS Rocky Linux 9,v2.0.0,2.3.2 Ensure chrony is configured,1,1,1,1 +chrony_not_root,CIS Rocky Linux 9,v2.0.0,2.3.3 Ensure chrony is not run as the root user,1,1,1,1 + +cron_enabled_n_active,CIS Rocky Linux 9,v2.0.0,2.4.1.1 Ensure cron daemon is enabled and active,1,1,1,1 +cron_permissions_configured,CIS Rocky Linux 9,v2.0.0,2.4.1.2 Ensure permissions on /etc/crontab are configured,1,1,1,1 +cron_hourly_configured,CIS Rocky Linux 9,v2.0.0,2.4.1.3 Ensure permissions on /etc/cron.hourly are configured,1,1,1,1 +cron_daily_configured,CIS Rocky Linux 9,v2.0.0,2.4.1.4 Ensure permissions on /etc/cron.daily are configured,1,1,1,1 +cron_weekly_configured,CIS Rocky Linux 9,v2.0.0,2.4.1.5 Ensure permissions on /etc/cron.weekly are configured,1,1,1,1 +cron_monthly_configured,CIS Rocky Linux 9,v2.0.0,2.4.1.6 Ensure permissions on /etc/cron.monthly are configured,1,1,1,1 +crond_permissions_configured,CIS Rocky Linux 9,v2.0.0,2.4.1.7 Ensure permissions on /etc/cron.d are configured,1,1,1,1 +crontab_auth_restricted,CIS Rocky Linux 9,v2.0.0,2.4.1.8 Ensure crontab is restricted to authorized users,1,1,1,1 + +at_auth_restricted,CIS Rocky Linux 9,v2.0.0,2.4.2.1 Ensure at is restricted to authorized users,1,1,1,1 + +ipv6_status_identified,CIS Rocky Linux 9,v2.0.0,3.1.1 Ensure IPv6 status is identified,1,0,1,1 +wireless_interfaces_disabled,CIS Rocky Linux 9,v2.0.0,3.1.2 Ensure wireless interfaces are disabled,1,1,1,0 +bluetooth_services_unused,CIS Rocky Linux 9,v2.0.0,3.1.3 Ensure bluetooth services are not in use,1,1,1,1 + +dccp_kernel_module_unavailable,CIS Rocky Linux 9,v2.0.0,3.2.1 Ensure dccp kernel module is not available,1,1,1,1 +tipc_kernel_module_unavailable,CIS Rocky Linux 9,v2.0.0,3.2.2 Ensure tipc kernel module is not available,1,1,1,1 +rds_kernel_module_unavailable,CIS Rocky Linux 9,v2.0.0,3.2.3 Ensure rds kernel module is not available,1,1,1,1 +sctp_kernel_module_unavailable,CIS Rocky Linux 9,v2.0.0,3.2.4 Ensure sctp kernel module is not available,1,1,1,1 + +ip_forwarding_disabled,CIS Rocky Linux 9,v2.0.0,3.3.1 Ensure ip forwarding is disabled,1,1,1,1 +packet_redirect_sending_disabled,CIS Rocky Linux 9,v2.0.0,3.3.2 Ensure packet redirect sending is disabled,1,1,1,1 +bogus_icmp_responses_ignored,CIS Rocky Linux 9,v2.0.0,3.3.3 Ensure bogus icmp responses are ignored,1,1,1,1 +icmp_broadcasted_requests_ignored,CIS Rocky Linux 9,v2.0.0,3.3.4 Ensure broadcast icmp requests are ignored,1,1,1,1 +icmp_redirects_not_accepted,CIS Rocky Linux 9,v2.0.0,3.3.5 Ensure icmp redirects are not accepted,1,1,1,1 +icmp_secure_redirects_not_accepted,CIS Rocky Linux 9,v2.0.0,3.3.6 Ensure secure icmp requests are not accepted,1,1,1,1 +reverse_path_filtering_enabled,CIS Rocky Linux 9,v2.0.0,3.3.7 Ensure reverse path filtering is enabled,1,1,1,1 +source_routed_packets_not_accepted,CIS Rocky Linux 9,v2.0.0,3.3.8 Ensure source routed packets are not accepted,1,1,1,1 +suspicious_packets_logged,CIS Rocky Linux 9,v2.0.0,3.3.9 Ensure suspicious packets are logged,1,1,1,1 +tcp_syn_cookies_enabled,CIS Rocky Linux 9,v2.0.0,3.3.10 Ensure tcp syn cookies is enabled,1,1,1,1 +ipv6_router_advertisements_not_accepted,CIS Rocky Linux 9,v2.0.0,3.3.11 Ensure ipv6 router advertisements are not accepted,1,1,1,1 + +nftables_installed,CIS Rocky Linux 9,v2.0.0,4.1.1 Ensure nftables is installed,1,1,1,1 +single_firewall_config_utility,CIS Rocky Linux 9,v2.0.0,4.1.2 Ensure a single firewall configuration utility is in use,1,1,1,1 +firewalld_drops_services_n_ports,CIS Rocky Linux 9,v2.0.0,4.2.1 Ensure firewalld drops unnecessary services and ports,0,0,1,1 +firewalld_loopback_traffic,CIS Rocky Linux 9,v2.0.0,4.2.2 Ensure firewalld loopback traffic is configured,1,1,1,1 + +nftables_base_chains_exist,CIS Rocky Linux 9,v2.0.0,4.3.1 Ensure nftables base chains exist,1,1,1,1 +nftables_established_connections_configured,CIS Rocky Linux 9,v2.0.0,4.3.2 Ensure nftables established connections are configured,0,0,1,1 +nftables_deny_firewall_policy,CIS Rocky Linux 9,v2.0.0,4.3.3 Ensure nftables default deny firewall policy,1,1,1,1 +nftables_loopback_traffic,CIS Rocky Linux 9,v2.0.0,4.3.4 Ensure nftables loopback traffic is configured,1,1,1,1 + +sshd_config_permissions,CIS Rocky Linux 9,v2.0.0,5.1.1 Ensure permissions on /etc/ssh/sshd_config are configured,1,1,1,1 +sshd_private_host_key_permissions,CIS Rocky Linux 9,v2.0.0,5.1.2 Ensure permissions on SSH private host key files are configured,1,1,1,1 +sshd_public_host_key_permissions,CIS Rocky Linux 9,v2.0.0,5.1.3 Ensure permissions on SSH public host key files are configured,1,1,1,0 +sshd_ciphers,CIS Rocky Linux 9,v2.0.0,5.1.4 Ensure sshd Ciphers are configured,1,1,1,1 +sshd_kex_algorithms,CIS Rocky Linux 9,v2.0.0,5.1.5 Ensure sshd KexAlgorithms is configured,1,1,1,1 +sshd_macs,CIS Rocky Linux 9,v2.0.0,5.1.6 Ensure sshd MACs are configured,1,1,1,1 +sshd_access,CIS Rocky Linux 9,v2.0.0,5.1.7 Ensure sshd access is configured,1,1,1,1 +sshd_banner,CIS Rocky Linux 9,v2.0.0,5.1.8 Ensure sshd Banner is configured,1,1,1,1 +sshd_client_alive,CIS Rocky Linux 9,v2.0.0,5.1.9 Ensure sshd ClientAliveInterval and ClientAliveCountMax are configured,1,1,1,1 +sshd_disable_forwarding,CIS Rocky Linux 9,v2.0.0,5.1.10 Ensure sshd DisableForwarding is enabled,1,1,1,1 +sshd_gssapi_authentication,CIS Rocky Linux 9,v2.0.0,5.1.11 Ensure sshd GSSAPIAuthentication is disabled,1,1,1,1 +sshd_host_based_authentication,CIS Rocky Linux 9,v2.0.0,5.1.12 Ensure sshd HostbasedAuthentication is disabled,1,1,1,1 +sshd_ignore_rhosts,CIS Rocky Linux 9,v2.0.0,5.1.13 Ensure sshd IgnoreRhosts is enabled,1,1,1,1 +sshd_login_grace_time,CIS Rocky Linux 9,v2.0.0,5.1.14 Ensure sshd LoginGraceTime is configured,1,1,1,1 +sshd_log_level,CIS Rocky Linux 9,v2.0.0,5.1.15 Ensure sshd LogLevel is configured,1,1,1,1 +sshd_max_auth_tries,CIS Rocky Linux 9,v2.0.0,5.1.16 Ensure sshd MaxAuthTries is configured,1,1,1,1 +sshd_max_startups,CIS Rocky Linux 9,v2.0.0,5.1.17 Ensure sshd MaxStartups is configured,1,1,1,1 +sshd_max_sessions,CIS Rocky Linux 9,v2.0.0,5.1.18 Ensure sshd MaxSessions is configured,1,1,1,1 +sshd_permit_empty_passwords,CIS Rocky Linux 9,v2.0.0,5.1.19 Ensure sshd PermitEmptyPasswords is disabled,1,1,1,1 +sshd_permit_root_login,CIS Rocky Linux 9,v2.0.0,5.1.20 Ensure sshd PermitRootLogin is disabled,1,1,1,1 +sshd_permit_user_environment,CIS Rocky Linux 9,v2.0.0,5.1.21 Ensure sshd PermitUserEnvironment is disabled,1,1,1,1 +sshd_use_pam,CIS Rocky Linux 9,v2.0.0,5.1.22 Ensure sshd UsePAM is enabled,1,1,1,1 + +sudo_installed,CIS Rocky Linux 9,v2.0.0,5.2.1 Ensure sudo is installed,1,1,1,1 +sudo_use_pty,CIS Rocky Linux 9,v2.0.0,5.2.2 Ensure sudo commands use pty,1,1,1,1 +sudo_logfile_exists,CIS Rocky Linux 9,v2.0.0,5.2.3 Ensure sudo log file exists,1,1,1,1 +sudo_password_required,CIS Rocky Linux 9,v2.0.0,5.2.4 Ensure users must provide password for escalation,1,1,1,1 +sudo_reauth_not_disabled_globally,CIS Rocky Linux 9,v2.0.0,5.2.5 Ensure re-authentication for privilege escalation is not disabled globally,1,1,1,1 +sudo_auth_timeout_configured,CIS Rocky Linux 9,v2.0.0,5.2.6 Ensure sudo authentication timeout is configured correctly,1,1,1,1 +su_access_restricted,CIS Rocky Linux 9,v2.0.0,5.2.7 Ensure access to the su command is restricted,1,1,1,1 + +latest_pam_installed,CIS Rocky Linux 9,v2.0.0,5.3.1.1 Ensure latest version of pam is installed,1,1,1,1 +latest_authselect_installed,CIS Rocky Linux 9,v2.0.0,5.3.1.2 Ensure latest version of authselect is installed,1,1,1,1 +latest_libpwquality_installed,CIS Rocky Linux 9,v2.0.0,5.3.1.3 Ensure latest version of libpwquality is installed,1,1,1,1 + +authselect_profile_includes_pam_modules,CIS Rocky Linux 9,v2.0.0,5.3.2.1 Ensure active authselect profile includes pam modules,1,1,1,1 +pam_faillock_enabled,CIS Rocky Linux 9,v2.0.0,5.3.2.2 Ensure pam_faillock module is enabled,1,1,1,1 +pam_pwquality_enabled,CIS Rocky Linux 9,v2.0.0,5.3.2.3 Ensure pam_pwquality module is enabled,1,1,1,1 +pam_pwhistory_enabled,CIS Rocky Linux 9,v2.0.0,5.3.2.4 Ensure pam_pwhistory module is enabled,1,1,1,1 +pam_unix_enabled,CIS Rocky Linux 9,v2.0.0,5.3.2.5 Ensure pam_unix module is enabled,1,1,1,1 + +password_failed_lockout_configured,CIS Rocky Linux 9,v2.0.0,5.3.3.1.1 Ensure password failed attempts lockout is configured,1,1,1,1 +password_unlock_time_configured,CIS Rocky Linux 9,v2.0.0,5.3.3.1.2 Ensure password unlock time is configured,1,1,1,1 +password_lockout_includes_root,CIS Rocky Linux 9,v2.0.0,5.3.3.1.3 Ensure password failed attempts lockout includes root account,1,1,1,1 + +password_changed_characters_configured,CIS Rocky Linux 9,v2.0.0,5.3.3.2.1 Ensure password number of changed characters is configured,1,1,1,1 +password_length_configured,CIS Rocky Linux 9,v2.0.0,5.3.3.2.2 Ensure password length is configured,1,1,1,1 +password_complexity_configured,CIS Rocky Linux 9,v2.0.0,5.3.3.2.3 Ensure password complexity is configured,1,0,1,1 +password_same_consecutive_chars_configured,CIS Rocky Linux 9,v2.0.0,5.3.3.2.4 Ensure password same consecutive characters is configured,1,1,1,1 +password_max_sequential_chars_configured,CIS Rocky Linux 9,v2.0.0,5.3.3.2.5 Ensure password maximum sequential characters is configured,1,1,1,1 +password_dictionary_check_enabled,CIS Rocky Linux 9,v2.0.0,5.3.3.2.6 Ensure password dictionary check is enabled,1,1,1,1 +password_quality_enforced_for_root,CIS Rocky Linux 9,v2.0.0,5.3.3.2.7 Ensure password quality is enforced for the root user,1,1,1,1 + +password_history_remember_configured,CIS Rocky Linux 9,v2.0.0,5.3.3.3.1 Ensure password history remember is configured,1,1,1,1 +password_history_enforced_for_root,CIS Rocky Linux 9,v2.0.0,5.3.3.3.2 Ensure password history is enforced for the root user,1,1,1,1 +pam_pwhistory_includes_use_authtok,CIS Rocky Linux 9,v2.0.0,5.3.3.3.3 Ensure pam_pwhistory includes use_authtok,1,1,1,1 + +pam_unix_no_nullok,CIS Rocky Linux 9,v2.0.0,5.3.3.4.1 Ensure pam_unix does not include nullok,1,1,1,1 +pam_unix_no_remember,CIS Rocky Linux 9,v2.0.0,5.3.3.4.2 Ensure pam_unix does not include remember,1,1,1,1 +pam_unix_strong_hashing_algorithm,CIS Rocky Linux 9,v2.0.0,5.3.3.4.3 Ensure pam_unix includes a strong password hashing algorithm,1,1,1,1 +pam_unix_includes_use_authtok,CIS Rocky Linux 9,v2.0.0,5.3.3.4.4 Ensure pam_unix includes use_authtok,1,1,1,1 + +password_expiration_configured,CIS Rocky Linux 9,v2.0.0,5.4.1.1 Ensure password expiration is configured,1,1,1,1 +minimum_password_days_configured,CIS Rocky Linux 9,v2.0.0,5.4.1.2 Ensure minimum password days is configured,1,0,1,1 +password_expiration_warning_days,CIS Rocky Linux 9,v2.0.0,5.4.1.3 Ensure password expiration warning days is configured,1,1,1,1 +strong_password_hashing_algorithm,CIS Rocky Linux 9,v2.0.0,5.4.1.4 Ensure strong password hashing algorithm is configured (Automated),1,1,1,1 +inactive_password_lock_configured,CIS Rocky Linux 9,v2.0.0,5.4.1.5 Ensure inactive password lock is configured (Automated),1,1,1,1 +users_last_password_change_in_past,CIS Rocky Linux 9,v2.0.0,5.4.1.6 Ensure all users last password change date is in the past (Automated),1,1,1,1 + +root_only_uid_0,CIS Rocky Linux 9,v2.0.0,5.4.2.1 Ensure root is the only UID 0 account (Automated),1,1,1,1 +root_only_gid_0,CIS Rocky Linux 9,v2.0.0,5.4.2.2 Ensure root is the only GID 0 account (Automated),1,1,1,1 +group_root_only_gid_0,CIS Rocky Linux 9,v2.0.0,5.4.2.3 Ensure group root is the only GID 0 group (Automated),1,1,1,1 +root_account_access_controlled,CIS Rocky Linux 9,v2.0.0,5.4.2.4 Ensure root account access is controlled (Automated),1,1,1,1 +root_path_integrity,CIS Rocky Linux 9,v2.0.0,5.4.2.5 Ensure root path integrity (Automated),1,1,1,1 +root_umask_configured,CIS Rocky Linux 9,v2.0.0,5.4.2.6 Ensure root user umask is configured (Automated),1,1,1,1 +system_accounts_no_valid_shell,CIS Rocky Linux 9,v2.0.0,5.4.2.7 Ensure system accounts do not have a valid login shell (Automated),1,1,1,1 +accounts_without_valid_shell_locked,CIS Rocky Linux 9,v2.0.0,5.4.2.8 Ensure accounts without a valid login shell are locked (Automated),1,1,1,1 + +nologin_not_listed_in_shells,CIS Rocky Linux 9,v2.0.0,5.4.3.1 Ensure nologin is not listed in /etc/shells (Automated),1,1,1,1 +default_user_shell_timeout_configured,CIS Rocky Linux 9,v2.0.0,5.4.3.2 Ensure default user shell timeout is configured (Automated),1,1,1,1 +default_user_umask_configured,CIS Rocky Linux 9,v2.0.0,5.4.3.3 Ensure default user umask is configured (Automated),1,1,1,1 + +aide_installed,CIS Rocky Linux 9,v2.0.0,6.1.1 Ensure AIDE is installed,1,1,1,1 +aide_integrity_checked,CIS Rocky Linux 9,v2.0.0,6.1.2 Ensure filesystem integrity is regularly checked,1,1,1,1 +audit_tools_protected,CIS Rocky Linux 9,v2.0.0,6.1.3 Ensure cryptographic mechanisms are used to protect the integrity of audit tools,1,1,1,1 + +journald_enabled,CIS Rocky Linux 9,v2.0.0,6.2.1.1 Ensure journald service is enabled and active,1,1,1,1 +journald_log_access,CIS Rocky Linux 9,v2.0.0,6.2.1.2 Ensure journald log file access is configured,1,0,1,1 +journald_log_rotation,CIS Rocky Linux 9,v2.0.0,6.2.1.3 Ensure journald log file rotation is configured,1,0,1,1 +single_logging,CIS Rocky Linux 9,v2.0.0,6.2.1.4 Ensure only one logging system is in use,1,1,1,1 + +journal_remote_pkg,CIS Rocky Linux 9,v2.0.0,6.2.2.1.1 Ensure systemd-journal-remote is installed,1,1,1,1 +journal_upload_auth,CIS Rocky Linux 9,v2.0.0,6.2.2.1.2 Ensure systemd-journal-upload authentication is configured,1,0,1,1 +journal_upload_enabled,CIS Rocky Linux 9,v2.0.0,6.2.2.1.3 Ensure systemd-journal-upload is enabled and active,1,1,1,1 +journal_remote_unused,CIS Rocky Linux 9,v2.0.0,6.2.2.1.4 Ensure systemd-journal-remote service is not in use,1,1,1,1 + +journald_no_syslog,CIS Rocky Linux 9,v2.0.0,6.2.2.2 Ensure journald ForwardToSyslog is disabled,1,1,1,1 +journald_compress,CIS Rocky Linux 9,v2.0.0,6.2.2.3 Ensure journald Compress is configured,1,1,1,1 +journald_storage,CIS Rocky Linux 9,v2.0.0,6.2.2.4 Ensure journald Storage is configured,1,1,1,1 + +rsyslog_installed,CIS Rocky Linux 9,v2.0.0,6.2.3.1 Ensure rsyslog is installed,1,1,1,1 +rsyslog_enabled,CIS Rocky Linux 9,v2.0.0,6.2.3.2 Ensure rsyslog service is enabled and active,1,1,1,1 +journald_to_rsyslog,CIS Rocky Linux 9,v2.0.0,6.2.3.3 Ensure journald is configured to send logs to rsyslog,1,1,1,1 +rsyslog_mode,CIS Rocky Linux 9,v2.0.0,6.2.3.4 Ensure rsyslog log file creation mode is configured,1,1,1,1 +rsyslog_logging,CIS Rocky Linux 9,v2.0.0,6.2.3.5 Ensure rsyslog logging is configured,1,0,1,1 +rsyslog_remote,CIS Rocky Linux 9,v2.0.0,6.2.3.6 Ensure rsyslog is configured to send logs to a remote log host,1,0,1,1 +rsyslog_no_receive,CIS Rocky Linux 9,v2.0.0,6.2.3.7 Ensure rsyslog is not configured to receive logs from a remote client,1,1,1,1 +rsyslog_logrotate,CIS Rocky Linux 9,v2.0.0,6.2.3.8 Ensure rsyslog logrotate is configured,0,0,1,1 + +logfiles_access,CIS Rocky Linux 9,v2.0.0,6.2.4.1 Ensure access to all logfiles has been configured,1,1,1,1 + +auditd_installed,CIS Rocky Linux 9,v2.0.0,6.3.1.1. Ensure auditd packages are installed,1,1,1,1 +auditd_prior_processes,CIS Rocky Linux 9,v2.0.0,6.3.1.2 Ensure auditing for processes that start prior to auditd is enabled,1,1,1,1 +auditd_backlog_limit,CIS Rocky Linux 9,v2.0.0,6.3.1.3 Ensure audit_backlog_limit is sufficient, 1,1,1,1 +auditd_active,CIS Rocky Linux 9,v2.0.0,6.3.1.4 Ensure auditd service is enabled and active,1,1,1,1 +audit_maxsize,CIS Rocky Linux 9,v2.0.0,6.3.2.1 Ensure audit log storage size is configured,1,1,1,1 +audit_keep_logs,CIS Rocky Linux 9,v2.0.0,6.3.2.2 Ensure audit logs are not automatically deleted,1,1,1,1 +audit_full_act,CIS Rocky Linux 9,v2.0.0,6.3.2.3 Ensure system is disabled when audit logs are full,1,1,1,1 +audit_lowspace,CIS Rocky Linux 9,v2.0.0,6.3.2.4 Ensure system warns when audit logs are low on space,1,1,1,1 + +audit_scope,CIS Rocky Linux 9,v2.0.0,6.3.3.1 Ensure changes to system administration scope (sudoers) is collected,1,1,1,1 +audit_su_exec,CIS Rocky Linux 9,v2.0.0,6.3.3.2 Ensure actions as another user are always logged,1,1,1,1 +audit_sudo_log,CIS Rocky Linux 9,v2.0.0,6.3.3.3 Ensure events that modify the sudo log file are collected,1,1,1,1 +audit_timechg,CIS Rocky Linux 9,v2.0.0,6.3.3.4 Ensure events that modify date and time information are collected,1,1,1,1 +audit_netenv,CIS Rocky Linux 9,v2.0.0,6.3.3.5 Ensure events that modify the system's network environment are collected,1,1,1,1 +audit_privcmd,CIS Rocky Linux 9,v2.0.0,6.3.3.6 Ensure use of privileged commands are collected,1,1,1,1 +audit_filefail,CIS Rocky Linux 9,v2.0.0,6.3.3.7 Ensure unsuccessful file access attempts are collected,1,1,1,1 +audit_identity,CIS Rocky Linux 9,v2.0.0,6.3.3.8 Ensure events that modify user/group information are collected,1,1,1,1 +audit_permchg,CIS Rocky Linux 9,v2.0.0,6.3.3.9 Ensure discretionary access control permission modification events are collected,1,1,1,1 +audit_mount,CIS Rocky Linux 9,v2.0.0,6.3.3.10 Ensure successful file system mounts are collected,1,1,1,1 +audit_session,CIS Rocky Linux 9,v2.0.0,6.3.3.11 Ensure session initiation information is collected,1,1,1,1 +audit_login,CIS Rocky Linux 9,v2.0.0,6.3.3.12 Ensure login and logout events are collected,1,1,1,1 +audit_delete,CIS Rocky Linux 9,v2.0.0,6.3.3.13 Ensure file deletion events by users are collected,1,1,1,1 +audit_mac,CIS Rocky Linux 9,v2.0.0,6.3.3.14 Ensure events that modify the system's Mandatory Access Controls are collected,1,1,1,1 +audit_chcon,CIS Rocky Linux 9,v2.0.0,6.3.3.15 Ensure successful and unsuccessful attempts to use the chcon command are collected,1,1,1,1 +audit_setfacl,CIS Rocky Linux 9,v2.0.0,6.3.3.16 Ensure successful and unsuccessful attempts to use the setfacl command are collected,1,1,1,1 +audit_chacl,CIS Rocky Linux 9,v2.0.0,6.3.3.17 Ensure successful and unsuccessful attempts to use the chacl command are collected,1,1,1,1 +audit_usermod,CIS Rocky Linux 9,v2.0.0,6.3.3.18 Ensure successful and unsuccessful attempts to use the usermod command are collected,1,1,1,1 +audit_kmod,CIS Rocky Linux 9,v2.0.0,6.3.3.19 Ensure kernel module loading unloading and modification is collected,1,1,1,1 +audit_config_immutable,CIS Rocky Linux 9,v2.0.0,6.3.3.20 Ensure the audit configuration is immutable,1,1,1,1 +audit_config_consistent,CIS Rocky Linux 9,v2.0.0,6.3.3.21 Ensure the running and on disk configuration is the same,1,0,1,1 + +audit_log_dir_mode,CIS Rocky Linux 9,v2.0.0,6.3.4.1 Ensure the audit log file directory mode is configured,1,1,1,1 +audit_log_mode,CIS Rocky Linux 9,v2.0.0,6.3.4.2 Ensure audit log files mode is configured,1,1,1,1 +audit_log_owner,CIS Rocky Linux 9,v2.0.0,6.3.4.3 Ensure audit log files owner is configured,1,1,1,1 +audit_log_group,CIS Rocky Linux 9,v2.0.0,6.3.4.4 Ensure audit log files group owner is configured,1,1,1,1 +audit_conf_mode,CIS Rocky Linux 9,v2.0.0,6.3.4.5 Ensure audit configuration files mode is configured,1,1,1,1 +audit_conf_owner,CIS Rocky Linux 9,v2.0.0,6.3.4.6 Ensure audit configuration files owner is configured,1,1,1,1 +audit_conf_group,CIS Rocky Linux 9,v2.0.0,6.3.4.7 Ensure audit configuration files group owner is configured,1,1,1,1 +audit_tools_mode,CIS Rocky Linux 9,v2.0.0,6.3.4.8 Ensure audit tools mode is configured,1,1,1,1 +audit_tools_owner,CIS Rocky Linux 9,v2.0.0,6.3.4.9 Ensure audit tools owner is configured,1,1,1,1 +audit_tools_group,CIS Rocky Linux 9,v2.0.0,6.3.4.10 Ensure audit tools group owner is configured,1,1,1,1 + +etc_passwd_permissions,CIS Rocky Linux 9,v2.0.0,7.1.1 Ensure permissions on /etc/passwd are configured,1,1,1,1 +etc_passwd_dash_permissions,CIS Rocky Linux 9,v2.0.0,7.1.2 Ensure permissions on /etc/passwd- are configured,1,1,1,1 +etc_group_permissions,CIS Rocky Linux 9,v2.0.0,7.1.3 Ensure permissions on /etc/group are configured,1,1,1,1 +etc_group_dash_permissions,CIS Rocky Linux 9,v2.0.0,7.1.4 Ensure permissions on /etc/group- are configured,1,1,1,1 +etc_shadow_permissions,CIS Rocky Linux 9,v2.0.0,7.1.5 Ensure permissions on /etc/shadow are configured,1,1,1,1 +etc_shadow_dash_permissions,CIS Rocky Linux 9,v2.0.0,7.1.6 Ensure permissions on /etc/shadow- are configured,1,1,1,1 +etc_gshadow_permissions,CIS Rocky Linux 9,v2.0.0,7.1.7 Ensure permissions on /etc/gshadow are configured,1,1,1,1 +etc_gshadow_dash_permissions,CIS Rocky Linux 9,v2.0.0,7.1.8 Ensure permissions on /etc/gshadow- are configured,1,1,1,1 +etc_shells_permissions,CIS Rocky Linux 9,v2.0.0,7.1.9 Ensure permissions on /etc/shells are configured,1,1,1,1 +etc_security_opasswd_permissions,CIS Rocky Linux 9,v2.0.0,7.1.10 Ensure permissions on /etc/security/opasswd are configured,1,1,1,1 +world_writable_files_and_directories_secured,CIS Rocky Linux 9,v2.0.0,7.1.11 Ensure world writable files and directories are secured,1,1,1,1 +no_unowned_or_ungrouped_files_exist,CIS Rocky Linux 9,v2.0.0,7.1.12 Ensure no files or directories without an owner and a group exist,1,1,1,1 +suid_sgid_files_reviewed,CIS Rocky Linux 9,v2.0.0,7.1.13 Ensure SUID and SGID files are reviewed,1,0,1,1 + +passwd_use_shadowed_passwords,CIS Rocky Linux 9,v2.0.0,7.2.1 Ensure accounts in /etc/passwd use shadowed passwords,1,1,1,1 +shadow_password_fields_not_empty,CIS Rocky Linux 9,v2.0.0,7.2.2 Ensure /etc/shadow password fields are not empty,1,1,1,1 +passwd_groups_exist_in_group,CIS Rocky Linux 9,v2.0.0,7.2.3 Ensure all groups in /etc/passwd exist in /etc/group,1,1,1,1 +no_duplicate_uids_exist,CIS Rocky Linux 9,v2.0.0,7.2.4 Ensure no duplicate UIDs exist,1,1,1,1 +no_duplicate_gids_exist,CIS Rocky Linux 9,v2.0.0,7.2.5 Ensure no duplicate GIDs exist,1,1,1,1 +no_duplicate_user_names_exist,CIS Rocky Linux 9,v2.0.0,7.2.6 Ensure no duplicate user names exist,1,1,1,1 +no_duplicate_group_names_exist,CIS Rocky Linux 9,v2.0.0,7.2.7 Ensure no duplicate group names exist,1,1,1,1 +local_interactive_user_home_directories_configured,CIS Rocky Linux 9,v2.0.0,7.2.8 Ensure local interactive user home directories are configured,1,1,1,1 +local_interactive_user_dot_files_access_configured,CIS Rocky Linux 9,v2.0.0,7.2.9 Ensure local interactive user dot files access is configured,1,1,1,1 diff --git a/stig/remediation.csv b/stig/remediation.csv new file mode 100644 index 00000000..94eab347 --- /dev/null +++ b/stig/remediation.csv @@ -0,0 +1,31 @@ +id,remediation_variable,mandatory,comment +sshd_client_alive,sshd__client_alive_count_max__cis_var: 3,0, +sshd_client_alive,sshd__client_alive_interval__cis_var: 15,0, +sshd_disable_forwarding,sshd__disable_forwarding__cis_var: true,0, +sshd_gssapi_authentication,sshd__gssapi_authentication__cis_var: false,0, +sshd_login_grace_time,sshd__login_grace_time__cis_var: 60,0, +sshd_log_level,sshd__log_level__cis_var: 'VERBOSE',0, +sshd_max_auth_tries,sshd__max_auth_tries__cis_var: 4,0, +sshd_banner,,0,"This also requires the linuxfabrik.lfops.motd role to be run, since this deploys the banner content." +sshd_access,,1,"Manually set at least one of the following variables: `sshd__allow_groups__*_var`, `sshd__allow_users__*_var`, `sshd__deny_groups__*_var`, `sshd__deny_users__*_var`" +sshd_max_startups,sshd__max_startups__cis_var: '10:30:60',0, +sshd_max_sessions,sshd__max_sessions__cis_var: 10,0, +sshd_permit_empty_passwords,sshd__permit_empty_passwords__cis_var: false,0, +sshd__permit_root_login,sshd__permit_root_login__cis_var: 'no',0, +sshd_permit_user_environment,sshd__permit_user_environment__cis_var: false,0, +sshd_host_based_authentication,,0,"""HostbasedAuthentication no"" is hardcoded in the linuxfabrik.lfops.sshd role." +sshd_ignore_rhosts,,0,"""IgnoreRhosts yes"" is hardcoded in the linuxfabrik.lfops.sshd role." +sshd_use_pam,,0,"""UsePAM yes"" is hardcoded in the linuxfabrik.lfops.sshd role." +sshd_config_permissions,,0,This is done by default in the linuxfabrik.lfops.sshd role. +sshd_private_host_key_permissions,,0,This is done by default in the linuxfabrik.lfops.sshd role. +sshd_public_host_key_permissions,,0,This is done by default in the linuxfabrik.lfops.sshd role. +sshd_ciphers,,0,Handled by linuxfabrik.lfops.crypto_policy with LINUXFABRIK-SSH-NO-CBC and LINUXFABRIK-SSH-NO-CHACHA20 modules. +sshd_kex_algorithms,,0,Handled by linuxfabrik.lfops.crypto_policy (DEFAULT policy provides secure kex algorithms). +sshd_macs,,0,Handled by linuxfabrik.lfops.crypto_policy with LINUXFABRIK-NO-WEAKMAC and LINUXFABRIK-SSH-NO-ETM modules. +crypto_policy_not_legacy,crypto_policy__policy__cis_var: 'DEFAULT:LINUXFABRIK-NO-SHA1:LINUXFABRIK-NO-WEAKMAC:LINUXFABRIK-SSH-NO-CBC:LINUXFABRIK-SSH-NO-CHACHA20:LINUXFABRIK-SSH-NO-ETM',0,Do not set `LEGACY` inside `crypto_policy__policy`. +crypto_policy_not_sshd,,0,"This is the case by default. Note that the `/etc/sysconfig/sshd` path in the CIS document is wrong, the relevant config is `/etc/ssh/sshd_config.d/50-redhat.conf`." +crypto_policy_disable_sha1,crypto_policy__policy__cis_var: 'DEFAULT:LINUXFABRIK-NO-SHA1:LINUXFABRIK-NO-WEAKMAC:LINUXFABRIK-SSH-NO-CBC:LINUXFABRIK-SSH-NO-CHACHA20:LINUXFABRIK-SSH-NO-ETM',0,Do not remove `LINUXFABRIK-NO-SHA1` from `crypto_policy__policy`. +crypto_policy_disable_mac_128_bits,crypto_policy__policy__cis_var: 'DEFAULT:LINUXFABRIK-NO-SHA1:LINUXFABRIK-NO-WEAKMAC:LINUXFABRIK-SSH-NO-CBC:LINUXFABRIK-SSH-NO-CHACHA20:LINUXFABRIK-SSH-NO-ETM',0,Do not remove `LINUXFABRIK-NO-WEAKMAC` from `crypto_policy__policy`. +crypto_policy_disable_ssh_cbc,crypto_policy__policy__cis_var: 'DEFAULT:LINUXFABRIK-NO-SHA1:LINUXFABRIK-NO-WEAKMAC:LINUXFABRIK-SSH-NO-CBC:LINUXFABRIK-SSH-NO-CHACHA20:LINUXFABRIK-SSH-NO-ETM',0,Do not remove `LINUXFABRIK-NO-CBC` from `crypto_policy__policy`. +crypto_policy_disable_ssh_chacha20_poly1305,crypto_policy__policy__cis_var: 'DEFAULT:LINUXFABRIK-NO-SHA1:LINUXFABRIK-NO-WEAKMAC:LINUXFABRIK-SSH-NO-CBC:LINUXFABRIK-SSH-NO-CHACHA20:LINUXFABRIK-SSH-NO-ETM',0,Do not remove `LINUXFABRIK-NO-CHACHA20` from `crypto_policy__policy`. +crypto_policy_disable_ssh_etm,crypto_policy__policy__cis_var: 'DEFAULT:LINUXFABRIK-NO-SHA1:LINUXFABRIK-NO-WEAKMAC:LINUXFABRIK-SSH-NO-CBC:LINUXFABRIK-SSH-NO-CHACHA20:LINUXFABRIK-SSH-NO-ETM',0,Do not remove `LINUXFABRIK-NO-ETM` from `crypto_policy__policy`.