펜테스트짐


훈련을 통해 당신의 실력을 향상시켜보세요.



SQL 인젝션 기초

모두 67명의 회원님이 완료했어요.


이번 훈련에서는 SQL 인젝션에 대해 알아봅니다. SQL 인젝션이 어떻게 동작하고 그 종류에는 어떠한 것들이 있는지 학습할 수 있습니다.





SQL Injection이란?


SQL Injection(SQL 주입)은 웹 애플리케이션이 백엔드에서 구동 중인 데이터베이스에 질의를 하는 과정에 사용되는 SQL 쿼리를 조작하여 데이터베이스를 대상으로 공격자가 의도한 악의적인 행위를 할 수 있는 Injection 기반의 웹 취약점입니다. 공격자가 SQL Injection 공격에 성공하게 되면 조직 내부의 민감한 데이터나 개인 정보를 획득할 수 있으며, 심각한 경우에는 조직의 데이터 전체를 장악하거나 완전히 손상시킬 수 있습니다. 


SQL Injection의 동작 원리


우리가 흔히 사용하는 웹 애플리케이션들은 대부분 아래의 그림과 같이 데이터베이스를 이용해 서비스 제공에 필요한 데이터를 조회하거나 저장, 수정, 삭제합니다. 데이터베이스는 웹 애플리케이션과 동일한 서버에 구축되거나 물리적/논리적으로 분리된 서버에 구축되기도 합니다. SQL Injection의 동작 원리를 알기 위해 사용자의 요청이 어떤 과정을 거쳐 서버측의 데이터베이스에 작업을 수행하는지 아래의 그림을 통해 살펴봅시다. 웹 서버측 기술 훈련에서도 이와 유사한 내용을 확인할 수 있습니다.


정상적인 로그인 요청 수행 과정


  1. 사용자가 웹 애플리케이션에서 데이터베이스와 연동된 기능을 사용할 때 사용자에 의해 입력된 데이터(또는 웹 애플리케이션에 의해 셋팅된 데이터)는 HTTP 요청(GET 매개변수 또는 POST Body 매개변수 등)을 통해 웹 애플리케이션으로 전송됩니다.
  2. 전송된 데이터는 서버측 스크립트에 의해 미리 정의 및 저장되어있던 SQL 쿼리 안의 지정된 위치로 대입됩니다.
  3. 서버측 스크립트는 데이터베이스에 완성된 SQL 쿼리 실행을 요청합니다.
  4. 데이터베이스는 SQL 쿼리를 실행한 후 조건에 부합하는 레코드셋이나 Boolean 값(참/거짓) 또는 오류를 반환해줍니다. 
  5. 서버측 스크립트는 데이터베이스에서 반환된 데이터에 맞게 HTTP 응답 메시지를 만들어 사용자에게 회신합니다.


그럼 위의 로그인 요청 수행 과정에서 SQL Injection 공격이 수행되는 상황을 살펴봅시다. 취약하게 구현된 로그인 기능은 SQL Injection을 통해 인증을 우회하고 다른 사용자의 계정으로 로그인할 수 있습니다. 


SQL Injection을 통한 로그인 우회


공격자는 아이디 입력칸에 다음과 같이 입력합니다. 

admin' or '1'='1

패스워드 입력칸에는 원하는 임의의 문자열을 입력합니다. 이 예제에서는 anything을 입력했다고 가정합니다.

공격자가 입력한 아이디와 패스워드의 값은 HTTP 요청을 통해 서버로 전달되고 최종적으로 다음과 같은 쿼리를 만들게 됩니다.

SELECT * FROM users WHERE id='admin' or '1'='1' AND password='anything';

이 SQL 쿼리는 웹 애플리케이션에 admin이라는 사용자 계정이 존재한다면 패스워드가 일치하는지 여부와는 상관없이 admin 계정으로 로그인하게 됩니다. 바로 SQL 쿼리의 논리 연산을 조작하여 무조건 참(TRUE)이 되었기 때문입니다.

그럼 왜 참(TRUE)이 되었는지 논리 연산 과정을 살펴봅시다.


보시는 바와 같이 공격자가 조작한 SQL 쿼리의 WHERE절에는 OR, AND 두 개의 논리 연산자가 있고, 각 논리 연산자의 좌우로 조건식이 배치되어 다음과 같은 총 3개의 조건식이 있습니다.     

구분내용조건식의 참/거짓 여부
조건식1id='admin' TRUE     (admin 계정이 있다고 가정하므로 '참'입니다.)
조건식2'1'='1'TRUE     (누가 봐도 '참'입니다.)
조건식3password='anything'FALSE    (admin 계정의 틀린 패스워드이므로 '거짓'입니다.)

자! SQL 쿼리의 논리 연산을 해볼까요? AND 연산자의 우선순위가 OR 연산자보다 높다는 것에 유념하세요. 

이해가 되시나요? 연산자 우선순위에 따라 조건식2와 조건식3의 AND 연산이 먼저 수행되고, 그 결과와 조건식1이 OR 연산을 수행하면 WHERE절 논리 연산의 결과는 최종적으로 TRUE가 됩니다. 즉, admin 계정의 패스워드를 알 수 없어도 admin 계정으로 로그인에 성공하는 것이죠.    

공격자가 사용할 쿼리는 개발자가 작성해둔 쿼리 구문에 종속됩니다. 개발자가 다음과 같이 쿼리를 작성했다면 공격자는 올바른 구문을 만들기 위해 괄호도 고려해야 합니다.

SELECT *
FROM users
WHERE ((id='spike') AND (password='ilovejulia'));

원래 쿼리에 있던 여는 괄호와 닫는 괄호 사이에 공격 구문이 주입되므로 짝에 맞도록 다음과 같은 구문을 만들어야 합니다.

admin') or ('1'='1


SQL Injection은 위에서 설명한 인증 우회 목적이 아닌 공격자가 의도한 새로운 데이터베이스 명령 문장을 실행할 수도 있습니다. 다음은 웹 애플리케이션 게시판에서 게시글을 조회하는 일반적인 과정입니다.  


정상적인 게시글 조회 요청 수행 과정


게시판 기능에서 사용자가 특정 게시글(게시글 고유번호가 4215로 가정)을 클릭하면 해당 게시글의 내용을 보여주기 위해 아래의 SQL 쿼리가 사용됩니다.

SELECT brd_no, subject, content, author, post_date FROM board WHERE brd_no=4215;

공격자는 다음과 같이 SQL 쿼리의 종료를 알리는 기호인 세미콜론(;)과 본인이 의도한 악의적인 SQL 문장을 이용하여 "brd_no"라는 GET 매개변수를 조작합니다. 9999는 존재하지 않는 게시글의 번호로 가정합니다.

/board/view.php?brd_no=9999;Drop%20table%20users;

위 요청을 통해 전달된 "brd_no" 매개변수의 값은 게시글을 조회하기 위해 미리 정의되어있던 SQL 쿼리에 포함되고 SQL 쿼리는 데이터베이스에서 실행됩니다. 하지만 실행되는 SQL 쿼리는 한 개가 아닙니다. 의도적으로 추가한 세미콜론(;)에 의해 SQL 쿼리는 두 개의 쿼리로 나뉘어졌고 원래 SQL 쿼리와 공격자가 추가한 악의적인 SQL 쿼리가 모두 하나의 트랜잭션에서 실행되게 됩니다.

SELECT brd_no, subject, content, author, post_date FROM board WHERE brd_no=9999;Drop table users;

만일 웹 애플리케이션이 사용하는 데이터베이스 계정이 테이블 삭제 권한을 보유하고 있다면 Drop table users; 명령에 의해 users 테이블은 삭제되고, 서비스에 치명적인 손상을 주게 됩니다.


SQL Injection을 통한 악의적인 명령 실행


이와 같이 SQL Injection은 공격자가 조작할 수 있는 HTTP 매개변수 등의 값이 안전하지 않은 방식, 즉 SQL 쿼리로 해석될 수 있는 홑따옴표('), 세미콜론(;) 등의 문자들을 허용하는 방식으로 처리하여 SQL 쿼리에 포함시키고, 이 쿼리가 데이터베이스에서 실행될 때 공격자는 악의적인 SQL 쿼리를 만들어 데이터베이스에 의도치 않은 행위를 하게 할 수 있습니다. 

 

SQL Injection 유형


SQL Injection은 데이터베이스에 접근하는 방법이나 공격 기법에 따라 일반적으로 아래와 같은 유형으로 분류할 수 있습니다.


오류(Error) 기반 SQL Injection

잘못된 문법이나 자료형 불일치 등에 의해 웹 브라우저에 표시되는 데이터베이스 오류를 기반으로 수행되는 공격 기법입니다. 공격자는 의도적인 오류를 유발시키고 해당 오류 정보를 바탕으로 데이터베이스명, 테이블, 컬럼 정보 등을 파악할 수 있게 됩니다. 

  더 읽어보세요.


UNION 기반 SQL Injection

공격자가 의도한 SQL 쿼리를 UNION 연산자를 이용하여 기존 SQL 쿼리에 덧붙이고 기존 SQL을 무효화시켜 원하는 내부 데이터를 절취할 수 있는 공격 기법입니다.

  더 읽어보세요.


Blind SQL Injection

웹 브라우저 화면상에 데이터베이스 오류 정보나 데이터가 직접적으로 노출되지 않을 때 이용되는 기법으로 공격자가 육안으로 확인할 수 있는 데이터베이스 오류나 데이터가 없다는 점에 착안되어 Blind SQL Injection 혹은 추론 기반 SQL Injection이라고 불립니다. 공격자는 공격이 성공했는지 판단하기 위해 미세한 서버의 응답과 동작 방식까지 관찰하고 공격 성공의 단서로서 활용해야 합니다. 다음은 일반적으로 사용되는 기법입니다.


Boolean 기반 

SQL 쿼리의 결과가 참 또는 거짓이냐에 따라 웹 애플리케이션의 응답이 다른 경우 사용됩니다. 오로지 참/거짓만을 판단할 수 있는 서버의 응답만으로 공격을 수행하므로 공격자는 논리적으로 문제가 없는 공격 쿼리를 작성하기 위해 많은 시간과 노력을 할애해야 합니다. 

 

Time 기반

SQL 쿼리의 결과가 참 또는 거짓이냐에 따라 서버의 응답 시간을 제어할 수 있을 때 사용됩니다. MySQL의 Sleep(5000);와 같이 밀리초의 단위의 시간 동안 대기하는 SQL 명령을 사용해 공격의 성공 여부를 판단합니다. 공격자가 지정한 시간만큼 응답이 지연된다면 SQL Injection에 취약한 것으로 간주할 수 있습니다. 


  더 읽어보세요.


SQL Injection의 영향


SQL Injection 공격은 인증을 우회하는 등의 웹 애플리케이션의 로직을 파괴하거나 조직의 데이터베이스 정보나 개인 정보, 또는 의도적으로 숨긴 데이터의 유출 및 데이터의 손상, 손실을 초래할 수 있습니다. 또한 MSSQL과 같은 일부 데이터베이스에서 제공되는 저장 프로시저(Stored Procedure)를 이용하면 원격 명령 실행 등에 노출되어 시스템의 손상까지 초래할 수 있습니다. 


SQL Injection 예방


SQL Injection을 예방하거나 방어하기 위한 다양한 방법이 존재합니다. 하지만 아직도 많은 개발자분들이 이런 다양한 방법들을 복합적으로 동원하지 않고 HTTP 요청을 통해 전달되는 데이터에서 SQL Injection에 사용되는 대표적인 문자들인 홑따옴표(')나 세미콜론(;) 등의 문자만 제거하면 충분하다고 생각하고 단편적인 보안 조치에 그치는 경우들이 있습니다. SQL Injection 공격을 안전하게 예방하기 위해서는 아래에 소개되는 방법들을 최대한 많이 적용하여 보안을 강화해야 합니다. 


입력값 검사

HTTP 요청을 통해 전달되는 사용자 데이터에 SQL 구문으로 해석될 수 있는 문자 또는 공격에 사용되는 SQL 구문들의 포함여부를 검사하고 포함시 요청을 차단하거나 해당 문자를 제거(또는 다른 문자로 대체)해야 합니다. 일부 SQL Injection 공격에 대해서는 효과가 있지만 이 방법에만 의존해서는 SQL Injection 공격을 안전하게 방어할 수 있습니다. 

  • SQL 기호: 홑따옴표('), 겹따옴표("), 세미콜론(;), 대시(-), 샵(#), 슬래시샵 (/*) 등
  • SQL 구문: SELECT, INSERT, UPDATE, DELETE, UNION, GROUP BY, HAVING, ORDER BY 등


저장 프로시저 사용

웹 애플리케이션에서 데이터베이스에 접속하여 어떤 작업을 수행할 때 저장 프로시저를 사용하는 방법입니다. 하지만 저장 프로시저 내에서도 SQL 쿼리가 사용되고, 이 SQL 쿼리가 저장 프로시저내에서 안전하지 않은 방식으로 처리된다면 이 또한 여전히 SQL Injection 공격에 노출되게 된다는 단점이 있습니다. 또한 저장 프로시저를 호출하는 명령에 매개변수로서 사용자 입력값이 안전하지 않은 방식으로 사용되는 경우에도 마찬가지입니다. 공격자는 저장 프로시저 실행 명령을 강제로 종료하고 임의의 저장 프로시저(대표적으로 xp_cmdshell이 있음)를 호출하도록 명령을 조작할 수 있습니다. 

다음은 sp_boardList 저장 프로시저 실행 명령을 종료시키고 뒤에 xp_cmdshell 저장 프로시져를 통해 C드라이브의 파일과 디렉토리를 출력하는 쉘 명령을 실행하는 경우입니다.

exec sp_boardList '30', 'test'; exec master..xp_cmdshell 'dir C:\'-- 


매개변수화된 쿼리 적용

매개변수화된 쿼리는 Prepared Statement라고 불리며, 위키백과의 정의를 인용하자면 데이터베이스에서 사용되는 동일하거나 유사한 SQL 쿼리를 효율적으로 반복적으로 실행하기 위해 사용되는 기능입니다. 쉽게 말해서 매개변수화된 쿼리는 자주 사용되는 SQL 쿼리를 데이터베이스에 준비해두었다가, 해당 SQL 쿼리 실행에 필요한 값들만 매개변수로 전달하여 실행하는 방식입니다. 매개변수화된 쿼리를 사용하면 공격자는 SQL 쿼리의 구조를 알 수 없고, 어떤 HTTP 매개변수가 SQL 쿼리의 어느 부분에 전달되는지 알지 못하므로 SQL Injection 방어에 유용하게 활용될 수 있습니다. 다음은 매개변수화된 쿼리를 적용하는 간단한 PHP 예제 소스코드입니다.

매개변수화된 쿼리를 적용하지 않고 데이터베이스에 데이터를 삽입하는 소스코드는  일반적으로 다음과 같습니다.

$conn = mysqli_connect("example.com","spike","ilovejulia","bebop_db");$sql = "INSERT INTO crews (firstname, lastname, email) VALUES ('$_POST["firstname"]','$_POST["lastname"]','$_POST["email"]')";$mysqli->query($conn, $sql);

보시는 바와 같이 SQL 쿼리에 HTTP 매개변수로 전달받은 값이 바로 입력되고 SQL 쿼리가 데이터베이스에 전달되어 실행됩니다. 

하지만 위의 소스코드에 매개변수화된 쿼리를 적용하면 다음과 같은 형태가 됩니다. 

$conn = mysqli_connect("example.com","spike","ilovejulia","bebop_db");$stmt = $conn->prepare("INSERT INTO crews (firstname, lastname, email) VALUES (?, ?, ?)");$stmt->bind_param("sss", $firstname, $lastname, $email);$firstname = $_POST['firstname'];$lastname = $_POST['lastname'];
$email = $_POST['email'];
$stmt->execute();

SQL 쿼리의 INSERT 구문을 보시면 VALUES 절에 데이터가 입력될 곳은 물음표(?)로 표기하였습니다. 이를 Placeholder라고 합니다. 이 SQL 쿼리는 데이터베이스에 전송되고, 추후 사용자로부터 전달받은 데이터를 매개변수를 통해 전달(바인딩)하여 SQL 쿼리를 실행합니다.


최소 권한 & 최소 기능 사용 

간혹 웹 애플리케이션이 사용하는 데이터베이스 계정이 데이터베이스 관리자 계정이거나 관리자급의 권한을 보유하고 있는 경우가 있습니다. 웹 애플리케이션 서비스에 필요한 최소한의 권한만을 보유한 별도의 데이터베이스 계정을 사용해야 합니다. 또한 편의상 제공되는 불필요한 기능들은 비활성화해야 합니다.


데이터베이스 최신 패치 적용

사용 중인 데이터베이스의 제작사에서 발표하는 최신 보안 패치를 수시로 확인하고 적용합니다.


참고 문헌