[MySQL & PHP] 20장 인터넷에 공개할 때 주의할 점

MySQL & PHP 학습/정리 그리고 체크! 2016. 3. 15. 00:31

728x90
반응형

1. 공유 폴더에는 중요한 정보를 저장하지 않는다.


1.1 PHP 파일의 구조

웹 페이지를 공개할 때에는 웹 서버의 공유 폴더에 파일을 저장합니다. 이렇게 공개된 파일은 인터넷에 접속할 수만 있다면 누구라도 내려받을 수 있습니다.


그렇다면, 18장에서의 데이터베이스를 조작하는 스크립트도 접속한 쪽에서 내려받을 수 있을까요? 만약, 누구라도 자유롭게 내려받을 수 있고 그 내용을 볼 수 있다면, 서버 이름과 사용자 이름뿐만 아니라 비밀번호까지도 온 세상에 모두 공개하는 것이 됩니다.


사실은 공유 폴더에서 PHP가 동작하도록 설정되어 있다면, .php 확장자를 가진 파일에 접속하면 해당 스크립트가 실행됩니다. PHP 스크립트 파일에 접속한 클라이언트에게 반환되는 것은 스크립트를 실행한 결과뿐입니다. 즉, .php 파일의 내용은 공개되지 않습니다.


또한, 서버에 있는 파일의 내용을 다른 사람에게 보여주고 싶지 않다면, 일반적으로 속성을 변경해서 접속을 제한할 수 있습니다.


따라서, 중요한 내용을 포함하는 파일도 "PHP 스크립트로 작성해서 접속에 제대한을 두면 절대 안전하다!"라고 생각할 수도 있습니다. 하지만 웹 응용프로그램을 구성하는 시스템에 보안상 결함이 있을 때에는 접속에 제한을 설정해도 어떤 이유에서인지 공개되고 마는 경우가 있습니다. 그리고 인터넷을 이용하는 사람 중에는 악의를 가진 사람도 있습니다. 그렇기 때문에, 비밀번호 등 중요한 정보가 적힌 파일을 공유 폴더에 저장하는 것은 매우 위험한 행동입니다.


그렇다면, 스크립트에서 중요한 정보를 다룰 때에는 어떻게 하면 좋을까요?


1.2 다른 파일에서 스크립트 불러오기

웹 서버 역할을 하는 컴퓨터에는 반드시 '공유 폴더'를 설정하게 됩니다. 중요한 정보를 포함하는 파일은 반드시 '파일의 위치를 추측할 수 없는 비공개 폴더'에 저장해야 합니다. 공유 폴더에 있는 파일에는 중요한 정보를 저장하지 않고, 필요한 정보는 다른 장소에서 가져오도록 하면 됩니다.


require_once

PHP 스크립트에는 다른 PHP 스크립트 파일을 불러오는 몇 가지 기능이 있습니다. 여기에서는 require_once 명령을 예를 들어 설명하겠습니다. 이 함수를 사용하면 파일의 내용을 한 번만 가져오며, 만일 불러오는 데 실패하면 처리를 중지합니다.


require_once

require_once(불러올_파일_이름)


먼저, 서버 이름과 사용자 이름, 비밀번호, 데이터베이스 이름을 작성한 파일을 만듭니다. 이 파일은 공유 폴더가 아닌 다른 폴더에 저장합니다.


예제 20-1과 같이 파일을 작성하고 db_info.php라는 파일 이름으로 비공개 폴더 data에 저장합니다.


예제 20-1 db_info.php

1
2
3
4
5
6
<?php 
    $SERV = "localhost";
    $USER = "root";
    $PASS = "1234";
    $DBNM = "db1";
 ?>
cs


'19장의 3.3 레코드를 추가하는 PHP 스크립트' 의 데이터베이스에 접속하는 스크립트 simple_select.php를 수정해서, 이 기본 정보를 저장한 파일 db_info.php를 불러오겠습니다. 변경할 내용은 데이터베이스에 접속하는 부분입니다.


변경전

$s = mysql_connect("localhost", "root", "1234") or die("실패입니다.");

print "접속 OK!<br>";

mysql_select_db("db1");


require_once("data/db_info.php")에서 data 폴더에 있는 db_info.php 파일을 불러옵니다.


여기에서는 이해하기 쉽게 하려고 공유 폴더(htdocs 등) 안에 data 폴더를 생성하고, 그 안에 db_info.php를 저장하고 있습니다. 그러나 원칙대로 라면 비밀번호 등을 작성한 파일은 공유 폴더보다 상위 폴더 즉, 비공개 폴더에 저장해야 합니다.


변경 후의 전체 스크립트는 예제 20-2와 같습니다.


예제 20-2 simple_select2.php (윈도우와 맥 으로 구분했습니다. 맥의 경우는 상위 폴더 '비공개 폴더'에 넣었습니다.)


변경 후 (윈도우의 경우엔 그냥 htdocs 의 하위에 넣었습니다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
    require_once("data/db_info.php");
    $s = mysql_connect($SERV$USER$PASS) or die("실패입니다.");
    print "접속 OK!<br>";
    mysql_select_db($DBNM);
    $re = mysql_query("SELECT * FROM tbk ORDER BY number");
    while ($result = mysql_fetch_array($re)) {
        print $result[0];
        print " : ";
        print $result[1];
        print " : ";
        print $result[2];
        print "<br>";
    }
    mysql_close($s);
    print "<br><a href='simple.html'>메인 화면으로</a>";
 ?>
cs


변경 후 (맥의 경우 애플리케이션 의 하위에 폴더를 생성하고 넣었습니다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
    require_once("/Applications/data/db_info.php");
    $s = mysql_connect($SERV$USER$PASS) or die("실패입니다.");
    print "접속 OK!<br>";
    mysql_select_db($DBNM);
    $re = mysql_query("SELECT * FROM tbk ORDER BY number");
    while ($result = mysql_fetch_array($re)) {
        print $result[0];
        print " : ";
        print $result[1];
        print " : ";
        print $result[2];
        print "<br>";
    }
    mysql_close($s);
    print "<br><a href='simple.html'>메인 화면으로</a>";
 ?>
cs


단순하게 require_once("data.db_info.php"); 부분에 불러올 파일의 내용이 삽입된다고 생각하면 됩니다.


같은 방법으로 simple_insert.php와 simple_delete.php, simple_search.php 파일도 수정해 두면, 만일 스크립트 내용이 공개되었다고 해도 비밀번호 등의 중요한 정보는 보호할 수 있습니다.


외부 파일을 불러오는 명령

require_once() 외에도 외부 파일을 불러오는 명령이 있습니다. 예를 들어, include_once() 함수의 경우, 파일을 한 번만 불러오는 것은 같지만, 실패했을 때에도 처리를 계속합니다.



2. 질의에 부정한 데이터를 입력할 수 없게 한다.


2.1 SQL 인젝션이란?

비밀번호 등 중요한 정보만 노출되지 않으면 PHP + MySQL은 안전할까요? 절대 그렇지 않습니다. 오히려 훨씬 더 위험하고 무서운 것이 SQL 인젝션 입니다.


인젝션(injection)은 '주입하다'또는 돈을 '들이 붓다'라는 의미로 쓰입니다. 즉, SQL 인젝션은 'SQL 문을 주입하다'라는 의미입니다. 웹 응용프로그램 제작자의 예상과는 다른 데이터를 주입함으로써 부정한 처리를 유발하는 공격 방법입니다.


그럼, SQL 인젝션의 공포를 한 번 체험해 보겠습니다.


19장의 예제 19-4 의 simple_delete.php를 예로 들어 보겠습니다. 19장에서 만든 간단한 게시판에는 지정한 번호의 레코드를 삭제하는 기능이 있었습니다. 다음이 그 부분에 해당합니다.


$b1_d = $_POST["b1"];

mysql_query("DELETE FROM tbk WHERE number=$b1_d");

$re = mysql_query("SELECT * FROM tbk ORDER BY number");


데이터를 전달하는 simple.html에서는 삭제할 레코드의 번호를 <input ... name="b1">의 텍스트 상제에 입력하여 전송합니다.


그러면, 데이터를 전달은 simple_delete.php의 $_POST["b1"]에서 번호를 전달받고 $b1_d = $_POST["b1"]에서 변수 $b1-d에 대입하고 다음과 같은 질의를 실행합니다.


DELETE FROM tbk WHERE number=$b1_d


프로그램을 만드는 입장에서는 변수 $b1_d에 당연히 존재하는 레코드 번호인 6이나 12가 입력될 것이라고 예상합니다.


SQL 인젝션 체험하기

여기에서 잠깐 생각해 봅시다. simple_html의 삭제 번호를 입력하는 텍스트 상자에 다음과 같이 입력하고 <확인> 단추를 누르면 어떻게 될까요?


1 or 1=1


맨 앞에 있는 1을 먼저 인식해서 1번째 레코드가 삭제될까요? 만일 그렇다면, 그 뒤에 입력된 or 1=1은 어떻게 처리될까요?


이 내용을 앞의 SQL문에 대입하면 다음과 같습니다.


DELETE FROM tbk WHERE number=1 or 1=1


여기에서 1=1은 항상 참이 되는 식입니다. 그렇기 때문에, 'number가 1일 때 또는 1=1일때'는 모두 참입니다. 즉, 1번째 레코드가 삭제될 뿐만 아니라 테이블 tbk의 모든 레코드가 삭제되고 맙니다.


1 or 1=1 입력하기




실제로 이 예에서 1 or 1=1을 전송하면 모든 레코드가 삭제됩니다. 이처럼 너무도 간단하게 웹 응용프로그램 제작자의 의도와는 상관없이 위험한 질의를 실행할 수 있게 됩니다. 이렇게 SQL 문의 일부를 포함한 데이터를 전송함으로써 시스템을 공격하는 방법이 SQL 인젝션 입니다.


그런의미에서 보면 19장의 simple_delete.php는 '마음대로 삭제해도 됩니다.'라는 의미의 구조로 된 스크립트입니다. 만일, 이 스크립트를 인터넷에 공개해서 본격적으로 데이터베이스를 운용한다면 어떨까요? 게다가, 상거래에 이용되는 소중한 고객의 데이터를 분실하는 일이 벌어진다면 어떻게 될까요?


이 세상에는 훨씬 더 교묘한 SQL 인젝션을 사용해서 다양한 방법으로 공격하는 사람들이 있습니다. 예를 들어, 스크립트의 내용이나 변수가 모두 보이지 않아도 변수나 처리 흐름을 추측해서 위험한 SQL 문을 주입해 올 수도 있습니다.


그렇다면 웹 응용프로그램을 공개할 때, 도대체 어떤 대책을 세우면 좋을까요?


숫자 외에는 입력할 수 없게 한다

앞의 예에서는 '숫자 외의 데이터가 전달되면 DELETE를 실행하지 않는다.'라는 구조로 만들면 됩니다. 전달된 데이터는 삭제할 레코드 번호이기 때문에, 만일 알파벳이 포함되어 있을 때에는 이 데이터를 삭제하지 않고 경고 메시지를 표시하도록 합니다.


알파벳이 포함되어 있는지를 검사하는 방법은 여러 가지가 있지만, 여기에서는 '정규 표현'과 preg_match() 함수를 이용하는 방법을 설명하겠습니다.



3. 정규 표현


3.1 정규 표현이란?

정규 표현이란, '문자 패턴(질서와 규칙)을 확인하는 조건'을 표현하는 방법입니다. 예를 들어, [0-9]라는 표현은 '0에서 9까지의 숫자가 포함되어 있다.'라는 의미입니다.


3.2 정규 표현의 예

다음은 정규 표현의 대표적인 예입니다.


[] 안의 문자가 포함되어 있다

정규 표현

내용 

 [7] 

 7이 포함되어 있다

 [0-9] 

 숫자가 포함되어 있다

 [a-z] 

 알파벳 소문자가 포함되어 있다

 [A-Z] 

 알파벳 대문자가 포함되어 있다 

 [A-Za-z]

 알파벳의 대문자 또는 소문자가 포함되어 있다 

 [A-Z][0-9] 

 시작 문자가 알파벳 대문자이고 다음 문자가 숫자로 된 연속 문자 패턴이 포함되어 있다 


^의 다음 문자로 시작한다

정규 표현

 내용 

 [^0-9] 

 0~9 문자 외의 문자가 포함되어 있다(숫자 이외의 문자가 포함되어 있다) 

 [^A] 

 A 이외의 문자가 포함되어 있다 

 [^A-Z] 

 알파벳 대문자 이외의 문자가 포함되어 있다 

 [^0-9a-zA-Z] 

 숫자와 알파벳 이외의 문자가 포함되어 있다 


^의 다음 문자로 시작한다

정규 표현

내용 

 ^h 

 h로 시작한다 


$의 앞 문자로 끝난다

정규 표현

내용 

 E$

 E로 끝난다 


{}의 앞 문자가 {} 안의 횟수만큼 연속된다

정규 표현

내용 

 7{3} 

 7이 3개 이상 연속해서 포함되어 있다 



3.3. preg_math() 함수

정규 표현을 사용해서 퍼지 검색을 수행하는 함수로 preg_match()가 있습니다. 퍼지 검색은 검색할 문자를 정확하게 지정하는 것이 아니라, 넓은 범위에서 '대략 이런 패턴의 문자'를 검색합니다.


preg_match() 함수는 다음과 같이 사용합니다.


preg_match() 함수

preg_match(정규_표현, 검색할_문자열)


preg_match() 함수는 정규 표현을 사용해서 검색합니다. 또한, preg_match() 함수에서는 일반적으로 정규 표현을 빗금(/)으로 감쌉니다. 예를 들어, '알파벳 대문자 또는 소문자를 포함한다.'라는 정규 표현은 [A-Za-z] 입니다. 다음은 'A에서 Z또는 a에서 z, 즉 알파벳이 포함되어 있다.'라는 정규 표현입니다.


/[A-Za-z]/


예제 20-3 include.php

1
2
3
4
5
6
7
<?php 
    if (preg_match("/[A-Za-z]/""1234")) {
        print "포함합니다.";
    } else {
        print "포함하지 않습니다.";
    }
 ?>
cs



3.4 정규 표현을 이용해서 부정한 입력 검사하기

다른 정규 표현을 하나 더 소개하겠습니다. ХХХ - ХХХ 형식의 우편번호가 입력되었는지 검사해 봅시다.


■ 0-9 중 3개의 문자로 시작한다 -> -(붙임표) -> 0-9 중 3개의 문자로 끝난다.


이러한 문자 조합이 포함되어 있는지를 표현하는 방법은 다음과 같습니다.


* 0~9 중 3개의 문자 -> [0-9]{3}

* 시작한다 -> ^

* 0~9 중 3개의 문자 -> [0-9]{3}

* 끝난다 -> $


예제 20-4는 변수 $m의 내용 107-052를 검사하는 예입니다.


예제 20-4 zipcode.php

1
2
3
4
5
6
7
8
<?php 
    $m = "107-052";
    if (preg_match("/^[0-9]{3}-[0-9]{3}$/"$m)) {
        print "일단 OK 입니다.";
    } else {
        print "오류가 있습니다.";
    }
 ?>
cs


이 예에서는 일치하기 때문에 '일단 OK 입니다.'가 표시됩니다.


물론, 실제로 인터넷에 공개하려면 이것만으로는 부족합니다. 철저하게 검사하려면 좀 더 많은 정규 표현을 추가해야 합니다.


DELETE 명령을 포함하는 질의는 실행하지 않도록 수정한다

그럼, 일단 19장에서 소개한 mysql_query("DELETE FROM tbk WHERE number=$b1_d"); 부분에서 숫자 이외의 데이터가 입력되면, 경고를 표시하고 DELETE 명령을 실행하지 않도록 수정해 보겠습니다.


mysql_query("DELETE FROM tbk WHERE number=$b1_d");에서 변수 $b1_d에 숫자 이외의 데이터가 포함되어 있을 때에는 다음과 같은 경고 메시지를 표시하고 DELETE 명령을 실행하지 않도록 수정합니다.


숫자 이외의 데이터가 포함되어 있을 때, 경고 메시지를 출력하는 명령은 다음과 같습니다.


print "<font color='red'>숫자 외에는 입력하지 마시오!!</font><br>";


예제 20-5는 수정한 simple_delete.php의 예입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php 
    $s = mysql_connect("localhost""root""1234") or die("실패입니다.");
    print "접속 OK!<br>";
    mysql_select_db("db1");
    $b1_d = $_POST["b1"];
 
    /* 수정된 부분 simple.html 에서 삭제 부분 action 속성을 simple_delete2.php 로 교체 */
    if (preg_match("/[^0-9]/"$b1_d)){
        print "<font color='red'>숫자 외에는 입력하지 마시오!!</font><br>";
    }else{
        mysql_query("DELETE FROM tbk WHERE number=$b1_d");
    }
 
    $re = mysql_query("SELECT * FROM tbk ORDER BY number");
    while ($result = mysql_fetch_array($re)) {
        print $result[0];
        print " : ";
        print $result[1];
        print " : ";
        print $result[2];
        print "<br>";
    }
    mysql_close($s);
    print "<br><a href='simple.html'>메인 화면으로</a>";
 ?>
cs


변수 $b1_d의 값에 만일 알파벳(0 ~ 9 이외의 값)이 입력되면 '숫자 외에는...'의 메시지가 표시되며, DELETE 명령을 포함하는 질의는 실행되지 않습니다. <font color='red'> ~ </font>에 설정된 문자열 '숫자 외에는 ...'은 빨간색으로 표시됩니다.


그리고 알파벳 (0~9 이외의 값)이 입력되지 않았다면, mysql_query("DELETE FROM tbk WHERE number=$b1_d);"가 실행되어 해당 데이터가 삭제됩니다.


물론, 빈틈없이 하려면 숫자 이외의 값만 검사하는 것이 아니라 실제로 존재하는 번호가 입력되었는지도 검사하면 좋을 것입니다.


최근 SQL 인젝션에 다양한 수법이 이용되고 있기 때문에 그 모든 수법을 설명하는 어렵지만, 적어도 가장 기본적인 대처 방법의 하나인 쓸데없는 SQL 문이 입력되지 않게 하는 정도의 대책은 마련하도록 합시다.



4. 의도하지 않은 태그는 실행하지 않는다.


4.1 악의가 있는 태그 전달

웹 응용프로그램을 작성할 때 태그도 중요한 포인트 입니다.


17장의 send.html과 receive.php 파일을 이용해서 설명하겠습니다. 이 2개의 파일은 send.html의 텍스트 상자에 문자를 입력하고 <확인> 단추를 누르면, receive.php가 내용을 출력하는 구조입니다.


먼저, receive.php가 실행되도록 준비합니다. 그리고 localhost에 send.html을 표시합니다. 텍스트 상자에 다음과 같이 <body bgcolor=yellow>이라고 입력하고 <확인> 단추를 누릅니다.


<body bgcolor=yellow>


과연, 어떤 결과가 표시될까요?


결과는 화면 전체가 노랗게 보이게 됩니다. 어떻게 된 일일까요?


send.html에서는 텍스트 상자에 입력된 문자를 receive.php에 전달할 뿐이고, receive.php는 전달받은 문자열을 print로 출력할 뿐입니다. 즉, 노란색으로 바뀐 화면은 <body bgcolor=yellow>이 출력된 결과입니다. 17장의 예제 17-2에서 '배경색을 ◯색으로 한다.'라는 태그가 실행된 것입니다.


send.html, receive.php와 같은 웹 응용프로그램에서는 입력된 태그에 따라 해당 기능이 실행되고 맙니다. 이 정도의 장난이라면 귀여운 정도에 속합니다. 그러나 스크립트를 실행하는 태그처럼, 경우에 따라서는 큰 피해를 주는 태그가 전달될 수도 있습니다. 그렇기 때문에, 인터넷에 공개할 때에는 만전을 기해야 합니다.



4.2 취약성 공격

앞에서처럼 웹 페이지에 입력된 그대로 화면에 출력하는 프로그램은 매우 위험합니다. 악의가 있는 사람이 보낸 스크립트를 일반 사용자가 아무것도 모른 채 실행하게 되는 취약성을 노린 공격은 끊이지 않고 발생합니다.


이런 문제를 방지하기 위해서라도 입력된 스크립트를 걸러낼 수 있는 웹 응용프로그램을 만들어야 합니다.



4.3 입력된 태그는 걸러낸다

입력된 문자를 웹 페이지에 그대로 출력하는 구조는 매우 위험합니다. 일단, 출력하는 문자열에 포함된 태그를 무효 처리하는 구조를 만들어 보겠습니다.


이럴 때에는 태그 등의 특수문자를 다른 문자열로 변환하는 htmlspecialchars() 함수를 사용합니다.


htmlspecialchars() 함수

htmlspecialchars(문자열)


htmlspecialchars() 함수는 태그 등의 특수 문자를 다음과 같이 변환합니다.


htmlspecialchars() 함수를 이용한 변환

변환 대상 특수 문자

변환 후 

<

&lt;

>

&gt;

&

&amp;

&quot; 

&#039; 


악의를 갖고 입력할 때, 주로 사용되는 것은 <과 >, &, ", '등의 기호입니다. htmlspecialchars() 함수는 이런 문자열의 기능이 실행되지 않도록 다른 문자열로 변환합니다.


예를 들어, 17장의 예제 17-17의 receive.php에 다음과 같이 htmlspecialchars() 함수를 사용해 보겠습니다.


예제 20-6 receive.php (17장 의 예제 17-17 수정)

1
2
3
<?php 
    print htmlspecialchars($_POST["a"]);
 ?>
cs


단순하게 전달된 값이 대입된 변수 $_POST["a"]를 htmlspacialchars() 함수로 처리했습니다. 스크립트를 변경했으면 저장하고 다시 한 번 send.html을 실행합니다. 텍스트 상자에 <body bgcolor=yellow>을 다시 입력하고 <확인> 단추를 누르면 어떻게 될까요?


이번에는 입력한 태그가 그대로 표시됩니다. 즉, 태그로서의 기능이 무효가 되었습니다. 다음은 화면에 표시된 결과의 소스를 표시한 것입니다. <의 정체가 &lt;란 것을 알 수 있습니다.




안전한 스크립트를 작성하려면

개발자는 웹 응용프로그램에 대한 공격에 대비하여 가능한 대책을 모두 세워야 합니다. 그러면, 구체적으로는 어떤 대책이 필요할까요?


다음은 그 중 중요한 몇 가지를 소개한 것입니다.


■ 공유 폴더에는 최소한의 파일만 저장한다. 비밀번호나 데이터베이스, 중요한 데이터는 절대로 공유 폴더에 저장하지 않는다. 파일과 폴더에는 접속 제한을 설정한다.

■ 입력된 데이터를 업격하게 검사하고, 지정된 형식 이외의 값은 무효로 하는 구조로 만든다.

■ 전달된 값에 의해 프로그램 작성자의 의도에 맞지 않는 동작은 일어나지 않는 구조로 만든다.


이와 같은 대책만 마련하면 안전할까요? 그렇지 않습니다. 웹 응용프로그램을 공격하는 방법은 매일매일 진화하고 있습니다. 평상시 이용하는 HTTP 프로토콜은 기본적으로는 한번의 요구 -> 응답으로 끝납니다. 이처럼 개인 인증을 하지 않고 누구라도 이용할 수 있는 시스템에서 '공격에 대한 만반의 대책'을 설정한다는 것은 무리일 수 있습니다.


우리 개발자들은 최악의 사태를 대비해서 항상 가능한 대책을 염두에 두어야 합니다.



정리


1. 인터넷에 웹 응용프로그램을 공개할 때 주의해야 할 점

2. 비밀번호 등 중요한 정보를 포함하는 파일을 다루는 방법

3. SQL 인젝션에 대한 대책

4. 태그에 의한 공격에 대한 대책


결코, 보안 대책을 게을리해서는 안 됩니다. 이는 자신이 저지른 실수가 자신뿐만 아니라 불특정 다수의 사람에게도 피해를 줄 수 있기 때문입니다. 물론, 이 장에서 배운 내용만으로는 충분하지 않습니다. 항상 보안 대책에 대해 의식하는 것이 중요합니다.


체크!

★ SQL 인젝셔이란 무엇인지 이해하고 있다.

★ require_once() 함수를 사용해서 다른 파일에서 스크립트를 불러올 수 있다.

★ 태그의 기능을 무효로 하는 방법을 이해하고 있다.

★ preg_match() 함수와 정규 표현을 사용한 검사 방법을 이해하고 있다.

반응형