Menu Close

EDR Script Runner (0.0.5)

A proof-of-concept, caveat emptor workflow for securely executing a repository-hosted script

Background

While EDR tools can excel at running one-off code on a limited number of endpoints, device management solutions are often best suited for executing predefined policies at scale.

EDR Script Runner strives to strike a balance between the immediate, dynamic needs of threat hunting teams and the reliability of a MDM server, by securely executing a repository-hosted script, only when necessary.

Workflow

  1. An authorized administrator edits edrScript.zsh, generates its hash (as edrScriptHash.txt), then commits and pushes both to a private, secure repository
  2. The script — and its hash — are ready to be downloaded client-side to the target Mac
  3. The MDM server instructs the Mac to execute edrScriptRunner.zsh, which first validates the checksum, provided by either:
    • edrScriptHash.txt
    • Jamf Pro Policy Parameter
  4. After the checksum has been validated, edrScript.zsh is downloaded client-side and the script’s scriptModificationTimestamp variable is compared to the last client-side execution
    • If the modification dates differ, the script is executed as root (caveat emptor)

Configuration

Complete the following steps to securely (?) execute a repository-hosted script with Jamf Pro.

A. Customize the scripts for your environment
edrScript.zsh
  1. Review and adjust the Global Variables as required for your environment
    • scriptLog (i.e., the location of your client-side logs)
####################################################################################################
#
# Global Variables
#
####################################################################################################

export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/

# Script Version
scriptVersion="0.0.3"

# Client-side Log
scriptLog="/var/log/org.test.edr.log"

# Script Modification Timestamp (i.e., $( date '+%Y-%m-%d-%H%M%S' | tr -d '\n' | pbcopy ) )
scriptModificationTimestamp="2024-09-18-045533"

# Initialize SECONDS
SECONDS="0"
  1. Set your preferred Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Script Human-readabale Name
humanReadableScriptName="EDR Script"

# Organization's Script Name
organizationScriptName="EDR"
  1. Edit the script’s actual command as desired
####################################################################################################
#
# Program
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Proof-of-concept Command Substitution: `jamf about`
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

notice "Proof-of-concept Command Substitution"
logComment "jamf about"

command=$( /usr/local/bin/jamf about )

if [[ "$?" == "0" ]]; then
    logComment "Successful execution"
    logComment "jamf about: ${command}"
else
    errorOut "Execution error"
fi
edrScriptRunner.zsh
  1. Review and adjust the Global Variables as required for your environment
    • scriptLog (i.e., the location of your client-side logs)
####################################################################################################
#
# Global Variables
#
####################################################################################################

export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/

# Script Version
scriptVersion="0.0.5"

# Client-side Log
scriptLog="/var/log/org.test.log"

# Initialize SECONDS
SECONDS="0"

# Temporary Working Directory
workDirectory=$( basename "${0%%.*}" )
tempDirectory=$( mktemp -d "/private/tmp/$workDirectory.XXXXXX" )
  1. Set your preferred Organization Variables
    • orgRepo (i.e., the location of your organization’s private, secure code repository)
    • orgFile (i.e., the actual file name of the script which will be executed)
    • orgPlist (i.e., your organization’s client-side .plist)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Script Human-readabale Name
humanReadableScriptName="EDR Script Runner"

# Organization's Script Name
organizationScriptName="EDRr"

# Organization's Repository
orgRepo="https://raw.githubusercontent.com/dan-snelson/Jamf-Pro-Scripts/development/EDR/"

# Organization's File
orgFile="edrScript.zsh"

# Organization's .plist
orgPlist="/Library/Preferences/org.test.plist"
  1. Set your preferred script defaults (which will be trumped in a policy’s script parameters)
    • Parameter 4: checksumSource (i.e., script (default) or policy)
    • Parameter 5: expectedScriptChecksum (i.e., the actual checksum of orgFile)
      • openssl dgst -sha256 "edrScript.zsh" | awk -F'= ' '{print $2}' > edrScriptHash.txt
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Jamf Pro Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Parameter 4: Checksum Source [ script (default) | policy ]
checksumSource="${4:-"script"}"

# Parameter 5: Expected Script Checksum
case ${checksumSource} in
    script      )   expectedScriptChecksum=$( curl --location --silent --fail "${orgRepo}/${orgFile%%.*}Hash.txt" ) ;;
    policy | *  )   expectedScriptChecksum="${5:-"55b9765ed20cd1563382e79d5af7f5505fb6be55488c9b1c7effbfe2b64c8c3b"}" ;;
esac
B. Add the edrScriptRunner.zsh script to your Jamf Pro server
  1. Add the edrScriptRunner.zsh script to your Jamf Pro server
  2. Specify the following for Options > Parameter Labels
    • Parameter 4: Checksum Source [ script (default) | policy ]
    • Parameter 5: Expected Script Checksum (used when "Checksum Source" is set to "policy")
  3. Click Save
C. Create a Jamf Pro Policy to execute edrScriptRunner.zsh
  1. Create a new Jamf Pro Policy, using the following as a guide for Options > General:
    • Set Display Name to EDR Script Runner (0.0.5)
    • Enable Trigger > Recurring Check-in
    • Set Execution Frequency to Once every month
  1. Select the Scripts payload and add the edrScriptRunner.zsh script, specifying the following Parameter Values
    • Checksum Source:
      • script 🔥 allows root execution with no interaction from the Device Management team
      • policy requires coordination between the SecOps and Device Management teams to update the checksum and flush policy logs
    • Expected Script Checksum: actual checksum of orgFile
  1. Scope the policy as desired
  2. Click Save
D. Testing

Checksum Generation

Each time the orgFile script is updated, execute the following command to update its checksum:

openssl dgst -sha256 "edrScript.zsh" | awk -F'= ' '{print $2}' > edrScriptHash.txt

Repeated Executions

To test repeated executions, SecOps and Device Management teams will need to actively coordinate to:

  1. Flush policy logs
  2. Delete client-side .plist keys:
    • defaults delete "${orgPlist}" "EDR Script Runner"
  3. Manually execute the Jamf Pro policy

Posted in CrowdStrike Falcon, Jamf Pro, Scripts, SecOps, Tips & Tricks

Related Posts