본문 바로가기
Develop/PHP

PHP 코드이그나이터 CSRF 토큰 사용법

by bellsilver7 2020. 2. 27.
728x90

안녕하세요. 은은한 개발자입니다.

코드이그나이터에 CSRF 토큰을 사용하는 방법에 대해서 포스팅해보도록 하겠습니다. 먼저 CSRF가 무엇인지 이미 알고 계신 분들도 있겠지만 아직 정의가 명확하게 세워지지 않은 분들을 위해 잠시 사전적 정의를 확인하고 들어가겠습니다.

 

 

CSRF 란?

사이트 간 요청 위조(또는 크로스 사이트 요청 위조, 영어: Cross-site request forgery, CSRFXSRF)는 웹사이트 취약점 공격의 하나로, 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격을 말한다.

  유명 경매 사이트인 옥O에서 발생한 개인정보 유출 사건에서 사용된 공격 방식 중 하나입니다.

  사이트 간 스크립팅(XSS)을 이용한 공격이 사용자가 특정 웹사이트를 신용하는 점을 노린 것이라면, 사이트간 요청 위조는 특정 웹사이트가 사용자의 웹 브라우저를 신용하는 상태를 노린 것이다. 일단 사용자가 웹사이트에 로그인한 상태에서 사이트간 요청 위조 공격 코드가 삽입된 페이지를 열면, 공격 대상이 되는 웹사이트는 위조된 공격 명령이 믿을 수 있는 사용자로부터 발송된 것으로 판단하게 되어 공격에 노출된다.

[참조] 위키백과

 

 

코드이그나이터의 CSRF

  코드이그나이터의 메뉴얼에는 form helper 의 form_open() 함수를 사용하면 폼의 CSRF 보호를 위한 hidden 필드가 자동으로 삽입된다고 나와있습니다. 하지만 보통의 경우 코드이그나이터의 html과 form에 관련된 헬퍼함수들은 사용하지 않기 때문에, 문제가 발생하게 됩니다. 

 

  여기서는 form_open() 함수를 사용하지 않고 CSRF 방어를 적용하는 방법을 설명하도록 하겠습니다. 일단 아래의 경로처럼 자신의 config.php 파일 이 있는 경로로 이동하여 config.php 파일을 열어보도록 하겠습니다. 일반적으로 아래와 같은 경로에 위치합니다.

 

/application/config/config.php

 

config.php 파일을 열었으면, CSRF 를 사용하기 위한 설정을 해주어야 합니다. 설정은 아래와 같은 구문을 찾아 값을 변경하면 됩니다.

<?php
// CSRF 방어를 사용할지 않할지 설정 (TRUE: 사용 | FALSE: 미사용)
$config['csrf_protection'] = TRUE; 

// CSRF 토큰의 이름을 설정 (input 요소의 속성 에서 name 에 해당되는 값)
$config['csrf_token_name'] = 'csrf_token'; 

// CSRF 쿠키의 이름을 설정
$config['csrf_cookie_name'] = 'csrf_cookie'; 

// CSRF 토큰의 생존기간이다. (csrf_regenerate 속성이 FALSE 일때만 유효)
$config['csrf_expire'] = 7200; 

// CSRF 토큰의 갱신 설정 (TRUE: 사용 | FALSE: 미사용)
// TRUE 로 사용하면, 매 요청시마다 토큰값이 변경됨
$config['csrf_regenerate'] = TRUE; 

// CSRF 방어에서 제외할 URI 를 배열로 설정 
// ex) http://bellsilver7.tistory.com/category/it 와 같은 URL이 있다면 아래와 같이 작성하면 됨
// $config['csrf_exclude_uris'] = array('category/it');  
$config['csrf_exclude_uris'] = array();

 

설정에서 보면 $config['csrf_regenerate'] 부분이 있는데 이 설정을 FALSE 로 해두어도 공격자가 임의 사용자의 토큰값을 알지 못하기 때문에 매 요청시 새로운 값으로 갱신이 되지 않더라도, 방어를 할 수 있습니다.

코드이그나이터에서는 CSRF 방어에 사용되는 토큰값을 가져오기 위해 아래와 같은 보안 클래스를 제공합니다.

<?php
// 토큰의 값을 가져온다
$this->security->get_csrf_hash();

// 토큰의 이름을 가져온다
$this->security->get_csrf_token_name();

 

아래의 예제는 jQuery의 Ajax 를 이용한 CSRF 방어 예제입니다. 

- HTML

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>코드이그나이터를 이용한 CSRF 방어 예제</title>
    <script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
    <script>
        $(document).ready(function (e) {

            $('#form').submit(function () {

                $.ajax({
                    url: $(this).attr('action'),
                    type: $(this).attr('method'),
                    data: $(this).serialize(),
                    success: function (data) {

                        // ajax 의 결과값
                        $('#ajax_result').html(data);

                        // csrf 갱신값
                        $('#csrf').val($('#token').data('value'));

                    },
                    error: function (xhr, status, error) {
                        console.log('error'); // 에러
                    }
                });

                return false;

            })

        });
    </script>
</head>
<body>
<div id="ajax_result">
</div>
<form id="form" method="post" action="/csrf">
    <input type="hidden" id="csrf" name="<?= $this->security->get_csrf_hash(); ?>"
           value="<?= $this->security->get_csrf_token_name(); ?>"/>

    <input type="text" name="text" placeholder="테스트 값을 입력"/>
    <input type="submit" value="전송"/>
</body>
</html>

- PHP

<?php 
defined('BASEPATH') OR exit('No direct script access allowed'); 

class Csrf extends CI_Controller { 
	public function index() 
	{ 
		// post 로 전송된 text 필드의 값을 출력 
		echo "테스트값 : ".$this->input->post('text',true); 
		
		// 토큰값 갱신 설정
		// $config['csrf_regenerate'] 이 TRUE 일 경우, 
		// 요청시마다 토큰값을 갱신해주어야 하기 때문에 
		// 아래와 같이 레이아웃을 이용하여 데이터에 토큰값을 생성해준다.
		echo "<span id='token' data-value=".$this->security->get_csrf_hash()."></span>"; 
	} 
}

 

여기서 중요한건 Ajax 방식은 페이지 이동이 없기 때문에, CSRF 방어를 위한 설정에서 $config['csrf_regenerate'] 값을 TRUE 로 설정하였다면, 결과값을 처리할시 다음번 요청을 위해 별도로 토큰 값을 갱신해 주어야 한다는 것입니다. 만약, Ajax 값을 처리할 시 CSRF 토큰값의 갱신이 이루어지지 않는다면, 다음번 요청시에는 값을 받아볼 수 없을 것입니다. 하지만, CSRF 방어의 예외처리 상황도 발생할 수 있습니다. CSRF 방어의 예외처리같은경우 CSRF 설정중에서 $config['csrf_exclude_uris'] 부분을 아래와 같은 형식으로 설정하면 됩니다.

$config['csrf_exclude_uris'] = array('category/it');

  위의 설정에서 array 의 값을 보면, main/siteinfo 라는 값이 설정되어있다. 이 값은 코드이그나이터에서 기본 URI 규칙을 사용할 시 적용되는 값들입니다. 즉 위의 URI 값은 호스트명을 제외한 uri 값이라고 생각하면된다. 만약 아래와 같은 싸이트 주소가 있다고 가정하면, 예외 처리를 해줄 URI 설정은 위의 설정값과 같이 됩니다.

http://bellsilver7.tistory.com/category/it

 
CSRF 설정에서 예외처리가 된 URI 들은 CSRF 방어에 대한 영향을 받지 않기 때문에, 자유롭게 코딩할 수 있습다. 이 설정은 상당히 유용합니다. 그 이유는, CSRF 방어가 필요없는 부분에서 항상 사용해야하는 불편함을 없애주기 때문입니다.

728x90

댓글