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


2773

사용자 입력이 수정없이 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;--')

이 문제가 발생하지 않도록하려면 어떻게해야합니까?

답변:


8957

준비된 명령문과 매개 변수화 된 쿼리를 사용하십시오. 이는 데이터베이스 서버에서 매개 변수와 별도로 보내거나 구문 분석하는 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의 데이터베이스가 아닌 다른 데이터베이스에 연결하는 경우 (예를 들어, 참조 할 수 드라이버 별 두 번째 옵션이 pg_prepare()pg_execute()PostgreSQL을위한)가. PDO는 보편적 인 옵션입니다.


연결을 올바르게 설정

PDOMySQL 데이터베이스에 액세스 할 때 실제 준비된 명령문은 기본적으로 사용되지 않습니다 . 이 문제를 해결하려면 준비된 명령문의 에뮬레이션을 비활성화해야합니다. 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무언가 잘못되었을 때 스크립트가 멈추지 않습니다 . 또한 개발자 catchthrown으로 n 인 모든 오류에 대한 기회를 제공합니다 PDOException.

무엇 필수 , 그러나, 처음으로 setAttribute()비활성화 에뮬레이트 준비된 문 및 사용에 PDO를 알려줍니다 라인, 실제 준비된 문을. 이를 통해 PHP가 명령문과 값을 구문 분석하지 않고이를 MySQL 서버로 전송합니다 (가능한 공격자에게 악의적 인 SQL을 삽입 할 기회를주지 않음).

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


설명

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

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

준비된 명령문을 사용할 때 전송하는 모든 매개 변수는 문자열로 취급됩니다 (데이터베이스 엔진이 일부 최적화를 수행하여 매개 변수도 물론 숫자로 끝날 수 있음). 위의 예에서 $name변수 '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';
}

86
여기서 다른 곳을 보지 못했기 때문에 추가하기 위해 또 다른 방어선은 웹 애플리케이션 방화벽 (WAF)으로, SQL 주입 공격을 찾기위한 규칙을 설정할 수 있습니다.
jkerak

37
또한 mysql_query의 공식 문서는 하나의 쿼리 만 실행할 수 있으므로 다른 쿼리는 제외합니다. 무시됩니다. 이것이 더 이상 사용되지 않더라도 PHP 5.5.0에는 많은 시스템이 있으며이 기능을 사용할 수 있습니다. php.net/manual/en/function.mysql-query.php
랜달 발렌시아 노

12
이것은 나쁜 습관이지만 사후 문제 해결 방법입니다. SQL 주입뿐만 아니라 모든 유형의 주입 (예 : F3 프레임 워크 v2에 뷰 템플릿 주입 구멍이 있음)에 대한 준비가 된 오래된 웹 사이트 또는 응용 프로그램이있는 경우 주입 결함에서 한 가지 해결책은 부트 스트랩에서 이스케이프 된 값으로 $ _POST와 같은 supperglobal 사전 정의 된 var의 값을 재지 정하는 것입니다. PDO에 의해 여전히 탈출 할 수있다 (오늘날 프레임 워크에서도) : substr ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1)
Alix

15
이 답변에는 준비된 명령문이 무엇인지에 대한 설명이 부족합니다. 한 가지-요청 중에 많은 준비된 명령문을 사용하면 성능에 영향을 미치며 때로는 10 배의 성능 히트를 차지합니다. 더 좋은 경우는 매개 변수 바인딩을 해제하고 명령문 준비를 해제 한 상태에서 PDO를 사용하는 것입니다.
donis

6
경우에 당신이 직접 쿼리 메이크업을 사용하고, PDO가 더 나은 사용하여 당신이 사용 mysqli :: escape_string
카셈 (이집트) Itani

1652

더 이상 사용되지 않는 경고 : 이 답변의 샘플 코드 (질문의 샘플 코드와 같은)는 MySQLPHP 5.5.0에서 더 이상 사용되지 않고 PHP 7.0.0에서 완전히 제거 된 PHP 확장을 사용합니다.

보안 경고 :이 답변은 보안 모범 사례와 일치하지 않습니다. 이스케이프 SQL 주입을 방지하기 위해 불충분 , 사용 준비가 문을 대신. 아래에 요약 된 전략을 사용하십시오. (또한 mysql_real_escape_string()PHP 7에서 제거되었습니다.)

최신 버전의 PHP를 사용하는 경우 mysql_real_escape_string아래에 설명 된 옵션을 더 이상 사용할 수 없습니다 ( mysqli::escape_string현대식 이지만 ). 요즘이 mysql_real_escape_string옵션은 이전 버전의 PHP에서는 레거시 코드에만 적합합니다.


의 특수 문자를 이스케이프 처리 unsafe_variable하거나 매개 변수화 된 쿼리를 사용하는 두 가지 옵션이 있습니다. 둘 다 SQL 인젝션으로부터 보호합니다. 파라미터 화 된 쿼리는 더 나은 방법으로 간주되지만 PHP를 사용하기 전에 새로운 MySQL 확장으로 변경해야합니다.

먼저 영향을 줄이는 낮은 문자열을 다룰 것입니다.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

mysql_real_escape_string기능 의 세부 사항도 참조하십시오 .

매개 변수화 된 쿼리를 사용하려면 MySQL 함수 대신 MySQLi 를 사용해야 합니다. 예제를 다시 작성하려면 다음과 같은 것이 필요합니다.

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

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

읽으려는 주요 기능은입니다 mysqli::prepare.

또한 다른 사람들이 제안했듯이 PDO 와 같은 추상화 계층을 강화하는 것이 유용하거나 더 쉬울 수 있습니다.

귀하가 요청한 사례는 매우 간단한 사례이며 더 복잡한 사례는 더 복잡한 접근법이 필요할 수 있습니다. 특히:

  • 사용자 입력을 기반으로 SQL의 구조를 변경하려는 경우 매개 변수화 된 쿼리가 도움이되지 않으며 필요한 이스케이프는에서 다루지 않습니다 mysql_real_escape_string. 이런 경우에는 '안전한'값만 허용되도록 화이트리스트를 통해 사용자 입력을 전달하는 것이 좋습니다.
  • 조건에서 사용자 입력의 정수를 사용하고 mysql_real_escape_string접근하면 아래 주석에서 다항식 으로 설명 된 문제가 발생 합니다. 정수가 따옴표로 묶이지 않기 때문에이 경우는 더 까다롭기 때문에 사용자 입력에 숫자 만 포함되어 있는지 확인하여 처리 할 수 ​​있습니다.
  • 내가 모르는 다른 경우가있을 수 있습니다. 당신은 찾을 수 발생할 수있는 미묘한 문제의 일부에 유용한 자원이다.

1
사용하는 mysql_real_escape_string것이 충분합니까, 아니면 매개 변수화를 사용해야합니까?
peiman F.

5
@peimanF. 로컬 프로젝트에서도 매개 변수화 된 쿼리를 사용하는 것이 좋습니다. 매개 변수화 쿼리를 사용하면된다 보장 SQL 주입 없을 것입니다. 그러나 당신이 (예 : 텍스트에 HTML 코드를 넣어으로, 즉 XSS 주입) 가짜 검색을 피하기 위해 데이터를 살균한다 명심 htmlentities예를 들어
Goufalite

2
@peimanF. 쿼리를 매개 변수화하고 값을 바인드하는 것이 좋습니다. 그러나 실제 이스케이프 문자열은 현재로서는 좋습니다
Richard

나는 mysql_real_escape_string()완전성을 포함하는 것을 이해 하지만 가장 오류가 발생하기 쉬운 접근 방식을 먼저 나열하는 팬은 아닙니다. 독자는 첫 번째 예를 빨리 잡을 수 있습니다. 좋은 점은이 : 지금은 사용되지 않는 것
스틴 SCHUTT에게

1
@ SteenSchütt-모든 mysql_*기능이 더 이상 사용되지 않습니다. 그것들은와 같은 유사한 mysqli_* 기능 으로 대체되었습니다 mysqli_real_escape_string.
Rick James

1073

여기에있는 모든 답변은 문제의 일부만을 다루고 있습니다. 실제로, 우리는 SQL에 동적으로 추가 할 수있는 가지 쿼리 부분이 있습니다 :-

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

그리고 준비된 진술은 그 중 두 가지만 다룹니다.

그러나 때로는 연산자 나 식별자를 추가하여 쿼리를 더욱 역동적으로 만들어야합니다. 따라서 다른 보호 기술이 필요합니다.

일반적으로 이러한 보호 방법은 허용 목록을 기반으로 합니다.

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

$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

프로세스를 쉽게하기 위해 한 줄에 모든 작업을 수행 하는 화이트리스트 도우미 기능 을 작성했습니다.

$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 구문 키워드에 문제가 (예이며 AND, DESC등)하지만, 화이트리스트는이 경우의 유일한 접근 방식을 보인다.

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

  • SQL 데이터 리터럴을 나타내는 변수 (또는 간단히 말해서 SQL 문자열 또는 숫자)는 준비된 명령문을 통해 추가해야합니다. 예외 없음.
  • SQL 키워드, 테이블 또는 필드 이름 또는 연산자와 같은 다른 쿼리 부분은 화이트리스트를 통해 필터링해야합니다.

최신 정보

SQL 주입 보호와 관련된 모범 사례에 대한 일반적인 합의가 있지만 여전히 많은 나쁜 사례가 있습니다. 그리고 그들 중 일부는 PHP 사용자의 마음에 너무 뿌리가 있습니다. 예를 들어,이 페이지 바로 위에 80 개 이상의 삭제 된 답변이 있습니다 . 모두 나쁜 품질로 인해 또는 커뮤니티에서 나쁜 오래된 기록을 홍보하여 ​​삭제되었습니다. 더 나쁜 것은, 잘못된 답변 중 일부가 삭제되지 않고 오히려 번영한다는 것입니다.

예를 들어, 수동 문자열 이스케이프를 제안 하는 두 번째로 많이 답변 된 답변을 포함하여 (1) are (2) still (3) many (4) 답변 (5) 이 있습니다.

또는 문자열 형식화의 또 다른 방법 을 제안 하고 궁극적 인 만병 통치약으로 자랑 하는 약간 더 나은 대답이 있습니다. 물론 그렇지 않습니다. 이 방법은 일반 문자열 형식보다 낫지는 않지만 모든 단점을 유지합니다. 문자열에만 적용 할 수 있으며 다른 수동 형식과 마찬가지로 본질적으로 선택적이고 의무적 인 조치이며 모든 종류의 사람의 오류가 발생하기 쉽습니다.

OWASP 또는 PHP manual 과 같은 기관에서 지원하는 매우 오래된 미신으로 인해이 모든 것이 "탈출"과 SQL 주입으로부터의 보호 사이의 평등을 보장한다고 생각합니다.

오랫동안 PHP 매뉴얼에서 언급 한 내용에 관계없이 *_escape_string데이터를 안전하게 보호 할 수 는 없었습니다. 문자열 이외의 SQL 부분에는 쓸모가 없지만 수동 이스케이프는 자동화와 반대되는 수동이기 때문에 잘못되었습니다.

그리고 OWASP는 전혀 말도 안되는 사용자 입력 을 피하는 데 중점을 둡니다 . 주입 보호와 관련하여 그러한 단어가 없어야합니다. 모든 변수는 소스에 관계없이 잠재적으로 위험합니다! 즉, 소스에 관계없이 모든 변수는 쿼리에 들어가도록 올바르게 형식화되어야합니다. 중요한 목적지입니다. 개발자가 양을 염소와 분리하기 시작하는 순간 (특정 변수가 "안전한지"생각하는 것) 재난을 향한 첫 걸음을 내딛습니다. 말할 것도없이, 문구조차도 진입 점에서 벌크 이스케이프를 제안하며, 이미 멸시, 더 이상 사용되지 않고 제거 된 매우 따옴표 기능과 유사합니다.

따라서 "이스케이프"와 달리 준비된 명령문 실제로 SQL 삽입 (해당되는 경우)으로부터 보호하는 측정입니다.


848

PDO (PHP Data Objects)를 사용하여 매개 변수화 된 SQL 쿼리를 실행하는 것이 좋습니다 .

이를 통해 SQL 삽입을 방지 할뿐만 아니라 쿼리 속도도 향상됩니다.

그리고보다는 PDO를 사용하여 mysql_, mysqli_pgsql_기능, 당신은 당신의 응용 프로그램에 더 스위치 데이터베이스 제공 업체에 가지고있는 드문에서, 데이터베이스에서 추출 조금합니다.


619

사용 PDO및 준비된 쿼리

( $connA는 PDO객체)

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

549

보시다시피 사람들은 준비된 진술을 최대한 사용하는 것이 좋습니다. 잘못된 것은 아니지만 쿼리가 프로세스 당 한 번만 실행 되면 약간의 성능 저하가 발생합니다.

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

내 접근 방식 :

  • 입력이 정수가 될 것으로 예상되면 실제로 정수 인지 확인하십시오 . PHP와 같은 변수형 언어에서는 이것이 매우 중요합니다. 예를 들어이 매우 간단하지만 강력한 솔루션을 사용할 수 있습니다.sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • 정수 16 진수 에서 다른 것을 기대 하면 . 16 진수라면 모든 입력을 완벽하게 피할 수 있습니다. C / C ++에는이라는 함수가 mysql_hex_string()있으며 PHP에서는을 사용할 수 있습니다 bin2hex().

    이스케이프 된 문자열은 원래 길이의 2 배 크기에 대해 걱정하지 않아도됩니다 mysql_real_escape_string. PHP 를 사용하더라도 PHP는 동일한 용량을 할당 해야하기 때문 ((2*input_length)+1)입니다.

  • 이 16 진 방법은 이진 데이터를 전송할 때 종종 사용되지만 SQL 주입 공격을 방지하기 위해 모든 데이터에 사용하지 않는 이유는 없습니다. 대신 0xMySQL 함수에 데이터를 추가 하거나 MySQL 함수 UNHEX를 사용해야합니다 .

예를 들어 쿼리는 다음과 같습니다.

SELECT password FROM users WHERE name = 'root'

될 것입니다:

SELECT password FROM users WHERE name = 0x726f6f74

또는

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

16 진수는 완벽한 탈출입니다. 주사 할 방법이 없습니다.

UNHEX 함수와 0x 접두사의 차이점

의견에 대한 토론이 있었으므로 마침내 분명히하고 싶습니다. 이 두 가지 접근 방식은 매우 유사하지만 몇 가지면에서 약간 다릅니다.

** 0x ** 접두사는 char, varchar, text, block, binary 등과 같은 데이터 열에 만 사용할 수 있습니다 .
또한 빈 문자열을 삽입하려는 경우 사용이 약간 복잡합니다. 완전히로 바꿔야합니다 ''. 그렇지 않으면 오류가 발생합니다.

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


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

이 16 진 방법은 종종 정수가 문자열과 같고로 이스케이프되는 SQL 주입 공격으로 사용됩니다 mysql_real_escape_string. 그런 다음 따옴표 사용을 피할 수 있습니다.

예를 들어 다음과 같이하면 :

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

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

SELECT ... WHERE id = -1 information_schema.tables에서 모든 select table_name을 통합

이제 테이블 구조를 추출하십시오.

SELECT ... WHERE id = -1 table_name = 0x61727469636c65 인 information_schema.column에서 모든 select column_name을 통합합니다.

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

그러나 주입 가능한 사이트의 코더가 16 진수라면 쿼리가 다음과 같이 표시되므로 주입이 불가능합니다. SELECT ... WHERE id = UNHEX('2d312075...3635')


6
@SumitGupta Yea, 그렇습니다. MySQL은와 연결되어 +있지 않지만와 연결되어 있습니다 CONCAT. 그리고 퍼포먼스 : mysql이 데이터를 파싱해야하기 때문에 퍼포먼스에 영향을 미친다고 생각하지 않습니다. 그리고 origin이 string인지 hex인지는 중요하지 않습니다
Zaffy

11
@YourCommonSense 당신은 개념을 이해하지 못합니다 ... mysql에 문자열을 원한다면 이와 같이 인용 'root'하거나 16 진수로 쓸 수 0x726f6f74있지만 숫자를 문자열로 보내면 아마도 문자가 아닌 '42'를 쓸 것입니다 (42 ) ... 16 진수의 '42'는 0x3432그렇지 않습니다0x42
Zaffy

7
@YourCommonSense 할 말이 없습니다 ... 그냥 lol ... 숫자 필드에서 16 진수를 시도하려면 두 번째 주석을 참조하십시오. 나는 그것이 효과가 있다고 당신과 내기했다.
Zaffy

2
대답은 정수 값으로는 그렇게 작동하지 않는다는 것을 분명히 나타냅니다 .bin2hex가 전달 된 값을 문자열로 변환하기 때문에 bin2hex (0)은 0x03이 아니라 0x30입니다 .- 아마도 혼란 스럽습니다. . 그렇게하면 완벽하게 작동합니다 (적어도 내 사이트에서는 데비안 머신의 4 가지 mysql 버전, 5.1.x ~ 5.6.x로 테스트 됨). 결국, 16 진수는 표현이 아니라 표현의 방법입니다.)
그리핀

5
@YourCommonSense 아직도 이해가 안 듭니까? 문자열이 비어 있으면 오류가 발생하기 때문에 0x와 concat을 사용할 수 없습니다. 검색어에 대한 간단한 대안을 원한다면이 방법을 시도해보십시오SELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')
Zaffy

499

더 이상 사용되지 않는 경고 : 이 답변의 샘플 코드 (질문의 샘플 코드와 같은)는 MySQLPHP 5.5.0에서 더 이상 사용되지 않고 PHP 7.0.0에서 완전히 제거 된 PHP 확장을 사용합니다.

보안 경고 :이 답변은 보안 모범 사례와 일치하지 않습니다. 이스케이프 SQL 주입을 방지하기 위해 불충분 , 사용 준비가 문을 대신. 아래에 요약 된 전략을 사용하십시오. (또한 mysql_real_escape_string()PHP 7에서 제거되었습니다.)

중대한

SQL 주입을 방지하는 가장 좋은 방법 은 허용되는 답변 에서 알 수 있듯이 이스케이프 대신 준비된 문 을 사용하는 것입니다 .

Aura.SqlEasyDB 와 같은 라이브러리 가있어 개발자가 준비된 명령문을보다 쉽게 ​​사용할 수 있습니다. 준비된 명령문이 SQL 삽입중지하는 데 더 나은 이유에 대해 자세히 알려면 mysql_real_escape_string()우회최근에 수정 된 WordPress의 유니 코드 SQL 주입 취약점을 참조하십시오 .

주입 방지 -mysql_real_escape_string ()

PHP는 이러한 공격을 방지하기 위해 특수 제작 된 기능을 가지고 있습니다. 당신이해야 할 모든 기능을 사용하는 것 mysql_real_escape_string입니다.

mysql_real_escape_stringMySQL 쿼리에서 사용될 문자열을 취하고 모든 SQL 주입 시도가 안전하게 이스케이프 된 동일한 문자열을 반환합니다. 기본적으로 사용자가 입력 할 수있는 귀찮은 따옴표 ( ')를 MySQL 안전 대체 문자 인 이스케이프 된 따옴표 \'로 대체합니다.

참고 : 이 기능을 사용하려면 데이터베이스에 연결되어 있어야합니다!

// MySQL에 연결

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

자세한 내용은 MySQL-SQL 주입 방지 에서 찾을 수 있습니다 .


30
이것은 레거시 mysql 확장으로 할 수있는 최선입니다. 새 코드의 경우 mysqli 또는 PDO로 전환하는 것이 좋습니다.
Álvaro González

7
나는이 '공격을 막기 위해 특별히 제작 된 기능'에 동의하지 않습니다. 그 mysql_real_escape_string목적은 모든 입력 데이터 문자열에 대해 올바른 SQL 쿼리를 작성하는 것입니다. 예방 sql-injection은이 기능의 부작용입니다.
sectus

4
올바른 입력 데이터 문자열을 작성하는 함수를 사용하지 마십시오. 탈출 할 필요가 없거나 이미 탈출 한 올바른 것을 작성하기 만하면됩니다. mysql_real_escape_string ()은 여러분이 언급 한 목적을 염두에두고 설계되었지만 유일한 값은 주입을 막는 것입니다.
Nazca

17
경고! mysql_real_escape_string() 완전하지 않다 .
eggyal

9
mysql_real_escape_string더 이상 사용되지 않으므로 더 이상 실행 가능한 옵션이 아닙니다. 향후 PHP에서 제거 될 예정입니다. PHP 또는 MySQL 사람들이 권장하는 것으로 이동하는 것이 가장 좋습니다.
jww

462

다음과 같은 기본적인 것을 할 수 있습니다.

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

이것은 모든 문제를 해결하지는 못하지만 매우 훌륭한 디딤돌입니다. 변수의 존재, 형식 (숫자, 문자 등)을 확인하는 것과 같은 명확한 항목을 제외했습니다.


28
나는 "이 모든 문제를 해결하지 않을 것이다"귀하의 예제와 안될까요에 대한 그것의 작업 벌금을 지울 시도
치누크

14
문자열을 인용하지 않으면 여전히 주사 가능합니다. 가지고 $q = "SELECT col FROM tbl WHERE x = $safe_var";예를 들어. 따옴표가 없기 때문에이 경우 작동 $safe_var하도록 설정하십시오 1 UNION SELECT password FROM users. CONCATand를 사용하여 쿼리에 문자열을 삽입 할 수도 있습니다 CHR.
다항식

4
@Polynomial 완전히 맞습니다. 그러나 이것은 단지 잘못된 사용법으로 간주됩니다. 올바르게 사용하면 확실히 작동합니다.
glglgl

22
경고! mysql_real_escape_string() 완전하지 않다 .
eggyal

8
mysql_real_escape_string더 이상 사용되지 않으므로 더 이상 실행 가능한 옵션이 아닙니다. 향후 PHP에서 제거 될 예정입니다. PHP 또는 MySQL 사람들이 권장하는 것으로 이동하는 것이 가장 좋습니다.
jww

380

무엇을 사용하든, 입력이 이미 엉망이 magic_quotes되었거나 다른 의미있는 쓰레기가 아닌지 확인하고 필요한 경우 입력 stripslashes하거나 소독 할 대상이 있는지 확인하십시오.


11
과연; magic_quotes를 켠 상태에서 실행하면 연습이 어려워집니다. 그러나 서버를 관리 할 수있는 권한이 없거나 응용 프로그램이 이러한 구성에 의존하는 응용 프로그램과 공존해야하는 경우가 있습니다. 이러한 이유로 휴대용 응용 프로그램을 작성하는 것이 좋습니다. 그러나 배포 환경을 제어하는 ​​경우 (예 : 사내 응용 프로그램이거나 특정 환경에서만 사용되기 때문에) 노력이 낭비됩니다.
Rob

24
PHP 5.4부터 '매직 인용구'로 알려진 가증 행위는 죽었습니다 . 그리고 나쁜 쓰레기에 대한 좋은 조롱.
BryanH

363

더 이상 사용되지 않는 경고 : 이 답변의 샘플 코드 (질문의 샘플 코드와 같은)는 MySQLPHP 5.5.0에서 더 이상 사용되지 않고 PHP 7.0.0에서 완전히 제거 된 PHP 확장을 사용합니다.

보안 경고 :이 답변은 보안 모범 사례와 일치하지 않습니다. 이스케이프 SQL 주입을 방지하기 위해 불충분 , 사용 준비가 문을 대신. 아래에 요약 된 전략을 사용하십시오. (또한 mysql_real_escape_string()PHP 7에서 제거되었습니다.)

매개 변수화 된 쿼리 및 입력 유효성 검사가 진행됩니다. SQL 인젝션 mysql_real_escape_string()이 사용 되었지만 많은 시나리오가 있습니다 .

이러한 예제는 SQL 삽입에 취약합니다.

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

또는

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

두 경우 모두 '캡슐화를 보호하는 데 사용할 수 없습니다 .

출처 : 예기치 않은 SQL 주입 (이스케이프가 충분하지 않은 경우)


2
사용자 입력이 길이, 유형 및 구문에 대해 정의 된 규칙 세트와 비즈니스 규칙에 대해 인증되는 입력 유효성 검증 기술을 채택하면 SQL 삽입을 방지 할 수 있습니다.
Josip Ivic

312

필자의 의견으로는 PHP 응용 프로그램 (또는 모든 웹 응용 프로그램)에서 SQL 삽입을 일반적으로 방지하는 가장 좋은 방법은 응용 프로그램의 아키텍처에 대해 생각하는 것입니다. SQL 인젝션을 방지하는 유일한 방법은 데이터베이스와 대화 할 때마다 올바른 것을 수행하는 특수한 방법이나 함수를 사용하는 것입니다. 그렇게하면 코드의 특정 시점에서 쿼리의 형식을 올바르게 잊어 버릴 때까지 시간 문제입니다.

MVC 패턴과 CakePHP 또는 CodeIgniter 와 같은 프레임 워크를 채택하는 것이 올바른 방법 일 것입니다. 안전한 데이터베이스 쿼리 생성과 같은 일반적인 작업이 해결되어 중앙에서 구현되었습니다. 웹 응용 프로그램을 합리적인 방식으로 구성하고 단일 SQL 쿼리를 안전하게 구성하는 것보다 객체를로드하고 저장하는 것에 대해 더 많이 생각하도록 도와줍니다.


5
첫 단락이 중요하다고 생각합니다. 이해가 핵심입니다. 또한 모든 사람이 회사에서 일하지 않습니다. 많은 사람들에게 프레임 워크는 실제로 이해 의 개념에 반대합니다 . 마감 기한 내에 작업하는 동안 기본 사항에 친밀 해지는 것은 가치가 없을 수 있지만, 그 일을하는 사람들은 손이 더러워지는 것을 좋아합니다. 프레임 워크 개발자는 특권이 없으므로 다른 모든 사람들이 실수하지 않고 절을해야합니다. 결정을 내리는 힘은 여전히 ​​중요합니다. 내 프레임 워크가 앞으로 다른 체계를 대체하지 않을 것이라고 누가 말합니까?
Anthony Rutledge

@AnthonyRutledge 당신은 절대적으로 정확합니다. 무슨 일이 일어나고 있는지 이해 하는 것이 매우 중요합니다 . 그러나 실제로 시도하고 적극적으로 사용하고 개발 한 프레임 워크가 많은 문제를 겪고 해결했으며 이미 많은 보안 허점을 패치했을 가능성은 매우 높습니다. 코드 품질을 느끼기 위해 소스를 살펴 보는 것이 좋습니다. 테스트되지 않은 엉망이라면 아마도 안전하지 않을 것입니다.
Johannes Fahrenkrug

3
여기. 여기. 좋은 지적입니다. 그러나 많은 사람들이 MVC 시스템 채택을 연구하고 배울 수 있지만 모든 사람이 MVC 시스템을 직접 재현 할 수는 없다는 데 동의합니다 (제어기 및 서버). 이 점으로 너무 멀리 갈 수 있습니다. 여자 친구가 만든 땅콩 버터 피칸 쿠키를 데우기 전에 전자 레인지를 이해해야합니까? ;-)
Anthony Rutledge

3
@AnthonyRutledge 동의합니다! 유스 케이스에도 차이가 있다고 생각합니다. 개인 홈페이지를위한 사진 갤러리를 구축하고 있습니까, 아니면 온라인 뱅킹 웹 애플리케이션을 구축하고 있습니까? 후자의 경우 보안의 세부 사항과 내가 사용하는 프레임 워크가 어떻게 해결되는지 이해하는 것이 매우 중요합니다.
Johannes Fahrenkrug

3
아, 보안 예외는 스스로 처리해야합니다. 나는 위험을 감수하고 파산하려고한다. :-) 키딩. 충분한 시간이 있으면 사람들은 꽤 안전한 애플리케이션을 만드는 법을 배울 수 있습니다. 너무 많은 사람들이 서두르고 있습니다. 그들은 손을 내밀고 프레임 워크가 더 안전 하다고 가정합니다 . 결국, 그들은 일을 테스트하고 알아낼 시간이 충분하지 않습니다. 또한 보안은 전담 연구가 필요한 분야입니다. 알고리즘과 디자인 패턴을 이해함으로써 프로그래머가 깊이 알고있는 것은 아닙니다.
Anthony Rutledge

298

보안 관점에서 저장 프로 시저 ( MySQL은 5.0 이후 저장 프로 시저를 지원 했음)를 선호 합니다. 장점은 다음과 같습니다.

  1. MySQL을 포함한 대부분의 데이터베이스는 사용자 액세스가 저장 프로 시저 실행으로 제한되도록합니다. 세분화 된 보안 액세스 제어는 권한 공격의 확대를 방지하는 데 유용합니다. 이렇게하면 손상된 응용 프로그램이 데이터베이스에 대해 SQL을 직접 실행할 수 없습니다.
  2. 이들은 응용 프로그램에서 원시 SQL 조회를 추상화하므로 응용 프로그램에 사용 가능한 데이터베이스 구조 정보가 적습니다. 따라서 사람들은 데이터베이스의 기본 구조를 이해하고 적절한 공격을 설계하기가 더 어려워집니다.
  3. 매개 변수 만 허용하므로 매개 변수화 된 쿼리의 장점이 있습니다. 물론 IMO는 특히 저장 프로 시저 내에서 동적 SQL을 사용하는 경우 입력을 삭제해야합니다.

단점은-

  1. 그것들은 (저장된 절차) 유지하기 힘들고 매우 빠르게 번식하는 경향이 있습니다. 이것은 그들을 관리하는 것을 문제로 만듭니다.
  2. 동적 쿼리에는 적합하지 않습니다. 동적 코드를 매개 변수로 받아들이도록 만들어지면 많은 장점이 무시됩니다.

297

SQL 삽입 및 기타 SQL 해킹을 방지하는 방법에는 여러 가지가 있습니다. 인터넷 (Google 검색)에서 쉽게 찾을 수 있습니다. 물론 PDO는 좋은 솔루션 중 하나입니다. 그러나 SQL 인젝션으로부터 좋은 링크 방지를 제안하고 싶습니다.

SQL 주입이란 무엇이며 방지하는 방법

SQL 주입을위한 PHP 매뉴얼

PHP에서의 SQL 삽입 및 예방에 대한 Microsoft 설명

그리고 MySQL과 PHP로 SQL 주입 방지 와 같은 다른 것들도 있습니다.

이제 왜 쿼리를 SQL 삽입하지 못하게해야합니까?

다음과 같은 간단한 예를 통해 왜 SQL 주입을 방지하려고합니까?

로그인 인증 일치 쿼리 :

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

이제 누군가 (해커)가

$_POST['email']= admin@emali.com' OR '1=1

그리고 무엇이든 암호 ....

쿼리는 다음과 같은 경우에만 시스템으로 구문 분석됩니다.

$query="select * from users where email='admin@emali.com' OR '1=1';

다른 부분은 폐기됩니다. 그래서 어떻게 될까요? 권한이없는 사용자 (해커)는 비밀번호없이 관리자로 로그인 할 수 있습니다. 이제 관리자 / 이메일 담당자가 할 수있는 모든 작업을 수행 할 수 있습니다. SQL 주입이 방지되지 않으면 매우 위험합니다.


267

누군가 PHP와 MySQL 또는 다른 데이터베이스 서버를 사용하고 싶다면 :

  1. PDO (PHP Data Objects) 학습에 대해 생각해보십시오. PDO 는 여러 데이터베이스에 대한 균일 한 액세스 방법을 제공하는 데이터베이스 액세스 계층입니다.
  2. MySQLi 학습에 대해 생각하십시오
  3. strip_tags , mysql_real_escape_string 같은 고유 PHP 함수를 사용 하거나 변수 숫자 인 경우 그냥 사용하십시오 (int)$foo. PHP의 변수 유형에 대한 자세한 내용은 여기를 참조하십시오 . PDO 또는 MySQLi와 같은 라이브러리를 사용하는 경우 항상 PDO :: quote ()mysqli_real_escape_string ()을 사용하십시오 .

라이브러리 예 :

---- PDO

----- 자리 표시 자 없음-SQL 주입에 적합합니다! 그것은 나쁜

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- 이름이없는 자리 표시 자

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- 명명 된 자리 표시 자

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

PS :

PDO는이 전투에서 쉽게 승리합니다. 12 개의 서로 다른 데이터베이스 드라이버와 명명 된 매개 변수를 지원함으로써 작은 성능 손실을 무시하고 API에 익숙해 질 수 있습니다. 보안 관점에서 개발자가 사용 방식을 사용하는 한 두 가지 모두 안전합니다.

그러나 PDO와 MySQLi는 상당히 빠르지 만, MySQLi는 벤치 마크에서 매우 빠릅니다. 준비되지 않은 문장의 경우 ~ 2.5 %, 준비된 문장의 경우 ~ 6.5 %입니다.

그리고 데이터베이스에 대한 모든 쿼리를 테스트하십시오. 주입을 방지하는 더 좋은 방법입니다.


mysqli가 올바르지 않습니다. 첫 번째 매개 변수는 데이터 유형을 나타냅니다.
mickmackusa

257

가능하면 매개 변수 유형을 캐스트하십시오. 그러나 int, bool 및 float와 같은 간단한 유형에서만 작동합니다.

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

3
이것은 준비된 진술 대신 "탈출 된 값"을 사용하는 몇 안되는 경우 중 하나입니다. 정수형 변환은 매우 효율적입니다.
HoldOffHunger

233

Redis 또는 Memcached 와 같은 캐시 엔진을 활용하려면 DALMP가 선택 될 수 있습니다. 순수한 MySQLi를 사용합니다 . PHP를 사용하는 MySQL 용 DALMP Database Abstraction Layer를 확인하십시오 .

또한 쿼리를 준비하기 전에 인수를 '준비'하여 동적 쿼리를 작성할 수 있고 결국 완전히 준비된 명령문 쿼리를 가질 수 있습니다. PHP를 사용하는 MySQL 용 DALMP 데이터베이스 추상화 계층.


224

PDO ( mysql_함수 에서 오는)를 사용하는 방법을 모르는 사람들을 위해 단일 파일 인 매우 간단한 PDO 래퍼 를 만들었습니다 . 응용 프로그램이 수행해야하는 모든 공통 작업을 수행하는 것이 얼마나 쉬운지를 보여주기 위해 존재합니다. PostgreSQL, MySQL 및 SQLite와 함께 작동합니다.

기본적으로, 그것을 읽고 당신이 매뉴얼 읽는 동안 간단 저장하고 형식의 값을 검색 할 수 있도록 실생활에서 사용하는 PDO 기능을 넣어하는 방법을보고 당신이 원하는합니다.

나는 단 하나의 열을 원한다

$count = DB::column('SELECT COUNT(*) FROM `user`);

배열 (키 => 값) 결과를 원합니다 (즉, 선택 상자 만들기)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

단일 행 결과를 원합니다

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

결과 배열을 원합니다

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));

222

이 PHP 기능 mysql_escape_string()을 사용하면 빠른 예방이 가능합니다.

예를 들면 다음과 같습니다.

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string — mysql_query에서 사용하기 위해 문자열을 이스케이프합니다

더 많은 예방을 위해 끝에 추가 할 수 있습니다 ...

wHERE 1=1   or  LIMIT 1

마지막으로 다음을 얻습니다.

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

220

SQL 문에서 특수 문자를 이스케이프하기위한 몇 가지 지침.

MySQL을 사용하지 마십시오 . 이 확장은 더 이상 사용되지 않습니다. 대신 MySQLi 또는 PDO 를 사용하십시오 .

MySQLi

문자열에서 특수 문자를 수동으로 이스케이프 처리하려면 mysqli_real_escape_string 함수를 사용할 수 있습니다 . mysqli_set_charset으로 올바른 문자 세트를 설정하지 않으면 함수가 제대로 작동하지 않습니다 .

예:

$mysqli = new mysqli('host', 'user', 'password', 'database');
$mysqli->set_charset('charset');

$string = $mysqli->real_escape_string($string);
$mysqli->query("INSERT INTO table (column) VALUES ('$string')");

준비된 명령문으로 값을 자동 이스케이프하려면 mysqli_preparemysqli_stmt_bind_param을 사용하십시오. 여기서 적절한 바인드 변수에 대한 유형을 적절히 변환해야합니다.

예:

$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();

준비된 명령문을 사용하든 mysqli_real_escape_string, 또는 작업중인 입력 데이터의 유형을 항상 알아야합니다.

따라서 준비된 명령문을 사용하는 경우 mysqli_stmt_bind_param함수 변수 유형을 지정해야합니다 .

그리고 mysqli_real_escape_string이름에서 알 수 있듯이 사용은 문자열에서 특수 문자를 이스케이프 처리하여 정수를 안전하게 만들지 않습니다. 이 함수의 목적은 SQL 문에서 문자열이 깨지거나 데이터베이스가 손상 될 수있는 것을 방지하기위한 것입니다. mysqli_real_escape_string제대로 사용될 때, 특히와 결합 할 때 유용한 기능입니다 sprintf.

예:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647

3
질문은 매우 일반적입니다. 위의 몇 가지 훌륭한 답변이지만 대부분 준비된 진술을 제안합니다. MySQLi 비동기는 준비된 명령문을 지원하지 않으므로 sprintf는이 상황에 적합한 옵션처럼 보입니다.
Dustin Graham

183

이 문제에 대한 간단한 대안은 데이터베이스 자체에 적절한 권한을 부여하여 해결할 수 있습니다. 예를 들어, MySQL 데이터베이스를 사용하는 경우 제공된 터미널 또는 UI를 통해 데이터베이스에 입력하고 다음 명령을 따르십시오.

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

이것은 사용자가 지정된 쿼리에만 제한되도록 제한합니다. 삭제 권한을 제거하면 PHP 페이지에서 실행 된 쿼리에서 데이터가 삭제되지 않습니다. 두 번째로해야 할 일은 MySQL이 권한과 업데이트를 갱신하도록 권한을 플러시하는 것입니다.

FLUSH PRIVILEGES; 

flush 에 대한 추가 정보 .

사용자에 대한 현재 권한을 보려면 다음 쿼리를 실행하십시오.

select * from mysql.user where User='username';

GRANT에 대해 자세히 알아보십시오 .


25
이 답변은 주사 예방을 방지하는 데 도움이되지 않고 결과를 부드럽게하려고하기 때문에 본질적으로 잘못 되었습니다. 헛된.
당신의 상식

1
맞습니다. 해결책을 제공하지는 않지만 미리 피하기 위해 할 수있는 일입니다.
Apurv Nerlekar

1
@Apurv 내 목표가 데이터베이스에서 개인 정보를 읽는 것이라면 DELETE 권한이 없으면 아무 의미가 없습니다.
Alex Holsgrove

1
@AlexHolsgrove : 결과를 완화하기위한 좋은 방법을 제안했습니다.
Apurv Nerlekar

2
@Apurv 당신은 "결과를 부드럽게"하고 싶지 않으며, 그것을 막기 위해 가능한 모든 것을하기를 원합니다. 공정하게 말하면 올바른 사용자 액세스를 설정하는 것이 중요하지만 실제로 OP가 요구하는 것은 아닙니다.
Alex Holsgrove

177

많은 유용한 답변과 관련 하여이 스레드에 가치를 더하기를 희망합니다.

SQL 주입은 사용자 입력 (사용자가 입력 한 다음 쿼리 내부에서 사용하는 입력)을 통해 수행 할 수있는 공격입니다. SQL 인젝션 패턴은 올바른 쿼리 구문이지만 호출 할 수 있습니다. 나쁜 이유는 잘못된 쿼리이며 보안의 세 가지 원칙 (기밀성)에 영향을주는 비밀 정보를 얻는 (액세스 제어 우회) 나쁜 사람이 있다고 가정합니다. , 무결성 및 가용성).

이제 우리의 요점은 SQL 주입 공격과 같은 보안 위협을 방지하는 것입니다. (PHP를 사용하여 SQL 주입 공격을 방지하는 방법) 더 사실적이며 데이터 필터링 또는 입력 데이터 지우기는 내부에서 사용자 입력 데이터를 사용할 때의 경우입니다 PHP 나 다른 프로그래밍 언어를 사용한 쿼리는 그렇지 않거나, 더 많은 사람들이 준비된 명령문이나 현재 SQL 주입 방지를 지원하는 다른 도구와 같은 최신 기술을 사용하도록 권장하는 경우 이러한 도구를 더 이상 사용할 수 없다고 생각하십니까? 애플리케이션을 어떻게 보호합니까?

SQL 삽입에 대한 나의 접근 방식은 사용자 입력 데이터를 데이터베이스로 보내기 전에 (쿼리 내에서 사용하기 전에) 지우는 것입니다.

데이터 필터링 (안전하지 않은 데이터를 안전한 데이터로 변환)

PDOMySQLi 를 사용할 수 없다는 것을 고려하십시오 . 애플리케이션을 어떻게 보호 할 수 있습니까? 내가 그들을 사용하도록 강요합니까? PHP 이외의 다른 언어는 어떻습니까? 나는 특정 언어뿐만 아니라 더 넓은 국경에 사용될 수있는 일반적인 아이디어를 선호한다.

  1. SQL 사용자 (제한된 사용자 권한) : 가장 일반적인 SQL 작업은 (SELECT, UPDATE, INSERT)인데, 왜 필요하지 않은 사용자에게 UPDATE 권한을 부여합니까? 예를 들어, 로그인 및 검색 페이지 는 SELECT 만 사용하는 경우 높은 권한으로이 페이지에서 DB 사용자를 사용하는 이유는 무엇입니까?

규칙 : 모든 권한에 대해 하나의 데이터베이스 사용자를 작성하지 마십시오. 모든 SQL 작업에서 (deluser, selectuser, updateuser)와 같은 체계를 사용자 이름으로 만들어 쉽게 사용할 수 있습니다.

최소 권한 원칙을 참조하십시오 .

  1. 데이터 필터링 : 쿼리 사용자 입력을 작성하기 전에 유효성을 검사하고 필터링해야합니다. 프로그래머의 경우 각 사용자 입력 변수에 대한 일부 속성 ( 데이터 유형, 데이터 패턴 및 데이터 길이) 을 정의하는 것이 중요합니다 . (x와 y) 사이의 숫자 인 필드는 정확한 규칙과 문자열 (텍스트) 인 필드에 대해 정확하게 유효성을 검사해야합니다. 예를 들어, 패턴은 경우입니다. [a-zA-Z0-9_-.]라고 말합니다. 길이는 (x와 n)에서 x와 n (정수, x <= n)입니다. 규칙 : 정확한 필터와 유효성 검사 규칙을 만드는 것이 가장 좋습니다.

  2. 다른 도구를 사용하십시오. 여기서는 준비된 문 (매개 변수화 된 쿼리) 및 저장 프로 시저에 동의합니다. 여기서 단점은 이러한 방식에는 대부분의 사용자에게는 존재하지 않는 고급 기술이 필요하다는 것입니다. 여기서 기본 아이디어는 SQL 쿼리와 내부에서 사용되는 데이터를 구별하는 것입니다. 여기서 사용자 입력 데이터는 (any 또는 x = x)와 같이 원래 쿼리에 아무것도 추가하지 않기 때문에 안전하지 않은 데이터에서도 두 가지 방법을 모두 사용할 수 있습니다.

자세한 내용은 OWASP SQL 인젝션 방지 치트 시트를 참조하십시오 .

이제 고급 사용자라면 원하는대로이 방어를 사용하기 시작하지만 초보자는 저장 프로 시저를 빠르게 구현하고 명령문을 준비 할 수 없으면 입력 데이터를 최대한 필터링하는 것이 좋습니다.

마지막으로, 사용자가 자신의 사용자 이름을 입력하는 대신 아래에이 텍스트를 전송한다고 가정 해 보겠습니다.

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

이 입력은 준비된 명령문 및 스토어드 프로 시저없이 조기에 점검 할 수 있지만 사용자 데이터 필터링 및 유효성 검증 후에 시작하여 안전하게 사용할 수 있습니다.

마지막 요점은 더 많은 노력과 복잡성이 필요한 예기치 않은 동작을 감지하는 것입니다. 일반적인 웹 애플리케이션에는 권장되지 않습니다.

위의 사용자 입력에서 예기치 않은 동작은 SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA 및 root입니다. 이러한 단어가 감지되면 입력을 피할 수 있습니다.

업데이트 1 :

사용자가이 게시물이 쓸모 없다고 댓글을 달았습니다. OWASP.ORG가 제공 한 내용은 다음과 같습니다 .

기본 방어 :

옵션 # 1 : 준비된 문 사용 (매개 변수화 된 쿼리)
옵션 # 2 : 저장 프로 시저 사용
옵션 # 3 : 모든 사용자 제공 입력 탈출

추가 방어 :

또한 시행 : 최소 권한
수행 : 화이트리스트 입력 유효성 검사

아시다시피, 기사를 주장하는 것은 적어도 하나의 참조로 유효한 주장에 의해 뒷받침되어야합니다! 그렇지 않으면 공격과 나쁜 주장으로 간주됩니다!

업데이트 2 :

PHP 매뉴얼, PHP : Prepared Statements-Manual :

이스케이프 및 SQL 인젝션

바운드 변수는 서버에 의해 자동으로 이스케이프됩니다. 서버는 실행 전에 적절한 위치에서 이스케이프 된 값을 명령문 템플리트에 삽입합니다. 적절한 변환을 작성하려면 바운드 변수 유형에 대한 힌트를 서버에 제공해야합니다. 자세한 내용은 mysqli_stmt_bind_param () 함수를 참조하십시오.

서버 내에서 자동으로 값을 이스케이프 처리하는 것은 SQL 삽입을 방지하기위한 보안 기능으로 간주됩니다. 입력 값이 올바르게 이스케이프되면 준비되지 않은 명령문으로 동일한 보안 수준을 달성 할 수 있습니다.

업데이트 3 :

준비된 명령문을 사용할 때 PDO 및 MySQLi가 쿼리를 MySQL 서버에 보내는 방법을 알기위한 테스트 사례를 작성했습니다.

PDO :

$user = "''1''"; // Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

쿼리 로그 :

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi :

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

쿼리 로그 :

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

준비된 진술이 데이터를 탈출하고 있다는 것은 분명합니다.

위의 진술에서 언급했듯이

서버 내에서 자동으로 값을 이스케이프 처리하는 것은 SQL 삽입을 방지하기위한 보안 기능으로 간주됩니다. 입력 값이 올바르게 이스케이프되면 준비되지 않은 명령문으로 동일한 보안 수준을 달성 할 수 있습니다.

따라서 이는 intval()쿼리를 보내기 전에 정수 값에 대한 데이터 유효성 검사 가 좋은 아이디어 임을 증명 합니다. 또한 쿼리를 보내기 전에 악의적 인 사용자 데이터를 방지 하는 것이 정확하고 유효한 방법 입니다.

자세한 내용은이 질문을 참조하십시오 : PDO는 원시 쿼리를 MySQL로 보내고 Mysqli는 준비된 쿼리를 보냅니다. 둘 다 동일한 결과를 생성합니다

참고 문헌 :

  1. SQL 인젝션 치트 시트
  2. SQL 인젝션
  3. 정보 보안
  4. 보안 원칙
  5. 데이터 유효성 검사

175

보안 경고 :이 답변은 보안 모범 사례와 일치하지 않습니다. 이스케이프 SQL 주입을 방지하기 위해 불충분 , 사용 준비가 문을 대신. 아래에 요약 된 전략을 사용하십시오. (또한 mysql_real_escape_string()PHP 7에서 제거되었습니다.)

더 이상 사용되지 않는 경고 : 현재 mysql 확장은 더 이상 사용되지 않습니다. PDO 확장을 사용하는 것이 좋습니다

웹 응용 프로그램이 SQL 주입에 취약하지 않도록 세 가지 방법을 사용합니다.

  1. 의 사용 mysql_real_escape_string()에 미리 정의 된 함수, PHP , 다음과 같은 문자 코드의 추가 백 슬래시 : \x00, \n, \r, \, ', "\x1a. 입력 값을 매개 변수로 전달하여 SQL 삽입 가능성을 최소화하십시오.
  2. 가장 진보 된 방법은 PDO를 사용하는 것입니다.

이것이 도움이되기를 바랍니다.

다음 쿼리를 고려하십시오.

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string ()은 여기서 보호하지 않습니다. 쿼리 내부의 변수 주위에 작은 따옴표 ( '')를 사용하면이를 방지 할 수 있습니다. 이에 대한 해결책은 다음과 같습니다.

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

질문 에는 이것에 대한 좋은 대답이 있습니다.

PDO를 사용하는 것이 가장 좋습니다.

편집하다:

mysql_real_escape_string()PHP 5.5.0부터 더 이상 사용되지 않습니다. mysqli 또는 PDO를 사용하십시오.

mysql_real_escape_string ()의 대안은 다음과 같습니다.

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

예:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");

169

간단한 방법은 CodeIgniter 또는 Laravel 과 같은 PHP 프레임 워크를 사용하여 필터링 및 활성 레코드와 같은 기능이 내장되어 있으므로 이러한 뉘앙스에 대해 걱정할 필요가 없습니다.


7
문제의 핵심은 그러한 프레임 워크를 사용하지 않고이 작업을 수행하는 것입니다.
Sanke

147

경고 :이 답변에 설명 된 방법은 매우 특정한 시나리오에만 적용되며 SQL 주입 공격은 주입에만 의존하지 않기 때문에 안전하지 않습니다 X=Y.

공격자가 PHP의 $_GET변수 또는 URL의 쿼리 문자열을 통해 양식을 해킹하려는 경우 안전하지 않은 경우이를 파악할 수 있습니다.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

때문에 1=1, 2=2, 1=2, 2=1, 1+1=2, 등 ... 공격자의 SQL 데이터베이스에 대한 일반적인 질문입니다. 또한 많은 해킹 응용 프로그램에서 사용되기도합니다.

그러나 사이트에서 안전한 쿼리를 다시 작성해서는 안됩니다. 위의 코드는 해킹 관련 동적 쿼리 문자열을 공격자의 IP 주소 또는 쿠키, 히스토리, 브라우저 또는 기타 민감한 내용을 저장하는 페이지 로 다시 작성하거나 리디렉션 (사용자에 따라 다름) 하는 팁을 제공합니다. 나중에 계정을 차단하거나 당국에 연락하여 정보를 처리 할 수 ​​있습니다.


무슨 일이야 1-1=0? :)
Rápli András

@ RápliAndrás 일종의 ([0-9\-]+)=([0-9]+).
5ervant

127

PHP와 MySQL에 대한 많은 답변이 있지만 다음은 SQL 인젝션을 방지하고 정기적으로 oci8 드라이버를 사용하는 PHP 및 Oracle 코드입니다 .

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);

oci_bind_by_name 매개 변수를 설명하십시오.
Jahanzeb Awan

127

좋은 아이디어는 Idiorm 과 같은 객체 관계형 매퍼 를 사용하는 것 입니다 .

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

SQL 삽입뿐만 아니라 구문 오류도 방지합니다! 또한 메소드 체인으로 모델 모음을 지원하여 한 번에 여러 개의 연결에서 여러 결과에 조치를 필터링하거나 적용 할 수 있습니다.


124

더 이상 사용되지 않는 경고 : 이 답변의 샘플 코드 (질문의 샘플 코드와 같은)는 MySQLPHP 5.5.0에서 더 이상 사용되지 않고 PHP 7.0.0에서 완전히 제거 된 PHP 확장을 사용합니다.

보안 경고 :이 답변은 보안 모범 사례와 일치하지 않습니다. 이스케이프 SQL 주입을 방지하기 위해 불충분 , 사용 준비가 문을 대신. 아래에 요약 된 전략을 사용하십시오. (또한 mysql_real_escape_string()PHP 7에서 제거되었습니다.)

PDOMYSQLi를 사용 하는 것은 SQL 삽입을 방지하는 좋은 방법이지만 실제로 MySQL 함수 및 쿼리로 작업하려면 사용하는 것이 좋습니다.

mysql_real_escape_string

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

이를 방지하는 더 많은 기능이 있습니다. 식별과 같이-입력이 문자열, 숫자, 문자 또는 배열 인 경우이를 감지하는 내장 함수가 너무 많습니다. 또한이 기능을 사용하여 입력 데이터를 확인하는 것이 좋습니다.

is_string

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

is_numeric

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

그리고 이러한 함수를 사용하여 입력 데이터를 확인하는 것이 훨씬 좋습니다 mysql_real_escape_string.


10
또한 is_string ()을 사용하여 $ _POST 배열 구성원을 확인하는 데 아무런 의미가 없습니다.
Common Sense

21
경고! mysql_real_escape_string() 완전하지 않다 .
eggyal

10
mysql_real_escape_string더 이상 사용되지 않으므로 더 이상 실행 가능한 옵션이 아닙니다. 향후 PHP에서 제거 될 예정입니다. PHP 또는 MySQL 사람들이 권장하는 것으로 이동하는 것이 가장 좋습니다.
jww

2
주제 : 사용자가 제출 한 데이터를 신뢰하지 마십시오. 당신이 기대하는 것은 특수 문자 또는 부울 논리를 가진 가비지 데이터이며, 그 자체가 실행중인 SQL 쿼리의 일부가되어야합니다. $ _POST 값을 SQL 부분이 아닌 데이터로만 유지하십시오.
Bimal Poudel

88

몇 년 전에이 작은 기능을 작성했습니다.

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

이를 통해 한 줄짜리 C # -ish String에서 명령문을 실행할 수 있습니다.

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

변수 유형을 고려하여 이스케이프됩니다. 테이블, 열 이름을 매개 변수화하려고하면 모든 문자열을 따옴표로 묶어 유효하지 않은 구문이므로 실패합니다.

보안 업데이트 : 이전 str_replace버전에서는 사용자 데이터에 {#} 토큰을 추가하여 주입을 허용했습니다. 이 preg_replace_callback교체는이 토큰을 포함하는 경우 버전은 문제가 발생하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.