너의 개발은/PHP

PHP: HTML 폼 (GET, POST)

HTML 폼

HTML 폼 HTTP 요청의 역사에서 오래된 것 중 하나이다. HTTP 요청은 cURL, Ajax 등 다양한 기술로 처리를 할 수 있으나 폼요청은 웹사이트에서 가장 많이 사용하는 요청이자 역사가 오래된 요청 방법이다. 여기서 HTML 폼은 우리가 흔히 볼 수 있는 회원가입, 로그인 화면에서 아이디와 패스워드를 입력하고 확인 버튼을 눌러 로그인을 할 수 있도록 해준다. 우리말로는 양식이라고 하는 듯하지만, 편의상 폼이라고 할 것이다.

로그인

HTML 폼의 예시로는 로그인이 제격이다. 로그인의 과정 자체는 언급하지 않겠지만, 폼은 데이터를 전달하기 위한 용도로 사용하므로 해당 데이터를 어떻게 사용할 지는 프로그래머의 몫이다. 따라서 여기서는 해당 포스팅에서는 폼으로 데이터를 전달하고 PHP 에서 받는 과정까지 할 것이다.

 

아래에 다음과 같은 로그인 폼이 있다고 가정해보자. 해당 폼은 /login.php 라는 경로에 POST 요청을 보낸다. HTTP 요청 메서드는 다양한 것이 있는데, 폼은 GET, POST 요청을 지원한다. 최근 Restful자원 관점의 API(Application Programming Interface) 설계가 늘어나면서, 더 많은 요청을 할 수 있게 되었지만, 폼에서 할 수 있는 요청은 가장 기초적인 것이다.

<form action="/login.php" method="POST">
    <input type="text" name="id">
    <input type="password" name="password">
    <input type="submit">
</form>

한 가지 주의해야 할 점은, 그 어떠한 데이터든 외부에서 오는 데이터는 믿으면 안 된다. 사용자가 프로그래머가 예상할 수 있는 데이터만을 넣을 것이라는 생각은 해킹을 당할 수 있는 지름길이다. 따라서 폼을 포함한 HTTP 요청으로 받을 수 있는 데이터는 모두 임의의 규칙에 따라 검증과정을 거쳐야 한다.

$_GET, $_POST

PHP의 슈퍼 글로벌 변수 중에는 GET, POST 를 담고있는 변수가 있다. 요청과 관련된 변수에는 $_COOKIE, $_REQUEST 등도 있다. $_GET 배열에는 GET 요청에 대한 파라매터가, $_POST 에는 POST 요청에 대한 파라매터가 담겨있는데, 즉 다음과 같이 온다고 볼 수 있다.

$_POST = [
    'id' => '__USER_TYPED_DATA__',
    'password' => '__USER_TYPED_DATA__'
];

[ 'id' => $id, 'password' => $password ] = $_POST;

이렇게 날라온 데이터는 Raw, 말 그대로 날것이라 그대로 사용하면 보안에 위험하다. 레거시 프로젝트에는 SQL Query 에 날라온 데이터를 직접 대입하는 놀라운 코드도 많으므로 각별한 주의가 필요하다. 

$_REQUEST

해당 배열에는 쿠키를 포함한 요청에 대한 대부분의 정보가 담겨있다. 따라서 GET, POST, COOKIE 에 상관없이 데이터를 처리하려면 해당 배열에 접근하면 된다.

유저 데이터 필터링하기

filter_input(int, string [, int [, mixed]]): mixed

이 함수는 데이터를 유저가 넘겨준 데이터를 필터링 함에 있어서 매우 중요하다. 과거에는 데이터의 형식이 이메일인지 검사하려면 정규표현식을 사용해야만 했는데, 이제는 그럴 필요없이 필터를 사용하면 된다. 참고로 filter_input_array, filter_var 도 존재하며, 사용법은 매우 유사하다. 참고로 필터는 여러개 적용시킬 수도 있다.

int $type INPUT_GET, INPUT_POST, INPUT_COOKIE ...
string $variable_name 요청 파라매터의 이름
int $filter 필터의 이름 (상수로 정의)
mixed $options 필터에 부여할 옵션

필터 함수는 아래와 같이 사용한다. 아이디이메일 형식인지 검사하며, 비밀번호는 다음과 같이 설명된 필터를 사용했다. 특수문자를 인코딩하고, HTML 태그를 몽땅 날린다.

Strip tags, optionally strip or encode special characters.
$id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_EMAIL);
$password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING);

// or,
[ 'id' => $id, 'password '=> $password] = filter_input_array(INPIT_POST, [
    'id' => FILTER_VALIDATE_EMAIL,
    'password '=> [
        'filter' => FILTER_SANITIZE_STRING
    ]
]);

필터를 사용하면 복잡한 정규표현식을 사용하거나, 굳이 조건문을 사용할 필요가 사라진다. 하지만, 중요한 것은 유저로부터 들어온 데이터는 반드시 검증을 거쳐야 한다는 점이다. 그래야 그 다음으로 데이터베이스에 대한 처리를 하든 검색을 하든 행동을 취할 수 있게된다. 폼은 단순히 데이터를 백엔드에 넘기는 용도지만, 웹 상에서 가장 많이 사용되는 요소이므로 중요하기도 하다.

XSS(Cross-Site Scripting)

XSS 공격은 간단히 말해 게시판이나 덧글과 같이 사용자가 직접 입력할 수 있는 공간에 자바스크립트와 같은 코드를 주입하여 웹 사이트의 취약점을 공격하고 해킹을 시도하는 방법이다. 보안에서 기본적으로 지켜줘야 할 사항이기도 하고, 아주 유명한 공격이기 때문에 이는 반드시 시켜야 한다.

간단한 게시판 폼

아래와 같은 폼이 있다고 가정해보자, 이 폼은 단순히 사용자에게 글을 입력받고 서버에 넘기는 역할을 한다.

<form action="/" method="POST">
    <textarea name="content" rows="25" cols="50"></textarea>
    <input type="submit">
</form>

그리고 서버에서는 이렇게 처리한다. 이것은 그냥 출력을 해주는 것 이외에 다른 의미를 가지지 않는다.

echo $_POST['content'];

이제 게시판 입력 본문에 다음과 같은 자바스크립트 코드를 주입해보자. 이것을 넣고 브라우저에 세션 쿠키 정보가 나타난다면, 이는 취약점이 생긴 것이다. 세션은 서버와 쿠키를 통해 사용자를 판별한다. 그런데, 누군가가 저런 코드를 주입해서 내 세션 아이디를 전송했다고 생각해보자. 그럼 영락없이 해킹 당한 것이다. 세션 키는 서버 내에서는 유일무이하여 사용자를 판별하기 때문이다.

<script>console.log(document.cookie)</script>

HTML 태그 출력 제어하기

위와 같은 사고를 방지하려면 사용자가 입력하는 HTML 태그에 대해 조치가 필요한데, 아래에서 언급하는 함수 중 하나를 사용하면 가능하다.

htmlentities(string): string

이 함수는 HTML 태그를 포함한 일부 특별한 동작을 일으킬 수 있는 문자들 HTML Entity 로 바꿔준다. 조금 더 작은 범위의 함수로는 htmlspecialchars() 가 있다.

string $string HTML 태그가 포함된 문자열
// -> <script>console.log(document.cookie)</script>
htmlentities($_POST['content']);

strip_tags(string [, mixed]): string

이 함수는 HTML 태그를 몽땅 날리고 내부에 있는 문자열만 가져온다. 다른 함수보다도 가장 효과적이며 사용자가 입력하는 HTML 태그를 원천 차단하기 때문에 안전하다고 볼 수 있다. 두 번째 파라매터를 사용하면 일부 태그는 허용할 수도 있다.

string $str HTML 태그가 포함된 문자열
mixed $allowable_tags 허용할 HTML 태그 목록
// -> console.log(document.cookie)
strip_tags($_POST['content'])

filter_input(int, string [, int [, mixed]]): mixed

여기서 중요한 것은 FILTER_SANITIZE_FULL_SPECIAL_CHARS 필터다. 해당 필터를 사용하면 지저분한 것들을 날려주는데, htmlentities() 와 비슷한 효과를 발휘한다.

// -> <script>console.log(document.cookie)</script>
filter_input(INPUT_POST, 'content', FILTER_SANITIZE_FULL_SPECIAL_CHARS);

CSRF(Cross-Site Request Forgery)

CSRF 공격은 공격자가 피해자의 개인정보를 탈취하거나 위조하여 피해자는 요청하지 않았음에도 공격을 통해 피해자가 서버에 요청을 한 것처럼 위조하여 의도하지 않은 결과가 발생하도록 하는 공격이라고 볼 수 있다 만약 XSS 이 발생되어 있는 경우라면 사이트에 임의의 을 기입할 것이고, 그 폼에 사용자를 식별하는 아이디가 담겨있다면 요청을 위조할 수 있다.

<form action="/" method="POST">
    <input type="hidden" name="uid" value="1">
    <input type="submit">
</form>

주로 위와 같은 코드로 폼이 작성되어 있다면, uid 라는 이름을 가진 필드를 조작할 수 있을 것이다. 만약 저 상태로 서버에서 어떠한 검증도 하지 않고 요청을 처리한다면 공격은 성사되어 서버에서 처리를 하게 될 것이다. 따라서 서버에서는 여러가지 검증을 해줄 필요성이 있는데, Csrf Token 이라는 것을 통해 해볼 수 있다. 

Csrf Token 만으로는 완전히 막을 수 없다. 폼에 담긴 고유 식별자와 현재 로그인한 유저의 식별자와 비교하는 검증도 물론 필요하다.

Csrf Token

이제 토큰을 만들고 서버에서 검증도 해보자. 이 또한 그렇게 어려운 일은 아니다.

생성하기

세션에다가 임의의 랜덤하게 만들어진 문자열을 대입한다. 이 토큰은 폼이 렌더링 될 때마다 바뀌어야 하므로 레이아웃에 페이지에 넣어두는 것이 좋다.

$_SESSION['_csrfToken'] = bin2hex(random_bytes(32));

필드 지정하기

폼에다가 토큰 필드를 만들어서 보내주면 되는데, 만들어둔 토큰을 넣어두면 된다.

<input type="hidden" name="_csrfToken" value="<?=$_SESSION['_csrfToken']?>">

검증하기

이제 서버에서 hash_equals() 로 검증해주면 된다. 같은 사람이 요청했다면 폼에 담겨있는 세션에 담긴 토큰과 요청한 토큰 값이 같겠지만, 다른 사람이 위조하려 요청하려고 해도 공격 대상의 토큰 값을 알 수 없기때문에 임의의 문자열을 넣는다고 해도 검증을 통과할 수 없게된다. 

if (hash_equals($_SESSION['_csrfToken'], $_POST['_csrfToken'])) {
    // ...
}

 

 

PHP 7+ 프로그래밍: 리부트 - 인프런

기초 문법부터 내장 함수, 웹 보안, 게시판 만들기까지 PHP 언어를 시작하는 분들을 위해 바이블이 될 수 있게 만들어보고자 하는 마음으로 이번 강좌를 만들어보았습니다. 입문 웹 개발 프로그��

www.inflearn.com

 

PHP 7+ 프로그래밍: 객체지향 - 인프런

PHP 객체지향, 내장 클래스, PSR, Composer, MVC(Model, View, Controller)까지 모던 PHP를 익히기 위한 근간을 이야기합니다. 초급 프로그래밍 언어 알고리즘 PHP 객체지향 알고리즘 온라인 강의 모던 PHP 프로��

www.inflearn.com

'너의 개발은 > PHP' 카테고리의 다른 글

PHP: 데이터베이스 (MySQLi, PDO)  (0) 2020.06.10
PHP: 쿠키와 세션  (0) 2020.04.29
PHP: HTML 폼 (GET, POST)  (0) 2020.04.29
PHP: 객체 비교와 복사  (0) 2020.04.25
PHP: 참조 (WeakReference)  (0) 2020.04.25
PHP: 제네레이터 (Iterator)  (0) 2020.04.24