산술 표현식에서 연관 배열을 안전하게 사용하는 방법은 무엇입니까?

산술 표현식에서 연관 배열을 안전하게 사용하는 방법은 무엇입니까?

Bourne과 같은 일부 쉘은 연관 배열을 지원합니다: ( ksh931993년부터), zsh(1998년부터), bash(2009년부터), 하지만 3 사이에는 동작에 약간의 차이가 있습니다.

일반적인 용도는 특정 문자열의 발생 횟수를 계산하는 것입니다.

그러나 나는 다음과 같은 것을 발견했습니다.

조판-A 개수
 ((count[$var]++))

특정 가치를 위해 일하지 마세요 $var.모든 명령 실행취약성콘텐츠가 $var공격자의 통제하에 있거나 그럴 가능성이 있는 경우.

왜 그런 겁니까? 문제가 있는 값은 무엇입니까? 이 문제를 어떻게 해결할 수 있나요?

답변1

문제는 다음과 같은 쉘 산술 표현식에 있습니다.

  • 내부 $((...))(POSIX),
  • ((...))(ksh/bash/zsh)
  • 배열 인덱스
  • 일부 쉘 내장 함수의 일부 매개변수
  • 내부 숫자 비교 연산자의 피연산자[[...]]

단어 확장( ${param}, $((...)), $[...], $(...), `...`, ${ ...; }실행첫 번째, 결과 텍스트를 산술 표현식으로 해석합니다.

이 경우 $((...))이는 POSIX 요구사항이기도 합니다.

이를 통해 이와 같은 op=+; echo "$(( 1 $op 2 ))"작업이 가능하며 출력이 평가되는 표현식 이기 때문에 a=1+1; echo "$(($a * 2))"출력이 3아닌 이유도 설명합니다.41+1 * 2

이것도 일부산술 표현식에서 정제되지 않은 데이터를 사용하는 것이 보안 취약점인 이유일반적으로 말하면.

다음과 같은 것에도 적용된다는 점을 간과하기 쉽습니다.

(( assoc[$var]++ ))

위를 제외하고 ksh93(편집하다이제 bash 5.2+에서는 아래 참조) $var먼저 확장한 다음 결과를 해석하십시오.

이는 or가 $var포함된 경우 or 표현식이 평가되고 /가 거기에서 특별한 의미를 갖는다는 것을 의미합니다. 이면 가 됩니다 .@*assoc[@]++assoc[*]++@*$varx] + 2 + assoc[yassoc[x] + 2 + assoc[y]

이제 일반적으로 에서는 그런 것이 포함되어 $(( $var ))있어도 2차 확장이 발생하지 않고 실행되지 않습니다. 하지만 이미 본 것처럼$var$(reboot)reboot쉘 산술 평가에서 정리되지 않은 데이터 사용의 보안 영향word[...], 재귀 확장을 허용하기 위해 내부적으로 발생하는 경우 예외가 존재합니다. 문제의 근원은 불행하다특징Korn 쉘의 if에는 var산술 표현식이 포함되어 있으며, $((var))의 산술 표현식은 $var재귀적으로(예: when var2='var3 + 1' var='var2 + 1') 평가됩니다. 이는 POSIX에서 허용되지만 요구되지 않는 연산입니다.

이는 배열 멤버로 확장되므로 배열 인덱스의 내용이 결국 재귀적으로 계산된다는 의미입니다. 따라서 이면 $var결국 에는 으로 호출 $(reboot)됩니다 .(( assoc[$var]++ ))reboot

ksh93어느 정도 해결 방법이 있는 것 같지만 $var포함되지 않은 경우에만 해당됩니다 $. 따라서 ksh93은 var=']', var='@'또는 와 함께 사용할 수 있지만 var='`reboot`'와 함께 사용할 수는 없습니다 $(reboot).

예를 들어, rebootharmless로 바꾸면 다음과 같습니다 uname>&2.

$ var='1$(uname>&2)' ksh -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
typeset -A a=([1]=1)
$ var='1$(uname>&2)' bash -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
Linux
declare -A a=([1]="1" )
$ var='1$(uname>&2)' zsh -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
Linux
typeset -A a=( [1]=1 )

명령 은 uname결국 실행되었습니다( bash(<5.2)에서 두 번, zsh현재 값을 얻기 위해 한 번, 할당을 수행하기 위해 두 번째로 추측).

버전 5.0에서 bash는 assoc_expand_once동작을 변경하는 옵션을 추가했습니다.

$ var='1$(uname>&2)' bash -O assoc_expand_once -c 'typeset -A a; ((a[$var]++)); typeset -p a'
declare -A a=(["1\$(uname>&2)"]="1" )

이제 작동하지만 , 또는 의 문제를 해결하지 못하므로 @해결 *되지 ]않습니다.모든 명령 실행취약점:

$ var='x]+b[1$(uname>&2)' bash -O assoc_expand_once -c 'typeset -A a; ((a[$var]++)); typeset -p a'
Linux
declare -A a

(이번에는 uname평가의 일환으로 실행분명히array( b) 인덱스 평가).

문제가 있는 문자 목록은 셸마다 다릅니다. $는 세 가지 모두에 대한 문제이고, \, `는 및 에 대한 문제 [입니다 ]. 와 동일하며 null 값입니다. 또한 일부 로케일에서는 일부 문자의 인코딩에 의 인코딩이 포함되거나 적어도 포함되지 않아 문제가 발생할 수 있습니다. 이를 탈출하는 방법은 세 가지 쉘 모두에서 다르게 수행되어야 합니다.bashzsh"'bash@*\`[]

이 문제를 해결하려면 다음을 수행할 수 있습니다.

assoc[$var]=$(( ${assoc[$var]} + 1 ))

대신에. 그건:

  1. 연관 배열 구성원에 대한 할당이 수행되지 않습니다.의 일부로산술 표현식이지만 연관 배열 구성원 할당만 수행합니다. 즉, 연관 배열 구성원을 대상으로 하는 =, ++, --, +=, ... 산술 연산자를 사용하지 마십시오./=
  2. 산술 표현식에서 연관 배열을 참조할 때 또는 단순한 숫자가 아닌 산술 표현식을 포함하려는 경우에는 , assoc[$var]그러나 ${assoc[$var]}(또는 $assoc[$var]​​in zsh) 을 사용하지 마십시오.(${assoc[$var]})

하지만 언제나처럼연관 배열 구성원은 사용자가 제어해야 하며 일반 숫자가 바람직하며 다른 매개변수 확장과 마찬가지로 주위에 공백을 두는 것이 가장 좋습니다. 예를 들어 음수 값 에 문제가 발생할 수 있는 후자 ((1 - $var))보다 선호됩니다 ( .((1-$var))((1--1))--1

주목해야 할 또 다른 점은 $var비어 있을 때 in은 (( 1 + var ))여전히 var​​산술 표현식 구문의 토큰이고 해당 값은 if입니다 0. 그러나 에서는 (( 1 + $var ))산술 표현식이 가 되는데 1 +이는 구문 오류입니다( 그러나 단항 연산자를 호출하면 (( $var + 1 ))가 되기 때문에 문제가 되지 않습니다 ).+ 1+

기타 방법 bash(5.1 이하, assoc_expand_once옵션이 다음과 같은 경우)아니요활성화됨) 또는 zsh(그러나 그렇지 않음ksh93]캐릭터 에는 \여전히 문제가 있습니다), 위에서 언급한 두 번째 재귀 해석이 나올 때까지 확장이 지연됩니다.

  • (( assoc[\$var]++ ))
  • let 'assoc[$var]++'(반드시 사용하세요.하나의여기에 인용됨)
  • incr='assoc[$var]++'; (($incr))(심지어 ((incr)))
  • ((' assoc[$var]++ '))또는 (( assoc['$var']++ ))( bash만).

이는 산술 평가에 의해 생성된 종료 상태를 유지한다는 장점이 있습니다(성공0이 아닌 경우) 다음을 수행할 수 있습니다.

if (( assoc[\$var]++ )); then
  printf '%s\n' "$var was already seen"
fi

이제 이는 쉘 관련 문제를 남깁니다 bash. bash연관 배열은 널 키를 지원하지 않습니다. 둘 다 and (not ) assoc[]=x에서는 실패 하지만 isempty는 or 에서는 작동 하지만 or에서는 작동하지 않습니다 . 이제 bash-5.1에서도 지원 되지만 .bashzshksh93assoc[$var]$varzshksh93bashzshassoc+=('' value)bash

따라서 독점적으로 사용되고 bashnull 키가 가능한 값 중 하나인 경우 유일한 옵션은 고정 접두사/접미사를 추가하는 것입니다. 예를 들어 다음을 사용하십시오.

assoc[.$var]=$(( ${assoc[.$var]} + 1 ))

또는:

let 'assoc[.$var]++'
(( assoc[.\$var]++ ))
...

2023년 편집

위의 해결 방법 중 대부분은 더 이상 bash-5.2에서 작동하지 않습니다.

bash-5.2.21에서는 (( assoc[$var]++ ))(비어 있지 않은 경우 ) 및 , 또는 을 포함하는 경우에도 $var안전한 것으로 보입니다 .(( assoc[.$var]++ ))$var]\*@

이는 여전히 ACE 취약점의 이전 버전입니다.

5.2 및 5.2 이전 버전에서 작동하는 유일한 방법은 다음과 같습니다.

이 중 두 번째만 ksh93, zsh 및 bash에서 작동합니다.

특히, (( assoc[\$var] ))zsh에서 작동했던 메서드는 더 이상 bash-5.2에서 작동하지 않습니다.

관련 정보