본문 바로가기

24년 1학기 학교공부/네트워크웹보안

[NW] Web SQL Injection

목차

    728x90
    반응형
    SMALL
    2024학년도 1학기 충남대학교 장진수 교수님의 네트워크 및 웹 보안 수업 정리자료입니다.

     

     

     

     

    Summary

    • SQL Injection attack and how to launch this type of attacks

    • The fundament cause of the vulnerability?

    • How to defend against SQL Injection attacks?

    • Prepared Statement

     

     

     

     

    웹 어플리케이션의 상호작용

    일반적으로 웹 어플리케이션은 Browser, Web Application Server, Database 세 요소로 구성되며 위와 같이 동작한다. 사용자는 데이터베이스가 아닌 웹 서버에 직접적으로 상호작용한다. 만약 이 채널이 적절히 구현되어있지 않다면, 악성 유저가 데이터베이스를 공격할 수 있다.

     

    SQL injection은 데이터베이스에 문제를 발생시키는 공격이다.

     

     

     

    1. 유저에게 데이터를 입력받는다.

    사용자가 본인의 아이디와 비밀번호를 입력할 수 있는 form이 있다고 하자. 정보를 입력하고 "Submit" 버튼을 클릭하면 입력한 데이터를 담은 HTTP request가 전송된다.

     

    <form action="getdata.php" method="get">
      EID:      <input type="text" name="EID"><br>
      Password: <input type="text" name="Password"><br>
                <input type="submit" value="Submit">
    </form>

    위 코드는 웹사이트의 HTML source code이다. <form> 태그의 method 속성 값이 get으로 설정되어있으므로 GET request를 생성한다.

    http://www.example.com/getdata.php?EID=EID5000&Password=paswd123

    GET request의 URL에는 물음표('?') 마크 이후부터 파라미터들이 나타나있다. 각 파라미터는 "&" 기호로 연결되고, name=value 형태로 표현된다.

     

    request가 서버의 php 스크립트에 도달하면, 해당 HTTP request의 파라미터들은 요청의 종류에 따라 $_GET 혹은 $_POST 배열에 저장된다. 위 예시에서는 GET 요청을 사용하고 있으므로 아래와 같이 getdata.php 파일이 구성된다.

    <?php
        $eid = $_GET['EID'];
        $pwd = $_GET['Password'];
        echo "EID: $eid --- Password: $pwd\n";
    ?>

     

    💡 HTTPS의 경우 URL의 파라미터 포맷은 비슷하지만 데이터가 암호화된다.

     

     

     

    2. 웹 어플리케이션이 데이터베이스와 상호작용한다.

    PHP 프로그램이 데이터베이스를 사용하여 쿼리를 수행하기 위해 가장 먼저 데이터베이스 서버에 연결하는 절차가 필요하다. 아래 코드는 $dbhost, $dbuser, $dbpass, $dbname 네 개의 인자로 mysqli()를 생성하여 데이터베이스에 연결하는 코드이다.

    function getDB() {
        $dbhost = "localhost";
        $dbuser = "root";
        $dbpass = "seedubuntu";
        $dbname = "dbtest";
        
        // Create a DB connection
        $conn = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
        if ($conn -> connect_error) {
        	die("Connection failed : " . $conn->connect_error . "\n");
        }
        return $conn;
    }

     

    쿼리스트링 실행을 위해 데이터베이스에 전송해야한다. 이때 만들어진 SQL statement가 유저와 데이터베이스 사이의 채널이 되어, 데이터베이스를 향한 attack surface가 될 수 있다.

    /* getdata.php */
    <?php
    	$eid = $_GET['EID'];
    	$pwd = $_GET['Password'];
    
    	$conn = new mysqli ("localhost", "root", "seedubuntu", "dbtest");
        // Constructing SQL statement
    	$sql = "SELECT Name, Salary, SSN
    			FROM employee
    			WHERE eid= '$eid' and password=' $pwd'";
    
    	$result = $conn->query($sql);
    	if ($result) {
    		// Print out the result
    		while ($row = $result->fetch_assoc()) {
            	printf ("Name: %s -- Salary: %s -- SSN: %s\n",
    					$row["Name"], $row["Salary"), $row['SSN']);
    		}
    		$result->free();
    	}
    	$conn->close();
    ?>

     

     

     

     

    SQL Injection 수행

    유저에 의해 제공되는 모든 정보는 SQL statement의 일부분이 될 수 있다. 그렇다면 유저가 SQL statement 자체를 바꿔버릴 수도 있을까?

    개발자는 유저가 입력한 데이터를 위 SQL statement의 빈 칸에 사용하려고 했을 것이다. 하지만 이때, 유저가 ID로 EID5002' # 라고 입력하고, 비밀번호는 아무런 값을 입력하면 어떻게 될까?

    SQL 문법에서 주석처리를 할 때는 '#'으로 표시한다. 즉 위와 같은 코드는 곧 아래와 같이 변환되어 실행될 것이다.

    위 코드를 실행하게 되면, 비밀번호를 몰라도 EID가 EID5002인 사람의 name, Salary, SSN을 반환받을 수 있다. 이는 보안적으로 취약하다.

     

     

    이번에는 유저가 EID 조차 모를때, 데이터베이스의 모든 기록을 가져오는 SQL Injection을 수행해보자. 아래처럼 모든 레코드에 대해 참이 되도록 WHERE절에 조건자와 참 조건문을 추가해보자.

     

    해당 공격은 커멘드라인 도구를 사용하는 것이 더 편하며, 그래픽 사용자 인터페이스 없이 자동화 공격으로 만들기가 더 쉽다. cURL을 사용해서 웹페이지 대신 아래와 같이 명령을 보내보자.

     

    하지만 위 명령은 작동하지 않는다. HTTP request에서는, data에 첨부된 특수문자가 인코딩되지 않으면 잘못 해석될 여지가 있다. 때문에 위 URL에서 어포스트로피('), 공백, # 기호 등을 인코딩 값으로 바꾸어 명령을 보내면 다음과 같은 결과를 얻을 수 있다.

     

     

     

    Modify Database

    SQL statement에 UPDATE 혹은 INSERT INTO와 같은 문법이 포함되는 경우, database를 수정할 수 있다.

    위 그림과 같이 비밀번호를 변경하는 form을 생각해보자. 유저가 EID, 현재 비밀번호, 새 비밀번호를 입력한 뒤 Submit 버튼을 클릭하면, HTTP POST request가 서버측 스크립트인 changepasswd.php로 전송된다. changepasswd.php는 유저의 비밀번호를 변경하기 위해 UPDATE statement를 수행한다.

    /* changepasswd.php */
    <?php
    	$eid = $_POST['EID'];
    	$oldpwd = $_POST['OldPassword'];
    	$newpwd = $_POST['NewPassword'];
    
    	$conn = new mysqli ("localhost", "root", "seedubuntu", "dbtest");
    	$sql = "UPDATE employee
    			SET password=' $newpwd'
    			WHERE eid= '$eid' and password=' $oldpwd'";
    	$result = $conn->query($sql);
    	$conn->close();
    ?>

     

    EID가 EID5000, 비밀번호가 paswd123인 Alice라는 유저가 있다. 이 유저가 위 input form 중 New Password 칸에 paswd456', salary=100000 # 이라고 입력한다고 생각해보자. 해당 입력값은 php문에서 $newpwd 변수에 저장되고, SQL statement는 아래와 같이 구성된다.

    위 SQL문을 실행하면 EID가 EID5000이고 password가 paswd123과 일치하는 유저의 경우 비밀번호를 paswd456으로 변경하고, 추가적으로 salary 값도 100000으로 변경할 수 있다.

     

    만약 EID만 알고 password를 알지 못하는 상대의 정보를 변경하고 싶을 땐, 다음과 같이 입력할 수 있다.

    EID 입력 값 끝에 # 기호가 붙음으로써, 이후에 비밀번호를 확인하는 SQL문이 주석처리되므로 EID만 검증 후 데이터베이스 변경이 가능하다.

     

     

     

    Multiple SQL Statements

    input 칸에 문제를 일으킬 수 있는 값을 입력해 넣을수는 있어도 기존 SQL statement 내의 요소들은 변경할 수 없기 때문에, 위와 같은 방법의 SQL Injection이 일으킬 수 있는 문제는 한정적이다.

     

    하지만, 만약 입력값에 임의의 SQL statement를 추가로 넣어 실행시킬 수 있다면 그 위험성은 더욱 커진다.

     

    예를들어, EID와 password로 정보를 얻을 수 있는 form이 있을 때 EID input에 a'; DROP DATABASE dbtest; # 라고 입력했다고 하자. 해당 입력값을 반영한 SQL statement는 아래와 같이 구성될 것이다.

     

    지금까지 포스팅 내에서 다뤘던 MySQL의 경우, 위 공격은 통하지 않는다. PHP의 mysqli extension이 기본적으로 query() 메서드가 여러개의 쿼리를 실행하도록 허용하지 않기 때문이다.

     

    실제로 코드를 실행해보면, 위와 같은 에러 메세지가 출력된다.

     

    만약 여러개의 SQL statement를 실행하고 싶을 때에는 $mysqli->multi_query() 라는 메서드를 사용할 수 있겠지만, 위와 같은 보안 취약성의 이유로 권장하지 않는다.

     

     

     

     

    SQL Injection의 근본적인 원인

    Wenliang Du. Computer & Internet Security 2nd edition

    data와 code를 섞는 것은 SQL Injection, XSS 공격, system() function 공격, format string 공격 등을 포함한 여러 공격들 및 취약점들의 원인이 된다.

     

     

     

     

    Countermeasures

    1.Filtering and Encoding Data

    사용자가 제공한 data를 code와 섞기 전에, 데이터를 검증한다. 코드로 해석될 여지가 있는 문자들을 필터링한다.

    특수문자들은 주로 SQL Injection 공격에 사용되므로, 이들을 제거하기 위해 인코딩한다.

     

    특수문자를 인코딩하는 것은 parser에게 인코딩된 글자들을 code가 아닌 data로서 다루라고 이야기하는 것과 같다. 인코딩 예시는 아래와 같다.

     

    PHP mysqli extension은 mysqli::real_escape_string()라는 메서드를 갖고있다. 해당 메서드는 SQL 내에서 특별한 의미를 갖는 글자들을 인코딩할 때 사용한다. 아래 코드와 같이 사용할 수 있다.

    / getdata_encoding.php */
    <?php
    	$conn = new mysqli ("localhost", "root", "seedubuntu", "dbtest");
    	$eid = $mysqli->real_escape_string($_GET['EID']);
    	$pwd = $mysqli->real_escape_string($_GET['Password'];
    	$sql "SELECT Name, Salary, SSN =
    		FROM employee
    		WHERE eid= '$eid' and password=' $pwd'";

     

     

     

    2. Prepared Statement

    SQL Injection의 근본적인 원인은 데이터와 코드가 섞이는 것이므로, 데이터와 코드를 분리함으로써 근본적으로 해결할 수 있다.

     

    데이터베이스 서버에 코드와 데이터를 별도의 채널로 보낸다. 이 방법은 데이터베이스 서버가 데이터 채널로부터 어떠한 코드도 가져오지 않음을 알게한다.

     

    prepared statement란, 더 좋은 성능으로 동일하거나 유사한 SQL문을 반복적으로 실행할 수 있게 해주는 최적화된 기능을 말한다. prepared statement를 사용하여, 매개변수가 아직 지정되지 않은 SQL statement template을 데이터베이스에 보낸다. 데이터베이스는 이 SQL statement template을 분석하고 컴파일하고, 쿼리 최적화를 진행한 후 결과를 실행하지 않고 저장한다. 추후, prepared statment에 데이터를 바인딩한다.

     

     

    $conn = new mysqli ("localhost", "root", "seedubuntu", "dbtest");
    $sql = "SELECT Name, Salary, SSN FROM employee WHERE eid= '$eid' and password=' $pwd'";
    $result = $conn->query($sql);

    위와 같이 코드와 데이터가 섞여 취약한 코드가 있다. 이를 prepared statement를 사용하여 아래와 같은 코드로 다시 작성해볼수 있다.

    $conn = new mysqli ("localhost", "root", "seedubuntu", "dbtest");
    $sql = "SELECT Name, Salary, SSN FROM employee WHERE eid=? and password=?";
    if ($stmt = $conn->prepare($sql)) {
    	$stmt->bind_param("ss", $eid, $pwd);
    	$stmt->execute();
    
    	$stmt->bind_result($name, $salary, $ssn);
    	while ($stmt->fetch()) {
    		printf ("%s %s %s\n", $name, $salary, $ssn);
    	}
    }

    위 코드를 보면, 3번째 줄에서 코드를 보내고, 4번째 줄에서 데이터를 보낸 후, 5번째 줄에서 실행한다. 이렇게 데이터와 코드를 분리하여 SQL Injection을 방지할 수 있다.

     

     

    prepared statement가 안전한 이유는 무엇일까?

    코드채널을 통해서는 신뢰할 수 있는 코드가 전송되고, 데이터 채널을 통해서는 신뢰할 수 없는 사용자가 제공한 데이터가 전송된다. 이를 통해 데이터베이스는 코드와 데이터의 경계를 명확하게 알 수 있다.

    또한, 데이터 채널에서 수신되는 데이터들은 구문분석되지 않는다. 즉, 공격자가 데이터에 코드를 입력하더라도 이는 절대로 코드로 취급되지 않으며, 구문분석되지 않으므로 공격이 되지 않는다.

     

     

     

     

     

    Blind SQL Injection

    몇몇 HTTP respond는 결과를 포함하지 않는다. Conditional Response, SQL error, Time delays 등의 기술들은 공격을 수행할 수 있게 만들어준다.

     

    1. Conditional Response

     

     

    2. SQL error

     

     

    3. Time Delays

     

     

    728x90
    반응형
    LIST