Posted on 06-24-2020 10:31 AM
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
Posted on 06-24-2020 10:52 AM
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.
Posted on 06-24-2020 09:03 PM
Alternatively, you can check if your devices are running the Crowdstrike (Falcon?) service.
Posted on 06-25-2020 08:51 AM
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.
Posted on 06-25-2020 01:44 PM
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.
Posted on 06-25-2020 02:17 PM
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
Posted on 06-25-2020 02:25 PM
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".
#!/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>"
Posted on 06-27-2020 12:20 PM
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.
Posted on 08-05-2020 09:25 AM
^ Tom starts talking about it around the 27min mark if anyone else is lurking like me.
Posted on 09-02-2020 02:38 PM
hey @tomt how did you get CS in the criteria section?
Posted on 09-02-2020 02:50 PM
@Anton That's the title of my EA.