2020년 6월 25일 목요일

PHP에서 잘못된 URL의 유입을 막는 방법

PHP에서 MVC 모델 형태로 웹 시스템을 개발하다보면 DB에 저장, 혹은 DB 정보를 수정하는 URL이 생성되는데 문제는 해당 URL을 막바로 웹브라우저 주소 창에 입력하게 될 경우 문제가 발생하게 된다.
DB에 insert하는 경우의 URL을 웹 브라우저에서 막바로 입력해서 막바로 접속할 경우 시스템을 정교하게 만들지 않을 경우 데이터가 없는 새로운 레코드가 생성되는 불상사가 발생하게 된다.

http://xxx.xxx.xxx/adm/reg/member ⇒ 회원가입 페이지(reg_member.php) ⇒ http://xxx.xxx.xxx/adm/save/member ⇒ DB에 회원 정보 insert(AdmModel.php -> insertMember())

여기서 사용자가 웹 브라우저 주소창에  http://xxx.xxx.xxx/adm/save/member 이 URL로 막바로 접속할 경우 AdmModel.php의 insertMember() 함수가 실행되어 DB에 입력 값이 없는 회원 레코드가 하나 생성되는 불상사가 발생한다는 것이다.
따라서 이런 경우를 막기 위해서는 아래와 같이 간단한 몇줄의 코드로 방어할수 있다.

        public function registeMember($regMbr){
            $prevPage = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_PATH);

            if($prevPage != '/adm/reg/member'){
                echo "<script>alert('허용되지 않는 잘못된 접근입니다.');</script>";
                return;
            }
        }

HTTP_REFERER 환경 변수는 이전 페이지의 URL 값을 담고 있는 환경변수이다. 따라서 정상적인 경우라면 /adm/save/member URL로 들어오기 전 URL은 /adm/reg/member 이어야 하는 것이다. 이 값이 아닌경우라면 차단하면 되는 것이다.
즉 브라우저에서 회원 정보를 등록하는 URL인 http://xxx.xxx.xxx/adm/save/member로 막바로 접속시 위의 $prevPage에는 아무 값이 없다. 따라서 그런 경우는 차단을 하면된다.

2020년 6월 10일 수요일

아파치 url rewrite 기능을 이용한 PHP에서 MVC 모델의 Controller 기능 구현하기

PHP에서 MVC 모델 방식에서 Controller 기능을 구현할려면 어떻게 해야 할까? 혹은 FrontController 기능을 구현할 경우 어떻게 해야 할 것인가?
php의 경우는 url 경로에 맞는 해당 경로에 php 소스가 1:1로 대응되도록 되어 있다.
즉 http://xxx.xxx.xxx/reserve/list.php일 경우 웹 Document Root 디렉토리 아래의 reserve 디렉토리 안에 list.php 소스가 있어야 되는 식이다.

문제는 이런 방식으로 동작하기 때문에 어느 특정 디렉토리의 특정 php 소스를 Controller 역할을 하게 할수가 없게된다. 달리말해서 어떤 형태의 url이 들어오더라도 무조건 특정 디렉토리의 특정 php 소스를 무조건 실행되게 할수가 없다는 것이다.
어떤 경우의 url 형태가 요청되더라도 항상 특정 php가 실행되어야 해당 php 소스가 Controller 역할을 수행할수 있는것 아니겠는가? 

또 url 경로에 맞는 디렉토리에 해당 소스가 있다는 것은 그 소스가 노출될 위험성을 안고 있는 보안상의 문제가 있게 된다.

PHP의 경우 이러한 난점을 해결하고 모든 url에 대해 특정 php로 redirect 시켜 해당 php가 FrontController 역할을 하도록 하기 위해 apache의 기술을 이용하면 Controller 기능을 구현할수 있다.
이때 사용하는 것이apache의 rewrite 모듈이다.

apache의 rewrite 모듈은 apache의 모듈의 한 종류로 서버로 접속해 오는 request를 정해진 규칙을 통해 특정 경로의 파일로 redirect 시키는 역할을 하는 모듈이다.
아래의 절차를 따라 진행하면 된다(Ubuntu 16.04를 기준으로 작성된 글이다).

(1) Apache rewrite 모듈 활성화
아래 명령어를 통해 apache rewrite 모듈을 활성화 한다.

# a2enmod rewrite

(2) Apache 설정에서 rewrite 기능을 사용가능하도록 설정 변경
아래의 경로에 있는 apache2.conf 파일을 열어 설정을 변경한다.

# vi /etc/apache2/apache2.conf
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>

위의 AllowOverride None을 AllowOverride All로 변경

(3) .htaccess 파일에서 규칙 생성
웹 Document Root 디렉토리에 .htaccess 파일을 생성한다. apache2의 웹 document root가 어디인지 확인할려면 Ubuntu 16.04의 경우 /etc/apache2/sites-available/000-default.conf 파일을 열면 아래와 같은 내용이 있을 것이다.

DocumentRoot /var/www/html

따라서 .htaccess 파일을 /var/www/html/ 에 생성해서 아래의 내용을 입력한다.

RewriteEngine On
RewriteBase /
RewriteRule ^([^.?]+)$ /ebook3/index.php

.htaccess 파일을 작성하는 규칙은 복잡한 많은 내용이 있지만 여기서는 최소한으로 간략히 표현 경우이다.
위의 설정 내용의  /ebook3/index.php에서 /가 의미하는 것은 어느 경로 위치를 의미하는가 하는 것이다. 즉 여기서 /의 위치는 어디이냐 하는 것이다. 
여기서 /ebook3/index.php의 위치는 Web DocumentRoot의 경로 위치인 /var/www/html/ebook3/index.php를 의미한다. 따라서 /가 의미하는 것은 Web DocumentRoot의 위치를 의미한다.

위의 내용은 어떤 형태의 url로 접근을 하더라도 무조건 /ebook3/index.php를 redirect 시키라는 규칙이다.
만일
http://xxx.xxx.xx/reserve/list
http://xxx.xxx.xx/reserve/view
http://xxx.xxx.xx/reserve/list/json
...
과 같이 url이 reserve로 시작하는 모든 경우 /ebook3/index.php로 이동시키고 싶다면 RewriteRule을 다음과 같이 하면 될 것이다.

RewriteRule ^reserve /ebook3/index.php

(4) apache2  재시작
이상의 모든 설정 사항이 적용되도록 아파치 재 구동

# service apache2 restart

rewrite 모듈이 정상적으로 구동중인지 확인할려면 phpinfo() 를 통해 나오는 페이지에서 "Loaded Modules" 항목을 보면 mod_rewrite라는 항목이 있으면 해당 모듈이 정상 동작하고 있는 것이다.

이상의 작업이 완료되면 이제 PHP에서 MVC 모델을 적용하되 FrontController 역할을 index.php로 할수 있게된다. 즉 접속해 오는 모든 url을 무조건 index.php로(혹은 index.php가 아니어도 상관없음) redirect 시키고 index.php에서 url을 분석해서 각각의 url에 맞는 기능으로 분기시키면 이제 php에서도 Java의 Controller 역할을 수행하는 것이 가능하게 된다.
아래는 index.php가 FrontController(혹은 Controller) 역할을 하도록 간단하게 코드의 뼈대만 작성하면 이런식이 될 것이다.

$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

switch($path)
{
case '/reserve/list' :
   //원하는 기능
   bareak;

case '/reserve/view' :
   //원하는 기능
   break;

case '/reserve/insert' :
   //원하는 기능
   bteak;

case '/reserve/list/json' :
   //원하는 기능
   bteak;

default :
   //에러 페이지
   break;
}