Skip to content

Commit 3191390

Browse files
fix(security): stop curl_command from carrying the bearer token across a redirect (#525)
* fix(security): stop curl_command from carrying the bearer token across a redirect curl_command() passed -L, so a redirect was followed with the OAuth bearer token still on curl's configuration input. curl fixed the cross-host case in 7.58.0 as CVE-2018-1000007, which leaves roughly 7.30 through 7.57 exposed. Measured against the live API: every endpoint curl_command touches answers in a single hop on the correct region, including download-installer/v3 and the registry tags list. So -L never fired on a correct-region run. -L only did work when FALCON_CLOUD named the wrong region, because the API answers a wrong region with a 308 to the right one. That never worked on a curl that strips the header, which is every supported version: on curl 7.29.0 and 8.5.0 the redirect was followed, the header was dropped, the call came back 401 and the run died with a misleading "No sensor found for OS" error. The only versions where -L produced a working request are the same versions that leak the token. Dropped -L, and with it --proto-redir, keeping the convention from #521 that --proto-redir appears only next to -L. The wrong-region case is now handled the way the OAuth token request already handles it: the x-cs-region hint is adopted instead of the redirect being followed, so it works on every curl version rather than only the leaky band. The warning naming the real region still prints. falcon-container-sensor-pull.sh already adopted the hint; install, uninstall and migrate only warned and kept the wrong region. Fixes #523 * fix(security): handle the region redirect in curl_command instead of following it Dropping -L closed the leak but left the wrong-region case relying on get_oauth_token having corrected cs_falcon_cloud first. That only covers the client id and secret path: with FALCON_ACCESS_TOKEN there is no token POST, so there is no x-cs-region to read, and every API call went to the wrong region. curl_command now reads x-cs-region off the un-followed redirect and re-issues against that region, resolved through cs_cloud(), so the retry host always comes from a closed allowlist and never from Location. Region correction now covers every request that carries the token, whichever way the token was obtained. There is no scope-free way to discover this up front: the 308 only comes back on a real routable path. An unknown path answers 404 with no x-cs-region, and the redirect is emitted after authentication, so an unauthenticated probe gets 401. The retry therefore rides on the caller's own request rather than a probe. The body is buffered because the redirect body is 107 bytes, not empty, so emitting it would corrupt the value the caller captures. Buffering is safe for the -o callers too: curl writes their file itself and stdout stays empty. The status is read from the last HTTP status line, because a proxy CONNECT dumps one of its own first. The exit code is captured and returned so behaviour under set -e is unchanged and #526's call-site guards still receive the real code - measured: rc=5 for an unresolvable proxy, both under set +e and through a command substitution. Verified against the live API on curl 7.29.0 and 8.5.0, with client credentials and with FALCON_ACCESS_TOKEN, for us-1, us-2 and eu-1: GET, GET with -o, PATCH with a JSON body, and the query holding a literal pipe all reach the correct region. The arg rewrite was checked separately under dash, bash and macOS sh.
1 parent dad81bf commit 3191390

4 files changed

Lines changed: 183 additions & 21 deletions

File tree

bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,19 +284,58 @@ handle_curl_error() {
284284

285285
curl_command() {
286286
# Dash does not support arrays, so we have to pass the args as separate arguments
287-
local token="$1" escaped_token auth_config
287+
local token="$1" escaped_token auth_config headers body status hint old_host new_host arg rc
288288
shift
289289
# The configuration value must be quoted, because it holds a space and a
290290
# colon. curl processes backslash escapes inside a quoted value, so a
291291
# backslash or a double quote in the token has to be escaped first.
292292
escaped_token=$(printf '%s' "$token" | sed 's/\\/\\\\/g; s/"/\\"/g')
293293
auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token")
294+
295+
headers=$(mktemp)
296+
body=$(mktemp)
297+
# No -L: the bearer token must never cross a redirect hop. The body is held
298+
# back so that a redirect body is not emitted ahead of the retry's.
294299
printf '%s\n' "$auth_config" |
295-
curl -s -L --proto '=https' --proto-redir '=https' -K- "$@"
300+
curl -s --proto '=https' --dump-header "$headers" -K- "$@" >"$body"
301+
rc=$?
302+
303+
# A wrong region answers with a redirect naming the right one in x-cs-region.
304+
# Re-issue against that region instead of following Location. The registry is
305+
# a different host, so its URLs are never rewritten.
306+
status=$(awk '/^HTTP\//{s=$2} END{print s}' "$headers")
307+
case "$status" in
308+
301 | 302 | 307 | 308)
309+
hint=$(grep -i ^x-cs-region: "$headers" | head -n 1 | tr '[:upper:]' '[:lower:]' | tr -d '\r' | sed 's/^x-cs-region: //g')
310+
if [ -n "$hint" ]; then
311+
old_host=$(cs_cloud)
312+
# cs_cloud() validates the hint against its own allowlist. Check
313+
# for empty rather than trusting its die, which does not stop bash.
314+
new_host=$(cs_cloud "$hint")
315+
if [ -n "$new_host" ] && [ "$new_host" != "$old_host" ]; then
316+
for arg in "$@"; do
317+
shift
318+
case "$arg" in
319+
"https://$old_host/"*)
320+
arg="https://$new_host/${arg#"https://$old_host/"}"
321+
;;
322+
esac
323+
set -- "$@" "$arg"
324+
done
325+
printf '%s\n' "$auth_config" |
326+
curl -s --proto '=https' -K- "$@" >"$body"
327+
rc=$?
328+
fi
329+
fi
330+
;;
331+
esac
332+
333+
cat "$body"
334+
rm -f "$headers" "$body"
335+
return "$rc"
296336
}
297337

298338
fetch_tags() {
299-
# No -L, so --proto-redir is dropped too; nothing follows a redirect here.
300339
bearer_result=$(echo "-u $ART_USERNAME:$ART_PASSWORD" |
301340
curl -s --proto '=https' \
302341
"https://$cs_registry/v2/token?account=$ART_USERNAME&scope=repository:$registry_opts/$repository_name:pull&service=$cs_registry" -K-)

bash/install/falcon-linux-install.sh

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -704,14 +704,54 @@ handle_curl_error() {
704704

705705
curl_command() {
706706
# Dash does not support arrays, so we have to pass the args as separate arguments
707-
local escaped_token auth_config
707+
local escaped_token auth_config headers body status hint old_host new_host arg rc
708708
# The configuration value must be quoted, because it holds a space and a
709709
# colon. curl processes backslash escapes inside a quoted value, so a
710710
# backslash or a double quote in the token has to be escaped first.
711711
escaped_token=$(printf '%s' "$cs_falcon_oauth_token" | sed 's/\\/\\\\/g; s/"/\\"/g')
712712
auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token")
713+
714+
headers=$(mktemp)
715+
body=$(mktemp)
716+
# No -L: the bearer token must never cross a redirect hop. The body is held
717+
# back so that a redirect body is not emitted ahead of the retry's.
713718
printf '%s\n' "$auth_config" |
714-
curl -s -x "$proxy" -L --proto '=https' --proto-redir '=https' -K- "$@"
719+
curl -s -x "$proxy" --proto '=https' --dump-header "$headers" -K- "$@" >"$body"
720+
rc=$?
721+
722+
# A wrong region answers with a redirect naming the right one in x-cs-region.
723+
# Re-issue against that region instead of following Location. Take the last
724+
# status line, because a proxy CONNECT dumps one of its own first.
725+
status=$(awk '/^HTTP\//{s=$2} END{print s}' "$headers")
726+
case "$status" in
727+
301 | 302 | 307 | 308)
728+
hint=$(grep -i ^x-cs-region: "$headers" | head -n 1 | tr '[:upper:]' '[:lower:]' | tr -d '\r' | sed 's/^x-cs-region: //g')
729+
if [ -n "$hint" ]; then
730+
old_host=$(cs_cloud)
731+
# cs_cloud() validates the hint against its own allowlist. Check
732+
# for empty rather than trusting its die, which does not stop bash.
733+
new_host=$(cs_cloud "$hint")
734+
if [ -n "$new_host" ] && [ "$new_host" != "$old_host" ]; then
735+
for arg in "$@"; do
736+
shift
737+
case "$arg" in
738+
"https://$old_host/"*)
739+
arg="https://$new_host/${arg#"https://$old_host/"}"
740+
;;
741+
esac
742+
set -- "$@" "$arg"
743+
done
744+
printf '%s\n' "$auth_config" |
745+
curl -s -x "$proxy" --proto '=https' -K- "$@" >"$body"
746+
rc=$?
747+
fi
748+
fi
749+
;;
750+
esac
751+
752+
cat "$body"
753+
rm -f "$headers" "$body"
754+
return "$rc"
715755
}
716756

717757
check_aws_instance() {
@@ -829,10 +869,11 @@ get_oauth_token() {
829869
die "Unable to obtain region hint from CrowdStrike Falcon OAuth API, Please provide FALCON_CLOUD environment variable as an override."
830870
fi
831871
cs_falcon_cloud="${region_hint}"
832-
else
833-
if [ "${FALCON_CLOUD}" != "${region_hint}" ]; then
834-
echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2
835-
fi
872+
elif [ -n "${region_hint}" ] && [ "${FALCON_CLOUD}" != "${region_hint}" ]; then
873+
echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2
874+
# Use the hint. The API answers the wrong region with a redirect, which
875+
# curl_command no longer follows.
876+
cs_falcon_cloud="${region_hint}"
836877
fi
837878
fi
838879

bash/install/falcon-linux-uninstall.sh

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -250,14 +250,54 @@ get_maintenance_token() {
250250

251251
curl_command() {
252252
# Dash does not support arrays, so we have to pass the args as separate arguments
253-
local escaped_token auth_config
253+
local escaped_token auth_config headers body status hint old_host new_host arg rc
254254
# The configuration value must be quoted, because it holds a space and a
255255
# colon. curl processes backslash escapes inside a quoted value, so a
256256
# backslash or a double quote in the token has to be escaped first.
257257
escaped_token=$(printf '%s' "$cs_falcon_oauth_token" | sed 's/\\/\\\\/g; s/"/\\"/g')
258258
auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token")
259+
260+
headers=$(mktemp)
261+
body=$(mktemp)
262+
# No -L: the bearer token must never cross a redirect hop. The body is held
263+
# back so that a redirect body is not emitted ahead of the retry's.
259264
printf '%s\n' "$auth_config" |
260-
curl -s -x "$proxy" -L --proto '=https' --proto-redir '=https' -K- "$@"
265+
curl -s -x "$proxy" --proto '=https' --dump-header "$headers" -K- "$@" >"$body"
266+
rc=$?
267+
268+
# A wrong region answers with a redirect naming the right one in x-cs-region.
269+
# Re-issue against that region instead of following Location. Take the last
270+
# status line, because a proxy CONNECT dumps one of its own first.
271+
status=$(awk '/^HTTP\//{s=$2} END{print s}' "$headers")
272+
case "$status" in
273+
301 | 302 | 307 | 308)
274+
hint=$(grep -i ^x-cs-region: "$headers" | head -n 1 | tr '[:upper:]' '[:lower:]' | tr -d '\r' | sed 's/^x-cs-region: //g')
275+
if [ -n "$hint" ]; then
276+
old_host=$(cs_cloud)
277+
# cs_cloud() validates the hint against its own allowlist. Check
278+
# for empty rather than trusting its die, which does not stop bash.
279+
new_host=$(cs_cloud "$hint")
280+
if [ -n "$new_host" ] && [ "$new_host" != "$old_host" ]; then
281+
for arg in "$@"; do
282+
shift
283+
case "$arg" in
284+
"https://$old_host/"*)
285+
arg="https://$new_host/${arg#"https://$old_host/"}"
286+
;;
287+
esac
288+
set -- "$@" "$arg"
289+
done
290+
printf '%s\n' "$auth_config" |
291+
curl -s -x "$proxy" --proto '=https' -K- "$@" >"$body"
292+
rc=$?
293+
fi
294+
fi
295+
;;
296+
esac
297+
298+
cat "$body"
299+
rm -f "$headers" "$body"
300+
return "$rc"
261301
}
262302

263303
handle_curl_error() {
@@ -490,10 +530,11 @@ get_oauth_token() {
490530
die "Unable to obtain region hint from CrowdStrike Falcon OAuth API, Please provide FALCON_CLOUD environment variable as an override."
491531
fi
492532
cs_falcon_cloud="${region_hint}"
493-
else
494-
if [ "${FALCON_CLOUD}" != "${region_hint}" ]; then
495-
echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2
496-
fi
533+
elif [ -n "${region_hint}" ] && [ "${FALCON_CLOUD}" != "${region_hint}" ]; then
534+
echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2
535+
# Use the hint. The API answers the wrong region with a redirect, which
536+
# curl_command no longer follows.
537+
cs_falcon_cloud="${region_hint}"
497538
fi
498539
fi
499540

bash/migrate/falcon-linux-migrate.sh

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,14 +228,54 @@ fi
228228

229229
curl_command() {
230230
# Dash does not support arrays, so we have to pass the args as separate arguments
231-
local escaped_token auth_config
231+
local escaped_token auth_config headers body status hint old_host new_host arg rc
232232
# The configuration value must be quoted, because it holds a space and a
233233
# colon. curl processes backslash escapes inside a quoted value, so a
234234
# backslash or a double quote in the token has to be escaped first.
235235
escaped_token=$(printf '%s' "$cs_falcon_oauth_token" | sed 's/\\/\\\\/g; s/"/\\"/g')
236236
auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token")
237+
238+
headers=$(mktemp)
239+
body=$(mktemp)
240+
# No -L: the bearer token must never cross a redirect hop. The body is held
241+
# back so that a redirect body is not emitted ahead of the retry's.
237242
printf '%s\n' "$auth_config" |
238-
curl -s -x "$proxy" -L --proto '=https' --proto-redir '=https' -K- "$@"
243+
curl -s -x "$proxy" --proto '=https' --dump-header "$headers" -K- "$@" >"$body"
244+
rc=$?
245+
246+
# A wrong region answers with a redirect naming the right one in x-cs-region.
247+
# Re-issue against that region instead of following Location. Take the last
248+
# status line, because a proxy CONNECT dumps one of its own first.
249+
status=$(awk '/^HTTP\//{s=$2} END{print s}' "$headers")
250+
case "$status" in
251+
301 | 302 | 307 | 308)
252+
hint=$(grep -i ^x-cs-region: "$headers" | head -n 1 | tr '[:upper:]' '[:lower:]' | tr -d '\r' | sed 's/^x-cs-region: //g')
253+
if [ -n "$hint" ]; then
254+
old_host=$(cs_cloud)
255+
# cs_cloud() validates the hint against its own allowlist. Check
256+
# for empty rather than trusting its die, which does not stop bash.
257+
new_host=$(cs_cloud "$hint")
258+
if [ -n "$new_host" ] && [ "$new_host" != "$old_host" ]; then
259+
for arg in "$@"; do
260+
shift
261+
case "$arg" in
262+
"https://$old_host/"*)
263+
arg="https://$new_host/${arg#"https://$old_host/"}"
264+
;;
265+
esac
266+
set -- "$@" "$arg"
267+
done
268+
printf '%s\n' "$auth_config" |
269+
curl -s -x "$proxy" --proto '=https' -K- "$@" >"$body"
270+
rc=$?
271+
fi
272+
fi
273+
;;
274+
esac
275+
276+
cat "$body"
277+
rm -f "$headers" "$body"
278+
return "$rc"
239279
}
240280

241281
handle_curl_error() {
@@ -409,10 +449,11 @@ get_oauth_token() {
409449
die "Unable to obtain region hint from CrowdStrike Falcon OAuth API, Please provide FALCON_CLOUD environment variable as an override."
410450
fi
411451
cs_falcon_cloud="${region_hint}"
412-
else
413-
if [ "${FALCON_CLOUD}" != "${region_hint}" ]; then
414-
echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2
415-
fi
452+
elif [ -n "${region_hint}" ] && [ "${FALCON_CLOUD}" != "${region_hint}" ]; then
453+
echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2
454+
# Use the hint. The API answers the wrong region with a redirect, which
455+
# curl_command no longer follows.
456+
cs_falcon_cloud="${region_hint}"
416457
fi
417458
fi
418459

0 commit comments

Comments
 (0)