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


질문

 

사용자 입력이 SQL 쿼리에 수정하지 않고 삽입 된 경우 다음 예제에서는 다음과 같은 응용 프로그램이 SQL 주입에 취약 해집니다.

$unsafe_variable = $_POST['user_input']; 

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

사용자는 사용자가 값 '과 같은 것을 입력 할 수 있기 때문입니다.');드롭 테이블 테이블; - 및 쿼리가 다음과 같습니다.

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

이것이 일어나지 않도록 할 수있는 일은 무엇입니까?


답변

 

사용하는 데이터베이스에 관계없이 SQL 주입 공격을 피하는 올바른 방법은 데이터가 데이터를 유지하며 SQL 파서가 명령으로 해석되지 않도록 SQL에서 데이터를 분리하는 것입니다.올바르게 형식이 지정된 데이터 파트가있는 SQL 문을 생성 할 수 있지만 세부 정보를 완전히 이해하지 못하면 준비한 문과 매개 변수화 된 쿼리를 항상 사용해야합니다.이들은 매개 변수와 별도로 데이터베이스 서버로 전송되고 파싱되는 SQL 문입니다.이렇게하면 공격자가 악의적 인 SQL을 주입하는 것은 불가능합니다.

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

  1. Using PDO (for any supported database driver):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    $stmt->execute([ 'name' => $name ]);
    
    foreach ($stmt as $row) {
        // Do something with $row
    }
    
  2. Using MySQLi (for 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이 아닌 다른 데이터베이스에 연결하는 경우, (예 : PG_Prepare () 및 PostgreSQL에 대한 PG_Execute ()을 참조 할 수있는 드라이버 특정 두 번째 옵션이 있습니다.PDO는 범용 옵션입니다.


연결을 올바르게 설정하십시오

PDO.

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

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8mb4', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

위의 예에서 오류 모드는 엄격히 필요하지 않지만 추가하는 것이 좋습니다.이 방법으로 PDO는 PDOException을 던지기 위해 모든 MySQL 오류를 알려 드리겠습니다.

그러나 필수 사항은 무엇인가, PDO에 에뮬레이트 된 준비한 진술을 사용 중지하고 실제 준비한 진술을 사용하도록 PDO를 알려주는 최초의 SetTtribute () 라인입니다.이렇게하면 명령문과 값이 PHP에 의해 파싱되지 않아 MySQL 서버로 전송하기 전에 (악의적 인 SQL을 주입 할 수있는 공격자가 없음).

생성자 옵션에서 charset을 설정할 수 있지만 '이전 버전의 PHP 버전 (5.3.6 이전)은 DSN에서 charset 매개 변수를 자동으로 무시했음을 기록하는 것이 중요합니다.

mysqli.

mysqli의 경우 우리는 동일한 루틴을 따라야합니다.

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // error reporting
$dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test');
$dbConnection->set_charset('utf8mb4'); // charset

설명

준비 할 SQL 문은 데이터베이스 서버가 구문 분석하고 컴파일됩니다.위의 예에서 이름 : 이름과 같은 이름 또는 이름과 같은 이름 또는 이름 지정된 매개 변수를 지정하면 데이터베이스 엔진을 필터링 할 위치에 알려줍니다.그런 다음 실행을 호출하면 준비된 명령문이 지정한 매개 변수 값과 결합됩니다.

여기서 중요한 것은 매개 변수 값이 SQL 문자열이 아닌 컴파일 된 문과 결합된다는 것입니다.SQL Injection은 SQL을 데이터베이스에 보내는 SQL을 생성 할 때 스크립트를 악의적 인 문자열을 포함하여 스크립트를 속이며 작동합니다.따라서 실제 SQL을 매개 변수와 별도로 보냄으로써, 의도하지 않은 것으로 끝나지 않는 위험을 제한합니다.

준비된 명령문을 사용할 때 보내는 모든 매개 변수는 문자열로 취급됩니다 (데이터베이스 엔진은 몇 가지 최적화를 수행 할 수 있으므로 매개 변수는 매개 변수가 숫자로 끝날 수 있으므로).위의 예에서 $ name 변수에 'sarah'가 들어있는 경우;직원에서 삭제 결과는 단순히 " 'sarah'문자열을 검색 할 것입니다. 직원에서 삭제"를하면 빈 테이블로 끝나지 않습니다.

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

오, 그리고 삽입물을 위해 그것을하는 방법에 대해 물어 봤으므로 다음과 같습니다 (PDO 사용).

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

$preparedStatement->execute([ 'column' => $unsafeValue ]);

Dynamic Queries에 준비된 문을 사용할 수 있습니까?

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

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

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}


답변

매개 변수화 된 쿼리를 사용하려면 mysqli 또는 pdo를 사용해야합니다.MySQLI로 예제를 다시 작성하려면 다음과 같은 것이 필요합니다.

<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("server", "username", "password", "database_name");

$variable = $_POST["user-input"];
$stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
// "s" means the database expects a string
$stmt->bind_param("s", $variable);
$stmt->execute();

거기에서 읽으려는 주요 기능은 mysqli :: 준비가 될 것입니다.

또한 다른 사람들이 제안한 것처럼, 당신은 PDO와 같은 추상화의 계층을 밟는 것이 유용한 / 쉽게 찾을 수 있습니다.

당신이 물었던 경우는 상당히 간단하고 더 복잡한 사례가 더 복잡한 접근이 필요할 수 있음을 유의하십시오.특히:

사용자 입력을 기반으로 SQL의 구조를 변경하려면 매개 변수화 된 쿼리가 도움이되지 않으며 필요한 이스케이프링은 mysql_real_escape_string에 의해 적용되지 않습니다.이러한 종류의 경우에는 '안전'값만 허용되도록 허용 된 허용 목록을 통해 사용자의 입력을 전달하는 것이 좋습니다.



답변

여기서 모든 대답은 문제의 일부만을 다룹니다. 실제로 SQL에 동적으로 추가 할 수있는 4 개의 다른 쿼리 부분이 있습니다.

문자열 숫자 식별자 구문 키워드

그리고 준비한 진술은 그들 중 두 명만을 덮습니다.

그러나 때로는 훨씬 더 역동적으로 쿼리를 만들어 운영자 또는 식별자를 추가해야합니다. 그래서 우리는 다른 보호 기술이 필요합니다.

일반적으로 이러한 보호 방법은 화이트리스트에 기반을두고 있습니다.

이 경우 모든 동적 매개 변수는 스크립트에서 하드 코드되어 해당 세트에서 선택되어야합니다. 예를 들어, 동적 순서를 수행하려면 다음을 수행하십시오.

$orders  = array("name", "price", "qty"); // Field names
$key = array_search($_GET['sort'], $orders)); // if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. 
$query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

프로세스를 쉽게 할 수있게하려면 내가 모든 작업을 한 줄로하는 Whittelist 도우미 함수를 썼습니다.

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

식별자를 보호하는 또 다른 방법이 있습니다. 그러나 나는 더 견고하고 명백한 접근 방식으로 화이트리스트를 찌르기를 막는다.아직 식별자가 인용 된 한, 견적 문자를 도출하여 안전하게 만들 수 있습니다.예를 들어, 기본적으로 MySQL은 인용문을 두 배로 늘리면 이스케이프를 이스케이로 처리해야합니다.다른 다른 DBMS 이스케이프 규칙은 다를 것입니다.

그러나 SQL 구문 키워드 (예 : DESC 및)와 같은 문제가 있지만이 경우에 흰색 목록이 유일한 방법으로 보입니다.

따라서 일반적인 권장 사항은 다음과 같이 구절 될 수 있습니다

SQL 데이터 리터럴, (또는 단순히 SQL 문자열 또는 숫자를 단순히 넣으려면)을 나타내는 모든 변수를 준비된 문을 통해 추가해야합니다.예외 없음. SQL 키워드, 테이블 또는 필드 이름 또는 운영자와 같은 다른 쿼리 부분은 흰색 목록을 통해 필터링해야합니다.

업데이트

SQL 주입 보호에 관한 모범 사례에 대한 일반적인 계약이 있지만 여전히 많은 나쁜 관행이 있습니다.그리고 그들 중 일부는 PHP 사용자의 마음에 너무 깊이 뿌리를 뿌렸다.예를 들어,이 페이지에서는 (대부분의 방문자에게 보이지만 보이지만) 80 개 이상의 답변이 있거나 나쁜 품질로 인해 커뮤니티가 제거하거나 나쁜 품질로 인해 커뮤니티가 제거하거나 나쁜 및 오래된 관행을 홍보합니다.더 나쁜 것은 아직 나쁜 답변 중 일부는 삭제되지 않고 오히려 번성합니다.

예를 들어, (1)은 (2) 여전히 (3) 대부분의 답변 대답을 포함하여 많은 (4) 답변 (5)을 포함하여 수동 문자열 이스케이프를 제안합니다.

아니면 문자열 형식의 또 다른 방법을 제안하고 심지어 궁극적 인 Panacea로 자랑하는 약간 더 나은 답변이 있습니다.물론, 그렇지 않습니다.이 방법은 일반 문자열 형식보다 더 낫지 않지만 모든 단점을 유지합니다. 문자열에만 적용 할 수 있으며 다른 수동 서식과 마찬가지로 본질적으로 어떤 종류의 인적 오류가 발생하기 쉬운 것입니다.

나는이 모든 것들이 "탈출"과 SQL 주입에서 보호하고 보호하는 것 사이에서 평등을 선포하는 IWASP 또는 PHP 매뉴얼이 지원하는 하나의 매우 오래된 미신 때문에이 모든 것을 생각합니다.

PHP 매뉴얼이 나이에 대해 말한 것과 상관없이 * _ESCAPE_STRING NO SECUT로 데이터가 안전하고 의도되지 않은 데이터를 안전하게 만듭니다.문자열 이외의 모든 SQL 부분에 대해 쓸모없는 것 외에도 수동 탈출은 자동화 된 수동으로서의 수동이기 때문에 잘못되었습니다.

그리고 owasp는 완전히 말도 안되는 사용자 입력 이스케이프에 대한 스트레스를 받아 그것을 더욱 악화시킵니다. 주입 보호의 맥락에서 그러한 단어가 없어야합니다.모든 변수는 잠재적으로 위험합니다 - 소스에 관계없이!또는 다른 변수는 모든 변수를 올바르게 포맷해야합니다.그것은 중요한 목적지입니다.개발자가 양을 염소에서 분리하기 시작하는 순간 (특정 변수가 "안전"을 생각하는지 여부를 생각하십시오.) 그 / 그녀는 재앙을 향한 첫 걸음을 가져옵니다.문구조차도 진입 점에서 벌크가 빠져 나오는 것을 제안하지 말고, 이미 멸시, 더 이상 사용되지 않고 제거되었습니다.

따라서 "이스케이프링"과는 달리 준비한 문은 실제로 SQL 주입 (적용 가능한 경우)에서 보호하는 측정 값입니다.



답변

매개 변수화 된 SQL 쿼리를 실행하려면 PDO (PHP 데이터 객체)를 사용하는 것이 좋습니다.

이는 SQL 주입으로부터 보호 할뿐만 아니라 쿼리를 가속화합니다.

그리고 mysql_, mysqli_ 및 pgsql_ 함수가 아닌 PDO를 사용하면 데이터베이스 공급자를 전환 해야하는 드문 경우에 데이터베이스에서 조금 더 추상화됩니다.



답변

PDO 및 준비 쿼리를 사용하십시오.

($ conn은 pdo 객체입니다)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();


답변

볼 수 있듯이 사람들은 준비된 진술을 가장 많이 사용하도록 제안합니다.틀리지는 않지만 프로세스 당 한 번만 쿼리가 실행되면 약간의 성능 패널티가있을 것입니다.

나는이 문제에 직면 해 있었지만, 나는 매우 정교한 방식으로 그것을 해결 한 것으로 생각합니다. 해커가 인용문을 사용하지 않도록 사용하는 방식.에뮬레이트 된 준비 진술과 함께 이것을 사용했습니다.모든 종류의 SQL 주입 공격을 방지하기 위해 사용합니다.

내 접근 방식 :

입력이 정수로 입력 할 것으로 기대하면 정당한 정수가 있는지 확인하십시오.PHP와 같은 가변형 언어로 매우 중요합니다.예를 들어 매우 간단하지만 강력한 솔루션 : SPRINTF ( "테이블에서 선택 1,2,3 선택, 4 = % U", $ 입력); 정수에서 다른 것을 기대하는 경우.당신이 그것을 16 진수라면, 당신은 모든 입력을 완벽하게 이스케이프하게 처리합니다.C / C ++에서는 mysql_hex_string ()이라는 함수가 있습니다. PHP에서 bin2hex ()를 사용할 수 있습니다. 이스케이프 된 문자열이 mysql_real_escape_string을 사용하는 경우에도 PHP가 동일한 용량 ((2 * input_length) +1)을 할당해야하기 때문에 이스케이프 된 문자열이 원래 길이의 크기가 2 배입니다. 이 HEX 메서드는 바이너리 데이터를 전송할 때 자주 사용되지만 SQL 주입 공격을 방지하기 위해 모든 데이터에서 사용하지 않는 이유는 없습니다.0x로 데이터를 준비하거나 MySQL 함수 위행을 대신 사용할 수 있습니다.

예를 들어 쿼리 :

SELECT password FROM users WHERE name = 'root';

될 것입니다:

SELECT password FROM users WHERE name = 0x726f6f74;

또는

SELECT password FROM users WHERE name = UNHEX('726f6f74');

육각은 완벽한 탈출입니다.주입하는 방법이 없습니다.

유엔줄 기능과 0x 접두사 간의 차이점

코멘트에 대한 토론이있었습니다. 그래서 마침내 그것을 분명히하고 싶습니다.이 두 가지 접근법은 매우 비슷하지만 몇 가지 방법으로 조금 다릅니다.

0x 접두사는 char, varchar, text, block, binary 등과 같은 데이터 열에만 사용할 수 있습니다. 또한 빈 문자열을 삽입하려는 경우에는 그 사용이 조금 복잡합니다.전적으로 그것을 ''로 바꿔야하거나 오류가 발생할 것입니다.

unhex ()는 모든 열에서 작동합니다.빈 문자열에 대해 걱정할 필요가 없습니다.


16 진수 방법은 종종 공격으로 사용됩니다

이 Hex 메서드는 종종 정수가 문자열과 같은 SQL 주입 공격으로 사용되며 mysql_real_escape_string으로 탈출됩니다.그런 다음 따옴표 사용을 피할 수 있습니다.

예를 들어, 당신이 이렇게 만하면 다음과 같이하십시오.

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

공격은 당신을 쉽게 쉽게 주입 할 수 있습니다.스크립트에서 반환 된 다음 주사 코드를 고려하십시오.

SELECT ... WHERE id = -1 UNION ALL SELECT table_name FROM information_schema.tables;

이제 테이블 구조를 추출합니다.

SELECT ... WHERE id = -1 UNION ALL SELECT column_name FROM information_schema.column WHERE table_name = __0x61727469636c65__;

그런 다음 무엇이든지 원하는 데이터를 선택하십시오.그것은 멋지지 않습니까?

그러나 주사 가능한 사이트의 코더가 삽입되면 쿼리가 다음과 같이 보이기 때문에 주입이 불가능합니다.

SELECT ... WHERE id = UNHEX('2d312075...3635');
출처:https://stackoverflow.com/questions/60174/how-can-i-prevent-sql-injection-in-php