diff --git a/.github/workflows/bash_migrate.yml b/.github/workflows/bash_migrate.yml index bb01084..5146d96 100644 --- a/.github/workflows/bash_migrate.yml +++ b/.github/workflows/bash_migrate.yml @@ -31,7 +31,7 @@ jobs: - name: Shellcheck run: | shellcheck --version - shellcheck bash/migrate/cs-migrate.sh + shellcheck bash/migrate/falcon-linux-migrate.sh molecule-test: name: Molecule Test diff --git a/bash/containers/falcon-container-sensor-pull/README.md b/bash/containers/falcon-container-sensor-pull/README.md index 01ed774..51f2266 100644 --- a/bash/containers/falcon-container-sensor-pull/README.md +++ b/bash/containers/falcon-container-sensor-pull/README.md @@ -50,11 +50,9 @@ CrowdStrike now provides unified images that work across all regions: ## Security recommendations -### Use cURL version 7.55.0 or later +### Credential handling -We've identified a security concern related to cURL versions 7.54.1 and earlier. In these versions, request headers were set using the `-H` option, which allowed potential secrets to be exposed via the command line. In newer versions of cURL, versions 7.55.0 and later, you can pass headers from stdin using the `@-` syntax, which addresses this security concern. **We recommend that you to upgrade cURL to version 7.55.0 or later**. If this is not possible, this script offers compatibility with the older method through the use of the `--allow-legacy-curl` optional command line flag. - -To check your version of cURL, run the following command: `curl --version` +Credentials travel on cURL's configuration input rather than the command line, and every request and redirect is restricted to HTTPS. This works with the cURL that RHEL and CentOS 7 ship, and with current cURL. ## Prerequisites @@ -136,7 +134,7 @@ Optional Flags: --get-pull-token Get the pull token of the selected SENSOR_TYPE for Kubernetes --get-cid Get the CID assigned to the API Credentials --list-tags List all tags available for the selected sensor type and platform, sorted in ascending order - --allow-legacy-curl Allow the script to run with an older version of curl + --allow-legacy-curl Deprecated. Accepted and ignored; no longer needed Internal Flags: --internal-build-stage (Internal only) Falcon Build Stage [release|stage] (Default: release) @@ -167,7 +165,7 @@ Help Options: | `--get-pull-token` | N/A | `None` | Get the pull token of the selected `SENSOR_TYPE` for Kubernetes. | | `--get-cid` | N/A | `None` | Get the CID assigned to the API Credentials. | | `--list-tags` | `$LISTTAGS` | `False` (Optional) | List all tags available for the selected sensor | -| `--allow-legacy-curl` | `$ALLOW_LEGACY_CURL` | `False` (Optional) | Allow the script to run with an older version of cURL | +| `--allow-legacy-curl` | `$ALLOW_LEGACY_CURL` | `False` (Optional) | Deprecated. Accepted and ignored; no longer needed | | `-h`, `--help` | N/A | `None` | Display help message | --- diff --git a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh index ecab4c0..01dedf2 100755 --- a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh +++ b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh @@ -1,4 +1,17 @@ #!/bin/bash + +case $- in + *x*) + set +x + printf '%s\n' 'WARNING: shell tracing disabled to protect credentials.' >&2 + ;; +esac + +falcon_client_secret=$FALCON_CLIENT_SECRET +unset FALCON_CLIENT_SECRET +FALCON_CLIENT_SECRET=$falcon_client_secret +unset falcon_client_secret + : <<'#DESCRIPTION#' File: falcon-container-sensor-pull.sh Description: Bash script to copy Falcon DaemonSet Sensor, Container Sensor, or Kubernetes Admission Controller images from CrowdStrike Container Registry. @@ -58,7 +71,7 @@ Optional Flags: --get-pull-token Get the pull token of the selected SENSOR_TYPE for Kubernetes --get-cid Get the CID assigned to the API Credentials --list-tags List all tags available for the selected sensor type and platform, sorted in ascending order - --allow-legacy-curl Allow the script to run with an older version of curl + --allow-legacy-curl Deprecated. Accepted and ignored; no longer needed Internal Flags: --internal-build-stage (Internal only) Falcon Build Stage [release|stage] (Default: release) @@ -228,30 +241,12 @@ while [ $# != 0 ]; do shift done -# Check if curl is greater or equal to 7.55 -old_curl=$( - version=$(curl --version | head -n 1 | awk '{ print $2 }') - minimum="7.55" - - # Check if the version is less than the minimum - if printf "%s\n" "$version" "$minimum" | sort -V -c >/dev/null 2>&1; then - echo 0 - else - echo 1 - fi -) - -# Old curl print warning message -if [ "$old_curl" -eq 0 ]; then - if [ "${ALLOW_LEGACY_CURL}" != "true" ]; then - echo """ -WARNING: Your version of curl does not support the ability to pass headers via stdin. -For security considerations, we strongly recommend upgrading to curl 7.55.0 or newer. +if ! command -v curl >/dev/null 2>&1; then + die "The 'curl' command is missing. Please install it before continuing. Aborting..." +fi -To bypass this warning, set the optional flag --allow-legacy-curl -""" - exit 1 - fi +if [ "${ALLOW_LEGACY_CURL:-false}" = "true" ]; then + echo "NOTICE: ALLOW_LEGACY_CURL is no longer needed and is ignored." >&2 fi # Handle error codes returned by curl @@ -287,13 +282,15 @@ handle_curl_error() { curl_command() { # Dash does not support arrays, so we have to pass the args as separate arguments - local token="$1" - set -- "$@" - if [ "$old_curl" -eq 0 ]; then - curl -s -L -H "Authorization: Bearer ${token}" "$@" - else - echo "Authorization: Bearer ${token}" | curl -s -L -H @- "$@" - fi + local token="$1" escaped_token auth_config + shift + # The configuration value must be quoted, because it holds a space and a + # colon. curl processes backslash escapes inside a quoted value, so a + # backslash or a double quote in the token has to be escaped first. + escaped_token=$(printf '%s' "$token" | sed 's/\\/\\\\/g; s/"/\\"/g') + auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token") + printf '%s\n' "$auth_config" | + curl -s -L --proto '=https' --proto-redir '=https' -K- "$@" } fetch_tags() { @@ -875,8 +872,7 @@ docker_api_token=$(echo "$raw_docker_api_token" | json_value "token") ART_PASSWORD=$(echo "$docker_api_token" | sed 's/ *$//g' | sed 's/^ *//g') if [ -z "$ART_PASSWORD" ]; then - die "Failed to retrieve the CrowdStrike registry password. Response from API: -$raw_docker_api_token + die "Failed to retrieve the CrowdStrike registry password. Ensure the following: - Correct API Scopes assigned for sensor type: ${SENSOR_TYPE} diff --git a/bash/install/README.md b/bash/install/README.md index 3ed5fa1..950429a 100644 --- a/bash/install/README.md +++ b/bash/install/README.md @@ -7,11 +7,9 @@ environment variable. Consult the Environment Variables for each script for more ## Security Recommendations -### Use cURL version 7.55.0 or newer +### Credential handling -We have identified a security concern related to cURL versions prior to 7.55, which required request headers to be set using the `-H` option, thus allowing potential secrets to be exposed via the command line. In newer versions of cURL, you can pass headers from stdin using the `@-` syntax, which addresses this security concern. Although our script offers compatibility with the older method by allowing you to set the environment variable `ALLOW_LEGACY_CURL=true`, we strongly urge you to upgrade cURL if your environment permits. - -To check your version of cURL, run the following command: `curl --version` +Credentials travel on cURL's configuration input rather than the command line, and every request and redirect is restricted to HTTPS. This works with the cURL that RHEL and CentOS 7 ship, and with current cURL. ## Table of Contents @@ -182,7 +180,7 @@ Other Options The path to download the falcon sensor to. - ALLOW_LEGACY_CURL (default: false) - To use the legacy version of curl; version < 7.55.0. + Deprecated. Accepted and ignored; no longer needed. - GET_ACCESS_TOKEN (default: false) Prints an access token and exits. diff --git a/bash/install/falcon-linux-install.sh b/bash/install/falcon-linux-install.sh index 19213f1..41b1b44 100755 --- a/bash/install/falcon-linux-install.sh +++ b/bash/install/falcon-linux-install.sh @@ -1,5 +1,21 @@ #!/bin/bash +case $- in + *x*) + set +x + printf '%s\n' 'WARNING: shell tracing disabled to protect credentials.' >&2 + ;; +esac + +falcon_client_secret=$FALCON_CLIENT_SECRET +falcon_access_token=$FALCON_ACCESS_TOKEN +falcon_provisioning_token=$FALCON_PROVISIONING_TOKEN +unset FALCON_CLIENT_SECRET FALCON_ACCESS_TOKEN FALCON_PROVISIONING_TOKEN +FALCON_CLIENT_SECRET=$falcon_client_secret +FALCON_ACCESS_TOKEN=$falcon_access_token +FALCON_PROVISIONING_TOKEN=$falcon_provisioning_token +unset falcon_client_secret falcon_access_token falcon_provisioning_token + print_usage() { cat </dev/null 2>&1; then + local_sha=$(sha256sum "$file" | awk '{ print $1 }') + elif command -v openssl >/dev/null 2>&1; then + local_sha=$(openssl dgst -sha256 "$file" | awk '{ print $NF }') + else + # Keep the file. The download is not known to be bad, only unverified. + die "Cannot verify the downloaded sensor installer: neither 'sha256sum' nor 'openssl' is available. Install one of them and try again. The download is kept at $file." + # die exits, so shellcheck reports the return below as unreachable and + # it is. Keep it anyway: if die ever stops exiting, control would reach + # the comparison with an empty digest and delete the file. + # shellcheck disable=SC2317 + return 1 + fi + if [ "$local_sha" != "$expected_sha" ]; then + rm -f "$file" + die "Downloaded sensor installer failed SHA-256 verification." + fi +} + cs_sensor_download() { local destination_dir="$1" existing_installers sha_list INDEX sha file_type installer @@ -352,7 +395,7 @@ cs_sensor_download() { if echo "$existing_installers" | grep "authorization failed"; then die "Access denied: Please make sure that your Falcon API credentials allow sensor download (scope Sensor Download [read])" elif echo "$existing_installers" | grep "invalid bearer token"; then - die "Invalid Access Token: $cs_falcon_oauth_token" + die "Invalid or expired Falcon access token." fi sha_list=$(echo "$existing_installers" | json_value "sha256") @@ -376,6 +419,8 @@ cs_sensor_download() { handle_curl_error $? + verify_sha256 "$installer" "$sha" + echo "$installer" } @@ -467,9 +512,9 @@ aws_ssm_parameter() { token=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") api_endpoint="AmazonSSM.GetParameters" - iam_role="$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/iam/security-credentials/)" - aws_my_region="$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/placement/availability-zone | sed s/.$//)" - _security_credentials="$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/iam/security-credentials/"$iam_role")" + iam_role="$(printf 'header = "X-aws-ec2-metadata-token: %s"\n' "$token" | curl -s -K- http://169.254.169.254/latest/meta-data/iam/security-credentials/)" + aws_my_region="$(printf 'header = "X-aws-ec2-metadata-token: %s"\n' "$token" | curl -s -K- http://169.254.169.254/latest/meta-data/placement/availability-zone | sed s/.$//)" + _security_credentials="$(printf 'header = "X-aws-ec2-metadata-token: %s"\n' "$token" | curl -s -K- http://169.254.169.254/latest/meta-data/iam/security-credentials/"$iam_role")" access_key_id="$(echo "$_security_credentials" | grep AccessKeyId | sed -e 's/ "AccessKeyId" : "//' -e 's/",$//')" access_key_secret="$(echo "$_security_credentials" | grep SecretAccessKey | sed -e 's/ "SecretAccessKey" : "//' -e 's/",$//')" security_token="$(echo "$_security_credentials" | grep Token | sed -e 's/ "Token" : "//' -e 's/",$//')" @@ -507,23 +552,24 @@ EOF ) response=$( - curl -s "https://ssm.$aws_my_region.amazonaws.com/" \ - -x "$proxy" \ - -H "Authorization: AWS4-HMAC-SHA256 \ - Credential=$access_key_id/$date/$aws_my_region/ssm/aws4_request, \ - SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, \ - Signature=$signature" \ - -H "x-amz-security-token: $security_token" \ - -H "x-amz-target: $api_endpoint" \ - -H "content-type: application/x-amz-json-1.1" \ - -d "$request_data" \ - -H "x-amz-date: $datetime" + { + printf 'header = "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/ssm/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=%s"\n' \ + "$access_key_id" "$date" "$aws_my_region" "$signature" + printf 'header = "x-amz-security-token: %s"\n' "$security_token" + printf 'header = "x-amz-target: %s"\n' "$api_endpoint" + printf 'header = "content-type: application/x-amz-json-1.1"\n' + printf 'header = "x-amz-date: %s"\n' "$datetime" + } | curl -s "https://ssm.$aws_my_region.amazonaws.com/" \ + -x "$proxy" -K- \ + -d "$request_data" ) handle_curl_error $? - if ! echo "$response" | grep -q '^.*"InvalidParameters":\[\].*$'; then - die "Unexpected response from AWS SSM Parameter Store: $response" - elif ! echo "$response" | grep -q '^.*'"${param_name}"'.*$'; then - die "Unexpected response from AWS SSM Parameter Store: $response" + if ! echo "$response" | grep -q '^.*"InvalidParameters":\[\].*$' || + ! echo "$response" | grep -q '^.*'"${param_name}"'.*$'; then + # The response body holds the decrypted parameter value, so report only + # the error message that AWS returns and never the body itself. + ssm_error=$(echo "$response" | json_value "message" 1) + die "Unexpected response from AWS SSM Parameter Store for parameter '$param_name'.${ssm_error:+ AWS reported: $ssm_error}" fi echo "$response" } @@ -613,34 +659,12 @@ cs_cloud() { esac } -# Check if curl is greater or equal to 7.55 -old_curl=$( - if ! command -v curl >/dev/null 2>&1; then - die "The 'curl' command is missing. Please install it before continuing. Aborting..." - fi - - version=$(curl --version | head -n 1 | awk '{ print $2 }') - minimum="7.55" - - # Check if the version is less than the minimum - if printf "%s\n" "$version" "$minimum" | sort -V -C; then - echo 0 - else - echo 1 - fi -) - -# Old curl print warning message -if [ "$old_curl" -eq 0 ]; then - if [ "${ALLOW_LEGACY_CURL}" != "true" ]; then - echo """ -WARNING: Your version of curl does not support the ability to pass headers via stdin. -For security considerations, we strongly recommend upgrading to curl 7.55.0 or newer. +if ! command -v curl >/dev/null 2>&1; then + die "The 'curl' command is missing. Please install it before continuing. Aborting..." +fi -To bypass this warning, set the environment variable ALLOW_LEGACY_CURL=true -""" - exit 1 - fi +if [ "${ALLOW_LEGACY_CURL:-false}" = "true" ]; then + echo "NOTICE: ALLOW_LEGACY_CURL is no longer needed and is ignored." >&2 fi # Handle error codes returned by curl @@ -678,13 +702,14 @@ handle_curl_error() { curl_command() { # Dash does not support arrays, so we have to pass the args as separate arguments - set -- "$@" - - if [ "$old_curl" -eq 0 ]; then - curl -s -x "$proxy" -L -H "Authorization: Bearer ${cs_falcon_oauth_token}" "$@" - else - echo "Authorization: Bearer ${cs_falcon_oauth_token}" | curl -s -x "$proxy" -L -H @- "$@" - fi + local escaped_token auth_config + # The configuration value must be quoted, because it holds a space and a + # colon. curl processes backslash escapes inside a quoted value, so a + # backslash or a double quote in the token has to be escaped first. + escaped_token=$(printf '%s' "$cs_falcon_oauth_token" | sed 's/\\/\\\\/g; s/"/\\"/g') + auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token") + printf '%s\n' "$auth_config" | + curl -s -x "$proxy" -L --proto '=https' --proto-redir '=https' -K- "$@" } check_aws_instance() { diff --git a/bash/install/falcon-linux-uninstall.sh b/bash/install/falcon-linux-uninstall.sh index 6b900fa..c2d4f35 100755 --- a/bash/install/falcon-linux-uninstall.sh +++ b/bash/install/falcon-linux-uninstall.sh @@ -1,5 +1,21 @@ #!/bin/bash +case $- in + *x*) + set +x + printf '%s\n' 'WARNING: shell tracing disabled to protect credentials.' >&2 + ;; +esac + +falcon_client_secret=$FALCON_CLIENT_SECRET +falcon_access_token=$FALCON_ACCESS_TOKEN +falcon_maintenance_token=$FALCON_MAINTENANCE_TOKEN +unset FALCON_CLIENT_SECRET FALCON_ACCESS_TOKEN FALCON_MAINTENANCE_TOKEN +FALCON_CLIENT_SECRET=$falcon_client_secret +FALCON_ACCESS_TOKEN=$falcon_access_token +FALCON_MAINTENANCE_TOKEN=$falcon_maintenance_token +unset falcon_client_secret falcon_access_token falcon_maintenance_token + print_usage() { cat </dev/null 2>&1; then - die "The 'curl' command is missing. Please install it before continuing. Aborting..." - fi - - version=$(curl --version | head -n 1 | awk '{ print $2 }') - minimum="7.55" - - # Check if the version is less than the minimum - if printf "%s\n" "$version" "$minimum" | sort -V -C; then - echo 0 - else - echo 1 - fi -) - curl_command() { # Dash does not support arrays, so we have to pass the args as separate arguments - set -- "$@" - - if [ "$old_curl" -eq 0 ]; then - curl -s -x "$proxy" -L -H "Authorization: Bearer ${cs_falcon_oauth_token}" "$@" - else - echo "Authorization: Bearer ${cs_falcon_oauth_token}" | curl -s -x "$proxy" -L -H @- "$@" - fi + local escaped_token auth_config + # The configuration value must be quoted, because it holds a space and a + # colon. curl processes backslash escapes inside a quoted value, so a + # backslash or a double quote in the token has to be escaped first. + escaped_token=$(printf '%s' "$cs_falcon_oauth_token" | sed 's/\\/\\\\/g; s/"/\\"/g') + auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token") + printf '%s\n' "$auth_config" | + curl -s -x "$proxy" -L --proto '=https' --proto-redir '=https' -K- "$@" } handle_curl_error() { @@ -288,6 +292,14 @@ die() { exit 1 } +if ! command -v curl >/dev/null 2>&1; then + die "The 'curl' command is missing. Please install it before continuing. Aborting..." +fi + +if [ "${ALLOW_LEGACY_CURL:-false}" = "true" ]; then + echo "NOTICE: ALLOW_LEGACY_CURL is no longer needed and is ignored." >&2 +fi + aws_ssm_parameter() { local param_name="$1" @@ -299,9 +311,9 @@ aws_ssm_parameter() { token=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") api_endpoint="AmazonSSM.GetParameters" - iam_role="$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/iam/security-credentials/)" - aws_my_region="$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/placement/availability-zone | sed s/.$//)" - _security_credentials="$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/iam/security-credentials/"$iam_role")" + iam_role="$(printf 'header = "X-aws-ec2-metadata-token: %s"\n' "$token" | curl -s -K- http://169.254.169.254/latest/meta-data/iam/security-credentials/)" + aws_my_region="$(printf 'header = "X-aws-ec2-metadata-token: %s"\n' "$token" | curl -s -K- http://169.254.169.254/latest/meta-data/placement/availability-zone | sed s/.$//)" + _security_credentials="$(printf 'header = "X-aws-ec2-metadata-token: %s"\n' "$token" | curl -s -K- http://169.254.169.254/latest/meta-data/iam/security-credentials/"$iam_role")" access_key_id="$(echo "$_security_credentials" | grep AccessKeyId | sed -e 's/ "AccessKeyId" : "//' -e 's/",$//')" access_key_secret="$(echo "$_security_credentials" | grep SecretAccessKey | sed -e 's/ "SecretAccessKey" : "//' -e 's/",$//')" security_token="$(echo "$_security_credentials" | grep Token | sed -e 's/ "Token" : "//' -e 's/",$//')" @@ -339,23 +351,24 @@ EOF ) response=$( - curl -s "https://ssm.$aws_my_region.amazonaws.com/" \ - -x "$proxy" \ - -H "Authorization: AWS4-HMAC-SHA256 \ - Credential=$access_key_id/$date/$aws_my_region/ssm/aws4_request, \ - SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, \ - Signature=$signature" \ - -H "x-amz-security-token: $security_token" \ - -H "x-amz-target: $api_endpoint" \ - -H "content-type: application/x-amz-json-1.1" \ - -d "$request_data" \ - -H "x-amz-date: $datetime" + { + printf 'header = "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/ssm/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=%s"\n' \ + "$access_key_id" "$date" "$aws_my_region" "$signature" + printf 'header = "x-amz-security-token: %s"\n' "$security_token" + printf 'header = "x-amz-target: %s"\n' "$api_endpoint" + printf 'header = "content-type: application/x-amz-json-1.1"\n' + printf 'header = "x-amz-date: %s"\n' "$datetime" + } | curl -s "https://ssm.$aws_my_region.amazonaws.com/" \ + -x "$proxy" -K- \ + -d "$request_data" ) handle_curl_error $? - if ! echo "$response" | grep -q '^.*"InvalidParameters":\[\].*$'; then - die "Unexpected response from AWS SSM Parameter Store: $response" - elif ! echo "$response" | grep -q '^.*'"${param_name}"'.*$'; then - die "Unexpected response from AWS SSM Parameter Store: $response" + if ! echo "$response" | grep -q '^.*"InvalidParameters":\[\].*$' || + ! echo "$response" | grep -q '^.*'"${param_name}"'.*$'; then + # The response body holds the decrypted parameter value, so report only + # the error message that AWS returns and never the body itself. + ssm_error=$(echo "$response" | json_value "message" 1) + die "Unexpected response from AWS SSM Parameter Store for parameter '$param_name'.${ssm_error:+ AWS reported: $ssm_error}" fi echo "$response" } diff --git a/bash/migrate/README.md b/bash/migrate/README.md index 65884e4..2b2459d 100644 --- a/bash/migrate/README.md +++ b/bash/migrate/README.md @@ -4,11 +4,9 @@ Bash script to migrate Falcon sensor from one CID to another through the Falcon ## Security Recommendations -### Use cURL version 7.55.0 or newer +### Credential handling -We have identified a security concern related to cURL versions prior to 7.55, which required request headers to be set using the `-H` option, thus allowing potential secrets to be exposed via the command line. In newer versions of cURL, you can pass headers from stdin using the `@-` syntax, which addresses this security concern. Although our script offers compatibility with the older method by allowing you to set the environment variable `ALLOW_LEGACY_CURL=true`, we strongly urge you to upgrade cURL if your environment permits. - -To check your version of cURL, run the following command: `curl --version` +Credentials travel on cURL's configuration input rather than the command line, and every request and redirect is restricted to HTTPS. This works with the cURL that RHEL and CentOS 7 ship, and with current cURL. ## Table of Contents @@ -184,7 +182,7 @@ Other Options Accepted values are [us-1|us-2|us-3|eu-1|us-gov-1|us-gov-2]. - ALLOW_LEGACY_CURL (default: false) - To use the legacy version of curl; version < 7.55.0. + Deprecated. Accepted and ignored; no longer needed. - USER_AGENT (default: unset) User agent string to append to the User-Agent header when making diff --git a/bash/migrate/falcon-linux-migrate.sh b/bash/migrate/falcon-linux-migrate.sh index 258ecc7..73abd5e 100755 --- a/bash/migrate/falcon-linux-migrate.sh +++ b/bash/migrate/falcon-linux-migrate.sh @@ -1,4 +1,24 @@ #!/bin/bash + +case $- in + *x*) + set +x + printf '%s\n' 'WARNING: shell tracing disabled to protect credentials.' >&2 + ;; +esac + +old_falcon_client_secret=$OLD_FALCON_CLIENT_SECRET +new_falcon_client_secret=$NEW_FALCON_CLIENT_SECRET +falcon_access_token=$FALCON_ACCESS_TOKEN +falcon_maintenance_token=$FALCON_MAINTENANCE_TOKEN +falcon_provisioning_token=$FALCON_PROVISIONING_TOKEN +unset OLD_FALCON_CLIENT_SECRET NEW_FALCON_CLIENT_SECRET FALCON_ACCESS_TOKEN FALCON_MAINTENANCE_TOKEN FALCON_PROVISIONING_TOKEN +OLD_FALCON_CLIENT_SECRET=$old_falcon_client_secret +NEW_FALCON_CLIENT_SECRET=$new_falcon_client_secret +FALCON_ACCESS_TOKEN=$falcon_access_token +FALCON_MAINTENANCE_TOKEN=$falcon_maintenance_token +FALCON_PROVISIONING_TOKEN=$falcon_provisioning_token +unset old_falcon_client_secret new_falcon_client_secret falcon_access_token falcon_maintenance_token falcon_provisioning_token # # Bash script to migrate Falcon sensor to another falcon CID. # @@ -111,7 +131,7 @@ Other Options Accepted values are [us-1|us-2|us-3|eu-1|us-gov-1|us-gov-2]. - ALLOW_LEGACY_CURL (default: false) - To use the legacy version of curl; version < 7.55.0. + Deprecated. Accepted and ignored; no longer needed. - USER_AGENT (default: unset) User agent string to append to the User-Agent header when making @@ -198,31 +218,24 @@ uninstall_sensor() { } # Shared functions -old_curl=$( - if ! command -v curl >/dev/null 2>&1; then - die "The 'curl' command is missing. Please install it before continuing. Aborting..." - fi - - version=$(curl --version | head -n 1 | awk '{ print $2 }') - minimum="7.55" +if ! command -v curl >/dev/null 2>&1; then + die "The 'curl' command is missing. Please install it before continuing. Aborting..." +fi - # Check if the version is less than the minimum - if printf "%s\n" "$version" "$minimum" | sort -V -C; then - echo 0 - else - echo 1 - fi -) +if [ "${ALLOW_LEGACY_CURL:-false}" = "true" ]; then + echo "NOTICE: ALLOW_LEGACY_CURL is no longer needed and is ignored." >&2 +fi curl_command() { # Dash does not support arrays, so we have to pass the args as separate arguments - set -- "$@" - - if [ "$old_curl" -eq 0 ]; then - curl -s -x "$proxy" -L -H "Authorization: Bearer ${cs_falcon_oauth_token}" "$@" - else - echo "Authorization: Bearer ${cs_falcon_oauth_token}" | curl -s -x "$proxy" -L -H @- "$@" - fi + local escaped_token auth_config + # The configuration value must be quoted, because it holds a space and a + # colon. curl processes backslash escapes inside a quoted value, so a + # backslash or a double quote in the token has to be escaped first. + escaped_token=$(printf '%s' "$cs_falcon_oauth_token" | sed 's/\\/\\\\/g; s/"/\\"/g') + auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token") + printf '%s\n' "$auth_config" | + curl -s -x "$proxy" -L --proto '=https' --proto-redir '=https' -K- "$@" } handle_curl_error() { @@ -472,7 +485,7 @@ get_maintenance_token() { die "Retrieved empty maintenance token from API." fi else - die "Failed to retrieve maintenance token. Response: $response" + die "Failed to retrieve a maintenance token from the Falcon API." fi } @@ -574,7 +587,7 @@ cs_sensor_policy_version() { if echo "$sensor_update_policy" | grep "authorization failed"; then die "Access denied: Please make sure that your Falcon API credentials allow access to sensor update policies (scope Sensor update policies [read])" elif echo "$sensor_update_policy" | grep "invalid bearer token"; then - die "Invalid Access Token: $cs_falcon_oauth_token" + die "Invalid or expired Falcon access token." fi sensor_update_versions=$(echo "$sensor_update_policy" | json_value "sensor_version") @@ -598,6 +611,33 @@ cs_sensor_policy_version() { IFS=$oldIFS } +# Compare the downloaded installer against the SHA-256 that the API supplied. +# That digest is the download id in the request URL, so this check finds +# truncation and alteration in transit. It is not a signature check: the digest +# and the file come from the same response, so it does not prove who built the +# installer. +verify_sha256() { + local file="$1" expected_sha="$2" local_sha + + if command -v sha256sum >/dev/null 2>&1; then + local_sha=$(sha256sum "$file" | awk '{ print $1 }') + elif command -v openssl >/dev/null 2>&1; then + local_sha=$(openssl dgst -sha256 "$file" | awk '{ print $NF }') + else + # Keep the file. The download is not known to be bad, only unverified. + die "Cannot verify the downloaded sensor installer: neither 'sha256sum' nor 'openssl' is available. Install one of them and try again. The download is kept at $file." + # die exits, so shellcheck reports the return below as unreachable and + # it is. Keep it anyway: if die ever stops exiting, control would reach + # the comparison with an empty digest and delete the file. + # shellcheck disable=SC2317 + return 1 + fi + if [ "$local_sha" != "$expected_sha" ]; then + rm -f "$file" + die "Downloaded sensor installer failed SHA-256 verification." + fi +} + cs_sensor_download() { local destination_dir="$1" existing_installers sha_list INDEX sha file_type installer @@ -621,7 +661,7 @@ cs_sensor_download() { if echo "$existing_installers" | grep "authorization failed"; then die "Access denied: Please make sure that your Falcon API credentials allow sensor download (scope Sensor Download [read])" elif echo "$existing_installers" | grep "invalid bearer token"; then - die "Invalid Access Token: $cs_falcon_oauth_token" + die "Invalid or expired Falcon access token." fi sha_list=$(echo "$existing_installers" | json_value "sha256") @@ -645,6 +685,8 @@ cs_sensor_download() { handle_curl_error $? + verify_sha256 "$installer" "$sha" + echo "$installer" } @@ -860,7 +902,7 @@ get_falcon_tags() { if echo "$response" | grep "authorization failed" >/dev/null; then die "Access denied: Please make sure your Falcon API credentials allow access to host data (scope Host [read])" elif echo "$response" | grep "invalid bearer token" >/dev/null; then - die "Invalid Access Token: $cs_falcon_oauth_token" + die "Invalid or expired Falcon access token." fi # Extract tags from response diff --git a/powershell/install/falcon_windows_install.ps1 b/powershell/install/falcon_windows_install.ps1 index e1ea7ff..7c38d39 100755 --- a/powershell/install/falcon_windows_install.ps1 +++ b/powershell/install/falcon_windows_install.ps1 @@ -135,6 +135,8 @@ param( [string] $UserAgent ) begin { + Set-PSDebug -Off + if ($PSVersionTable.PSVersion -lt '3.0') { throw "This script requires a miniumum PowerShell 3.0" } @@ -214,7 +216,6 @@ begin { try { $response = Invoke-WebRequest @WebRequestParams -Uri "$($BaseUrl)/oauth2/token" -UseBasicParsing -Method 'POST' -Headers $Headers -Body $Body $content = ConvertFrom-Json -InputObject $response.Content - Write-VerboseLog -VerboseInput $content -PreMessage 'Invoke-FalconAuth - $content:' if ([string]::IsNullOrEmpty($content.access_token)) { $message = 'Unable to authenticate to the CrowdStrike Falcon API. Please check your credentials and try again.' @@ -597,7 +598,7 @@ process { # Begin installation Write-FalconLog 'Installer' 'Installing Falcon Sensor...' - Write-FalconLog 'StartProcess' "Starting installer with parameters: '$InstallParams'" + Write-FalconLog 'StartProcess' 'Starting installer; command-line parameters omitted from the log because they may contain sensitive values' try { $process = (Start-Process -FilePath $LocalFile -ArgumentList $InstallParams -PassThru -ErrorAction SilentlyContinue) Write-FalconLog 'StartProcess' "Started '$LocalFile' ($($process.Id))" diff --git a/powershell/install/falcon_windows_uninstall.ps1 b/powershell/install/falcon_windows_uninstall.ps1 index a7fb5b2..916bb70 100755 --- a/powershell/install/falcon_windows_uninstall.ps1 +++ b/powershell/install/falcon_windows_uninstall.ps1 @@ -115,6 +115,8 @@ param( [string] $UserAgent ) begin { + Set-PSDebug -Off + if ($FalconAccessToken) { if ($FalconCloud -eq "autodiscover") { @@ -200,7 +202,6 @@ begin { try { $response = Invoke-WebRequest @WebRequestParams -Uri "$($BaseUrl)/oauth2/token" -UseBasicParsing -Method 'POST' -Headers $Headers -Body $Body $content = ConvertFrom-Json -InputObject $response.Content - Write-VerboseLog -VerboseInput $content -PreMessage 'Invoke-FalconAuth - $content:' if ([string]::IsNullOrEmpty($content.access_token)) { $Message = 'Unable to authenticate to the CrowdStrike Falcon API. Please check your credentials and try again.' @@ -529,7 +530,6 @@ process { try { $response = Invoke-WebRequest @WebRequestParams -Uri $url -UseBasicParsing -Method 'POST' -Body $bodyJson -MaximumRedirection 0 $content = ConvertFrom-Json -InputObject $response.Content - Write-VerboseLog -VerboseInput $content -PreMessage 'GetToken - $content:' if ($content.errors) { $Message = 'Failed to retrieve maintenance token: ' @@ -539,7 +539,7 @@ process { } else { $MaintenanceToken = $content.resources[0].uninstall_token - Write-FalconLog 'GetToken' "Retrieved maintenance token: $MaintenanceToken" + Write-FalconLog 'GetToken' 'Retrieved maintenance token' $UninstallParams += " MAINTENANCE_TOKEN=$MaintenanceToken" } } @@ -576,9 +576,8 @@ process { if ($UninstallTool -eq 'standalone') { # Check if /uninstall parameter is present if ($UninstallParams -match '/?uninstall') { - $OriginalParams = $UninstallParams $UninstallParams = $UninstallParams -replace '/?uninstall\s*', '' -replace '^\s+|\s+$', '' - Write-FalconLog 'ParamValidation' "Removed '/uninstall' parameter for standalone uninstaller. Original: '$OriginalParams', Modified: '$UninstallParams'" + Write-FalconLog 'ParamValidation' "Removed '/uninstall' parameter for standalone uninstaller; parameter values omitted from the log" } # Ensure we have at least /quiet parameter @@ -590,7 +589,7 @@ process { # Begin uninstallation Write-FalconLog 'Uninstaller' 'Uninstalling the Falcon Sensor...' - Write-FalconLog 'StartProcess' "Starting uninstaller with parameters: '$UninstallParams'" + Write-FalconLog 'StartProcess' 'Starting uninstaller; command-line parameters omitted from the log because they may contain sensitive values' $UninstallerProcess = Start-Process -FilePath "$UninstallerPath" -ArgumentList $UninstallParams -PassThru -Wait $UninstallerProcessId = $UninstallerProcess.Id Write-FalconLog 'StartProcess' "Started '$UninstallerPath' ($UninstallerProcessId)" diff --git a/powershell/migrate/falcon_windows_migrate.ps1 b/powershell/migrate/falcon_windows_migrate.ps1 index 2f196ac..909a552 100644 --- a/powershell/migrate/falcon_windows_migrate.ps1 +++ b/powershell/migrate/falcon_windows_migrate.ps1 @@ -135,6 +135,8 @@ param( [string] $UserAgent ) +Set-PSDebug -Off + function Write-RecoveryCsv { param ( @@ -324,7 +326,6 @@ function Invoke-FalconUninstall ([hashtable] $WebRequestParams, [string] $Uninst $response = Invoke-WebRequest @WebRequestParams -Uri $url -UseBasicParsing -Method 'POST' -Headers $oldCloudHeaders -Body $bodyJson -MaximumRedirection 0 $content = ConvertFrom-Json -InputObject $response.Content - Write-VerboseLog -VerboseInput $content -PreMessage 'GetToken - $content:' if ($content.errors) { $Message = 'Failed to retrieve maintenance token: ' @@ -334,7 +335,7 @@ function Invoke-FalconUninstall ([hashtable] $WebRequestParams, [string] $Uninst } else { $MaintenanceToken = $content.resources[0].uninstall_token - Write-FalconLog -Source 'Invoke-FalconUninstall' -Message "Retrieved maintenance token: $MaintenanceToken" + Write-FalconLog -Source 'Invoke-FalconUninstall' -Message 'Retrieved maintenance token' $UninstallParams += " MAINTENANCE_TOKEN=$MaintenanceToken" } } @@ -369,7 +370,7 @@ function Invoke-FalconUninstall ([hashtable] $WebRequestParams, [string] $Uninst # Begin uninstallation Write-FalconLog -Source 'Invoke-FalconUninstall' -Message 'Uninstalling Falcon Sensor...' - Write-FalconLog -Source 'Invoke-FalconUninstall' -Message "Starting uninstaller with parameters: '$UninstallParams'" + Write-FalconLog -Source 'Invoke-FalconUninstall' -Message 'Starting uninstaller; command-line parameters omitted from the log because they may contain sensitive values' $UninstallerProcess = Start-Process -FilePath "$UninstallerPath" -ArgumentList $UninstallParams -PassThru -Wait $UninstallerProcessId = $UninstallerProcess.Id Write-FalconLog -Source 'Invoke-FalconUninstall' -Message "Started '$UninstallerPath' ($UninstallerProcessId)" @@ -579,7 +580,7 @@ function Invoke-FalconInstall ([hashtable] $WebRequestParams, [string] $InstallP # Begin installation Write-FalconLog -Source 'Invoke-FalconInstall' -Message "Installing Falcon Sensor..." - Write-FalconLog -Source 'Invoke-FalconInstall' -Message "Starting installer '$LocalFile' with parameters '$InstallParams'" + Write-FalconLog -Source 'Invoke-FalconInstall' -Message "Starting installer '$LocalFile'; command-line parameters omitted from the log because they may contain sensitive values" $process = (Start-Process -FilePath $LocalFile -ArgumentList $InstallParams -PassThru -ErrorAction SilentlyContinue) Write-FalconLog -Source 'Invoke-FalconInstall' -Message "Started '$LocalFile' ($($process.Id))" @@ -1008,7 +1009,6 @@ function Invoke-FalconAuth([hashtable] $WebRequestParams, [string] $BaseUrl, [ha try { $response = Invoke-WebRequest @WebRequestParams -Uri "$($BaseUrl)/oauth2/token" -UseBasicParsing -Method 'POST' -Headers $Headers -Body $Body $content = ConvertFrom-Json -InputObject $response.Content - Write-VerboseLog -VerboseInput $content -PreMessage 'Invoke-FalconAuth - $content:' if ([string]::IsNullOrEmpty($content.access_token)) { $message = 'Unable to authenticate to the CrowdStrike Falcon API. Please check your credentials and try again.'