SQL 삽입공격이란?
SQL 삽입공격은 웹에플리케이션에 대해서 가해지는 가장 흔한 공격 기법 중의 하나로 에플리케이션의 허점을 이용해서 데이터베이스를 비정상적으로 조작하는 기법이다.
실습준비
실습을 해보자. 아래의 디비를 만들고 아래의 SQL문을 실행한다.
CREATE TABLE `sql_injection` ( `id` int(11) NOT NULL AUTO_INCREMENT, `description` text NOT NULL, `type` enum('public','private') NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; INSERT INTO `sql_injection` VALUES (1, 'public 1', 'public'); INSERT INTO `sql_injection` VALUES (2, 'public 2', 'public'); INSERT INTO `sql_injection` VALUES (3, 'private 3', 'private'); INSERT INTO `sql_injection` VALUES (4, 'private 4', 'private');
에플리케이션 코드
아래와 같은 코드를 가진 php 에플리케이션을 하나 만들자.
<html> <body> <?php mysql_connect('localhost', 'egoing', '111111'); mysql_select_db('advanced_php'); $sql = "SELECT * FROM SQL_Injection WHERE type='".$_GET['type']."'"; //$sql = "SELECT * FROM SQL_Injection WHERE type='".mysql_real_escape_string($_GET['type'])."'"; echo "SQL : $sql"; ?> <ul> <?php $result = mysql_query($sql); while($row = mysql_fetch_array($result)){ echo "<li>{$row['description']}</li>"; } ?> </ul> </body> </html>
위의 PHP 에플리케이션으로 접근하는 정상적인 URL은 아래와 같다.
http://localhost/advanced_php/sqlinjection.php?type=public
그런데 사용자가 아래와 같이 URL을 바꾼다면 어떻게 될까?
http://localhost/advanced_php/sqlinjection.php?type=public' or 1='1
위의 URL로 접근 했을 때 php 에플리케이션 내부적으로 생성되는 SQL문은 아래와 같다.
SELECT * FROM SQL_Injection WHERE type='public' or 1='1'
하나씩 보자.
id의 값으로 아래 값이 전달 되었다.
public' or 1='1
이 값은 PHP 코드 상에서 아래의 코드로 전달된다.
id='".($_GET['type'])."'";
값을 코드에 대입하면 아래와 같은 형태가 된다.
type='public' or 1='1'";
WHERE 조건에 OR가 추가 됐고, 1=1이라는 언제나 참인 명제를 WHERE 조건으로 추가했다. 그 결과 데이터베이스는 모든 정보를 출력하게 된다.
php 코드를 바꿔보자.
위의 예제에서 8번째 라인의 주석을 해제하고, 7라인에 주석을 적용해보자. 코드는 아래와 같이 된다.
//$sql = "SELECT * FROM SQL_Injection WHERE type='".$_GET['type']."'"; $sql = "SELECT * FROM SQL_Injection WHERE type='".mysql_real_escape_string($_GET['type'])."'";
동일한 URL로 접속을 시도해보자.
SELECT * FROM SQL_Injection WHERE type='public\' or 1=\'1'
아무 결과도 출력하지 않는다.
SQL 삽입공격은 많은 패턴이 존재한다. 이 모든 것을 다 이해할 수 있다면 좋겠지만, mysql_real_escape_string을 사용하는 것만으로도 많은 공격을 차단할 수 있다.
mysql_real_escape_string을 사용하는 것만으로도 대부분의 공격을 차단할 수 있다.
다음은 PHP에서는 객체지향적으로 mysql에 접근 할 수 있는 방법을 지원한다. 다음은 mysqli 를 이용해서 데이터베이스에 접근하는 방법을 보여주는 예제다. mysqli의 prepared statments 기능을 이용하면 자동으로 escaping을 해주기 때문에 이를 위한 특별한 처리를 하지 않아도 된다. mysql_query 보다 성능도 좋고, 보안도 강화된 방식이기 때문에 권장된다.
<html> <body> <ul> <?php $mysqli = new mysqli("localhost", "egoing", "111111", "advanced_php"); $stmt = $mysqli->prepare("SELECT * FROM SQL_Injection WHERE type=?"); $stmt->bind_param("s", $_GET['type']); $stmt->execute(); $stmt->bind_result($id, $description, $type); while($stmt->fetch()){ print("<li>$description</li>"); } $stmt->close(); $mysqli->close(); ?> </ul> </body> </html>
참고
- SQL Injection Cheatsheet
- PHP 보안 (O'REILLY) p.62
- PHP mysqli