날짜: 1996-06-04 | 글쓴이: 도아 | 7751 번 | 프린트 | 메일로보내기

제 4강 - 펄 언어로 안전한 스크립트 작성하기


믿을것은 없다

많은 CGI 작성자가 범하는 첫번째 실수는 사용자의 입력을 믿을 수 있다고 가정하는 것이다. 사실 믿을 수있는 것은 거의 아무것도 없다 - 이 스크립트를 호출하는 httpd 조차 믿을 수 없다. 폼의 입력

결코 폼의 입력을 믿지 마라. 다음과 같은 것들은 모두 거짓이다:

  • 만약 선택 목록을 만든다면 이 필드에대한 입력은 Option 태그의 값중 하나일 것이다.
  • 입력 필드의 최대길이를 설정한다면 브로우저는 기껏해야 지정된 값만큼의 문자를 보낼 것이다.
  • QUERY_STRING 변수내의 필드는 자신의 페이지내의 필드와 일치할 것이다.
파일명 파일 열기

아마 어떠한 파일명이던 CGI에서 직접 처리한 것은 안전하다. 폼, PATH_INFO와 다른 소스로부터 가져온 파일명은 의심해봐야한다. 때때로 받아들일 수 있는 파일명을 유지하는 것이 실용적이다. 그렇치 않으면 /를 사용하지 않거나 아마 ..와 루트를 나타내는 /의 사용을 금지할 필요가 있다. 보통 CGI 작성자는 받아들일 파일의 위치를 정확히 지정할 수 있다. 파일의 생성

일반적으로 간단한 이름을 갖는 파일을 생성하려고 한다면 문자를 A-Za-z0-9_로 제한하는 것이 상당히 안전한다. Unix 하에서 파일명은 .로 시작할 수 없다. 공백과 -와 쉘 메타문자 또한 파일명으로는 좋지않다. 타당하지않은 문자의 목록을 사용하는 것보다 타당한 문자 집합을 사용하는 것이 훨씬 낫다.

보안을 걱정하는 CGI 작성자는 누구나 쓸 수 있는 디렉토리(/tmp)에 쓰는 것은 피해야 한다. /tmp내에 디렉토리를 만듬으로서 프로그램이 CGI 호출하고 다음 CGI를 호출하는 사이에 나타나지 않는 디렉토리를 처리할 수 있다. 그러나 이 것은 악의적인 사람들이 중요한 파일이나 디렉토리에대해 쉽게 링크할 수있도록 한다. 따라서 항상 열은 파일이 변경하려는 파일인지 검증해야 한다. umask 설정

많은 httpd의 기본 umask는 0이으로 CGI에의해 생성된 파일은 기본적으로 전세계 모든 사람이 읽고 쓸 수있다. umask를 022로 설정(읽기만 가능)하거나 077(자신만 읽고 쓰기)로 설정해야 한다. 프로그램 호출

많은 유용한 CGI 프로그램은 사용자가 작성한 프로그램이나 표준 Unix 프로그램을 호출한다. fortune으로 검색프로그램을 얼마나 쉽게 구현할 수 있는가를 생각해보자. 불행하게도 대부분의 CGI 보안문제는 다른 프로그램을 호출함으로서 발생한다.

다음은 CGI 프로그래머가 직면한 문제가 무엇이고, 여러 형태의 오용을 막는 기법에는 어떤 것이 있는가 알아보자. 각 예는 PERL로 작성되었다. 기본적인 문제

CGI는 문자열 검색을 위해 grep을 호출하며 폼에서 정규식을 사용할 수 있다고 가정하자. PERL의 경우 사실 정규식을 통해 grep을 구현하는 것이 보다 간단하다(또한 훨씬 안전하다)는 것에 주의한다.

system("grep $exp database"); 

이나

sprintf(tmp, "grep %s database", exp); system(tmp);

와 같은 접근법은 많은 문제점을 가지고 있다. "root /etc/passwd;rm"의 값을 갖는 exp을 고려하자. 실제 잘못된 파일을 읽을 뿐만아니라 database를 삭제하게된다. 가장 간단한 해결책은 인용부호를 추가하는 것이다. 인용부호만으로는 충분하지 않다.

system("grep \"$exp\" database");

이나

sprintf(tmp, "grep \"%s\" database", exp); system(tmp);

작은 따옴표든 큰 따옴표든 문제는 해결된다. 예를들어 큰 따옴표로 exp는 ""rm -rf /"와 같이 될 수 있다. 작은 따옴표로도 이 것을 해결할 수 있지만 둘다 ``'root /etc/passwd;rm'''와 같은 문제를 발생한다. 따옴표는 변수를 감싸는 것과 일치하며, 결국 이들의 효과를 없애버린다. 개개의 문자를 Escape하는 것이 훨씬 낫다

특수문자앞에 "\"를 두는 것은 상당히 쉽다.

$exp =~ s/[^\w]/\\&/g; 
system("grep \"$exp\" database"); 

이 방법으로 지금까지 논의한 모든 문제를 처리할 수 있다. 그러나 exp가 "-i"라면 아직도 문제점을 가지고 있다. "grep"은 표준입력에서 문자열 "database"를 찾으려할 것이다(대소문자를 구분하지 않고).

grep에 "-e" 옵션을 사용함으로서 이러한 문제를 막을 수 있다. 일반적으로 exp에대해 가능한 값을 제한하는 경우를 제외하고, 주어진 변수가 스위치가 아니라는 것을 통보할 수 없는 프로그램을 호출하고 싶지 않을 것이다. GNU 유틸리티를 사용하는 것은 스위치 표식의 끝으로서 "--"를 허용하기때문에 사실 아주 훌륭한 방법이다. 더 나은 방법

다음 방법으로 프로그램을 호출한다면 문자들을 Escape 시킬 필요가 없다.

system("grep", "-e", $exp, "database");

이 방법으로 grep를 호출함으로서 쉘이 호출되는 것을 막을 수 있다. 비록 globbing과 같은 쉘 특징을 요구하는 경우 그렇게 편리한 방법은 아니다.

위와같은 경우에 또다른 접근방법이 사용될 수 있다. 이것은 쉘의 특징을 취할 수 있다는 잇점이 있다.

$ENV{'FOO'} = $exp; 
system 'grep -ei "$FOO" *.c'; 
안전하게 외부 프로그램을 호출하는 방법

펄에서는 외부프로그램을 호출하는 방법이 여러 가지가 있다. 첫 번째로 백틱을 사용하는 방법,

$date = `/bin/date`;

파이프를 쓰는 파일 핸들을 open()하는 방법,

open (SORT, " | /usr/bin/sort | /usr/bin/uniq");

system() 명령어를 사용해서 프로그램 수행이 끝날 때까지 기다리는 방법,

system "/usr/bin/sort < foo.in";

exec() 명령어를 사용해서 리턴하지 않는 방법,

exec "/usr/bin/sort < foo.in";

네 가지가 있다. 이 네가지 방법 모두 입력에 쉘 메타문자가 있을 경우 보안 헛점을 만들 가능성이 있다. system()과 exec()을 사용할 경우 쉘을 통해서 프로그램을 수행하지 않고 바로 외부프로그램을 수행하도록 할 수 있다.

외부 프로그램으로 인자를 보내야 할 경우 한꺼번에 한 문자열로 프로그램과 인자를 묶지 않고 각각 따로 문자열로 만들어서 리스트를 보내면 쉘을 사용하지 않게 된다. 예를 들면

system "/usr/bin/sort","foo.in";

과 같다. 이 방법은 파이프를 쓰는 파일 핸들을 open()할 때도 사용할 수 있다. 다음과 같이 하면 마찬가지로 쉘을 거치지 않고 바로 프로그램이 수행된다.

open (SORT,"|-") || exec "/usr/bin/sort",$uservariable;
while $line (@lines) {
  print SORT $line,"\n";
}
close SORT;

쉘을 수행하지 않고 파이프로부터 프로그램의 입력을 받고 싶은 경우에는 다음과 같이 '-|'를 사용할 수 있다.

open(GREP,"-|") || exec "/usr/bin/grep",$userpattern,$filename;
while (<GREP>) {
  print "match: $_";
}
close GREP;
펄의 taint 체킹

지금까지 살펴본 대로 CGI 프로그램의 보안에 문제가 발생할 소지가 있는 곳은 사용자 입력을 검사하지 않고 쉘로 보내는 경우이다.

펄은 이런 것들을 방지하기 위해 taint 체킹이라는 메카니즘을 제공한다. 프로그램 외부(환경 변수, 표준 입력, 명령행 인자를 포함해서)로부터 입력 받은 모든 변수들은 "tainted"로 간주되고 "tainted" 변수들은 외부 프로그램을 실행하는데 이용될 수 없다.

이 "tainted" 변수의 값이 다른 변수의 값을 설정하는데 사용되면 그 변수도 마찬가지로 "tainted"로 간주된다. 이런 "tainted" 변수들은 eval(), system(), exec(), 파이프 open()을 위해서는 사용될 수 없다.

또한, 프로그램 안에서 명시적으로 PATH 환경 변수를 설정하지 않고 외부프로그램을 수행하려고 할 경우 펄은 경고메세지를 내고 종료하게 된다. 펄 버전 4에서 "taint" 체킹을 사용하기 위해서는 taintperl이라는 펄의 특별 버전을 사용하면 되고, 버전 5에서는 다음과 같이 -T 옵션을 주면 된다.

#!/usr/local/bin/perl -T

일단 "taint" 체킹을 켜 놓으면 "Insecure $ENV{PATH} at line XX"라는 메시지를 자주 보게 된다. 이것은 방금 말한 것과 같이 PATH 환경변수를 설정하지 않은 상태로 외부 프로그램을 호출했기 때문이다. 이 에러메세지를 피하고 싶다면 프로그램 안에서 직접 PATH 환경 변수를 설정해야 한다.

$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';

일단 어떤 변수가 "tainted"로 간주되면 system(), exec(), 파이프 open(), eval(), 백틱 명령어, unlink와 같은 외부에 영향을 미치게 되는 함수들을 수행하는데 이용될 수가 없다.

"tainted" 변수에 tr/// 연산자를 사용하거나 s/// 연산자를 사용해서는 "tainted"를 없앨 수가 없다. "tainted" 속성을 없애는 방법은 "tainted" 변수에 대해서 패턴 매치를 수행한 다음에 매치된 문자열을 꺼내서 쓰는 방법밖에 없다. 예를 들어 e-mail 주소를 얻어낸다면 다음과 같이 할 수 있다.

$mail_address=~/([\w-.]+\@[\w-.]+)/;
$untainted_address = ;


다음글: 제 5강 - CERT 권고 (5157)1996-06-05
이전글: 제 3강 - 외부함수 호출시 주의점 (6184)1996-06-03

세상사는 이야기

  • 만원대 피젯 스피너를 >
  • 망하는 길을 택한 쿠팡 >
  • 물놀이에 적당한 가성 >
  • 컴퓨터를 IPTV로 2, po >
  • 컴퓨터를 IPTV로 만들 >
  • Warning.or.kr도 우회 >
  • 한국의 100대 부자, 어 >
  • 세상을 바꾼 크롬: 크 >
  • 장난(?)으로 시작한 여 >
  • 탈옥의 필수, QuickDo >


  • RSS 구독 (익명 | 회원 | 강좌 | 포럼)
    (C) 1996 ~ 2017 QAOS.com All rights reserved.