SQL Injection 취약점(2) : 실습으로 배우는 웹 보안 강화 방법

안녕하세요, 웹 보안에 관심 있는 분들을 위해 “SQL Injection” 취약점에 대한 2편의 포스팅을 준비했습니다. 이번 포스팅에서는 이전에 다뤘던 내용을 보완하면서 더욱 깊이 있는 실습과 보안 강화 방법에 대해 다룰 예정입니다. SQL Injection은 웹 보안에서 가장 흔하게 발생하는 취약점 중 하나이며, 이를 통해 해커들은 웹 애플리케이션을 공격하여 데이터베이스를 조작하거나 민감한 정보를 탈취할 수 있습니다.

 

sql injection

1. SQL Injection 실습 환경 구축

  • 이번 포스팅에서 사용할 SQL Injection 실습 환경을 구축하는 방법을 안내합니다.
  • Docker와 같은 도구를 이용하여 웹 애플리케이션과 데이터베이스를 쉽게 설정하는 방법을 설명합니다.

    1. Docker 설치

    Docker는 가상 컨테이너 기반의 오픈소스 플랫폼으로, 컨테이너를 사용하여 애플리케이션을 더 쉽고 효율적으로 실행, 배포 및 관리할 수 있습니다. 먼저 Docker를 설치해야 합니다. 설치 방법은 Docker 공식 사이트(https://www.docker.com/)에서 제공하는 가이드를 따르면 됩니다.

    2. 웹 애플리케이션 컨테이너 구성

    SQL Injection 실습을 위해 간단한 웹 애플리케이션을 구성합니다. 예를 들어 PHP와 MySQL을 사용하는 웹 애플리케이션을 구축하겠습니다.

    가. 웹 애플리케이션 코드 작성

    예시로 index.php 파일을 작성하여 간단한 로그인 폼을 만듭니다. 이 폼은 사용자가 입력한 아이디와 비밀번호를 바탕으로 MySQL 데이터베이스와 연동하여 로그인을 시도하는 기능을 가집니다.

    <!-- index.php --> <!DOCTYPE html> <html> <head> <title>Login Page</title> </head> <body> <h2>Login</h2> <form action="login.php" method="post"> Username: <input type="text" name="username"><br> Password: <input type="password" name="password"><br> <input type="submit" value="Login"> </form> </body> </html>

     

    나. Docker 컨테이너 생성 파일 작성

    Docker 컨테이너를 생성하기 위해 Dockerfile을 작성합니다.

    # Dockerfile
    FROM php:7.4-apacheCOPY index.php /var/www/html/

    다. 웹 애플리케이션 컨테이너 빌드 및 실행

    터미널에서 Dockerfile이 있는 디렉토리로 이동한 후, 다음 명령어를 실행하여 웹 애플리케이션 컨테이너를 빌드하고 실행합니다.

    docker build -t my-webapp .
    docker run -d -p 8080:80 my-webapp

     

    3. 데이터베이스 컨테이너 구성

    SQL Injection 실습을 위해 MySQL 데이터베이스를 구성합니다.

    가. MySQL Docker 컨테이너 생성 및 실행

    터미널에서 다음 명령어를 실행하여 MySQL Docker 컨테이너를 생성하고 실행합니다. 여기서 my-secret-password는 MySQL의 루트 패스워드로 사용할 비밀번호입니다.

    docker run -d -p 3306:3306 --name my-mysql -e MYSQL_ROOT_PASSWORD=my-secret-password mysql:5.7

    4. 웹 애플리케이션과 데이터베이스 연동

    웹 애플리케이션과 MySQL 데이터베이스를 연동해야 합니다. 웹 애플리케이션의 login.php 파일을 수정하여 데이터베이스와 연동하는 기능을 추가합니다.

    <!-- login.php -->

    <?php $servername = "localhost"; $username = "root"; $password = "my-secret-password"; // MySQL 컨테이너 실행 시 설정한 루트 비밀번호 $dbname = "my_database";$conn = new mysqli($servername, $username, $password, $dbname);if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); }if ($_SERVER["REQUEST_METHOD"] == "POST") { $username = $_POST["username"]; $password = $_POST["password"]; $sql = "SELECT * FROM users WHERE username='$username' AND password='$password'"; $result = $conn->query($sql); if ($result->num_rows > 0) { echo "Login success!"; } else { echo "Login failed!"; } } $conn->close(); ?>

    5. 실습 환경 확인

    웹 애플리케이션과 데이터베이스가 성공적으로 구성되었는지 확인합니다. 웹 브라우저에서 http://localhost:8080으로 접속하여 로그인 폼이 표시되는지 확인하고, 유효한 아이디와 비밀번호를 입력하여 로그인을 시도해보세요. 이후 웹 애플리케이션과 MySQL 데이터베이스가 정상적으로 연동되었는지 확인합니다

2. UNION 기반 SQL Injection 실습

  • UNION 기반 SQL Injection에 대해 더 깊이 있는 실습을 진행합니다.
  • UNION 쿼리를 활용하여 데이터베이스에서 정보를 추출하는 방법을 자세히 설명합니다.UNION 기반 SQL Injection은 웹 애플리케이션에서 가장 흔히 발생하는 SQL Injection 공격 중 하나입니다. 이 공격은 여러 개의 SELECT 문을 결합하여 원하는 데이터를 추출하는데 사용됩니다. 이를 실습하기 위해 웹 애플리케이션과 데이터베이스가 정상적으로 연동되어 있다고 가정하고 진행하겠습니다.


    1. UNION 기반 SQL Injection 이해

    UNION 기반 SQL Injection은 여러 SELECT 문의 결과를 합치는 UNION 연산자를 이용하여 SQL 쿼리를 조작하는 방법입니다. 일반적으로 웹 애플리케이션은 사용자의 입력을 받아와서 SQL 쿼리를 생성합니다. 이때 사용자 입력을 충분히 검증하지 않으면 공격자가 악의적인 UNION 쿼리를 삽입하여 데이터베이스 정보를 탈취할 수 있습니다.

    2. UNION 기반 SQL Injection 실습

    가. 로그인 폼에 입력 가능한 아이디란에 다음과 같이 입력합니다. (공격 시나리오를 위해 사용자 입력이라고 가정)

    ' UNION SELECT username, password FROM users; --

    이때, 입력한 아이디가 SQL 쿼리에 삽입되는 구문은 다음과 같을 것입니다.

    SELECT * FROM users WHERE username='' UNION SELECT username, password FROM users; --' AND password='';
    

     

    나. SQL 쿼리를 조작하여 데이터베이스에서 사용자명(username)과 비밀번호(password)를 추출합니다.

    3. 실습 예시 코드

    이제 실습을 위한 예시 코드를 소개하겠습니다. 실제로는 실제 웹 애플리케이션과 데이터베이스가 연동된 환경에서 이러한 공격을 시도해야 합니다.

    
    
    <!-- login.php -->
    <?php
    $servername = "localhost";
    $username = "root";
    $password = "my-secret-password"; //
    2024
     MySQL 컨테이너 실행 시 설정한 루트 비밀번호
    $dbname = "my_database";$conn = new mysqli($servername, $username, $password, $dbname);if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
    }if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username = $_POST["username"];
    $password = $_POST["password"];
    
    
    
    
    // 웹 애플리케이션은 아래와 같이 SQL 쿼리를 생성할 수 있습니다.
    $sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";
    $result = $conn->query($sql);
    
    if ($result->num_rows > 0) {
    echo "Login success!";
    } else {
    echo "Login failed!";
    }
    }
    
    $conn->close();
    ?>

    위 코드에서 주의할 점은 사용자로부터 입력을 받아와서 직접 SQL 쿼리를 생성하는 부분입니다. 이 부분을 충분히 검증하지 않으면 UNION 기반 SQL Injection과 같은 취약점이 발생할 수 있습니다.

    4. 실습 주의사항

    실습은 개인용 로컬 환경에서 진행하거나 가상 환경에서 도커 컨테이너 등을 사용하여 격리된 환경에서 진행하는 것을 권장합니다. 또한, 실제 웹 애플리케이션에 대해 공격을 시도하기 전에 웹 애플리케이션과 데이터베이스가 연동되어 있음을 확인하고, 허가 받지 않은 공격은 법적인 문제가 발생할 수 있으므로 반드시 자신의 서버 또는 허가받은 테스트 환경에서만 실습해야 합니다.

    5. 대응 방안

    UNION 기반 SQL Injection을 방지하기 위해 입력 유효성 검사를 강화하는 것이 중요합니다. 사용자 입력을 SQL 쿼리에 바로 삽입하는 것이 아니라, 파라미터화된 쿼리 또는 ORM(Object-Relational Mapping)을 사용하여 데이터베이스와 상호작용해야 합니다. 또한 웹 방화벽을 활용하여 악의적인 UNION 쿼리를 탐지하고 차단하는 것도 효과적인 대응 방안 중 하나입니다.

3. 블라인드(SQL 쿼리 결과 간접 확인) SQL Injection 실습

  • 블라인드(SQL 쿼리 결과를 직접 확인하지 않고 간접적인 방법으로 정보를 노출시키는 방법을 실습합니다.
  • 블라인드 SQL Injection을 통해 데이터베이스 정보를 탈취하는 과정을 단계별로 안내합니다.블라인드 SQL Injection은 SQL 쿼리의 결과를 직접 확인하지 않고 간접적인 방법으로 정보를 노출시키는 공격 기법입니다. 이를 통해 데이터베이스 정보를 탈취할 수 있습니다.

    1. 블라인드(SQL 쿼리 결과 간접 확인) SQL Injection 이해

    일반적으로 웹 애플리케이션은 SQL 쿼리의 결과를 웹 페이지에 표시하여 사용자에게 보여줍니다. 하지만 공격자가 SQL 쿼리의 결과를 직접 확인하지 못할 때도 있습니다. 예를 들어, 결과가 웹 페이지에 출력되지 않고, 로그인 성공/실패 메시지 등으로만 표시될 수 있습니다. 이런 경우 공격자는 결과를 직접 확인할 수 없지만, 간접적인 방법을 사용하여 데이터베이스 정보를 추출합니다.

    2. 블라인드(SQL 쿼리 결과 간접 확인) SQL Injection 실습

    가. 로그인 폼에 입력 가능한 아이디란에 다음과 같이 입력합니다. (공격 시나리오를 위해 사용자 입력이라고 가정)

    ' AND 1=1; --

    이때, 입력한 아이디가 SQL 쿼리에 삽입되는 구문은 다음과 같을 것입니다.

    SELECT * FROM users WHERE username='' AND password='' AND 1=1; --' AND password='';

    나. 위 SQL 쿼리의 결과는 항상 참(True)일 것이므로 로그인은 항상 성공으로 간주됩니다.

    다. 이제 공격자는 AND 연산자의 조건을 조절하여 원하는 데이터를 노출시킬 수 있습니다. 예를 들어, 데이터베이스의 사용자 테이블의 구조를 파악하거나, 사용자의 아이디(username)를 추출하려고 할 수 있습니다.

    3. 실습 예시 코드

    이제 실습을 위한 예시 코드를 소개하겠습니다. 실제로는 실제 웹 애플리케이션과 데이터베이스가 연동된 환경에서 이러한 공격을 시도해야 합니다.

    <!-- login.php -->
    <?php
    $servername = "localhost";
    $username = "root";
    $password = "my-secret-password"; // MySQL 컨테이너 실행 시 설정한 루트 비밀번호
    $dbname = "my_database";
    
    $conn = new mysqli($servername, $username, $password, $dbname);
    
    if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
    }
    
    if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username = $_POST["username"];
    $password = $_POST["password"];
    
    // 웹 애플리케이션은 아래와 같이 SQL 쿼리를 생성할 수 있습니다.
    $sql = "SELECT * FROM users WHERE username='$username' AND password='$password' AND 1=1";
    $result = $conn->query($sql);
    
    if ($result->num_rows > 0) {
    echo "Login success!";
    } else {
    echo "Login failed!";
    }
    }
    
    $conn->close();
    ?>
    
    
    
    
    <!-- login.php -->
    <?php
    $servername = "localhost";
    $username = "root";
    $password = "my-secret-password"; // MySQL 컨테이너 실행 시 설정한 루트 비밀번호
    $dbname = "my_database";$conn = new mysqli($servername, $username, $password, $dbname);if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
    }if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username = $_POST["username"];
    $password = $_POST["password"];
    
    
    
    
    // 웹 애플리케이션은 아래와 같이 SQL 쿼리를 생성할 수 있습니다.
    $sql = "SELECT * FROM users WHERE username='$username' AND password='$password' AND 1=1";
    $result = $conn->query($sql);
    
    if ($result->num_rows > 0) {
    echo "Login success!";
    } else {
    echo "Login failed!";
    }
    }
    
    $conn->close();
    ?>

    4. 실습 주의사항

    실습은 개인용 로컬 환경에서 진행하거나 가상 환경에서 도커 컨테이너 등을 사용하여 격리된 환경에서 진행하는 것을 권장합니다. 또한, 실제 웹 애플리케이션에 대해 공격을 시도하기 전에 웹 애플리케이션과 데이터베이스가 연동되어 있음을 확인하고, 허가 받지 않은 공격은 법적인 문제가 발생할 수 있으므로 반드시 자신의 서버 또는 허가받은 테스트 환경에서만 실습해야 합니다.

    5. 대응 방안

    블라인드 SQL Injection을 방지하기 위해서는 입력 유효성 검사를 강화하는 것이 중요합니다. 또한 데이터베이스의 오류 메시지를 웹 페이지에 표시하지 않도록 설정하고, 개발자에게 오류 로그를 보고받아 시스템 상태를 파악하는 것이 도움이 됩니다. 또한 웹 방화벽을 활용하여 악의적인 입력을 차단하는 것도 효과적인 대응 방안 중 하나입니다.

4. 대응 방안: 입력 유효성 검사 강화

  • SQL Injection을 방지하기 위해 입력 유효성 검사를 어떻게 강화할 수 있는지에 대해 설명합니다.
  • 정규식과 필터링 기법을 활용하여 악성 입력을 차단하는 방법을 소개합니다.

    입력 유효성 검사를 강화하는 것은 SQL Injection과 같은 보안 취약점을 방지하는 중요한 대응 방안 중 하나입니다. 이를 통해 악의적인 사용자 입력을 필터링하고, 안전한 형태로 변환하여 데이터베이스에 전달함으로써 보안을 강화할 수 있습니다. 아래는 입력 유효성 검사 강화를 위한 상세한 절차와 예시 코드를 설명합니다.

    1. 파라미터화 쿼리 사용

    SQL Injection을 방지하기 위해 가장 좋은 방법 중 하나는 파라미터화 쿼리를 사용하는 것입니다. 파라미터화 쿼리는 SQL 문에 사용자 입력을 직접 삽입하는 대신, 쿼리의 매개 변수에 값을 전달하는 방식으로 작성됩니다. 이렇게 하면 데이터베이스는 입력값을 데이터로 처리하고 SQL 쿼리의 구문을 미리 컴파일하여 보다 안전하게 실행할 수 있습니다.

    예시 (PHP + MySQLi):
    
    
    
    // 웹 애플리케이션 코드
    $servername = "localhost";
    $username = "root";
    $password = "my-secret-password"; // MySQL 컨테이너 실행 시 설정한 루트 비밀번호
    $dbname = "my_database";
    
    $conn = new mysqli($servername, $username, $password, $dbname);
    
    if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
    }
    
    if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username = $_POST["username"];
    $password = $_POST["password"];
    
    // 파라미터화 쿼리 사용
    $sql = "SELECT * FROM users WHERE username=? AND password=?";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("ss", $username, $password);
    $stmt->execute();
    
    $result = $stmt->get_result();
    
    if ($result->num_rows > 0) {
    echo "Login success!";
    } else {
    echo "Login failed!";
    }
    }
    
    $conn->close();

    2. 입력 필터링 (Input Filtering)

    입력 필터링은 사용자 입력을 특정한 패턴이나 형식으로 제한하는 과정입니다. 예를 들어, 정규식(Regular Expression)을 사용하여 특정 문자나 문자열 패턴을 차단하거나 허용할 수 있습니다.

    
    
    // 웹 애플리케이션 코드
    if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username = $_POST["username"];
    $password = $_POST["password"];
    
    // 입력 필터링을 통해 허용되지 않은 문자 제거
    $username = preg_replace("/[^a-zA-Z0-9]/", "", $username);
    $password = preg_replace("/[^a-zA-Z0-9]/", "", $password);
    
    // 이후 파라미터화 쿼리 사용
    // ...
    }

    3. 데이터 타입 검사

    입력 값을 데이터 타입에 맞게 검사하는 것도 중요합니다. 예를 들어, 숫자를 요구하는 필드에 문자열이 입력되는 것을 방지하거나, 정수형 입력을 요구하는 필드에 부동소수점 숫자가 입력되는 것을 방지해야 합니다.

    4. 최소/최대 길이 제한

    입력 값의 최소/최대 길이를 제한하여 공격자가 너무 긴 입력을 사용하여 데이터베이스를 공격하는 것을 방지할 수 있습니다.

    5. 웹 방화벽 사용

    웹 방화벽은 악의적인 입력을 탐지하고 차단하는 데에 유용한 보안 도구입니다. 웹 방화벽을 통해 SQL Injection과 같은 웹 공격을 감지하여 보안을 강화할 수 있습니다.

    입력 유효성 검사 강화는 보안을 강화하는 데에 중요한 역할을 합니다. 하지만 단독으로 모든 보안 취약점을 방지할 수는 없으므로 다른 대응 방안과 함께 사용하는 것이 좋습니다. 또한 보안 업데이트를 항상 적용하고 주기적인 보안 검토를 수행하여 웹 애플리케이션의 보안을 유지하는 것이 중요합니다.

    마무리

    SQL Injection은 여전히 많은 웹 애플리케이션에서 취약점으로 남아 있습니다. 이번 포스팅을 통해 더욱 깊이 있는 실습과 보안 강화 방법을 배워보았습니다. 개발자들은 보안에 대한 경각심을 가지고 적절한 대응 방안을 적용하여 사용자 데이터를 안전하게 보호해야 합니다. 웹 보안에 대한 이해와 실습을 통해 더욱 안전한 웹 애플리케이션을 개발하는 데 기여해봅시다.

댓글 남기기