Crowdstrike Smart Group

Switchfly_IT
New Contributor III

Hi,

We are trying to create a smart group that targets machines that don't have the Falcon Sensor on them.

Weird question. In the criteria for this we want to select an "application title" but are not sure which file that would be. There is no ".app" file for this. Just wondering if anyone has created a smart group with this criteria and knows what to target.

Thanks
57dac12dd49a434ab0931e091b5bd386

10 REPLIES 10

tdclark
Contributor

There is a Crowdstrike EA already available from Jamf that you could use in the Resources>Extention Attributes. It was working as of March of this year when I last needed it.

Mando1313
New Contributor II

Alternatively, you can check if your devices are running the Crowdstrike (Falcon?) service.

Switchfly_IT
New Contributor III

Yeah, someone else had mentioned the services option (or i think the criteria is "running processes") but said that the service isn't always guaranteed to be running. I have the attribute from CS resources and it's uploaded ("Displays version of CrowdStrike Falcon Sensor if installed") and it appears now as a criteria for the SG just not sure what goes in the value field.

Mando1313
New Contributor II

Run a recon/inventory update on any machine. The script should return a value that you can check in a computer's inventory, then use it as the criteria value for your Smart Group.

paula_mendez
New Contributor II

This is what I use as an Extension Attribute

#!/bin/sh
if [ -e /Library/LaunchDaemons/com.crowdstrike.falcond.plist ]; then
    echo "<result>Installed</result>"
else 
    echo "<result>Not Installed</result>"
fi
exit 0

tomt
Valued Contributor

This is the EA that I use to pull the CS version. It is named CS Falcon Version. I then have a Smart Group called CS Falcon Installed keyed on that EA and looking for the word "version".
c162faefb14f47b8b7fd625c76485e39

#!/bin/sh

## Run the sysctl command to check for Falcon Host, and print column 2 from the result
FHCheck=$(sysctl cs.version 2>&1 | awk '{print $2}')

## If Falcon Host is not installed, column 2 from the sysctl command is "unknown". Check to see if that was our result
if [ "$FHCheck" == "unknown" ]; then
   result="Not Installed"
else
   ## If the output was not "unknown", set the result variable to the command's output
   result="Version $FHCheck Installed"
fi

## This line is what actually gets picked up in the JSS for the Extension Attribute
echo "<result>$result</result>"

tlarkin
Honored Contributor

I have a daily compliance script that runs and checks security agents and writes statuses to a flat file. EAs pick it up and read the values in for reporting. However, I can tell you that this can lead to false positives. I actually spoke about this specific issue in the Mac Admin's Pod Cast when they graciously had me on as a guest. You can listen here

This can lead to many false positives, as technically you can have a healthy agent on the client endpoint, but it may not be communicating with the CS tenant. Typically these are edge cases, but they do happen. Now add in tamper protection and you can find yourself in a pickle.

my daily compliance script:

#!/usr/bin/python

"""

This script will be the data collector and compliance checker
it will run on a scheduled basis and report or put devices in scope for remediation
through automation

"""

# import modules
import sys
from SystemConfiguration import SCDynamicStoreCopyConsoleUser
import plistlib
import subprocess
from CoreFoundation import CFPreferencesCopyAppValue
import platform
import os
import time

# global vars

# grabbing user info in case we need it
USER, UID, GID = SCDynamicStoreCopyConsoleUser(None, None, None)
# location of the plist file we will use to record system state
PLIST_FILE = (
    "/Library/Application Support/JAMF/snowflake/com.snowflake.systemstate.plist"
)
# detect OS version, in case we need it later
OS_VERS = platform.mac_ver()[0]

# start functions


def validate_folder_path():
    """detect if our folder path exists, create if it does not, set permissions, force non zero exit on error"""
    folder_path = "/Library/Application Support/JAMF/snowflake"
    if not os.path.exists(folder_path):
        try:
            os.mkdir(folder_path, 0o755)
        except OSError as e:
            print("Could not create folder with error %s" % e)
            sys.exit(1)


def validate_fv2():
    """validate that FV2 is enabled and FDE is present"""
    cmd = ["/usr/bin/fdesetup", "status"]
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()
    result = out.strip()
    # testing the return status of the command, create dict of success or failure
    if proc.returncode != 0:
        fde_dict = {"fv2status": "error"}
        print("Error message: %s" % err)
        return fde_dict
    if "On" in result:
        fde_dict = {"fv2status": "true"}
        return fde_dict
    elif "Off" in result:
        fde_dict = {"fv2status": "false"}
        return fde_dict


def validate_screenlock():
    """validate that the screen will lock if idle for 10 minutes"""
    # grab the preference set on device for the screen saver timeout
    domain = "/Library/Managed Preferences/com.apple.screensaver.plist"
    idle_time = CFPreferencesCopyAppValue("idleTime", domain)
    # test for 10 minutes of idle time for compliance
    # return a pass/fail dict value
    if idle_time == 600:
        idle_dict = {"screencompliant": "true"}
        return idle_dict
    else:
        idle_dict = {"screencompliant": "false"}
        return idle_dict


def health_check_cs():
    """detect if CS is healthy"""
    # if this command has zero output, CS is not healthy
    cmd = ["sysctl", "cs"]
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()
    if proc.returncode != 0:
        # crowd strike is not installed, or is not in a healthy state
        print("error %s" % err)
        cs_dict = {"cs_sys": "false"}
        return cs_dict
    if proc.returncode == 0:
        cs_dict = {"cs_sys": "true"}
        return cs_dict


def cs_daemon_check():
    """checks if the cs daemon is running"""
    cmd = ["/bin/launchctl", "list"]
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()
    if "com.crowdstrike.falcond" not in out:
        cs_daemon_dict = {"cs_daemon": "false"}
        return cs_daemon_dict
    else:
        cs_daemon_dict = {"cs_daemon": "true"}
        return cs_daemon_dict


def cs_comms():
    """this function will test if the client can talk to the CS """
    cmd = ["/Library/CS/falconctl", "stats"]
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()
    if proc.returncode != 0:
        return {"cs_con": "error"}
    outlines = out.splitlines()
    for line in outlines:
        if "State:" in line:
            result = line.split(":")[1]
            if "connected" not in result:
                cs_dict = {"cs_con": "false"}
                return cs_dict
            elif "connected" in result:
                cs_dict = {"cs_con": "true"}
                return cs_dict


def health_check_tenable():
    """check if tenable agent is healthy, may need to check if the SaaS is also healthy
    and not in maintenance or down, which would return a false positive"""
    cmd = ["/Library/NessusAgent/run/sbin/nessuscli", "agent", "status"]
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()
    if proc.returncode == 0:
        tenable_dict = {"tenablestatus": "true"}
        return tenable_dict
    else:
        tenable_dict = {"tenablestatus": "false"}
        print("Error: %s" % err)
        return tenable_dict


def set_computername():
    """function to set the computer's hostname is set to the serial number"""
    cmd = ["system_profiler", "SPHardwareDataType", "-xml"]
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()
    data = plistlib.readPlistFromString(out)
    serial = data[0]["_items"][0]["serial_number"]
    options = ["HostName", "ComputerName", "LocalHostName"]
    for option in options:
        subprocess.call(["scutil", "--set", option, serial])


# gotta have a main


def main():
    """gotta have some main"""
    # check if our custom folder exists
    validate_folder_path()
    # test if the hostname is compliant
    set_computername()
    # generate epoch date stamp to record last run
    date = int(time.time())
    # create the master dict
    main_dict = {}
    main_dict.update(validate_fv2())
    main_dict.update(validate_screenlock())
    main_dict.update(health_check_cs())
    main_dict.update(health_check_tenable())
    main_dict.update(cs_daemon_check())
    if main_dict.get("cs_daemon") == "true":
        main_dict.update(cs_comms())
    elif main_dict.get("cs_daemon") == "false":
        main_dict.update({"cs_comms": "false"})
    main_dict.update({"os_vers": OS_VERS})
    # build the data to write to the flat file
    data = dict(timestamp=date, healthstate=main_dict)
    plistlib.writePlist(data, PLIST_FILE)


# run the main, or the jewels
if __name__ == "__main__":
    main()

What we ended up doing is shipping all the CS data and jamf data to our data platform and then compare how many active Macs are checking into Jamf Pro, but not into our Crowdstrike tenant. My Jamf data was off, it was only reporting very few failures, but when comparing the Jamf data to the CS data I could see that we had more failed agents. In the end, the only really good way to understand the health of your third party agents is to compare data from your management tool (like Jamf Pro) to the data of your third party agents.

Garci4
New Contributor III

^ Tom starts talking about it around the 27min mark if anyone else is lurking like me.

Anton
New Contributor

hey @tomt how did you get CS in the criteria section?

tomt
Valued Contributor

@Anton That's the title of my EA.