Add check for CIS 5.6 (#9756)

#9260

- [X] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or
docs/Contributing/API-for-contributors.md)~
- ~[ ] Documented any permissions changes~
- ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)~
- ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for
new osquery data ingestion features.~
- [X] Added/updated tests
- [X] Manual QA for all new/changed functionality
  - For Orbit and Fleet Desktop changes:
- [X] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- ~[ ] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
This commit is contained in:
Lucas Manuel Rodriguez 2023-02-09 14:27:40 -03:00 committed by GitHub
parent 8af2b56cd5
commit 4638e8564f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 269 additions and 6 deletions

View file

@ -1735,6 +1735,7 @@ spec:
);
purpose: Informational
tags: compliance, CIS, CIS_Level1, CIS5.4
contributors: lucasmrod
---
apiVersion: v1
kind: policy
@ -1765,6 +1766,28 @@ spec:
---
apiVersion: v1
kind: policy
spec:
name: CIS - Ensure the "root" Account Is Disabled (Fleetd Required)
platforms: macOS
platform: darwin
description: |
Enabling and using the root account puts the system at risk since any successful exploit or mistake
while the root account is in use could have unlimited access privileges within the system.
Using the sudo command allows users to perform functions as a root user while limiting and password
protecting the access privileges. By default the root account is not enabled on a macOS computer.
An administrator can escalate privileges using the sudo command (use -s or -i to get a root shell).
resolution: |
Automated method:
Ask your system administrator to deploy the following script:
/usr/bin/sudo /usr/sbin/dsenableroot -d
query: |
SELECT 1 from dscl WHERE command = 'read' AND path = '/Users/root' AND key = 'AuthenticationAuthority' AND value = '';
purpose: Informational
tags: compliance, CIS, CIS_Level1, CIS5.6
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
@ -1827,7 +1850,7 @@ spec:
If EFI does not pass the integrity check, you may send a report to Apple. Backing up files and clean installing a
known good Operating System and Firmware is recommended.
query: |
SELECT 1 FROM firmware_eficheck_integity_check
SELECT 1 FROM firmware_eficheck_integrity_check
WHERE chip != 'intel-t1' OR (
chip = 'intel-t1' AND
output LIKE '%Primary allowlist version match found. No changes detected in primary hashes%' AND

View file

@ -0,0 +1,3 @@
#!/bin/bash
/usr/bin/sudo /usr/sbin/dsenableroot -d

View file

@ -0,0 +1 @@
* Add `dscl` table to Orbit for CIS check 5.6 on macOS.

View file

@ -1 +1 @@
* Add `firmware_eficheck_integity_check` table for macOS CIS 5.9.
* Add `firmware_eficheck_integrity_check` table for macOS CIS 5.9.

View file

@ -0,0 +1,113 @@
//go:build darwin
// +build darwin
// Package dscl allows querying dscl read commands on the local domain.
package dscl
import (
"context"
"errors"
"fmt"
"os/exec"
"regexp"
"strings"
"github.com/osquery/osquery-go/plugin/table"
)
// Columns is the schema of the table.
func Columns() []table.ColumnDefinition {
return []table.ColumnDefinition{
table.TextColumn("command"), // required (currently only read is supported)
table.TextColumn("path"), // required
table.TextColumn("key"), // required (could be relaxed in the future)
table.TextColumn("value"),
}
}
// 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) {
supportedCommands := []string{"read"}
getArgumentOpEqual := func(argName string) string {
argValue := ""
if constraints, ok := queryContext.Constraints[argName]; ok {
for _, constraint := range constraints.Constraints {
if constraint.Operator == table.OperatorEquals {
argValue = constraint.Expression
}
}
}
return argValue
}
command := getArgumentOpEqual("command")
if command == "" {
return nil, fmt.Errorf("missing command argument, supported commands: %+v", supportedCommands)
}
supported := false
for _, supportedCommand := range supportedCommands {
if supportedCommand == command {
supported = true
break
}
}
if !supported {
return nil, fmt.Errorf("unsupported command: %s, supported commands: %+v", command, supportedCommands)
}
path := getArgumentOpEqual("path")
if path == "" {
return nil, errors.New("missing path argument")
}
key := getArgumentOpEqual("key")
if key == "" {
// In the future we can allow this to be empty and return all key/values of a path.
return nil, errors.New("missing key argument")
}
cmd := exec.Command("dscl", ".", "-"+command, path, key)
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("command failed: %w", err)
}
value, err := parseDSCLReadOutput(out)
if err != nil {
return nil, fmt.Errorf("failed to parse dscl value: %w", err)
}
m := []map[string]string{{
"command": command,
"path": path,
"key": key,
"value": "",
}}
if value != nil {
m[0]["value"] = *value
}
return m, nil
}
func parseDSCLReadOutput(out []byte) (*string, error) {
regex := regexp.MustCompile(`(\S):[ \n]([\S\t\f\r\n ]+)`)
outs := string(out)
if strings.TrimSpace(outs) == "" || strings.HasPrefix(outs, "No such key: ") {
return nil, nil
}
matches := regex.FindSubmatch(out)
if matches == nil {
return nil, fmt.Errorf("unexpected entry: %q", outs)
}
value := string(matches[2])
if value[0] == ' ' {
value = value[1:]
}
return &value, nil
}

View file

@ -0,0 +1,97 @@
//go:build darwin
// +build darwin
package dscl
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestParseDSCLOutput(t *testing.T) {
// NOTE(lucas): I've seen the following behavior when running the command as non-root.
const noKeySample = `No such key: Foobar`
value, err := parseDSCLReadOutput([]byte(noKeySample))
require.NoError(t, err)
require.Nil(t, value)
// NOTE(lucas): I've seen the following behavior when running the command as root.
const noKeySample2 = ``
value, err = parseDSCLReadOutput([]byte(noKeySample2))
require.NoError(t, err)
require.Nil(t, value)
const keySample0 = `PrimaryGroupID: 20`
value, err = parseDSCLReadOutput([]byte(keySample0))
require.NoError(t, err)
require.NotNil(t, value)
require.Equal(t, "20", *value)
const keySample1 = `Picture:
/Library/User Pictures/Animals/Penguin.tif`
value, err = parseDSCLReadOutput([]byte(keySample1))
require.NoError(t, err)
require.NotNil(t, value)
require.Equal(t, "/Library/User Pictures/Animals/Penguin.tif", *value)
const keySample2 = `RecordName: foo com.apple.idms.appleid.prd.0A771AC1-B614-4A18-9FA5-0ADFA8EED4BC`
value, err = parseDSCLReadOutput([]byte(keySample2))
require.NoError(t, err)
require.NotNil(t, value)
require.Equal(t, "foo com.apple.idms.appleid.prd.0A771AC1-B614-4A18-9FA5-0ADFA8EED4BC", *value)
const keySample3 = `dsAttrTypeNative:accountPolicyData:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>creationTime</key>
<real>1634811726.5620289</real>
<key>failedLoginCount</key>
<integer>0</integer>
<key>failedLoginTimestamp</key>
<integer>0</integer>
<key>passwordLastSetTime</key>
<real>1636975330.6275649</real>
</dict>
</plist>`
value, err = parseDSCLReadOutput([]byte(keySample3))
require.NoError(t, err)
require.NotNil(t, value)
require.Equal(t, `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>creationTime</key>
<real>1634811726.5620289</real>
<key>failedLoginCount</key>
<integer>0</integer>
<key>failedLoginTimestamp</key>
<integer>0</integer>
<key>passwordLastSetTime</key>
<real>1636975330.6275649</real>
</dict>
</plist>`, *value)
const keySample4 = `RecordType: dsRecTypeStandard:Users`
value, err = parseDSCLReadOutput([]byte(keySample4))
require.NoError(t, err)
require.NotNil(t, value)
require.Equal(t, "dsRecTypeStandard:Users", *value)
const keySample5 = `RecordName:
root
BUILTIN\Local System`
value, err = parseDSCLReadOutput([]byte(keySample5))
require.NoError(t, err)
require.NotNil(t, value)
require.Equal(t, `root
BUILTIN\Local System`, *value)
const keySample6 = `NFSHomeDirectory: /var/root /private/var/root`
value, err = parseDSCLReadOutput([]byte(keySample6))
require.NoError(t, err)
require.NotNil(t, value)
require.Equal(t, `/var/root /private/var/root`, *value)
}

View file

@ -5,7 +5,8 @@ package table
import (
"github.com/fleetdm/fleet/v4/orbit/pkg/table/authdb"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/csrutil_info"
firmware_eficheck_integity_check "github.com/fleetdm/fleet/v4/orbit/pkg/table/firmware_eficheck_integrity_check"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/dscl"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/firmware_eficheck_integrity_check"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/nvram_info"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/pmset"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/privaterelay"
@ -32,7 +33,8 @@ func platformTables() []osquery.OsqueryPlugin {
table.NewPlugin("authdb", authdb.Columns(), authdb.Generate),
table.NewPlugin("pmset", pmset.Columns(), pmset.Generate),
table.NewPlugin("sudo_info", sudo_info.Columns(), sudo_info.Generate),
table.NewPlugin("firmware_eficheck_integity_check", firmware_eficheck_integity_check.Columns(), firmware_eficheck_integity_check.Generate),
table.NewPlugin("firmware_eficheck_integrity_check", firmware_eficheck_integrity_check.Columns(), firmware_eficheck_integrity_check.Generate),
table.NewPlugin("dscl", dscl.Columns(), dscl.Generate),
// Macadmins extension tables
table.NewPlugin("filevault_users", filevaultusers.FileVaultUsersColumns(), filevaultusers.FileVaultUsersGenerate),

View file

@ -3,7 +3,7 @@
// Package firmware_integrity_check implements a table
// to perform an integrity check for Legacy EFI.
package firmware_eficheck_integity_check
package firmware_eficheck_integrity_check
import (
"context"

24
schema/tables/dscl.yml Normal file
View file

@ -0,0 +1,24 @@
name: dscl
platforms:
- darwin
description: Returns the output of the `dscl . -read` command (local domain).
columns:
- name: command
type: text
required: true
description: The dscl command to execute, only "read" is currently supported.
- name: path
type: text
required: true
description: The path to use in the read command.
- name: key
type: text
required: true
description: The key to query on the read command and path.
- name: value
type: text
required: false
description: The value of the read path and key. The value is the empty string if the key doesn't exist.
notes: >-
- This table is not a core osquery table. It is included as part of Fleetd, the osquery manager from Fleet.
evented: false

View file

@ -1,4 +1,4 @@
name: firmware_eficheck_integity_check
name: firmware_eficheck_integrity_check
platforms:
- darwin
description: Performs eficheck's integrity check on macOS Intel T1 chips (CIS 5.9).