diff --git a/ee/cis/macos-13/cis-policy-queries.yml b/ee/cis/macos-13/cis-policy-queries.yml index af0dd508a5..336c67f145 100644 --- a/ee/cis/macos-13/cis-policy-queries.yml +++ b/ee/cis/macos-13/cis-policy-queries.yml @@ -220,7 +220,7 @@ spec: apiVersion: v1 kind: policy spec: - name: CIS - Ensure Time Is Set Within Appropriate Limits (fleetd required) + name: CIS - Ensure Time Is Set Within Appropriate Limits (Fleetd Required) platforms: macOS platform: darwin description: | @@ -1033,7 +1033,7 @@ spec: apiVersion: v1 kind: policy spec: - name: CIS - Ensure Users' Accounts Do Not Have a Password Hint (fleetd required) + name: CIS - Ensure Users' Accounts Do Not Have a Password Hint (Fleetd Required) platforms: macOS platform: darwin description: | @@ -1640,7 +1640,7 @@ spec: apiVersion: v1 kind: policy spec: - name: CIS - Ensure Password Age Is Configured (fleetd required) + name: CIS - Ensure Password Age Is Configured (Fleetd Required) platforms: macOS platform: darwin description: | @@ -1665,7 +1665,7 @@ spec: apiVersion: v1 kind: policy spec: - name: CIS - Ensure Password History Is Configured (fleetd required) + name: CIS - Ensure Password History Is Configured (Fleetd Required) platforms: macOS platform: darwin description: | @@ -1683,6 +1683,60 @@ spec: --- apiVersion: v1 kind: policy +spec: + name: CIS - Ensure the Sudo Timeout Period Is Set to Zero (Fleetd Required) + platforms: macOS + platform: darwin + description: | + The sudo command stays logged in as the root user for five minutes before timing out and re-requesting a password. + This five-minute window should be eliminated since it leaves the system extremely vulnerable. + resolution: | + Automated method: + Ask your system administrator to deploy the following script: + echo 'Defaults timestamp_timeout=0' | sudo tee -a /etc/sudoers.d/CIS_54_sudoconfiguration + /usr/bin/sudo /usr/sbin/chown -R root:wheel /etc/sudoers.d/ + query: | + SELECT 1 WHERE EXISTS( + SELECT * FROM file WHERE path = '/etc/sudoers.d' AND uid = 0 AND gid = 0 + ) AND EXISTS( + SELECT + COALESCE(JSON_EXTRACT( + json_result, '$.Authentication timestamp timeout' + ), '') AS authentication_timestamp_timeout + FROM sudo_info WHERE authentication_timestamp_timeout = '0.0 minutes' + ); + purpose: Informational + tags: compliance, CIS, CIS_Level1, CIS5.4 +--- +apiVersion: v1 +kind: policy +spec: + name: CIS - Ensure a Separate Timestamp Is Enabled for Each User/tty (Fleetd Required) + platforms: macOS + platform: darwin + description: | + Using tty tickets ensures that a user must enter the sudo password in each Terminal session. + With sudo versions 1.8 and higher, introduced in 10.12, the default value is to have tty tickets + for each interface so that root access is limited to a specific terminal. + The default configuration can be overwritten or not configured correctly on earlier versions of macOS. + resolution: | + Automated method: + Ask your system administrator to deploy the following script: + echo 'Defaults timestamp_type=tty' | sudo tee /etc/sudoers.d/CIS_55_sudoconfiguration + query: | + SELECT 1 WHERE EXISTS( + SELECT + COALESCE(JSON_EXTRACT( + json_result, '$.Type of authentication timestamp record' + ), '') AS type_of_auth_timestamp_record + FROM sudo_info WHERE type_of_auth_timestamp_record = 'tty' + ); + purpose: Informational + tags: compliance, CIS, CIS_Level1, CIS5.5 + contributors: lucasmrod +--- +apiVersion: v1 +kind: policy spec: name: CIS - Ensure an Administrator Account Cannot Login to Another User's Active and Locked Session (Fleetd Required) platforms: macOS diff --git a/ee/cis/macos-13/test/scripts/CIS_5.4.sh b/ee/cis/macos-13/test/scripts/CIS_5.4.sh new file mode 100644 index 0000000000..2a0085ffb9 --- /dev/null +++ b/ee/cis/macos-13/test/scripts/CIS_5.4.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +echo 'Defaults timestamp_timeout=0' | sudo tee /etc/sudoers.d/CIS_54_sudoconfiguration +/usr/bin/sudo /usr/sbin/chown -R root:wheel /etc/sudoers.d/ \ No newline at end of file diff --git a/ee/cis/macos-13/test/scripts/CIS_5.5.sh b/ee/cis/macos-13/test/scripts/CIS_5.5.sh new file mode 100644 index 0000000000..d09a92d551 --- /dev/null +++ b/ee/cis/macos-13/test/scripts/CIS_5.5.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo 'Defaults timestamp_type=tty' | sudo tee /etc/sudoers.d/CIS_55_sudoconfiguration \ No newline at end of file diff --git a/orbit/changes/9260-add-sudo_info-table-cis-5.X b/orbit/changes/9260-add-sudo_info-table-cis-5.X new file mode 100644 index 0000000000..a9954832a7 --- /dev/null +++ b/orbit/changes/9260-add-sudo_info-table-cis-5.X @@ -0,0 +1 @@ +* Add `sudo_info` table to Orbit for CIS checks 5.4 and 5.5 on macOS. diff --git a/orbit/pkg/table/extension_darwin.go b/orbit/pkg/table/extension_darwin.go index 26035ee9d8..251fb4ff8b 100644 --- a/orbit/pkg/table/extension_darwin.go +++ b/orbit/pkg/table/extension_darwin.go @@ -9,6 +9,7 @@ import ( "github.com/fleetdm/fleet/v4/orbit/pkg/table/pmset" "github.com/fleetdm/fleet/v4/orbit/pkg/table/privaterelay" "github.com/fleetdm/fleet/v4/orbit/pkg/table/pwd_policy" + "github.com/fleetdm/fleet/v4/orbit/pkg/table/sudo_info" "github.com/fleetdm/fleet/v4/orbit/pkg/table/user_login_settings" "github.com/macadmins/osquery-extension/tables/filevaultusers" "github.com/macadmins/osquery-extension/tables/macos_profiles" @@ -29,6 +30,7 @@ func platformTables() []osquery.OsqueryPlugin { table.NewPlugin("nvram_info", nvram_info.Columns(), nvram_info.Generate), table.NewPlugin("authdb", authdb.Columns(), authdb.Generate), table.NewPlugin("pmset", pmset.Columns(), pmset.Generate), + table.NewPlugin("sudo_info", sudo_info.Columns(), sudo_info.Generate), // Macadmins extension tables table.NewPlugin("filevault_users", filevaultusers.FileVaultUsersColumns(), filevaultusers.FileVaultUsersGenerate), diff --git a/orbit/pkg/table/sudo_info/sudo_info_darwin.go b/orbit/pkg/table/sudo_info/sudo_info_darwin.go new file mode 100644 index 0000000000..da701256d0 --- /dev/null +++ b/orbit/pkg/table/sudo_info/sudo_info_darwin.go @@ -0,0 +1,76 @@ +//go:build darwin +// +build darwin + +package sudo_info + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "os/exec" + "strings" + + "github.com/osquery/osquery-go/plugin/table" +) + +// Columns is the schema of the table. +func Columns() []table.ColumnDefinition { + return []table.ColumnDefinition{ + table.TextColumn("json_result"), + } +} + +// Generate is called to return the results for the table at query time. +// +// Constraints for generating can be retrieved from the queryContext. +func Generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { + out, err := exec.Command("/usr/bin/sudo", "-V").Output() + if err != nil { + return nil, fmt.Errorf("command failed: %w", err) + } + result := parseSudoVOutput(out) + + jsonResult, err := json.Marshal(result) + if err != nil { + return nil, err + } + + return []map[string]string{{ + "json_result": string(jsonResult), + }}, nil +} + +func parseSudoVOutput(output []byte) map[string]interface{} { + scanner := bufio.NewScanner(bytes.NewReader(output)) + + result := make(map[string]interface{}) + curKey := "" + for scanner.Scan() { + line := scanner.Text() + if strings.TrimSpace(line) == "" { + continue + } + if line[0] == ' ' || line[0] == '\t' { + if result[curKey] == nil { + result[curKey] = []string{} + } + result[curKey] = append(result[curKey].([]string), strings.TrimSpace(line)) + continue + } + colonIndex := strings.IndexByte(line, ':') + if colonIndex == -1 { + curKey = line + result[line] = nil + continue + } + if colonIndex == len(line)-1 { + curKey = line[:len(line)-1] + continue + } + result[line[0:colonIndex]] = line[colonIndex+2:] // +2 because of space character after colon + } + + return result +} diff --git a/orbit/pkg/table/sudo_info/sudo_info_darwin_test.go b/orbit/pkg/table/sudo_info/sudo_info_darwin_test.go new file mode 100644 index 0000000000..aa61b308be --- /dev/null +++ b/orbit/pkg/table/sudo_info/sudo_info_darwin_test.go @@ -0,0 +1,216 @@ +//go:build darwin +// +build darwin + +package sudo_info + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseSudoVOutput(t *testing.T) { + const sample = `Sudo version 1.9.5p2 +Configure options: --with-password-timeout=0 --disable-setreuid --with-env-editor --with-pam --with-libraries=bsm --with-noexec=no --sysconfdir=/private/etc --without-lecture --enable-static-sudoers --with-rundir=/var/db/sudo +Sudoers policy plugin version 1.9.5p2 +Sudoers file grammar version 48 + +Sudoers path: /etc/sudoers +Authentication methods: 'pam' +Syslog facility if syslog is being used for logging: authpriv +Syslog priority to use when user authenticates successfully: notice +Syslog priority to use when user authenticates unsuccessfully: alert +Send mail if the user is not in sudoers +Lecture user the first time they run sudo +File containing the sudo lecture: /etc/sudo_lecture +Require users to authenticate by default +Root may run sudo +Allow some information gathering to give useful error messages +Visudo will honor the EDITOR environment variable +Set the LOGNAME and USER environment variables +Length at which to wrap log file lines (0 for no wrap): 80 +Authentication timestamp timeout: 5.0 minutes +Password prompt timeout: 0.0 minutes +Number of tries to enter a password: 3 +Umask to use or 0777 to use user's: 022 +Path to mail program: /usr/sbin/sendmail +Flags for mail program: -t +Address to send mail to: root +Subject line for mail messages: *** SECURITY information for %h *** +Incorrect password message: Sorry, try again. +Path to lecture status dir: /var/db/sudo/lectured +Path to authentication timestamp dir: /var/db/sudo/ts +Default password prompt: Password: +Default user to run commands as: root +Path to the editor for use by visudo: /usr/bin/vi +When to require a password for 'list' pseudocommand: any +When to require a password for 'verify' pseudocommand: all +File descriptors >= 3 will be closed before executing a command +Reset the environment to a default set of variables +Environment variables to check for safety: + TZ + TERM + LINGUAS + LC_* + LANGUAGE + LANG + COLORTERM +Environment variables to remove: + *=()* + RUBYOPT + RUBYLIB + PYTHONUSERBASE + PYTHONINSPECT + PYTHONPATH + PYTHONHOME + TMPPREFIX + ZDOTDIR + READNULLCMD + NULLCMD + FPATH + PERL5DB + PERL5OPT + PERL5LIB + PERLLIB + PERLIO_DEBUG + JAVA_TOOL_OPTIONS + SHELLOPTS + BASHOPTS + GLOBIGNORE + PS4 + BASH_ENV + ENV + TERMCAP + TERMPATH + TERMINFO_DIRS + TERMINFO + DYLD_* + _RLD* + LD_* + PATH_LOCALE + NLSPATH + HOSTALIASES + RES_OPTIONS + LOCALDOMAIN + CDPATH + IFS +Environment variables to preserve: + MAIL + HOME + VISUAL + EDITOR + TZ + SSH_AUTH_SOCK + LSCOLORS + COLUMNS + LINES + LC_TIME + LC_NUMERIC + LC_MONETARY + LC_MESSAGES + LC_CTYPE + LC_COLLATE + LC_ALL + LANGUAGE + LANG + CHARSET + __CF_USER_TEXT_ENCODING + COLORTERM + COLORFGBG + BLOCKSIZE + XAUTHORIZATION + XAUTHORITY + PS2 + PS1 + PATH + LS_COLORS + KRB5CCNAME + HOSTNAME + DISPLAY + COLORS +Locale to use while parsing sudoers: C +Compress I/O logs using zlib +Directory in which to store input/output logs: /var/log/sudo-io +File in which to store the input/output log: %{seq} +Add an entry to the utmp/utmpx file when allocating a pty +PAM service name to use: sudo +PAM service name to use for login shells: sudo +Attempt to establish PAM credentials for the target user +Create a new PAM session for the command to run in +Perform PAM account validation management +Enable sudoers netgroup support +Check parent directories for writability when editing files with sudoedit +Allow commands to be run even if sudo cannot write to the audit log +Allow commands to be run even if sudo cannot write to the log file +Log entries larger than this value will be split into multiple syslog messages: 960 +File mode to use for the I/O log files: 0600 +Execute commands by file descriptor instead of by path: digest_only +Type of authentication timestamp record: tty +Ignore case when matching user names +Ignore case when matching group names +Log when a command is allowed by sudoers +Log when a command is denied by sudoers +Sudo log server timeout in seconds: 30 +Enable SO_KEEPALIVE socket option on the socket connected to the logserver +Verify that the log server's certificate is valid +Set the pam remote user to the user running sudo +The format of logs to produce: sudo + +Local IP address and netmask pairs: + fe80::aede:48ff:fe00:1122/ffff:ffff:ffff:ffff:: + fe80::142f:6d87:1e52:591d/ffff:ffff:ffff:ffff:: + 192.168.0.230/255.255.255.0 + fe80::70f9:67ff:fe50:983f/ffff:ffff:ffff:ffff:: + fe80::70f9:67ff:fe50:983f/ffff:ffff:ffff:ffff:: + fe80::8372:c8a:ecf8:40b5/ffff:ffff:ffff:ffff:: + fe80::612f:3c9d:f33a:9e7d/ffff:ffff:ffff:ffff:: + fe80::ce81:b1c:bd2c:69e/ffff:ffff:ffff:ffff:: + 192.168.103.1/255.255.255.0 + fe80::682f:67ff:fee8:7464/ffff:ffff:ffff:ffff:: + 172.16.132.1/255.255.255.0 + fe80::682f:67ff:fee8:7465/ffff:ffff:ffff:ffff:: + +Sudoers I/O plugin version 1.9.5p2 +Sudoers audit plugin version 1.9.5p2` + result := parseSudoVOutput([]byte(sample)) + _, err := json.Marshal(result) + require.NoError(t, err) + + // First line. + require.Contains(t, result, "Sudo version 1.9.5p2") + require.Nil(t, result["Sudo version 1.9.5p2"]) + + // Key without value. + require.Contains(t, result, "Compress I/O logs using zlib") + require.Nil(t, result["Compress I/O logs using zlib"]) + + // Key and value pairs defined in one line. + require.Equal(t, "C", result["Locale to use while parsing sudoers"]) + require.Equal(t, "tty", result["Type of authentication timestamp record"]) + require.Equal(t, "5.0 minutes", result["Authentication timestamp timeout"]) + + // Last line. + require.Contains(t, result, "Sudoers audit plugin version 1.9.5p2") + require.Nil(t, result["Sudoers audit plugin version 1.9.5p2"]) + + // Key and value paris defined in multiple lines + v := result["Local IP address and netmask pairs"] + require.NotNil(t, v) + localIPAddrAndNetmaskPairs, ok := v.([]string) + require.True(t, ok) + require.Len(t, localIPAddrAndNetmaskPairs, 12) + require.Contains(t, localIPAddrAndNetmaskPairs, "fe80::aede:48ff:fe00:1122/ffff:ffff:ffff:ffff::") + require.Contains(t, localIPAddrAndNetmaskPairs, "192.168.103.1/255.255.255.0") + require.Contains(t, localIPAddrAndNetmaskPairs, "fe80::682f:67ff:fee8:7465/ffff:ffff:ffff:ffff::") + v = result["Environment variables to preserve"] + require.NotNil(t, v) + envVariablesToPreserve, ok := v.([]string) + require.True(t, ok) + require.Len(t, envVariablesToPreserve, 33) + require.Contains(t, envVariablesToPreserve, "MAIL") + require.Contains(t, envVariablesToPreserve, "COLORS") + + // Line with multiple colons: + require.Equal(t, "Password: ", result["Default password prompt"]) +} diff --git a/schema/tables/sudo_info.yml b/schema/tables/sudo_info.yml new file mode 100644 index 0000000000..645277bdce --- /dev/null +++ b/schema/tables/sudo_info.yml @@ -0,0 +1,12 @@ +name: sudo_info +platforms: + - darwin +description: Returns the output of `sudo -V` in JSON format. +columns: + - name: json_result + type: text + required: false + description: A JSON document with the key value pairs parsed from `sudo -V` output. +notes: >- + - This table is not a core osquery table. It is included as part of Fleetd, the osquery manager from Fleet. +evented: false diff --git a/tools/tuf/test/create_repository.sh b/tools/tuf/test/create_repository.sh index fbd2acaf54..99d03aeca7 100755 --- a/tools/tuf/test/create_repository.sh +++ b/tools/tuf/test/create_repository.sh @@ -24,7 +24,7 @@ if [[ -d "$TUF_PATH" ]]; then exit 0 fi -OSQUERY_MACOS_APP_BUNDLE_VERSION=5.6.0 +OSQUERY_MACOS_APP_BUNDLE_VERSION=5.7.0 SYSTEMS=${SYSTEMS:-macos linux windows} mkdir -p $TUF_PATH/tmp