이 포스트에서는 PHP에서 mysqli의 bind_param을 사용하는 방법에 대해 포스팅합니다.
먼저, 간단하게 할 말을 남기는 프로그램을 작성해 봅시다. 일련번호로 ‘no'(int), 댓글로 ‘comment'(varchar 255)라는 이름의 테이블 ‘comments’가 있다고 가정합니다.
댓글을 남기기 위한 폼은 다음과 같이 작성합니다. (전체적인 코드는 편의상 생략)
<form method="POST" action="comment_ok.php"> <p>하고 싶은 말: <input type="text" name="comment" style="width: 400px;" /> <input type="submit" value="남기기" /> </p> </form>
그리고 위의 질의를 보낼 comment_ok.php라는 프로그램을 다음과 같이 bind_param을 사용하지 않은 채로 작성해 봅시다.
<?php /* Load DB */ $conn = mysqli_connect('localhost', 'id', 'passwd', 'hello'); if ( !$conn ) die('DB Error'); /* Set to UTF-8 Encoding */ mysqli_query($conn, 'set session character_set_connection=utf8;'); mysqli_query($conn, 'set session character_set_results=utf8;'); mysqli_query($conn, 'set session character_set_client=utf8;'); if ( isset($_POST['comment']) ) { $comment = trim($_POST['comment']); if ($comment) { $query = "INSERT INTO comments (comment) VALUES ('" . $comment . "')"; mysqli_query($conn, $query); } } mysqli_close($conn); ?> <meta http-equiv="refresh" content="0;url=comment.php" />
위에서 강조 표시한 14번과 15번 줄이 문제가 될 부분입니다.
댓글을 남기기 위한 comment.php 페이지로 접속 후 댓글 입력 폼이 뜨면 ‘Hello, world!’라고 입력한 후 서버측에서 SQL 프로그램을 실행시켜
SELECT * FROM comments;
위와 같은 쿼리를 입력해 보면,
+----+---------------+ | no | comment | +----+---------------+ | 1 | Hello, world! | +----+---------------+
위와 같이 Hello, world!라고 입력한 사항이 반영되어 있습니다.
여기서 한 번 장난을 쳐 봅시다. 이번에는 댓글 입력란에 [메롱’), (‘메롱] 이렇게 입력해 봅시다. (사각괄호 []는 제외) 그리고 서버측 SQL 프로그램에서
SELECT * FROM comments;
다시 위와 같은 쿼리를 입력해서 조회해 보면,
+----+---------------+ | no | comment | +----+---------------+ | 1 | Hello, world! | | 2 | 메롱 | | 3 | 메롱 | +----+---------------+
이렇게 메롱이 두 번 들어갔습니다. 왜냐하면, [Hello, world!]와 같이 입력할 경우는
INSERT INTO comments (comment) VALUES ('Hello, world!');
이와 같이 되어 그냥 정상적으로 ‘Hello, world!’가 한 번 입력됩니다.
하지만 [메롱’), (‘메롱] 이렇게 입력할 경우는
INSERT INTO comments (comment) VALUES ('메롱'), ('메롱');
이렇게 (‘메롱’)이 두 개가 되어 두 줄이 동시에 들어가는 것입니다.
이것이 문제가 되는 이유는 해커가 악의적인 목적을 가지고 이를 악용할 수 있다는 점에 있습니다. 예를 들어, 로그인 단계에서의 검증을 뚫어서 관리자 계정을 탈취한 후 관리자 권한을 남용하거나, 데이터베이스나 테이블을 삭제해 버리는 등 무궁무진한 방법으로 이러한 취약점을 악용하여 사이버 공격을 감행할 수 있습니다. 이를 SQL 인젝션(SQL Injection)이라고 합니다. 데이터베이스에 비정상적인 쿼리를 삽입하여 개발자가 의도하지 않은 방향으로 프로그램을 동작하게 하기 때문에 이런 이름이 붙었습니다.
이러한 SQL 인젝션을 방어하기 위한 대책으로 바인딩(binding)을 생각해 볼 수 있습니다. 이는 쿼리에 값을 직접 입력하지 않고 일단 ‘?’로 처리한 다음 그 자리에 값을 삽입하는 방법입니다. PHP에서는 mysqli_stmt::bind_param
을 사용해 바인딩할 수 있습니다.
앞의 comment_ok.php 프로그램을 다음과 같이 다시 작성해 봅시다.
<?php /* Load DB */ $conn = mysqli_connect('localhost', 'id', 'passwd', 'hello'); if ( !$conn ) die('DB Error'); /* Set to UTF-8 Encoding */ mysqli_query($conn, 'set session character_set_connection=utf8;'); mysqli_query($conn, 'set session character_set_results=utf8;'); mysqli_query($conn, 'set session character_set_client=utf8;'); if ( isset($_POST['comment']) ) { $comment = trim($_POST['comment']); if ($comment) { $stmt = $conn->prepare("INSERT INTO comments (comment) VALUES (?)"); $stmt->bind_param('s', $comment); $stmt->execute(); } } mysqli_close($conn); ?> <meta http-equiv="refresh" content="0;url=comment.php" />
위와 같이 쿼리에 값을 직접 넣지 않고 값이 들어갈 자리에 ?를 넣은 다음 bind_param을 이용하여 값을 넣습니다. 앞의 ‘s’는 문자열을 의미합니다. 컬럼이 두 개 이상일 때도 함께 바인딩할 수 있는데 첫 번째와 두 번째가 문자열이고 세 번째가 정수형이면 bind_param('ssi', $str1, $str2, $intg)
식으로 할 수도 있습니다. 그리고 execute 메소드를 실행하면 비로소 바인딩된 쿼리가 실행됩니다.
이렇게 하면 쿼리에 직접 값을 넣는 앞의 방식과 같은 효과를 가져오면서도 차이가 있는데, 다시 [메롱’), (‘메롱] 이렇게 입력해 봅시다. 그리고
SELECT * FROM comments;
다시 서버측 SQL 콘솔에서 이 쿼리를 실행시켜 보면,
+----+--------------------+ | no | comment | +----+--------------------+ | 1 | Hello, world! | | 2 | 메롱 | | 3 | 메롱 | | 4 | 메롱'), ('메롱 | +----+--------------------+
이렇게 메롱이 두 번 들어가지 않고 입력한 그대로 삽입됨을 알 수 있습니다.
이와 같이 PHP와 MySQL을 연동할 때 바인딩을 통해 SQL 인젝션을 방어할 수 있습니다.