SecureReality 는 "A Study in Scarlet - Exploiting Common Vulnerabilities in PHP" 라는 매우 흥미로운 논문을 발표하였다 [Clowes 2001]. 이 논문은 PHP, 특히 PHP 4.1.0 이전 버전에서 보안적인 프로그램을 작성하는데 있어서 다소의 문제들을 논의하고 있다. Clowes 는 ``디폴트 PHP 설정에서 보안적인 PHP 애플리케이션을 작성하는 것은 각자 노력한다고 하더라도 매우 어렵다" 고 결론지었다.
모든 언어에 보안 문제가 있다고 하더라도 PHP 에는 PHP 를 대부분의 언어들보다 비보안적이게 하는 한가지 특별한 문제가 있다; 이는 데이타를 이름공간에 적재하는 방식이다. 디폴트로 PHP (버전 4.1.0 이하) 에서는 웹을 통해 PHP 로 보내지는 모든 환경 변수 및 값이 일반적인 변수들이 적재되는 동일한 이름공간 (전역변수) 내로 자동적으로 적재된다 - 따라서 공격자는 임의의 변수에 임의의 값을 설정할 수 있으며 PHP 프로그램이 명시적으로 재설정하지 않는다면 이 값이 유지된다. 게다가 PHP 는 처음 요청될 때 디폴트 값을 갖는 변수를 자동적으로 생성하는데 따라서 PHP 프로그램이 변수를 초기화하지 않는 것이 일반적이다. 변수 설정을 하지 않는 경우 PHP 가 이를 보고할 수는 있지만 디폴트로 그렇게 하지 않을 것이다 - 이는 단순히 에러 보고로 이러한 에러를 야기하는 예외적인 방식을 발견한 공격자를 막지 못할 것음을 주목해라. 따라서 디폴트로 PHP 는 프로그램이 공격자를 무시하기 위해 특별히 주의를 하지 않는다면 공격자가 프로그램내의 모든 변수 값을 완전히 제어할 수 있도록 한다. 일단 공격자가 프로그램을 제어하게 된다면 이러한 변수를 재설정할 수 있으며 따라서 각자 어떤 변수(명확하진 않지만 적어도 한개의 변수) 를 재설정하지 못한다면 PHP 프로그램을 취약하게 만들 수도 있다.
예를 들어 다음 PHP 프로그램 (Clowes 로부터의 예제) 은 패스워드를 알고 있는 사람들에게 단지 어떤 중요한 정보를 얻도록 하지만 공격자는 그 사람들의 웹 브라우저에 ``auth" 를 설정해 인가 검사를 파괴할 수 있다:
<?php if ($pass == "hello") $auth = 1; ... if ($auth == 1) echo "some important information"; ?> |
저자와 많은 사람들은 이러한 특별히 위험한 문제에 대해 푸념해왔지만 이는 PHP 가 널리 사용되고 있기 때문에 특별한 문제이다. 결국 사용하기 쉽다고 알려진 언어는 보안적인 프로그램 작성을 더욱 쉽게한다. PHP 에서 ``register_globals" 를 ``off" 로 설정함으로써 이러한 잘못된 특징을 금지하는 것은 가능하지만 디폴트로 4.1.0 이상의 PHP 버전은 이를 ``on" 으로 설정하며 4.1.0 이전 PHP 버전은 register_globals 를 off 설정하는 경우 더욱 사용하기 어렵다. PHP 개발자들은 4.1.0 버전 발표에서 ``다음 semi-major 버전에서는 PHP 의 신규 설치시 디폴트로 register_globals 를 off 로 설정할 것이다" 라고 공표했다.
``register_globals" 가 ``on" 으로 설정되어 있는 경우 중요한 프로그램에 대해 PHP 는 위험하다 - 비보안적인 프로그램을 작성하는 것이 너무나도 쉽다. 그러나 ``register_globals" 가 일단 ``off" 로 설정된다면 PHP 는 합당한 개발 언어이다.
보안적인 디폴트는 ``register_globals" 를 ``off" 로 설정해야 하며 또한 사용자들이 외부 출처로부터 받아들일 수 있는 입력을 지정하고 제한하는 것을 더욱 쉽게 하는 몇몇 함수를 포함해야 한다. 아파치와 같은 웹 서버들은 이러한 보안적인 PHP 설치를 개별적으로 설정할 수 있다. 사용자들이 받아들이길 원하는 입력 변수를 열거하기 쉽게 하는 루틴들이 PHP 라이브러리에 놓일 수도 있다; 몇몇 함수들은 이러한 변수들이 가져야하는 패턴 및/또는 요구되는 변수 타입을 검사해야 한다. 저자 의견으로는 PHP (register_globals 가 on 으로 설정) 는 현재 훌륭한 보안적인 웹 개발 언어는 아니다. 그러나 합당한 개발 언어로 사용되도록 쉽게 수정될 수 있다.
PHP 를 사용하기로 한 경우 다음은 얼마간의 저자의 충고이다 (이러한 충고 중 많은 부분은 Clowes 가 제기한 문제에 대처하기 위한 방법에 기초한다):
PHP 설정 옵션 ``register_globals" 를 off 로 설정하고 PHP 4.1.0 이상 버전을 사용해라. PHP 4.1.0 에는 몇몇 특별한 배열이 추가되어 있는데, 특히 $_REQUEST 는 ``register_globals" 가 off 로 설정되어 있을 때 PHP 로 소프트웨어를 개발하는 것을 더욱 간단하게 한다. register_globals 를 설정함으로써 대부분의 일반적인 PHP 공격을 완전히 제거할 수 있는데 이러한 설정이 신규 설치시의 디폴트가 될 때까지 이에 대비해라. register_globals 가 off 로 설정되어 있다고 가정하더라도 이를 우선적으로 검사해야 하며 그렇지 않은 경우는 중지해야 한다 - 이렇게 함으로써 프로그램을 설치한 사람들은 문제가 있음을 재빨리 알아차릴 것이다. 이러한 설정시 작동하는 제삼자 (third-party) 의 PHP 애플리케이션은 거의 없음을 주목해라. 따라서 현재 전체 웹 사이트에 대해 이를 off 로 유지하기는 힘들다. 또한 ``register_globals" 를 금지하는 것이 제삼자에 의해 호스트되고 있을 때는 더욱더 어렵다. 단지 몇몇 프로그램에 대해서만 register_globals 를 off 로 설정하는 것이 가능한데 예를 들어 아파치의 경우 PHP 디렉토리내의 .htaccess 파일에 다음을 삽입할 수 있다 (또는 이를 더욱 제어하기 위해 Directory 지시를 사용해라).
php_flag register_globals Off php_flag track_vars On |
그러나 아파치 웹 서버가 override 를 허가하도록 설정되어 있지 않다면 .htaccess 파일 자체는 무시된다; 대개 아파치 전역 설정이 되어 있어 AllowOverride 는 None 으로 설정되어 있다. 따라서 아파치 사용자를 위해 웹 호스팅 서비스가 설정 파일 (대개 /etc/http/conf/http.conf) 내에 ``AllowOver 옵션" 을 설정하도록 한다면 그렇게 해라. 그 후 필요한 데이타 적재를 간단하게 하기 위해 helper 함수를 작성해라.
널리 사용되고 있는 PHP 애플리케이션을 실행시키면서 register_globals 가 on 으로 설정될 수도 있는 소프트웨어를 개발해야 한다면 사용자가 제공하지 않은 값을 언제나 설정하지 마라. PHP 디폴트 값에 의존하지 말고 명시적으로 설정하지 않은 모든 변수를 신뢰하지 마라. 모든 시작 부분 (예, 모든 PHP 프로그램 또는 PHP 를 사용하는 HTML 파일) 에 대해 이를 해야 함을 주목해라. 가장 좋은 접근 방법은 사용할 모든 변수들을 간단히 평범한 디폴트 값 ( "" 또는 0) 으로 재설정할 예정이라도 이들을 설정함으로써 각각의 PHP 프로그램을 시작하는 것이다. 이는 포함됨 파일내에서 참조되거나 모든 라이브러리내에서 과도적이라도 참조되는 전역 변수를 포함한다. 불행히도 자신들이 호출하는 모든 함수에 의해 사용될 수도 있는 모든 전역 변수를 정확히 알거나 이해하는 개발자들은 거의 없기 때문에 위의 충고대로 하는 것은 어렵다. 한가지 대안은 사용자가 데이타를 제공했는지 알아보기 위해 HTTP_GET_VARS, HTTP_POST_VARS, HTTP_COOKIE_VARS 및 HTTP_POST_FILES 를 검색하는 것이다 - 그러나 대개 프로그래머들은 모든 소스를 검사하는 것을 잊어버리는데 PHP 가 새로운 데이타 소스 (HTTP_POST_FILES 는 예전 PHP 버전에는 없다) 를 추가한다면 어떤 일이 일어나겠는가.
에러 보고 수준을 E_ALL 로 설정하고 테스팅 동안에 보고된 모든 에러를 해결해라. 다른 무엇보다도 이는 PHP 에서의 주요 문제인 초기화되지 않은 변수들에 대해 에러를 보고할 것이다. 이는 프로그램을 디버깅하는데 도움을 주기 때문에 PHP 를 사용하려고 할 때 좋은 개념이다. 에러 보고 수준을 설정하는 방법은 많이 있는데 이는 ``php.ini" 파일 (전역), ``.httpd.conf" 파일 (단일 호스트), ``.htaccess" 파일 (다중 호스트) 또는 error-reporting 함수를 통해 스크립트의 상단을 포함한다. 저자는 php.ini 파일 및 스크립트 상단 모두에 에러 보고 수준을 설정하라고 권한다: 이렇게 함으로써 (1) 스크립트 상단에 명령을 넣는 것을 잊는 경우 또는 (2) 프로그램을 다른 머신으로 옮기고 php.ini 파일 변경을 잊는 경우 보호받을 수 있다. 따라서 모든 PHP 프로그램은 다음과 같이 시작해야 한다:
<?php error_reporting(E_ALL);?> |
이러한 에러 보고는 개발시 설정되어야 하고 실제 사이트에서 실행될 때는 해제되어야 한다고 (에러 메시지가 공격자에 유용한 정보를 제공할 수 있기 때문에) 주장할 수도 있다. 문제는 ``실제 사용"시 에러 보고 기능이 금지된다면 개발시 금지된채 남아있기가 쉽다는 것이다. 따라서 저자는 에러 보고 수준이 설정될 수 있는 모든 파일에 단순히 이를 포함하는 간단한 방법을 제안한다.
특히 원격 파일 접근을 예방하기 위해 파일 이름 생성에 사용되는 모든 사용자 정보를 필터링해라. PHP 는 다른 언어에서 로컬 파일만을 단지 오픈할 수 있는 fopen() 과 같은 파일-오프닝 명령이 다른 사이트로부터의 웹 또는 ftp 요청을 수행하는데 사용될 수 있음을 의미하는 ``remote filew" 기능성이 디폴트로 설정되어 있다.
예전 스타일의 파일 업로드를 사용하지 마라; HTTP_POST_FILES 배열과 관련된 함수를 사용해라. PHP 는 특별한 이름을 갖는 어떤 임시디렉토리에 파일을 업로드함으로써 파일 업로드를 지원한다. PHP 는 그 파일 이름이 존재했던 곳을 가리키기 위해 원래 많은 변수들을 설정한다. 그러나 공격자가 변수 이름 및 그 값을 제어할 수 있기 때문에 이들을 사용해 커다란 악영향을 야기할 수 있다. 대신 업로드된 파일에 접근하기 위해서는 언제나 HTTP_POST_FILES 및 관련된 함수를 사용해라. 이 경우라도 PHP 는 공격자가 임의의 내용을 갖는 파일을 업로드할 수 있게 하며 이는 그 자체로 위험함을 주목해라.
단지 보호된 시작 파일 (entry point) 들만 문서 트리내에 놓아라; 모든 다른 코드 (대부분의 코드일 것이다) 는 문서 트리 외부에 놓아라. PHP 는 이 주제에 일련의 잘못된 충고를 하였다. 원래 PHP 사용자는 ``포함 (included)" 파일에 ``.inc" (include) 확장자를 사용한다고 가정되었다. 그러나 이러한 포함 파일은 대개 패스워드와 다른 정보를 갖고 있으며 아파치는 ``.inc" 파일이 문서 트리내에 있을 때 요청된 경우 요청자에게 이 파일의 내용을 제공할 것이다. 따라서 개발자들은 모든 파일의 확장자를 ``.php" 로 하였다 - 이는 파일 내용이 보여지지 않지만 entry point 가 전혀 아닌 파일이 entry point 가 되어 악용될 수 있음을 의미한다. 초기에 언급했듯이 보통의 보안 충고가 최선이다: 단지 보호된 entry points 만 문서 트리내에 놓고 라이브러리 등의 다른 코드는 문서 트리 외부에 놓아라. 문서 트리내에 어떠한 ``.inc" 파일도 있어서는 안된다.
세션 메카니즘을 피해라. ``세션" 메카니즘은 영구적인 데이타를 저장하는데 유용하지만 현재 구현은 많은 문제를 갖고 있다. 우선 디폴트로 세션은 임시 파일에 정보를 저장한다 - 따라서 multi-hosted 시스템에 있다면 많은 공격자에 노출될 수 있으며 비밀이 누설될 수 있다. 현재 multi-hosted 되고 있지 않은 사람일지라도 나중에 multi-hosted 되고 있는 자신을 발견할 수도 있다. 이러한 정보를 파일시스템 대신 데이타베이스에 연결시킬 수 있지만 multi-hosted 데이타베이스의 다른 사람들이 동일한 허가권을 갖고 그 데이타베이스에 접근할 수 있다면 문제는 동일하다. 주의하지 않는 경우 애매모호한 점이 있는데 (``세션값인지 아니면 공격자의 값인지?") 이는 공격자에게 그들이 선택한 내용을 갖고 있는 파일 또는 키를 서버에 놓을 수 있도록 한다. 또한 이는 공격자가 이러한 자료가 놓일 파일 또는 키의 이름을 어느 정도 제어할 수 있게 한다.
모든 입력에 대해 다른 언어에서와 같이 받아들일 수 있는지 패턴과의 일치 여부를 검사하고 그 후 문자열이 아닌 데이타를 요구되는 타입으로 맞추기 위해 타입 캐스팅을 사용해라. (예상되는) 입력의 선택된 리스트를 쉽게 검사하고 import 하기 위해 ``helper" 함수를 개발해라. PHP 는 부정확하게 타입이 정해질 수 있는데 (loosely-typed) 이는 문제를 야기할 수 있다. 예를 들어 입력 데이타가 "000" 값을 갖는다면 이는 "0" 와 같지 않으며 또한 empty() 도 아니다. 이는 특히 결합 (associative) 배열의 경우 중요한데 이들의 인덱스가 문자열이기 때문이다; 이는 $data["000"] 과 $data["0"] 이 다르다는 것을 의미한다. 예를 들어 $bar 가 double 타입임을 확인하기 위해 (double 에 적합한 포맷을 갖는지 확인한 후) 다음과 같이 해라:
$bar = (double) $bar; |
위험한 함수에 특히 주의해라. 코드 실행 함수 (예, require(), include(), eval(), preg_replace()), 명령 실행 함수 (예, exec(),passthru(), backtick 연산자, system() 과 popen()) 과 오픈 파일 함수 (예, fopen(), readfile() 과 file()) 들이 이러한 PHP 함수들이다. 이는 완전한 목록은 아니다!.
magic_quotes_gpc() 를 사용해라 - 이는 많은 종류의 공격을 제거한다.