프로그래밍 언어/Database

PHP에서 SQL 공격(injection)을 방지하는 방법

Rateye 2021. 7. 12. 11:55
728x90
반응형

 

질문 : PHP에서 SQL 주입을 어떻게 방지 할 수 있습니까?

사용자 입력이 SQL 쿼리에 수정없이 삽입되면 다음 예제와 같이 애플리케이션이 SQL 주입에 취약 해집니다.

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

사용자가 값과 같은 것을 입력 할 수 있기 때문입니다 value'); DROP TABLE table;-- , 쿼리는 다음과 같습니다.

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

이를 방지하기 위해 무엇을 할 수 있습니까?

답변

준비된 문과 매개 변수가있는 쿼리를 사용합니다. 매개 변수와는 별도로 데이터베이스 서버로 전송되고 구문 분석되는 SQL 문입니다. 이렇게하면 공격자가 악성 SQL을 주입 할 수 없습니다.

기본적으로이를 달성하기위한 두 가지 옵션이 있습니다.

  1. PDO 사용 (지원되는 모든 데이터베이스 드라이버 용) :
     $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
  2. MySQLi 사용 (MySQL 용) :
     $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string' $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // Do something with $row }

 

 

MySQL 이외의 데이터베이스에 연결하는 경우 참조 할 수있는 드라이버 별 두 번째 옵션이 있습니다 (예 : PostgreSQL의 pg_prepare()pg_execute() PDO는 범용 옵션입니다.

PDO 를 사용하여 MySQL 데이터베이스에 액세스 할 때 실제 준비된 명령문은 기본적으로 사용되지 않습니다 . 이 문제를 해결하려면 준비된 명령문의 에뮬레이션을 비활성화해야합니다. PDO를 사용하여 연결을 만드는 예는 다음과 같습니다.

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');
                  
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

위의 예에서 오류 모드는 꼭 필요한 것은 아니지만 추가하는 것이 좋습니다 . 이렇게하면 스크립트가 문제가 Fatal Error 그리고 그것은 개발자에게 PDOException throw 지는 오류 catch 수있는 기회를줍니다.

그러나 필수 는 첫 번째 setAttribute() 행으로, PDO에 에뮬레이트 된 준비된 명령문을 비활성화하고 실제 준비된 명령문을 사용하도록 지시합니다. 이렇게하면 문과 값이 MySQL 서버로 보내기 전에 PHP가 구문 분석하지 않도록합니다 (가능한 공격자가 악의적 인 SQL을 주입 할 기회를주지 않음).

charset 을 설정할 수 있지만, '이전'버전의 PHP (5.3.6 이전)는 DSN 의 문자셋 매개 변수를 자동으로 무시한다는 점에 유의해야합니다.

prepare 하기 위해 전달하는 SQL 문은 데이터베이스 서버에 의해 구문 분석되고 컴파일됩니다. 매개 변수 ( ? :name 과 같은 명명 된 매개 변수)를 지정하여 필터링 할 위치를 데이터베이스 엔진에 알립니다. 그런 다음 execute 를 호출하면 준비된 문이 지정한 매개 변수 값과 결합됩니다.

여기서 중요한 것은 매개 변수 값이 SQL 문자열이 아닌 컴파일 된 명령문과 결합된다는 것입니다. SQL 주입은 데이터베이스에 보낼 SQL을 생성 할 때 스크립트가 악성 문자열을 포함하도록 속이는 방식으로 작동합니다. 따라서 실제 SQL을 매개 변수와 별도로 보내면 의도하지 않은 결과가 나올 위험이 제한됩니다.

준비된 명령문을 사용할 때 보내는 모든 매개 변수는 문자열로 처리됩니다 (데이터베이스 엔진이 일부 최적화를 수행 할 수 있으므로 매개 변수도 물론 숫자로 끝날 수 있음). 위의 예에서 $name 'Sarah'; DELETE FROM employees 가 포함 된 경우; 'Sarah'; DELETE FROM employees 결과는 단순히 "'Sarah'; DELETE FROM employees" 문자열을 검색하는 것이며 빈 테이블로 끝나지 않습니다.

준비된 명령문을 사용하는 또 다른 이점은 동일한 세션에서 동일한 명령문을 여러 번 실행하면 한 번만 구문 분석되고 컴파일되어 속도가 향상된다는 것입니다.

아, 그리고 삽입물에 대해 어떻게하는지 물었 기 때문에 여기에 예가 있습니다 (PDO 사용) :

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
                  
$preparedStatement->execute([ 'column' => $unsafeValue ]);

쿼리 매개 변수에 대해 준비된 명령문을 계속 사용할 수 있지만 동적 쿼리 자체의 구조는 매개 변수화 할 수 없으며 특정 쿼리 기능은 매개 변수화 할 수 없습니다.

이러한 특정 시나리오의 경우 가장 좋은 방법은 가능한 값을 제한하는 화이트리스트 필터를 사용하는 것입니다.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
	$dir = 'ASC';
}
출처 : https://stackoverflow.com/questions/60174/how-can-i-prevent-sql-injection-in-php

 

728x90
반응형