Skip to content

Commit e35b09c

Browse files
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 cf7ee1b commit e35b09c

4 files changed

Lines changed: 168 additions & 16 deletions

File tree

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

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -284,17 +284,55 @@ 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-
# No -L: the bearer token must never cross a redirect hop. The API corrects a
295-
# wrong region before this runs, and the registry answers in a single hop.
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.
296299
printf '%s\n' "$auth_config" |
297-
curl -s --proto '=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"
298336
}
299337

300338
fetch_tags() {

bash/install/falcon-linux-install.sh

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -704,16 +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-
# No -L: the bearer token must never cross a redirect hop. A wrong region is
714-
# corrected in get_oauth_token, so no call here needs to follow a redirect.
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.
715718
printf '%s\n' "$auth_config" |
716-
curl -s -x "$proxy" --proto '=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"
717755
}
718756

719757
check_aws_instance() {

bash/install/falcon-linux-uninstall.sh

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,16 +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-
# No -L: the bearer token must never cross a redirect hop. A wrong region is
260-
# corrected in get_oauth_token, so no call here needs to follow a redirect.
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.
261264
printf '%s\n' "$auth_config" |
262-
curl -s -x "$proxy" --proto '=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"
263301
}
264302

265303
handle_curl_error() {

bash/migrate/falcon-linux-migrate.sh

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,16 +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-
# No -L: the bearer token must never cross a redirect hop. A wrong region is
238-
# corrected in get_oauth_token, so no call here needs to follow a redirect.
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.
239242
printf '%s\n' "$auth_config" |
240-
curl -s -x "$proxy" --proto '=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"
241279
}
242280

243281
handle_curl_error() {

0 commit comments

Comments
 (0)