diff --git a/ee/cis/macos-13/cis-policy-queries.yml b/ee/cis/macos-13/cis-policy-queries.yml index 0435b96020..5a3399c405 100644 --- a/ee/cis/macos-13/cis-policy-queries.yml +++ b/ee/cis/macos-13/cis-policy-queries.yml @@ -1270,6 +1270,42 @@ spec: --- apiVersion: v1 kind: policy +spec: + name: CIS - Ensure Apple Mobile File Integrity (AMFI) Is Enabled (fleetd required) + platforms: macOS + platform: darwin + description: | + Apple Mobile File Integrity (AMFI) was first released in macOS 10.12. The daemon and service block attempts to run unsigned code. AMFI uses launchd, code signatures, certificates, entitlements, and provisioning profiles to create a filtered entitlement dictionary for an app. AMFI is the macOS kernel module that enforces code-signing and library validation. + Note: AMFI cannot be disabled with SIP enabled, but a change attempt can be made that will appear successful, and report incorrectly as successful. If the AMFI audit fails, and the SIP audit passes, this is still an issue the admin should research. + resolution: | + Automated method: + Ask your system administrator to deploy the following script which will Ensure Apple Mobile File Integrity (AMFI) Is Enabled: + /usr/bin/sudo /usr/sbin/nvram boot-args="" + query: SELECT 1 FROM nvram_info WHERE amfi_enabled="1"; + purpose: Informational + tags: compliance, CIS, CIS_Level1, CIS5.1.3 + contributors: sharon-fdm +--- +apiVersion: v1 +kind: policy +spec: + name: CIS - Ensure Sealed System Volume (SSV) Is Enabled (fleetd required) + platforms: macOS + platform: darwin + description: | + Sealed System Volume is a security feature introduced in macOS 11.0 Big Sur. + During system installation, a SHA-256 cryptographic hash is calculated for all immutable system files and stored in a Merkle tree which itself is hashed as the Seal. Both are stored in the metadata of the snapshot created of the System volume. + The seal is verified by the boot loader at startup. macOS will not boot if system files have been tampered with. If validation fails, the user will be instructed to reinstall the operating system. + During read operations for files located in the Sealed System Volume, a hash is calculated and compared to the value stored in the Merkle tree. + resolution: | + If SSV has been disabled, assume that the operating system has been compromised. Back up any files, and do a clean install to a known good Operating System. + query: SELECT 1 FROM csrutil_info WHERE ssv_enabled="1"; + purpose: Informational + tags: compliance, CIS, CIS_Level1, CIS5.1.4 + contributors: sharon-fdm +--- +apiVersion: v1 +kind: policy spec: name: CIS - Ensure Password Account Lockout Threshold Is Configured (Fleetd required) platforms: macOS diff --git a/orbit/changes/orbit-extensions-csrutil_info b/orbit/changes/orbit-extensions-csrutil_info new file mode 100644 index 0000000000..45ae2df7f2 --- /dev/null +++ b/orbit/changes/orbit-extensions-csrutil_info @@ -0,0 +1 @@ +- Implement table to hold csrutil_info extension via Orbit diff --git a/orbit/changes/orbit-extensions-nvram-info b/orbit/changes/orbit-extensions-nvram-info new file mode 100644 index 0000000000..d187c9048b --- /dev/null +++ b/orbit/changes/orbit-extensions-nvram-info @@ -0,0 +1 @@ +- Implement table to hold nvram_info extension via Orbit diff --git a/orbit/pkg/table/csrutil_info/csrutil_info.go b/orbit/pkg/table/csrutil_info/csrutil_info.go new file mode 100644 index 0000000000..cd53ba1a78 --- /dev/null +++ b/orbit/pkg/table/csrutil_info/csrutil_info.go @@ -0,0 +1,54 @@ +//go:build darwin +// +build darwin + +package csrutil_info + +import ( + "context" + "github.com/osquery/osquery-go/plugin/table" + "github.com/rs/zerolog/log" + "os/exec" + "strings" + "time" +) + +// Columns is the schema of the table. +func Columns() []table.ColumnDefinition { + return []table.ColumnDefinition{ + table.IntegerColumn("ssv_enabled"), + } +} + +// 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) { + SSVEnabled, err := getSSVEnabled(ctx) + return []map[string]string{ + {"ssv_enabled": SSVEnabled}, + }, err +} + +func getSSVEnabled(ctx context.Context) (SSVEnabled string, err error) { + res, err := runCommand(ctx, "/usr/bin/csrutil", "authenticated-root", "status") + SSVEnabled = "" + if err == nil { + SSVEnabled = "0" + if strings.Contains(res, "Authenticated Root status: enabled") { + SSVEnabled = "1" + } + } + return SSVEnabled, err +} + +func runCommand(ctx context.Context, name string, arg ...string) (res string, err error) { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, name, arg...) + + out, err := cmd.Output() + if err != nil { + log.Debug().Err(err).Msg("failed while generating csrutil_info table") + return "", err + } + return string(out), nil +} diff --git a/orbit/pkg/table/csrutil_info/csrutil_info_test.go b/orbit/pkg/table/csrutil_info/csrutil_info_test.go new file mode 100644 index 0000000000..c32ca6bbb6 --- /dev/null +++ b/orbit/pkg/table/csrutil_info/csrutil_info_test.go @@ -0,0 +1,37 @@ +//go:build darwin +// +build darwin + +package csrutil_info + +import ( + "github.com/osquery/osquery-go/plugin/table" + "golang.org/x/net/context" + "testing" + "time" +) + +func TestGenerate(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + var tbl table.QueryContext + + table, err := Generate(ctx, tbl) + if err != nil { + t.Fatalf(`Expected no error. got %s`, err) + } + if table[0]["ssv_enabled"] != "0" && table[0]["ssv_enabled"] != "1" { + t.Fatalf(`ssvEnabled expected 0 or 1. got %s`, table[0]["ssvEnabled"]) + } +} + +func TestGetSSVEnabled(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + ssvEnabled, err := getSSVEnabled(ctx) + if ssvEnabled != "0" && ssvEnabled != "1" { + t.Fatalf(`ssvEnabled expected 0 or 1. got %s`, ssvEnabled) + } + if err != nil { + t.Fatalf(`Expected no error. got %s`, err) + } +} diff --git a/orbit/pkg/table/extension_darwin.go b/orbit/pkg/table/extension_darwin.go index 5620cc15d7..bddd5c5df5 100644 --- a/orbit/pkg/table/extension_darwin.go +++ b/orbit/pkg/table/extension_darwin.go @@ -3,6 +3,8 @@ package table import ( + "github.com/fleetdm/fleet/v4/orbit/pkg/table/csrutil_info" + "github.com/fleetdm/fleet/v4/orbit/pkg/table/nvram_info" "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/user_login_settings" @@ -22,6 +24,8 @@ func platformTables() []osquery.OsqueryPlugin { table.NewPlugin("icloud_private_relay", privaterelay.Columns(), privaterelay.Generate), table.NewPlugin("user_login_settings", user_login_settings.Columns(), user_login_settings.Generate), table.NewPlugin("pwd_policy", pwd_policy.Columns(), pwd_policy.Generate), + table.NewPlugin("csrutil_info", csrutil_info.Columns(), csrutil_info.Generate), + table.NewPlugin("nvram_info", nvram_info.Columns(), nvram_info.Generate), // Macadmins extension tables table.NewPlugin("filevault_users", filevaultusers.FileVaultUsersColumns(), filevaultusers.FileVaultUsersGenerate), diff --git a/orbit/pkg/table/nvram_info/nvram_info.go b/orbit/pkg/table/nvram_info/nvram_info.go new file mode 100644 index 0000000000..f70fcaa622 --- /dev/null +++ b/orbit/pkg/table/nvram_info/nvram_info.go @@ -0,0 +1,55 @@ +//go:build darwin +// +build darwin + +package nvram_info + +import ( + "context" + "github.com/osquery/osquery-go/plugin/table" + "github.com/rs/zerolog/log" + "os/exec" + "strings" + "time" +) + +// Columns is the schema of the table. +func Columns() []table.ColumnDefinition { + return []table.ColumnDefinition{ + table.IntegerColumn("amfi_enabled"), + } +} + +// 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) { + amfiEnabled, err := getAMFIEnabled(ctx) + + return []map[string]string{ + {"amfi_enabled": amfiEnabled}, + }, err +} + +func getAMFIEnabled(ctx context.Context) (amfiEnabled string, err error) { + res, err := runCommand(ctx, "/usr/sbin/nvram", "-p") + amfiEnabled = "" + if err == nil { + amfiEnabled = "0" + if !strings.Contains(res, "amfi_get_out_of_my_way=1") { + amfiEnabled = "1" + } + } + return amfiEnabled, err +} + +func runCommand(ctx context.Context, name string, arg ...string) (res string, err error) { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, name, arg...) + + out, err := cmd.Output() + if err != nil { + log.Debug().Err(err).Msg("failed while generating nvram table") + return "", err + } + return string(out), nil +} diff --git a/orbit/pkg/table/nvram_info/nvram_info_test.go b/orbit/pkg/table/nvram_info/nvram_info_test.go new file mode 100644 index 0000000000..b25f5f866b --- /dev/null +++ b/orbit/pkg/table/nvram_info/nvram_info_test.go @@ -0,0 +1,37 @@ +//go:build darwin +// +build darwin + +package nvram_info + +import ( + "github.com/osquery/osquery-go/plugin/table" + "golang.org/x/net/context" + "testing" + "time" +) + +func TestGenerate(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + var tbl table.QueryContext + + table, err := Generate(ctx, tbl) + if err != nil { + t.Fatalf(`Expected no error. got %s`, err) + } + if table[0]["amfi_enabled"] != "0" && table[0]["amfi_enabled"] != "1" { + t.Fatalf(`amfiEnabled expected 0 or 1. got %s`, table[0]["amfi_enabled"]) + } +} + +func TestGetAMFIEnabled(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + amfiEnabled, err := getAMFIEnabled(ctx) + if amfiEnabled != "0" && amfiEnabled != "1" { + t.Fatalf(`amfiEnabled expected 0 or 1. got %s`, amfiEnabled) + } + if err != nil { + t.Fatalf(`Expected no error. got %s`, err) + } +} diff --git a/schema/tables/csrutil_info.yml b/schema/tables/csrutil_info.yml new file mode 100644 index 0000000000..9e06b8fa08 --- /dev/null +++ b/schema/tables/csrutil_info.yml @@ -0,0 +1,16 @@ +name: csrutil_info +platforms: + - darwin +description: Information from csrutil system call. +columns: + - name: ssv_enabled + type: integer + required: false + description: | + Sealed System Volume is a security feature introduced in macOS 11.0 Big Sur. + During system installation, a SHA-256 cryptographic hash is calculated for all immutable system files and stored in a Merkle tree which itself is hashed as the Seal. Both are stored in the metadata of the snapshot created of the System volume. + The seal is verified by the boot loader at startup. macOS will not boot if system files have been tampered with. If validation fails, the user will be instructed to reinstall the operating system. + During read operations for files located in the Sealed System Volume, a hash is calculated and compared to the value stored in the Merkle tree. +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/schema/tables/nvram_info.yml b/schema/tables/nvram_info.yml new file mode 100644 index 0000000000..8a7b868fa5 --- /dev/null +++ b/schema/tables/nvram_info.yml @@ -0,0 +1,14 @@ +name: nvram_info +platforms: + - darwin +description: Information from nvram system call. +columns: + - name: amfi_enabled + type: integer + required: false + description: | + Apple Mobile File Integrity (AMFI) was first released in macOS 10.12. The daemon and service block attempts to run unsigned code. AMFI uses lanchd, code signatures, certificates, entitlements, and provisioning profiles to create a filtered entitlement dictionary for an app. AMFI is the macOS kernel module that enforces code-signing and library validation. + Note: AMFI cannot be disabled with SIP enabled, but a change attempt can be made that will appear successful, and report incorrectly as successful. If the AMFI audit fails, and the SIP audit passes, this is still an issue the admin should research. +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/schema/tables/pwd_policy.yml b/schema/tables/pwd_policy.yml index aa66347313..20d797f6fc 100644 --- a/schema/tables/pwd_policy.yml +++ b/schema/tables/pwd_policy.yml @@ -3,22 +3,22 @@ platforms: - darwin description: Password Policiy (e.g max failed password attempts). columns: - - name: maxFailedAttempts + - name: max_failed_attempts type: integer required: false description: | The account lockout threshold specifies the amount of times a user can enter an incorrect password before a lockout will occur. Ensure that a lockout threshold is part of the password policy on the computer. - - name: expiresEveryNDays + - name: expires_every_n_days type: integer required: false description: | How many days for a new password to expire. - - name: daysToExpiration + - name: days_to_expiration type: integer required: false description: | How many days are left for the expiration of the current password. - - name: historyDepth + - name: history_depth type: integer required: false description: |