하지만 만약...?

하지만 만약...?

unix.stackexchange.com을 한동안 팔로우했다면 이제 echo $varBourne/POSIX 쉘(zsh는 예외)과 같은 목록 컨텍스트에 따옴표 없는 변수를 남겨 두는 것이 매우 특별한 의미가 있다는 것을 알아야 합니다. 합당한 이유가 있거나 그렇게 해서는 안 됩니다.

이에 대해서는 여기의 많은 Q&A에서 자세히 논의됩니다(예:공백이나 기타 특수 문자 때문에 쉘 스크립트가 멈추는 이유는 무엇입니까?,언제 큰따옴표가 필요합니까?,쉘 변수의 확장과 glob 및 분할의 영향,따옴표 붙은 문자열과 따옴표 없는 문자열 확장)

이는 70년대 후반 Bourne 쉘이 처음 출시된 이래로 그랬고, Korn 쉘(그 중 하나)은 변경되지 않았습니다.David Korn의 가장 큰 후회(문제 #7)) 또는 bash주로 POSIX/Unix가 지정하는 Korn 쉘을 복제합니다.

이제 우리는 여전히 많은 답변을 볼 수 있으며 때로는 변수를 인용하지 않는 공개적으로 게시되는 셸 코드도 볼 수 있습니다. 지금쯤이면 사람들이 그걸 배웠을 거라고 생각하실 겁니다.

내 경험에 따르면 참조 변수를 생략하는 세 가지 주요 유형의 사람들이 있습니다.

  • 초보자. 이것은 완전히 직관적이지 않은 구문이기 때문에 모두 용서할 수 있습니다. 이 사이트에서 우리의 역할은 그들을 교육하는 것입니다.

  • 건망증이 심한 사람들.

  • 여러 번 맞아도 여전히 확신이 없는 사람들은 이렇게 생각합니다.물론 Bourne 쉘 작성자는 우리가 모든 변수를 참조하는 것을 원하지 않습니다..

그러한 행동과 관련된 위험을 노출한다면 아마도 그들을 설득할 수 있을 것입니다.

변수를 인용하는 것을 잊어버린 경우 발생할 수 있는 최악의 상황은 무엇입니까? 진짜야저것나쁜?

여기서는 어떤 종류의 취약점에 대해 이야기하고 있습니까?

어떤 상황에서 문제가 발생할 수 있나요?

답변1

머리말

우선, 이는 문제를 해결하는 올바른 방법이 아니라는 점을 말씀드리고 싶습니다. "라고 말하는 것과 좀 비슷해요.사람을 죽이면 안 된다. 그렇지 않으면 감옥에 갈 것이다.".

마찬가지로 변수를 인용하면 보안 허점이 생기기 때문에 변수를 인용하지 않습니다. 그렇게 하지 않는 것이 잘못되었기 때문에 변수를 인용하고 있습니다(그러나 감옥에 대한 두려움이 도움이 될 수 있다면 왜 안 됩니까).

이제 막 기차에 올라타려는 사람들을 위한 간략한 요약입니다.

대부분의 쉘에서 따옴표가 없는 변수 확장(및 이 답변의 나머지 부분은 명령 대체( `...`OR $(...)) 및 산술 확장( $((...))OR $[...])에도 적용되지만)은 매우 특별한 의미를 갖습니다. 이를 설명하는 가장 좋은 방법은 일종의 암시적 호출과 같다는 것입니다.분할+전역운영자 1.

cmd $var

다른 언어에서는 다음과 같이 작성됩니다.

cmd(glob(split($var)))

$var먼저 관련된 특수 매개변수에 따라 $IFS(나뉘다부분), 그 분할로 인한 각 단어가 고려됩니다.무늬일치하는 파일 목록으로 확장됩니다(전반적인 상황부분).

예를 들어, $varContains *.txt,/var/*.xml$IFS Contains ,cmd여러 매개변수로 호출되며 첫 번째 매개변수는 현재 디렉터리의 파일이고 다음 cmd매개변수는 입니다.txtxml/var

cmd두 개의 리터럴 매개변수 및 를 사용하여 cmd 호출 하려는 경우 다음과 같이 *.txt,/var/*.xml작성할 수 있습니다.

cmd "$var"

다음은 좀 더 친숙한 다른 언어입니다.

cmd($var)

우리가 말한 것셸의 취약점?

결국 보안에 민감한 상황에서는 쉘 스크립트를 사용해서는 안 된다는 것이 처음부터 알려져 있었습니다. 물론이죠. 변수를 따옴표로 묶지 않은 채 놔두는 것은 실수입니다. 하지만 그다지 해롭지는 않습니다. 그렇죠?

글쎄, 어떤 사람들은 웹 CGI에 쉘 스크립트를 사용해서는 안 된다거나 고맙게도 요즘 대부분의 시스템은 setuid/setgid 쉘 스크립트를 허용하지 않는다고 말하지만, shellshock(2014년 헤드라인을 장식한 원격으로 악용 가능한 bash 버그) 2016년 9월)에서는 쉘이 사용되어서는 안 되는 곳(CGI, DHCP 클라이언트 후크 스크립트, sudoers 명령, 호출 등)에서 여전히 널리 사용되고 있음을 밝혔습니다.통과(그렇지 않다면~처럼) setuid 명령...

때로는 무의식적으로. 예를 들어, // CGI 스크립트 system('cmd $PATH_INFO') 에서 쉘은 실제로 명령줄을 해석하기 위해 호출됩니다. ( 쉘 스크립트 자체가 쉘 스크립트일 수도 있고 작성자가 CGI에서 호출될 것이라고 전혀 예상하지 못했을 수도 있습니다.)phpperlpythoncmd

권한 상승을 위한 경로가 있을 때 누군가(그를 부르자)공격자) 그는 하지 말아야 할 일을 할 수 있습니다.

항상 의미공격자대부분의 경우 버그로 인해 수행해서는 안되는 작업을 실수로 수행한 권한 있는 사용자/프로세스에 의해 처리된 데이터를 제공하십시오.

기본적으로 결함이 있는 코드가 다음 제어 하에 데이터를 처리할 때 문제가 발생합니다.공격자.

이제 항상 명확하지는 않습니다.데이터이는 발생할 수 있으며 코드가 신뢰할 수 없는 데이터를 처리할지 여부를 말하기 어려운 경우가 많습니다.

변수에 관한 한 CGI 스크립트의 경우 데이터는 쿠키, 경로, 호스트... 및 기타 매개변수뿐만 아니라 CGI GET/POST 매개변수라는 것이 분명합니다.

setuid 스크립트(다른 사용자가 호출할 때 한 사용자로 실행)의 경우 이는 매개변수 또는 환경 변수입니다.

또 다른 매우 일반적인 벡터는 파일 이름입니다. 디렉터리에서 파일 목록을 가져오면 파일이 해당 디렉터리에 심어졌을 수 있습니다.공격자.

이와 관련하여 대화형 셸 프롬프트(예: 파일 작업 시 /tmp) 에서도 취약할 수 있습니다 ~/tmp .

심지어 a도 ~/.bashrc취약할 수 있습니다(예: 일부 변수가 클라이언트 제어 하에 있는 서버 배포와 같은 작업을 실행하기 위해 호출하는 경우 bash설명 가능).sshForcedCommandgit

이제 신뢰할 수 없는 데이터를 처리하기 위해 스크립트를 직접 호출할 수는 없지만 다른 명령에 의해 호출될 수 있습니다. 또는 잘못된 코드를 복사하여 스크립트에 붙여넣을 수도 있습니다(그리고 3년 후 귀하나 동료가 복사하여 붙여넣을 수도 있습니다). 특히 간과되기 쉬운 부분 중 하나는비판적인코드 사본이 어디에 있을지 모르기 때문에 Q&A 사이트의 답변에 있습니다.

집에 가까우면 얼마나 안 좋은가요?

따옴표가 없는 변수(또는 명령 대체)는 쉘 코드와 관련된 보안 취약점의 가장 큰 원인입니다. 이는 부분적으로는 이러한 오류가 종종 취약점으로 해석되기 때문일 뿐만 아니라 인용되지 않은 변수를 보는 것이 일반적이기 때문이기도 합니다.

실제로 쉘 코드의 취약점을 찾을 때 가장 먼저 해야 할 일은 따옴표가 없는 변수를 찾는 것입니다. 이는 발견하기 쉽고 일반적으로 좋은 후보이며 일반적으로 공격자가 제어하는 ​​데이터를 쉽게 추적할 수 있습니다.

인용되지 않은 변수는 다양한 방식으로 취약점이 될 수 있습니다. 여기서는 몇 가지 일반적인 경향을 알려 드리겠습니다.

정보 공개

대부분의 사람들은 인용되지 않은 변수와 관련된 오류를 경험합니다.나뉘다(예를 들어, 이제 파일 이름에 공백이 포함되는 것이 일반적이며 공백은 IFS의 기본값입니다.) 많은 사람들이 그것을 무시할 것이다. 전반적인 상황부분. 이것전반적인 상황부분적으로는 적어도 관련이 있습니다. 나뉘다부분.

정리되지 않은 외부 입력 방법에 대한 와일드카드 완성공격자모든 디렉터리의 내용을 읽을 수 있습니다.

존재하다:

echo You entered: $unsanitised_external_input

$unsanitised_external_input포함된 경우 /*다음을 의미합니다.공격자무엇을 볼 수 있습니다 /. 별거 아니야. 하지만 개별적으로 이름을 지정하지 않고도 다른 위험한 행동을 제안하고 서비스를 활성화하기 위해 /home/*시스템의 사용자 이름 목록을 제공하므로 더욱 흥미로워집니다 . 값은 전체 파일 시스템만 나열합니다./tmp/*/home/*/.forward/etc/rc*/*/* /*/* /*/*/*...

서비스 거부 취약점.

이전 예는 너무 멀리 진행되어 DoS를 받았습니다.

실제로 목록 컨텍스트에서 인용되지 않은 변수와 정리되지 않은 입력은적어도DoS 취약점.

전문적인 쉘 스크립터들조차 종종 다음을 인용하는 것을 잊어버립니다:

#! /bin/sh -
: ${QUERYSTRING=$1}

:동작하지 않는 명령입니다. 어떤 문제가 발생할 수 있나요?

이는 설정되지 않은 경우 할당된다는 의미입니다 $1. 이는 명령줄에서 CGI 스크립트를 호출할 수 있게 만드는 빠른 방법이기도 합니다.$QUERYSTRING$QUERYSTRING

그러나 $QUERYSTRING이는 참조되지 않기 때문에 여전히 확장이며,분할+전역교환원에게 전화하세요.

이제 일부 글로브는 확장하는 데 특히 비용이 많이 듭니다. 이는 /*/*/*/*최대 4개 수준의 디렉토리를 나열한다는 의미이므로 매우 좋지 않습니다. 이는 디스크 및 CPU 활동 외에도 수만 개의 파일 경로(여기서는 10,000개의 디렉터리가 있는 가장 작은 서버 VM의 경우 40,000개)를 저장하는 것을 의미합니다.

이제 /*/*/*/*/../../../../*/*/*/*이는 40k x 10k를 의미하며 /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*가장 강력한 기계도 무릎을 꿇기에 충분합니다.

직접 시도해 보세요. 단, 컴퓨터가 충돌하거나 중단될 수 있다는 점에 대비하세요.

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

물론, 코드가 다음과 같은 경우:

echo $QUERYSTRING > /some/file

그런 다음 디스크를 채울 수 있습니다.

그냥 구글 검색을 해보세요껍데기또는배쉬 CGI또는크슈케지, 쉘에서 CGI를 작성하는 방법을 보여주는 페이지를 찾을 수 있습니다. 처리 매개변수의 절반이 취약하다는 점에 유의하세요.

심지어데이비드 코헨의 것 중 하나 취약합니다(쿠키 처리 참조).

최대 임의 코드 실행 취약점

임의 코드 실행은 가장 심각한 유형의 취약점입니다.공격자어떤 명령이라도 실행할 수 있으며, 수행할 수 있는 작업에는 제한이 없습니다.

이것은 일반적으로 그렇습니다나뉘다이로 이어지는 부분입니다. 이렇게 분할하면 하나의 인수만 필요한 경우 여러 인수가 명령에 전달됩니다. 이들 중 첫 번째는 의도한 맥락에서 사용되지만 나머지는 다른 맥락에서 사용되므로 해석이 다를 수 있습니다. 예를 들어보는 것이 가장 좋습니다.

awk -v foo=$external_input '$2 == foo'

여기서 목적은 쉘 변수의 내용을 $external_input해당 foo awk변수에 할당하는 것입니다.

지금:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

분할 후 두 번째 단어는 $external_input 할당되지 않고 코드 foo로 처리됩니다 awk(여기서 임의 명령 실행: uname).

이는 특히 다른 명령( awk, env, sed(GNU one), perl... find)을 실행할 수 있는 명령, 특히 GNU 변형(인수 뒤에 옵션을 허용하는)에서 문제가 됩니다. 때로는 명령이 다른 명령( 예 ksh: , 's 또는 ...) 을 실행할 수 있다고 의심하지 않는 경우가 있습니다.bashzsh[printf

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

라는 디렉터리를 생성하면 x -o yes우리가 평가하는 조건식과 완전히 다르기 때문에 테스트 결과는 긍정적입니다.

x -a a[0$(uname>&2)] -gt 1더 나쁜 것은 최소한 모든 ksh 구현(대부분의 상용 Unices 및 일부 BSD 포함)을 포함하는 파일을 생성하는 경우 해당 쉘이 명령의 숫자 비교 연산자에 대한 산술 평가를 수행하기 때문에 sh 해당 파일이 실행된다는 것입니다 .uname[

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

bash비슷 하다 x -a -v a[0$(uname>&2)].

물론 임의로 실행할 수 없다면,공격자피해가 적은 것에 만족할 수 있습니다(임의적 집행이 용이할 수 있음). 파일에 쓸 수 있거나 권한, 소유권을 변경할 수 있거나 주요 효과나 부작용이 있는 모든 명령이 잠재적으로 악용될 수 있습니다.

파일 이름으로 할 수 있는 모든 종류의 작업이 있습니다.

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

결국에는 쓰기 가능하게 됩니다 ..(GNU 재귀 사용 chmod).

공개적으로 쓰기 가능한 영역에서 파일을 자동으로 처리하는 스크립트는 /tmp세심한 주의를 기울여 작성해야 합니다.

무엇에 대해[ $# -gt 1 ]

이것은 내가 짜증나게 생각하는 것입니다. 어떤 사람들은 따옴표를 생략할 수 있는지 결정하기 위해 특정 확장자에 문제가 있는지 궁금해하는 수고를 합니다.

마치 말하는 것처럼.안녕하세요, 분할+글로브 연산자로 묶일 수 없는 것 같습니다 $#. 셸에서 분할+글로브를 수행하도록 하겠습니다.. 또는야, 버그가 맞을 확률이 적으니까 잘못된 코드를 작성하자.

지금은 그럴 가능성이 얼마나 되나요? 좋습니다. $#(또는 $!, $?또는 산술 대체) 숫자(또는 -일부²) 만 포함할 수 있으므로전반적인 상황부품은 이미 나왔어요. ~을 위한나뉘다$IFS단, 숫자(또는 ) 만 포함하면 됩니다 -.

일부 쉘의 경우 $IFS환경에서 상속하는 것이 가능하지만 환경이 안전하지 않으면 어쨌든 게임이 끝납니다.

이제 다음과 같은 함수를 작성하면:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

즉, 함수의 동작은 호출되는 컨텍스트에 따라 달라집니다. 즉, $IFS 입력 중 하나가 됩니다. 엄밀히 말하면 함수에 대한 API 문서를 작성할 때 다음과 같아야 합니다.

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

함수를 호출하는 코드에는 $IFS숫자가 포함되지 않도록 해야 합니다. 이 모든 것은 두 개의 큰따옴표 문자를 입력하고 싶지 않기 때문입니다.

이제 이 버그가 취약점이 되려면 어떻게든 다음 의 가치를 창출 [ $# -eq 2 ]해야 합니다 .$IFS공격자. 상상할 수 있듯이 일반적으로 이런 일은 다음과 같은 경우에는 발생하지 않습니다.공격자다른 버그를 악용해 보세요.

하지만 들어본 적이 없는 것은 아닙니다. 일반적인 상황은 사람들이 산술 표현식에 데이터를 사용하기 전에 데이터를 삭제하는 것을 잊어버리는 것입니다. 위에서 우리는 일부 쉘에서는 임의의 코드 실행을 허용할 수 있지만 모든 쉘에서는 허용된다는 것을 확인했습니다. 공격자모든 변수에 정수 값을 제공하십시오.

예를 들어:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

$1값이 있는 경우 (IFS=-1234567890)이 산술 평가는 IFS 설정의 부작용이 있으며 다음 [ 명령이 실패합니다.매개변수가 너무 많습니다.우회.

언제분할+전역교환원이 호출되지 않나요?

변수 및 기타 확장에 따옴표가 필요한 또 다른 경우가 있습니다. 패턴으로 사용될 때입니다.

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

$a및 가 동일한지 테스트하지 않지만 $b( 제외 zsh) $aif 및 의 패턴과 일치합니다 $b. $b문자열로 비교하려면 인용을 해야 합니다(패턴으로 간주하지 않으면 "${a#$b}"or "${a%$b}"또는 where에서 "${a##*$b*}"동일하게 인용 해야 합니다).$b

즉, 둘 다 다르면 [[ $a = $b ]](예: is 와 is 인 경우) true를 반환할 수 있고, 동일할 경우(예: 둘 다 및 are 인 경우) false를 반환할 수 있습니다.$a$b$aanything$b*$a$b[a]

이로 인해 보안 취약점이 발생합니까? 예, 어떤 실수라도 마찬가지입니다. 여기,공격자스크립트의 논리적 코드 흐름을 변경하거나 스크립트의 가정을 깨뜨릴 수 있습니다. 예를 들어 다음 코드를 사용합니다.

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

공격자를 전달하면 검사를 우회할 수 있습니다 '[a]' '[a]'.

이제 패턴이 일치하고분할+전역연산자 적용, 인용되지 않은 변수의 위험은 무엇입니까?

나는 다음과 같이 썼다는 것을 인정해야 한다:

a=$b
case $a in...

여기에 참조를 포함하는 것이 나쁠 것은 없지만 반드시 필요한 것은 아닙니다.

그러나 이러한 상황(예: Q&A 답변)에서 따옴표를 생략하면 초보자에게 잘못된 메시지를 보낼 수 있다는 부작용이 있습니다.변수를 인용하지 않아도 괜찮을 것입니다..

a=$b예를 들어, 그들은 그것이 가능 하다면 그렇게 할 수 있다고 생각하기 시작할 수 있습니다 export a=$b(이것은그것은 많은 껍질에 있지 않습니다export목록 컨텍스트 에서 ) 또는 env a=$b.

하지만 제안을 받아들이지 않는 곳도 있습니다. 주요한 것은 많은 쉘에서 Korn 스타일 산술 표현식입니다. 예를 들어 인용이 허용되지 않는 echo "$(( $1 + 1 ))" "${array[$1 + 1]}" "${var:$1 + 1}"경우 $1(목록 컨텍스트 - 간단한 명령에 대한 인수 - 그러나 전체 확장에는 여전히 인용이 필요함)입니다.

내부적으로 쉘은 C에서 영감을 받은 완전히 독립적인 언어를 이해합니다. ksh예를 들어 AT&T에서는 C와 같이 3으로 확장되지만 C $(( 'd' - 'a' ))와는 다릅니다. $(( d - a ))ksh93에서는 큰따옴표가 무시되지만 다른 많은 쉘에서는 구문 오류가 발생합니다. C에서는 "d" - "a"C 문자열에 대한 포인터 간의 차이가 반환됩니다. 쉘에서 동일한 작업을 수행하는 것은 의미가 없습니다.

무엇에 대해 zsh?

zsh실제로 대부분의 디자인 당혹감을 해결합니다. zsh(적어도 sh/ksh 에뮬레이션 모드가 아닌 경우) 원하는 경우나뉘다, 또는와일드카드, 또는패턴 매칭, 변수의 내용을 패턴으로 $=var분할, 전역 또는 처리하는 등 명시적으로 요청해야 합니다 .$~var

그러나 따옴표가 없는 명령 대체에서는 분할(와일드카드 제외)이 여전히 암시적으로 수행됩니다(그림 참조 echo $(cmd)).

또한 변수를 인용하지 않으면 때때로 원치 않는 부작용이 발생할 수 있습니다.삭제 지우기. 이 동작은 와일드카드( 사용 ) 및 분할( 사용 ) zsh을 완전히 비활성화하여 다른 셸에서 달성한 것과 유사합니다. 아직도 안에:set -fIFS=''

cmd $var

없을 겁니다분할+전역, 그러나 $var비어 있으면 cmd빈 인수를 받는 대신 인수를 받지 않습니다.

이로 인해 오류가 발생할 수 있습니다(분명한 사실입니다 [ -n $var ]). 이로 인해 스크립트의 기대와 가정이 깨지고 취약점이 발생할 수 있습니다.

빈 변수로 인해 매개변수가 생성될 수 있으므로삭제됨, 이는 다음 인수가 잘못된 컨텍스트에서 해석될 수 있음을 의미합니다.

예를 들어,

printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2

$attacker_supplied1비어 있으면 문자열(for )이 아닌 $attacker_supplied2산술 표현식(for )으로 해석됩니다 .%d%s산술 표현식에 사용되는 처리되지 않은 데이터는 zsh와 같은 Korn 유사 셸의 명령 주입 취약점입니다..

$ attacker_supplied1='x y' attacker_supplied2='*'
$ printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2
[1] <x y>
[2] <*>

괜찮습니다. 하지만:

$ attacker_supplied1='' attacker_supplied2='psvar[$(uname>&2)0]'
$ printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2
Linux
[1] <2>
[0] <>

이것uname 모든 명령실행됩니다.

또한 대체 시 와일드카드는 기본적으로 수행되지 않지만 zsh의 와일드카드는 다른 셸의 와일드카드보다 훨씬 강력하기 때문에 zsh해당 옵션도 활성화하거나 비활성화하지 않고 실수로 일부 변수를 남겨두면 더 많은 피해를 입힐 수 있음을 의미합니다. 인용되지 않았습니다.globsubstextendedglobbareglobqual

예를 들어, 심지어:

set -o globsubst
echo $attacker_controlled

e예를 들어 glob 한정자의 평가를 사용하여 명령이 glob 확장의 일부로 실행될 수 있기 때문에 임의 명령 실행 취약점이 됩니다 .

$ set -o globsubst
$ attacker_controlled='.(e[uname])'
$ echo $attacker_controlled
Linux
.
emulate sh # or ksh
echo $attacker_controlled

bareglobqualsh/ksh 에뮬레이션에서는 비활성화되어 있기 때문에 ACE 취약성을 유발하지 않습니다(sh와 같은 DoS 취약성은 여전히 ​​존재하지만) . globsubstsh/ksh 코드를 해석하려는 경우 sh/ksh 에뮬레이션 외부에서 이러한 기능을 활성화할 이유가 없습니다.

그럼 당신이하다필요분할+전역운영자?

예, 이는 일반적으로 변수를 인용 해제하기를 원하는 경우입니다. 하지만 그런 다음에는 반드시 조정을 해야 합니다.나뉘다그리고전반적인 상황사용하기 전에 올바르게 작동하십시오. 당신이 원한다면나뉘다대신에 부분전반적인 상황섹션(대부분의 경우)에서 와일드카드( set -o noglob/ set -f)를 비활성화하고 수정 해야 합니다 $IFS. 그렇지 않으면 위에서 언급한 David Korn의 CGI 예와 같이 취약점이 발생할 수도 있습니다.

결론적으로

간단히 말해서, 셸에서 인용되지 않은 변수(또는 명령 대체 또는 산술 확장)는 실제로 매우 위험할 수 있으며, 특히 잘못된 컨텍스트에서 실행될 때 어떤 변수가 잘못된 컨텍스트인지 알기가 어렵습니다.

이것이 고려되는 이유 중 하나이다.나쁜 습관.

지금까지 읽어주셔서 감사합니다. 상상 그 이상이라면 걱정하지 마세요. 모든 사람이 코드를 작성하는 방식으로 코드 작성의 모든 의미를 이해할 것이라고 기대할 수는 없습니다. 그래서 우리는 모범 사례 권장 사항, 이유를 이해하지 않고도 따라갈 수 있습니다.

(아직 명확하지 않은 경우 셸에 보안에 민감한 코드를 작성하지 마세요.)

그리고이 사이트의 답변에 변수를 인용해 주세요!


¹ ksh93합계 pdksh와 도함수로,버팀대 확장와일드카드가 비활성화되어 있지 않으면 실행됩니다( ksh93ksh93u+ 버전 에서 이 braceexpand옵션이 비활성화된 경우에도).

² ksh93및 에서는 산술 확장에 , , 등도 yash포함될 수 있습니다 . glob 연산자를 포함하여 더 많은 기능이 있지만 시뮬레이션에서도 산술 확장 시 분할+glob이 수행되지 않습니다.1,21e+66infnanzsh#extendedglobzshsh

답변2

[에서 영감을 받다이 답변통과카스.]

하지만 만약...?

하지만 내 스크립트가 변수를 사용하기 전에 알려진 값으로 설정하면 어떻게 될까요? 특히 변수를 둘 이상의 가능한 값 중 하나로 설정하는 경우(그러나언제나알려진 값으로 설정) 모든 값에 공백이나 전역 문자가 포함되어 있지 않습니까? 따옴표 없이 사용하면 안전하지 않나요?이 경우?

가능한 값 중 하나가 빈 문자열이고 "null 제거"에 의존하는 경우에는 어떻게 되나요? 즉, 변수에 빈 문자열이 포함되어 있으면 명령에서 빈 문자열을 가져오고 싶지 않습니다. 예를 들어,

만약에특정_조건
그 다음에
    대소문자 무시="-i"
기타
    대소문자 무시=""
필리핀 제도
                                  # 위 명령의 따옴표는 다음과 같습니다.아니요엄격하게 요구됩니다.  
grep $대소문자 무시  다른_grep_args

빈 문자열이면 실패할 것이라고는 말할 수 없습니다.grep "$ignorecase" other_grep_args$ignorecase

회신하다:

다른 답변에서 설명한 것처럼 IFSa 또는 an 이 포함되어 있으면 -여전히 실패합니다 i. 변수에 문자가 포함되어 있지 않은지 확인하고 변수에 전역 문자가 포함되어 있지 않은지 확인하면 IFS안전할 수 있습니다 .

하지만 더 안전한 방법이 있습니다(다소 추악하고 매우 직관적이지 않지만). ${ignorecase:+"$ignorecase"}.from을 사용하세요.POSIX 쉘 명령 언어 사양, 아래에 2.6.2 매개변수 확장,

${parameter:+[word]}

    대체 값을 사용하십시오.  만약에parameter설정되지 않았거나 null입니다. 그렇지 않으면 null로 바꿔야 합니다.word (또는 빈 문자열인 경우word생략)을 교체해야 합니다.

여기서의 비결은 우리가 ignorecase사용하는 것입니다parameter 그리고"$ignorecase"word. 그러니까 ${ignorecase:+"$ignorecase"}그 뜻은

$ignorecase설정되지 않았거나 null(즉, 비어 있음) 인 경우 null(즉, 따옴표 없음)아무것도 없다)을 교체해야 하며, 그렇지 않으면 "$ignorecase"확장을 교체해야 합니다.

이를 통해 우리는 가고 싶은 곳으로 이동합니다. 변수가 빈 문자열로 설정되면 "제거"됩니다(전체 복잡한 표현식은 다음과 같이 평가됩니다).아무것도 없다— 빈 문자열도 아님) 변수에 null이 아닌 값이 있으면 따옴표로 묶인 해당 값을 얻습니다.


하지만 만약...?

하지만 단어로 분할하고 싶거나 분할해야 하는 변수가 있는 경우에는 어떻게 해야 합니까? (이것은 첫 번째 경우와 비슷합니다. 내 스크립트에는 변수 세트가 있고 전역 문자가 포함되어 있지 않을 것이라고 확신합니다. 그러나 공백이 포함될 수 있으므로 공백에서 별도의 매개 변수로 분할하고 싶습니다. PS I 여전히 빈 항목을 삭제하고 싶습니다)

예를 들어,

만약에특정_조건
그 다음에
    표준="-typef"
기타
    표준=""
필리핀 제도
만약에다른 조건
그 다음에
    기준="$criteria -mtime +42"
필리핀 제도
"$start_directory" $criteria 찾기  다른_찾다_args

회신하다:

를 사용하고 있다고 생각할 수도 있습니다 eval.  아니요!eval여기에서 사용하고 싶은 유혹을   뿌리치세요 .

IFS마찬가지로 변수에 문자(공백 제외)가 포함되어 있지 않은지 확인하고 변수에 전역 문자가 포함되어 있지 않은지 확인하면 위의 내용은 아마도 안전할 것입니다.

그러나 bash(또는 ksh, zsh 또는 yash)를 사용하는 경우 더 안전한 방법이 있습니다. 배열을 사용하는 것입니다.

만약에특정_조건
그 다음에
    Criteria=(-type f) # `criteria=("-type" "f")`라고 말할 수 있지만 꼭 필요한 것은 아닙니다.
                        # 하지만원하지 않는다`criteria=("-type f")` 또는 `criteria="(-type f)"`라고 말하세요.
기타
    표준=() #원하지 않는다이 명령에는 따옴표를 사용하세요!
필리핀 제도
만약에다른 조건
그 다음에
    기준+=(-mtime +42) # 참고: `=`가 아니라 `+=`, 배열에 추가(추가)합니다.
필리핀 제도
"$start_directory" "${criteria[@]}" 찾기  다른_찾다_args

~에서큰 타격(1),

참조 배열의 모든 요소를 ​​사용할 수 있습니다. … 만약에 ${name[subscript]}subscript예 @또는 *, 이 단어는 모든 구성원에게 적용됩니다.name. 이러한 아래첨자는 단어가 큰따옴표 안에 나타날 때만 다릅니다. 단어가 큰따옴표로 묶인 경우... 각 요소를 확장합니다.${name[@]}name별도의 단어로.

따라서 "${criteria[@]}"위의 예에서는 배열의 0개, 2개 또는 4개의 요소로 확장되며 criteria각 요소는 참조됩니다. 특히 둘 다 아니라면상황 s가 true이고 criteria배열에 내용이 없으며( criteria=()문으로 설정됨) "${criteria[@]}"다음과 같이 평가됩니다 .아무것도 없다 (불편한 빈 문자열도 아닙니다).


이는 여러 단어를 처리할 때 특히 흥미롭고 복잡해집니다. 그 중 일부는 미리 알지 못하는 동적(사용자) 입력이고 공백이나 기타 특수 문자가 포함될 수 있습니다. 고려하다:

printf "찾고 싶은 파일 이름을 입력하세요:"
파일 이름 읽기
if [ "$fname" != "" ]
그 다음에
    표준 +=(-이름 "$fname")
필리핀 제도

$fname인용하였으니 참고하세요사용시간. 이는 사용자가 foo bar또는 같은 것을 입력하는 경우에도 작동합니다 foo*"${criteria[@]}"결과는 -name "foo bar"또는 입니다 -name "foo*". (배열의 각 요소는 참조된다는 점을 기억하세요.)

배열은 모든 POSIX 셸에서 사용할 수 없습니다. 배열은 ksh/bash/zsh/yash-isms입니다. 게다가... 있습니다.하나모든 쉘에서 지원되는 배열: 인수 목록, 즉 "$@"호출에 대한 인수 목록을 완료한 경우(예: 모든 "위치 인수"(매개변수)를 변수에 복사했거나 다른 방법으로 처리한 경우) 다음을 수행할 수 있습니다. 목록은 배열로 사용됩니다.

만약에특정_조건
그 다음에
    set -- -type f # `set -- "-type" "f"`라고 말할 수 있지만 꼭 그럴 필요는 없습니다.
기타
    놓다 -
필리핀 제도
만약에다른 조건
그 다음에
    설정 --"$@"-mtime +42
필리핀 제도
#유사하게: set -- "$@" -name "$fname"
"$start_directory" "$@" 찾기  다른_찾다_args

"$@"구성(역사적으로 첫 번째)은 동일한 의미를 갖습니다. 즉, 입력한 대로 각 인수(즉, 인수 목록의 각 요소)를 별도의 단어로 확장합니다."${name[@]}""$1" "$2" "$3" …

에서 발췌POSIX 쉘 명령 언어 사양, 아래에2.5.2 특수 매개변수,

@

    1부터 시작하여 위치 매개변수로 확장되며 처음에는 각 위치 매개변수 세트에 대한 필드를 생성합니다. …, 초기 필드는 별도의 필드로 유지되어야 합니다.… 위치 인수가 없으면 확장합니다.@다음과 같은 경우에도 0 필드를 생성해야 합니다.@큰따옴표 안에 …

전체 텍스트는 다소 모호합니다. 핵심은 "$@"위치 인수가 없을 때 0개의 필드가 생성되어야 함을 지정한다는 것입니다. 역사: 1979년 Bourne 쉘(bash의 전신)에 처음 도입되었을 때 위치 인수가 없을 때 "$@"위치 인수가 단일 빈 문자열로 대체되는 버그가 있었습니다 ."$@"${1+"$@"}쉘 스크립트에서 이는 무엇을 의미합니까? 와 어떻게 다른가요 "$@"?전통적인 Bourne Shell 시리즈...무슨 ${1+"$@"}뜻인가요? 필요한 곳은 어디입니까?, 그리고"$@"비교적${1+"$@"}.


배열은 첫 번째 경우에도 도움이 됩니다.

만약에특정_조건
그 다음에
    ignorecase=(-i) # `ignorecase=("-i")`라고 말할 수도 있지만 꼭 그럴 필요는 없습니다.
기타
    대소문자 무시=()#원하지 않는다이 명령에는 따옴표를 사용하세요!
필리핀 제도
grep "${ignorecase[@]}"  다른_grep_args

____________________

PS(csh)

이것은 자명하지만, 초보자를 위해 csh, tcsh 등은 Bourne/POSIX 쉘이 아닙니다. 그들은 완전히 다른 가족입니다. 다른 색깔의 말. 그것은 완전히 다른 야구 게임입니다. 고양이의 다른 품종. 외계인. 그리고 가장 독특하게도 다른 종류의 벌레가 있습니다.

예를 들어 이 페이지에서 설명하는 내용 중 일부는 csh에 적용됩니다. 그렇게 하지 않을 타당한 이유가 없고 자신이 수행 중인 작업을 확실히 알고 있지 않는 한 모든 변수를 인용하는 것이 가장 좋습니다. 그러나 csh에서는 모든 변수가 배열입니다. 거의 모든 변수가 하나의 요소만 포함하는 배열이고 Bourne/POSIX 쉘의 일반 쉘 변수와 매우 유사하게 동작합니다. 그리고 구문은 매우 다릅니다(내 말은매우). 따라서 여기서는 csh 쉘 계열에 대해 더 이상 이야기하지 않겠습니다.

답변3

Stéphane의 답변에 대해 의문이 있지만 잘못 사용될 수 있습니다 $#.

$ set `seq 101`

$ IFS=0

$ printf '%s\n' $#
1
1

$ printf '%s\n' "$#"
101

아니면 $? :

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ status=$?

$ printf '%s\n' $status
1
1

$ printf '%s\n' "$status"
101

이것은 인위적인 예이지만 잠재력은 현실입니다.

답변4

내 스크립트 또는 SO에 대한 답변의 모든 스크립트에서 내 변수를 모두 참조하지 않으면 내가 도덕적으로 부패했거나 기술적으로 무능하다는 개념을 부정하는 것입니다.

인기 있는 답변은 마지막 줄로 요약됩니다.

셸에 보안에 민감한 코드를 작성하지 마세요.

이 게시물은 공격자를 가정합니다. 나는 99%의 쉘 스크립트가 어떤 공격에도 직면하지 않으며 앞으로도 결코 직면하지 않을 것이라고 제안합니다. 사용자의 편의를 /usr/local/bin위해 존재합니다 . $HOME일종의 초기화를 제어합니다.정의에 따르면임의 입력의 영향을 받지 않습니다.

당신은 ~/.profile공격자를 마주하고 있지 않습니다. 로그 파일을 구문 분석하거나 로드할 데이터베이스를 준비하는 30줄 awk 스크립트도 그렇지 않을 것입니다. 항상 공격에 노출되어 있는 것처럼 쉘 스크립트를 작성하라는 보편적이고 일반적인 조언은 대다수의 스크립트를 필요 이상으로 복잡하게 만들고 안전할 수 있다는 생각만 장려합니다.

스크립트가 공격자에게 노출되어 있나요? 자, 조심해서 이 상황에서 루트 권한으로 쉘 스크립트를 실행하는 것이 좋은 생각인지 생각해 보세요.

편집하다

Always Quote Recommendation 쉘 변수를 인용하면 항상 오류가 방지된다고 주장합니다. 그러나 실제로는 그렇지 않습니다. 도착하다맹목적으로입력에 의미상 오류를 일으키는 공백이나 와일드카드가 포함되어서는 안 되는 경우 분할 및 확장을 방지합니다.

name=$(ls foo*.cfg)
...
if [ ! -f $name ]
then 
    touch $name
fi

$name여러 파일 이름이 실수로 포함 되면 위 코드가 중단됩니다.예상되는 이진 연산자. 마법의 오류 방지 따옴표를 사용하여 "올바르게" 작성하면 자동으로 부정확하게 성공하여 새로운 가짜 파일이 생성됩니다. 의심할 바 없이 나중에 이해할 수 없는 방식으로 문제가 발생하여 디버그하기가 더욱 어려워질 것입니다.

를 인용하지 않음으로써 $name스크립트는 내용에 공백이 포함되지 않은 이름이 포함된다는 것을 암시적으로 주장합니다. 이것은 유용한 주장이다. 여러 이름을 사용하거나 어리석은 이름을 만들지 않습니다. 실행 중인 경우(해당 경우) 중지됩니다 -e. 그렇지 않으면 적어도 무엇이 잘못되었는지 경고하는 메시지가 생성됩니다.

관련 정보