From 9436c28ca45d8d33cc05254329c6884fc7a18511 Mon Sep 17 00:00:00 2001 From: Dominik Richter Date: Wed, 23 Jul 2014 15:46:04 +0200 Subject: [PATCH 1/3] rename modules_disabled -> lockdown I.e. create tests for a special hardening profile whose configuration is to lock down all settings. This will include scanning for all unkown SUID-bits as well as kernel configuration with module lockdown. Signed-off-by: Dominik Richter --- {modules_disabled => lockdown}/serverspec/modules_spec.rb | 0 {modules_disabled => lockdown}/serverspec/spec_helper.rb | 0 {modules_disabled => lockdown}/serverspec/sysctl_spec.rb | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {modules_disabled => lockdown}/serverspec/modules_spec.rb (100%) rename {modules_disabled => lockdown}/serverspec/spec_helper.rb (100%) rename {modules_disabled => lockdown}/serverspec/sysctl_spec.rb (100%) diff --git a/modules_disabled/serverspec/modules_spec.rb b/lockdown/serverspec/modules_spec.rb similarity index 100% rename from modules_disabled/serverspec/modules_spec.rb rename to lockdown/serverspec/modules_spec.rb diff --git a/modules_disabled/serverspec/spec_helper.rb b/lockdown/serverspec/spec_helper.rb similarity index 100% rename from modules_disabled/serverspec/spec_helper.rb rename to lockdown/serverspec/spec_helper.rb diff --git a/modules_disabled/serverspec/sysctl_spec.rb b/lockdown/serverspec/sysctl_spec.rb similarity index 100% rename from modules_disabled/serverspec/sysctl_spec.rb rename to lockdown/serverspec/sysctl_spec.rb From 69546f61ffe1da5287c9aed2314a86476bbef316 Mon Sep 17 00:00:00 2001 From: Dominik Richter Date: Wed, 23 Jul 2014 15:50:17 +0200 Subject: [PATCH 2/3] add all current requirements from default -> lockdown Signed-off-by: Dominik Richter --- lockdown/serverspec/os_spec.rb | 129 +++++++++++++++++++ lockdown/serverspec/spec_helper.rb | 59 ++++++++- lockdown/serverspec/sysctl_spec.rb | 198 +++++++++++++++++++++++++++++ 3 files changed, 379 insertions(+), 7 deletions(-) create mode 100644 lockdown/serverspec/os_spec.rb diff --git a/lockdown/serverspec/os_spec.rb b/lockdown/serverspec/os_spec.rb new file mode 100644 index 0000000..b5753b1 --- /dev/null +++ b/lockdown/serverspec/os_spec.rb @@ -0,0 +1,129 @@ +# encoding: utf-8 +# +# Copyright 2014, Deutsche Telekom AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +RSpec.configure do |c| + c.filter_run_excluding skipOn: backend(Serverspec::Commands::Base).check_os[:family] +end + +# GIS: Req 3.21-4 +describe command('find / -name \'.rhosts\' | wc -l ') do + its(:stdout) { should match(/^0/) } +end + +# GIS: Req 3.21-4 +describe command('find / -name \'hosts.equiv\' | wc -l ') do + its(:stdout) { should match(/^0/) } +end + +# GIS: Req 3.21-7 +describe file('/etc/shadow') do + it { should be_owned_by 'root' } +end + +# GIS: Req 3.21-7 +describe file('/etc/shadow') do + it { should be_mode 600 } +end + +# GIS: Req 3.21-8 +describe command('echo $PATH | grep -ci \'\.\'') do + its(:stdout) { should match(/^0/) } +end + +# GIS: Req 3.21-8 +describe file('/etc/login.defs') do + its(:content) { should match(%r{^ENV_SUPATH\s+PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}) } +end + +# GIS: Req 3.21-8 +describe file('/etc/login.defs') do + its(:content) { should match(%r{^ENV_PATH\s+PATH=/usr/local/bin:/usr/bin:/bin}) } +end + +# GIS: Req 3.21-10 +describe file('/etc/login.defs') do + its(:content) { should match(/^UMASK +027/) } +end + +# GIS: Req 3.21-12 +describe 'SUID/ SGID whitelist check' do + it 'found only whitelist suid/sgid' do + whitelist = [ + # whitelist as provided by NSA + '/bin/mount', '/bin/ping', '/bin/su', '/bin/umount', '/sbin/pam_timestamp_check', + '/sbin/unix_chkpwd', '/usr/bin/at', '/usr/bin/gpasswd', '/usr/bin/locate', + '/usr/bin/newgrp', '/usr/bin/passwd', '/usr/bin/ssh-agent', '/usr/libexec/utempter/utempter', '/usr/sbin/lockdev', + '/usr/sbin/sendmail.sendmail', '/usr/bin/expiry', + # whitelist ipv6 + '/bin/ping6', '/usr/bin/traceroute6.iputils', + # whitelist nfs + '/sbin/mount.nfs', '/sbin/umount.nfs', + # whitelist nfs4 + '/sbin/mount.nfs4', '/sbin/umount.nfs4', + # whitelist cron + '/usr/bin/crontab', + # whitelist consolemssaging + '/usr/bin/wall', '/usr/bin/write', + # whitelist: only SGID with utmp group for multi-session access + # impact is limited; installation/usage has some remaining risk + '/usr/bin/screen', + # whitelist locate + '/usr/bin/mlocate', + # whitelist usermanagement + '/usr/bin/chage', '/usr/bin/chfn', '/usr/bin/chsh', + # whitelist fuse + '/bin/fusermount', + # whitelist pkexec + '/usr/bin/pkexec', + # whitelist sudo + '/usr/bin/sudo', '/usr/bin/sudoedit', + # whitelist postfix + '/usr/sbin/postdrop', '/usr/sbin/postqueue', + # whitelist apache + '/usr/sbin/suexec', + # whitelist squid + '/usr/lib/squid/ncsa_auth', '/usr/lib/squid/pam_auth', + # whitelist kerberos + '/usr/kerberos/bin/ksu', + # whitelist pam_caching + '/usr/sbin/ccreds_validate', + # whitelist Xorg + '/usr/bin/Xorg', # xorg + '/usr/bin/X', # xorg + '/usr/lib/dbus-1.0/dbus-daemon-launch-helper', # freedesktop ipc + '/usr/lib/vte/gnome-pty-helper', # gnome + '/usr/lib/libvte9/gnome-pty-helper', # gnome + '/usr/lib/libvte-2.90-9/gnome-pty-helper' # gnome + ] + actual = command('find / -perm -4000 -o -perm -2000 -type f ! -path \'/proc/*\' -print 2>/dev/null | grep -v \'^find:\'').stdout.split(/\r?\n/) + (actual - whitelist).count.should be 0 + end +end + +# GIS: Req 3.21-16 +describe 'Unique uid' do + it 'check for unique uids' do + actual = command('cat /etc/passwd | cut -d \':\' -f 3').stdout.split(/\r?\n/) + hm = actual.each_with_object(Hash.new(0)) { |d, counts| counts[d] += 1 } + hm.each do |k, v| + str = "User: UID #{k} instances: " + ("#{str}#{v}").should eq("#{str}1") + end + end +end diff --git a/lockdown/serverspec/spec_helper.rb b/lockdown/serverspec/spec_helper.rb index 9323aea..f844cc5 100644 --- a/lockdown/serverspec/spec_helper.rb +++ b/lockdown/serverspec/spec_helper.rb @@ -15,14 +15,59 @@ # limitations under the License. # -require 'serverspec' -require 'pathname' +if ENV['STANDALONE_SPEC'] -include Serverspec::Helper::Exec -include Serverspec::Helper::DetectOS + require 'serverspec' + require 'pathname' + require 'net/ssh' + require 'highline/import' -RSpec.configure do |c| - c.before :all do - c.os = backend(Serverspec::Commands::Base).check_os + include Serverspec::Helper::Ssh + include Serverspec::Helper::DetectOS + + RSpec.configure do |c| + + if ENV['ASK_SUDO_PASSWORD'] + c.sudo_password = ask('Enter sudo password: ') { |q| q.echo = false } + else + c.sudo_password = ENV['SUDO_PASSWORD'] + end + + options = {} + + if ENV['ASK_LOGIN_PASSWORD'] + options[:password] = ask("\nEnter login password: ") { |q| q.echo = false } + else + options[:password] = ENV['LOGIN_PASSWORD'] + end + + if ENV['ASK_LOGIN_USERNAME'] + user = ask("\nEnter login username: ") { |q| q.echo = false } + else + user = ENV['LOGIN_USERNAME'] || ENV['user'] || Etc.getlogin + end + + if user.nil? + puts 'specify login user env LOGIN_USERNAME= or user=' + exit 1 + end + + c.host = ENV['TARGET_HOST'] + options.merge(Net::SSH::Config.for(c.host)) + c.ssh = Net::SSH.start(c.host, user, options) + c.os = backend.check_os + + end + +else + require 'serverspec' + + include Serverspec::Helper::Exec + include Serverspec::Helper::DetectOS + + RSpec.configure do |c| + c.before :all do + c.path = '/sbin:/usr/sbin' + end end end diff --git a/lockdown/serverspec/sysctl_spec.rb b/lockdown/serverspec/sysctl_spec.rb index 6718f9a..cb1cd30 100644 --- a/lockdown/serverspec/sysctl_spec.rb +++ b/lockdown/serverspec/sysctl_spec.rb @@ -17,10 +17,208 @@ require 'spec_helper' +RSpec.configure do |c| + c.filter_run_excluding skipOn: backend(Serverspec::Commands::Base).check_os[:family] +end + +describe 'IP V4 networking' do + + # GIS: Req 3.21-1 + context linux_kernel_parameter('net.ipv4.ip_forward') do + its(:value) { should eq 0 } + end + + # GIS: Req 3.21-1 + context linux_kernel_parameter('net.ipv4.conf.all.forwarding') do + its(:value) { should eq 0 } + end + + # GIS: Req 3.21-3 + context linux_kernel_parameter('net.ipv4.conf.all.rp_filter') do + its(:value) { should eq 1 } + end + + # GIS: Req 3.21-3 ; GIS: Req 3.37-10 + context linux_kernel_parameter('net.ipv4.conf.default.rp_filter') do + its(:value) { should eq 1 } + end + + # GIS: Req 3.21-1 + context linux_kernel_parameter('net.ipv4.icmp_echo_ignore_broadcasts') do + its(:value) { should eq 1 } + end + + context linux_kernel_parameter('net.ipv4.icmp_ignore_bogus_error_responses') do + its(:value) { should eq 1 } + end + + # GIS: Req 3.01-9 + context linux_kernel_parameter('net.ipv4.icmp_ratelimit') do + its(:value) { should eq 100 } + end + + context linux_kernel_parameter('net.ipv4.icmp_ratemask') do + its(:value) { should eq 88089 } + end + + context linux_kernel_parameter('net.ipv4.tcp_timestamps') do + its(:value) { should eq 0 } + end + + # GIS: Req 3.21-3 + context linux_kernel_parameter('net.ipv4.conf.all.arp_ignore') do + its(:value) { should eq 1 } + end + + # GIS: Req 3.21-3 + context linux_kernel_parameter('net.ipv4.conf.all.arp_announce') do + its(:value) { should eq 2 } + end + + context linux_kernel_parameter('net.ipv4.tcp_rfc1337') do + its(:value) { should eq 1 } + end + + context linux_kernel_parameter('net.ipv4.tcp_syncookies') do + its(:value) { should eq 1 } + end + + context linux_kernel_parameter('net.ipv4.conf.all.shared_media') do + its(:value) { should eq 1 } + end + + context linux_kernel_parameter('net.ipv4.conf.default.shared_media') do + its(:value) { should eq 1 } + end + + # GIS: Req 3.37-12 + context linux_kernel_parameter('net.ipv4.conf.all.accept_source_route') do + its(:value) { should eq 0 } + end + + # GIS: Req 3.37-12 + context linux_kernel_parameter('net.ipv4.conf.default.accept_source_route') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv4.conf.default.accept_redirects') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv4.conf.all.accept_redirects') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv4.conf.all.secure_redirects') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv4.conf.default.secure_redirects') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv4.conf.all.send_redirects') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv4.conf.all.send_redirects') do + its(:value) { should eq 0 } + end + + # log_martians can cause a denial of service attack to the host + context linux_kernel_parameter('net.ipv4.conf.all.log_martians') do + its(:value) { should eq 0 } + end + +end + +describe 'IP V6 Networking' do + + context linux_kernel_parameter('net.ipv6.conf.all.disable_ipv6') do + its(:value) { should eq 1 } + end + + # GIS: Req 3.21-1 + context linux_kernel_parameter('net.ipv6.conf.all.forwarding') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv6.conf.default.accept_redirects') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv6.conf.all.accept_redirects') do + its(:value) { should eq 0 } + end + +end + +describe 'NSA 2.5.3.2.5 Limit Network-Transmitted Configuration' do + + context linux_kernel_parameter('net.ipv6.conf.default.router_solicitations') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv6.conf.default.accept_ra_rtr_pref') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv6.conf.default.accept_ra_pinfo') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv6.conf.default.accept_ra_defrtr') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv6.conf.default.autoconf') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv6.conf.default.dad_transmits') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('net.ipv6.conf.default.max_addresses') do + its(:value) { should eq 1 } + end + +end + describe 'System sysctl' do context linux_kernel_parameter('kernel.modules_disabled') do its(:value) { should eq 1 } end + context linux_kernel_parameter('kernel.sysrq') do + its(:value) { should eq 0 } + end + + context linux_kernel_parameter('fs.suid_dumpable') do + its(:value) { should eq 0 } + end +end + +describe 'ExecShield' do + + # GIS: Req 3.21-5 + # check if we find the nx flag + if command('cat /proc/cpuinfo').return_stdout?(/^flags.*?:.*? nx( .*?)?$/) + true + else + # if no nx flag is present, we require exec-shield + context 'No nx flag detected' do + it 'require kernel.exec-shield' do + context linux_kernel_parameter('kernel.exec-shield') do + its(:value) { should eq 1 } + end + end + end + end + + # GIS: Req 3.21-5 + context linux_kernel_parameter('kernel.randomize_va_space') do + its(:value) { should eq 2 } + end end From de8b8f15fbef7562deb52f5ba07c76950edfe91e Mon Sep 17 00:00:00 2001 From: Dominik Richter Date: Wed, 23 Jul 2014 15:59:08 +0200 Subject: [PATCH 3/3] default profile checks SUID/SGID blacklist Instead of going for the whitelist and expecting all other SUID/SGID bits to be removed, go for the blacklist in the default profile. This behavior is preferred, since we don't want to enable a search through all nodes on a system for any SUID/SGID bits by default. This search is desired and reasonable in all cases, but many new users will be turned away if we activate it by default. It causes issues with any regularly mounted network filesystems (which take very long) or very large (amount of entries on the filesystem) storage nodes. We will add this point to the documentation, as it's the user's task to mount these components with a nosuid configuration. Signed-off-by: Dominik Richter --- default/serverspec/os_spec.rb | 79 +++++++++++++---------------------- 1 file changed, 30 insertions(+), 49 deletions(-) diff --git a/default/serverspec/os_spec.rb b/default/serverspec/os_spec.rb index b5753b1..6f41d8b 100644 --- a/default/serverspec/os_spec.rb +++ b/default/serverspec/os_spec.rb @@ -62,57 +62,38 @@ end # GIS: Req 3.21-12 -describe 'SUID/ SGID whitelist check' do - it 'found only whitelist suid/sgid' do - whitelist = [ - # whitelist as provided by NSA - '/bin/mount', '/bin/ping', '/bin/su', '/bin/umount', '/sbin/pam_timestamp_check', - '/sbin/unix_chkpwd', '/usr/bin/at', '/usr/bin/gpasswd', '/usr/bin/locate', - '/usr/bin/newgrp', '/usr/bin/passwd', '/usr/bin/ssh-agent', '/usr/libexec/utempter/utempter', '/usr/sbin/lockdev', - '/usr/sbin/sendmail.sendmail', '/usr/bin/expiry', - # whitelist ipv6 - '/bin/ping6', '/usr/bin/traceroute6.iputils', - # whitelist nfs - '/sbin/mount.nfs', '/sbin/umount.nfs', - # whitelist nfs4 - '/sbin/mount.nfs4', '/sbin/umount.nfs4', - # whitelist cron - '/usr/bin/crontab', - # whitelist consolemssaging - '/usr/bin/wall', '/usr/bin/write', - # whitelist: only SGID with utmp group for multi-session access - # impact is limited; installation/usage has some remaining risk - '/usr/bin/screen', - # whitelist locate - '/usr/bin/mlocate', - # whitelist usermanagement - '/usr/bin/chage', '/usr/bin/chfn', '/usr/bin/chsh', - # whitelist fuse - '/bin/fusermount', - # whitelist pkexec - '/usr/bin/pkexec', - # whitelist sudo - '/usr/bin/sudo', '/usr/bin/sudoedit', - # whitelist postfix - '/usr/sbin/postdrop', '/usr/sbin/postqueue', - # whitelist apache - '/usr/sbin/suexec', - # whitelist squid - '/usr/lib/squid/ncsa_auth', '/usr/lib/squid/pam_auth', - # whitelist kerberos - '/usr/kerberos/bin/ksu', - # whitelist pam_caching - '/usr/sbin/ccreds_validate', - # whitelist Xorg - '/usr/bin/Xorg', # xorg - '/usr/bin/X', # xorg - '/usr/lib/dbus-1.0/dbus-daemon-launch-helper', # freedesktop ipc - '/usr/lib/vte/gnome-pty-helper', # gnome - '/usr/lib/libvte9/gnome-pty-helper', # gnome - '/usr/lib/libvte-2.90-9/gnome-pty-helper' # gnome +describe 'SUID/ SGID blacklist check' do + it 'found no blacklisted suid/sgid' do + blacklist = [ + # blacklist as provided by NSA + '/usr/bin/rcp', '/usr/bin/rlogin', '/usr/bin/rsh', + # sshd must not use host-based authentication (see ssh cookbook) + '/usr/libexec/openssh/ssh-keysign', + '/usr/lib/openssh/ssh-keysign', + # misc others + '/sbin/netreport', # not normally required for user + '/usr/sbin/usernetctl', # modify interfaces via functional accounts + # connecting to ... + '/usr/sbin/userisdnctl', # no isdn... + '/usr/sbin/pppd', # no ppp / dsl ... + # lockfile + '/usr/bin/lockfile', + '/usr/bin/mail-lock', + '/usr/bin/mail-unlock', + '/usr/bin/mail-touchlock', + '/usr/bin/dotlockfile', + # need more investigation, blacklist for now + '/usr/bin/arping', + '/usr/sbin/uuidd', + '/usr/bin/mtr', # investigate current state... + '/usr/lib/evolution/camel-lock-helper-1.2', # investigate current state... + '/usr/lib/pt_chown', # pseudo-tty, needed? + '/usr/lib/eject/dmcrypt-get-device', + '/usr/lib/mc/cons.saver' # midnight commander screensaver ] + actual = command('find / -perm -4000 -o -perm -2000 -type f ! -path \'/proc/*\' -print 2>/dev/null | grep -v \'^find:\'').stdout.split(/\r?\n/) - (actual - whitelist).count.should be 0 + (actual & blacklist).count.should be 0 end end