SQL 삽입이란 무엇이며 PHP 애플리케이션에서 방지하는 방법은 무엇입니까?

그렇다면 SQL 데이터베이스가 성능이 뛰어나고 즉각적인 파괴로부터 안전하다고 생각하십니까? 음, SQL 주입은 동의하지 않습니다!

예, “보안 강화” 및 “악의적 액세스 방지”와 같은 일반적인 용어로 이 기사를 열고 싶지 않기 때문에 우리가 말하는 것은 즉각적인 파괴입니다. SQL 인젝션은 모든 개발자가 그것에 대해 잘 알고 있으며 이를 방지하는 방법을 잘 알고 있는 책에 나오는 오래된 트릭입니다. 그들이 미끄러지는 그 이상한 시간을 제외하고는 그 결과는 재앙이나 다름없을 수 있습니다.

SQL 인젝션이 무엇인지 이미 알고 있다면 기사의 후반부로 건너뛰어도 됩니다. 그러나 이제 막 웹 개발 분야에 입문하고 더 많은 고위직을 꿈꾸는 사람들을 위해 몇 가지 소개가 필요합니다.

SQL 인젝션이란?

SQL 주입을 이해하는 열쇠는 SQL + 주입이라는 이름에 있습니다. 여기서 “주사”라는 단어는 의학적 의미가 아니라 “주입하다”라는 동사의 용법입니다. 이 두 단어를 함께 사용하면 SQL을 웹 애플리케이션에 넣는다는 개념이 전달됩니다.

SQL을 웹 애플리케이션에 넣기 . . . 흠. . . 어쨌든 우리가 하고 있는 일이 아닌가? 예, 하지만 공격자가 데이터베이스를 구동하는 것을 원하지 않습니다. 예를 들어 이해해 봅시다.

지역 전자 상거래 상점을 위한 일반적인 PHP 웹 사이트를 구축하고 있으므로 다음과 같은 문의 양식을 추가하기로 결정했다고 가정해 보겠습니다.

<form action="record_message.php" method="POST">
  <label>Your name</label>
  <input type="text" name="name">
  
  <label>Your message</label>
  <textarea name="message" rows="5"></textarea>
  
  <input type="submit" value="Send">
</form>

그리고 상점 주인이 나중에 사용자 메시지를 읽을 수 있도록 send_message.php 파일이 모든 것을 데이터베이스에 저장한다고 가정해 봅시다. 다음과 같은 코드가 있을 수 있습니다.

<?php

$name = $_POST['name'];
$message = $_POST['message'];

// check if this user already has a message
mysqli_query($conn, "SELECT * from messages where name = $name");

// Other code here

따라서 먼저 이 사용자가 이미 읽지 않은 메시지를 가지고 있는지 확인하려고 합니다. name = $name인 메시지의 SELECT * 쿼리는 충분히 간단해 보입니다.

잘못된!

결백하게 우리는 데이터베이스를 즉시 파괴할 수 있는 문을 열었습니다. 이를 위해서는 공격자가 다음 조건을 충족해야 합니다.

  • 애플리케이션이 SQL 데이터베이스에서 실행 중입니다(현재 거의 모든 애플리케이션이 실행 중임).
  • 현재 데이터베이스 연결에는 데이터베이스에 대한 “편집” 및 “삭제” 권한이 있습니다.
  • 중요한 테이블의 이름을 추측할 수 있습니다.
  iPhone의 홈 화면에서 위젯을 추가 및 제거하는 방법

세 번째 요점은 이제 공격자가 귀하가 전자 상거래 상점을 운영하고 있다는 것을 알았으므로 주문 데이터를 주문 테이블에 저장할 가능성이 매우 높다는 것입니다. 이 모든 것으로 무장한 공격자는 다음을 이름으로 제공하기만 하면 됩니다.

조; 주문을 자릅니다;? 알겠습니다! 쿼리가 PHP 스크립트에 의해 실행될 때 쿼리가 어떻게 되는지 살펴보겠습니다.

SELECT * FROM 메시지 WHERE 이름 = Joe; 주문을 자릅니다.

좋아요, 쿼리의 첫 번째 부분에 구문 오류가 있지만(“Joe” 주위에 따옴표가 없음) 세미콜론은 MySQL 엔진이 새로운 구문인 truncate orders를 해석하기 시작하도록 강제합니다. 마찬가지로 한 번의 급습으로 전체 주문 내역이 사라집니다!

이제 SQL 인젝션이 어떻게 작동하는지 알았으니 중지하는 방법을 살펴볼 차례입니다. 성공적인 SQL 주입을 위해 충족해야 하는 두 가지 조건은 다음과 같습니다.

  • PHP 스크립트에는 데이터베이스에 대한 수정/삭제 권한이 있어야 합니다. 나는 이것이 모든 응용 프로그램에 해당되며 응용 프로그램을 읽기 전용으로 만들 수 없을 것이라고 생각합니다. 🙂 그리고 우리가 모든 수정 권한을 제거하더라도 SQL 삽입을 통해 누군가가 여전히 SELECT 쿼리를 실행하고 모든 데이터베이스, 민감한 데이터를 볼 수 있도록 허용할 수 있습니다. 즉, 데이터베이스 액세스 수준을 낮추는 것은 효과가 없으며 애플리케이션에 어쨌든 필요합니다.
  • 사용자 입력이 처리 중입니다. SQL 주입이 작동할 수 있는 유일한 방법은 사용자로부터 데이터를 수락할 때입니다. 다시 한 번 말하지만 SQL 삽입이 걱정된다는 이유만으로 애플리케이션에 대한 모든 입력을 중지하는 것은 실용적이지 않습니다.
  • PHP에서 SQL 삽입 방지

    이제 데이터베이스 연결, 쿼리 및 사용자 입력이 삶의 일부라는 점을 감안할 때 SQL 주입을 방지하는 방법은 무엇입니까? 고맙게도 매우 간단하며 이를 수행하는 두 가지 방법이 있습니다. 1) 사용자 입력을 삭제하고 2) 준비된 문을 사용합니다.

    사용자 입력 삭제

    이전 PHP 버전(5.5 이하, 공유 호스팅에서 자주 발생)을 사용하는 경우 mysql_real_escape_string()이라는 함수를 통해 모든 사용자 입력을 실행하는 것이 좋습니다. 기본적으로 데이터베이스에서 사용할 때 의미를 잃도록 문자열의 모든 특수 문자를 제거합니다.

      Mac용 Safari에서 웹 페이지를 번역하는 방법

    예를 들어 I’m a string과 같은 문자열이 있는 경우 공격자는 작은따옴표 문자(‘)를 사용하여 생성 중인 데이터베이스 쿼리를 조작하고 SQL 주입을 일으킬 수 있습니다. mysql_real_escape_string()을 통해 실행하면 작은따옴표에 백슬래시를 추가하여 이스케이프 처리하는 I’m a string이 생성됩니다. 결과적으로 전체 문자열은 이제 쿼리 조작에 참여할 수 있는 대신 무해한 문자열로 데이터베이스에 전달됩니다.

    이 접근 방식에는 한 가지 단점이 있습니다. PHP의 이전 형식의 데이터베이스 액세스와 함께 사용되는 정말 정말 오래된 기술입니다. PHP 7부터 이 함수는 더 이상 존재하지 않으므로 다음 솔루션으로 이동합니다.

    준비된 문 사용

    준비된 문은 데이터베이스 쿼리를 보다 안전하고 안정적으로 만드는 방법입니다. 아이디어는 원시 쿼리를 데이터베이스로 보내는 대신 먼저 보낼 쿼리의 구조를 데이터베이스에 알리는 것입니다. 이것이 진술을 “준비”한다는 의미입니다. 명령문이 준비되면 데이터베이스가 이전에 보낸 쿼리 구조에 입력을 연결하여 “간격을 채울” 수 있도록 정보를 매개변수화된 입력으로 전달합니다. 이렇게 하면 입력이 가질 수 있는 특별한 권한이 없어져 전체 프로세스에서 입력이 단순한 변수(또는 페이로드)로 취급됩니다. 준비된 문은 다음과 같습니다.

    <?php
    $servername = "localhost";
    $username = "username";
    $password = "password";
    $dbname = "myDB";
    
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    
    // Check connection
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    
    // prepare and bind
    $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $firstname, $lastname, $email);
    
    // set parameters and execute
    $firstname = "John";
    $lastname = "Doe";
    $email = "[email protected]";
    $stmt->execute();
    
    $firstname = "Mary";
    $lastname = "Moe";
    $email = "[email protected]";
    $stmt->execute();
    
    $firstname = "Julie";
    $lastname = "Dooley";
    $email = "[email protected]";
    $stmt->execute();
    
    echo "New records created successfully";
    
    $stmt->close();
    $conn->close();
    ?>

    준비된 진술을 처음 접하는 경우 프로세스가 불필요하게 복잡하게 들리지만 개념은 노력할 가치가 있습니다. 여기 그것에 대한 좋은 소개.

    이미 PHP의 PDO 확장에 익숙하고 이를 사용하여 준비된 명령문을 만드는 사람들에게 작은 조언이 있습니다.

    경고: PDO를 설정할 때 주의하십시오.

    데이터베이스 액세스에 PDO를 사용하면 잘못된 보안 감각에 빠질 수 있습니다. “아, 음, 저는 PDO를 사용하고 있습니다. 이제 나는 다른 것에 대해 생각할 필요가 없습니다.” – 이것이 우리의 생각이 일반적으로 진행되는 방식입니다. PDO(또는 MySQLi 준비된 명령문)이면 모든 종류의 SQL 주입 공격을 충분히 방지할 수 있지만 설정 시 주의해야 합니다. 튜토리얼이나 이전 프로젝트에서 코드를 복사하여 붙여넣고 계속 진행하는 것이 일반적이지만 이 설정은 모든 것을 취소할 수 있습니다.

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

    이 설정이 하는 일은 데이터베이스의 준비된 명령문 기능을 실제로 사용하는 대신 준비된 명령문을 에뮬레이션하도록 PDO에 지시하는 것입니다. 결과적으로 PHP는 코드가 준비된 명령문을 생성하고 매개변수를 설정하는 것처럼 보이더라도 데이터베이스에 간단한 쿼리 문자열을 보냅니다. 즉, 이전과 마찬가지로 SQL 인젝션에 취약합니다. 🙂

      Python unittest 모듈을 사용한 단위 테스트

    해결책은 간단합니다. 이 에뮬레이션이 false로 설정되어 있는지 확인하십시오.

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

    이제 PHP 스크립트는 데이터베이스 수준에서 준비된 명령문을 사용하도록 강제되어 모든 종류의 SQL 삽입을 방지합니다.

    WAF 사용 방지

    WAF(웹 애플리케이션 방화벽)를 사용하여 SQL 인젝션으로부터 웹 애플리케이션을 보호할 수도 있다는 사실을 알고 계십니까?

    음, SQL 인젝션뿐만 아니라 사이트 간 스크립팅, 인증 손상, 사이트 간 위조, 데이터 노출 등과 같은 다른 많은 레이어 7 취약점이 있습니다. 다음과 같이 Mod Security와 같은 자체 호스팅 또는 클라우드 기반을 사용할 수 있습니다.

    SQL 삽입 및 최신 PHP 프레임워크

    SQL 인젝션은 너무 일반적이고, 너무 쉽고, 너무 실망스럽고, 너무 위험해서 모든 최신 PHP 웹 프레임워크에는 대책이 내장되어 있습니다. 예를 들어 WordPress에는 $wpdb->prepare() 함수가 있지만 MVC 프레임워크를 사용하는 경우 모든 더러운 작업을 수행하므로 SQL 삽입 방지에 대해 생각할 필요조차 없습니다. 워드프레스에서는 진술을 명시적으로 준비해야 한다는 점이 조금 짜증나지만, 우리가 이야기하고 있는 것은 워드프레스입니다. 🙂

    어쨌든 제 요점은 현대 웹 개발자들은 SQL 주입에 대해 생각할 필요가 없으며 결과적으로 그 가능성조차 인식하지 못한다는 것입니다. 따라서 애플리케이션에 하나의 백도어를 열어두더라도(아마도 $_GET 쿼리 매개변수와 더티 쿼리를 시작하는 오래된 습관일 수 있습니다) 결과는 치명적일 수 있습니다. 따라서 시간을 들여 기초에 대해 더 깊이 파고드는 것이 항상 더 좋습니다.

    결론

    SQL 인젝션은 웹 애플리케이션에 대한 매우 고약한 공격이지만 쉽게 피할 수 있습니다. 이 기사에서 보았듯이 사용자 입력을 처리할 때 주의를 기울이고(그런데 SQL 인젝션이 사용자 입력을 처리하는 데 따르는 유일한 위협은 아닙니다.) 데이터베이스를 쿼리하는 것이 전부입니다. 즉, 우리는 항상 웹 프레임워크의 보안 작업을 수행하지 않으므로 이러한 유형의 공격을 인식하고 이에 속지 않는 것이 좋습니다.