강좌
클라우드/리눅스에 관한 강좌입니다.
리눅스 분류

SQL Query Vulnerability In PHP

작성자 정보

  • 웹관리자 작성
  • 작성일

컨텐츠 정보

본문

icon01.giftitle01.gif

Release date Aug 8, 2002
Author 윤성진 - chaos@ecrobot.com
한상진 - komnaru@ecrobot.com
Source (주) 이씨로봇 연구소
Application magic_quotes_gpc 옵션이 Off인 상태에서 작동되는 DBMS연동 PHP 프로그램.
Risk Critical
Reference http://lab.ecrobot.com/advisories/ec2002080801
Last modified 2002/08/09
문의 info@ecrobot.com

차례

0x00. Overview
0x01. 해당 환경
0x02. DBMS를 연동하는 프로그램의 일반적인 구조
0x03. 공격이 이루어지는 원리
0x04. Exploit: 제로보드의 예
0x05. 해결책
0x06. \';\' 문자를 이용한 SQL statement 조작
0x07. 결론


title02.gif

최근 웹 프로그래밍은 대부분 자료의 효율적인 저장 및 검색을 위해 DBMS를 거의 필수적으로 이용하는 추세이다. 웹 프로그래밍은 보통 PHP, JSP, ASP 등의 스크립트 언어를 이용하여 DBMS를 연동하여 프로그래밍을 하게 된다. 이러한 프로그램에서 생기는 문제점으로서, 잘못된 값을 웹 프로그램으로 넘겨줌으로써, 부적절한 SQL Query를 실행시키도록 할 수가 있다.
이 공격을 이용하면 불법적인 데이터베이스 query를 이용하여 인증을 위해 사용하는 SQL query 등을 비정상적으로 만들어서, 아이디나 암호가 맞지 않음에도 불구하고 인증을 정상적으로 한 것처럼 만들 수가 있다.
현재 대부분의 웹 기반 공개 게시판과 수많은 사이트들이 이 문제점에 노출되어 있는 것으로 확인되었다.
이 문서는 DBMS를 연동하는 웹 프로그램이 가질 수 있는 치명적인 문제점과 그 해결책에 대해 다룬다. 주로 PHP의 경우를 다루지만 다른 언어에서도 얼마든지 있을 수 있는 문제이다.


title03.gif

이 문제점은 플랫폼과는 무관하다. 다만 PHP로 DBMS를 연동하는 경우, PHP의 설정에서 magic_quotes_gpc 옵션이 Off로 되어 있는 경우에 이 문제가 발생한다. 다른 프로그래밍 언어의 경우에도 사용자로부터 넘겨받은 데이터에 포함된 따옴표에 자동으로 escape처리가 되지 않는 경우에는 이 문제가 발생할 수 있다. 참고로 PHP 4.x 버전에 포함된 2개의 php 설정 파일 중, PHP 측에서 권장하는 설정 파일인 php.ini-recommend에는 성능상의 이유로 이 옵션이 기본적으로 Off로 설정되어 있어서 이 공격을 당할 수 있다.


title04.gif

웹 프로그램은 HTML에서 form tag를 이용하여 사용자로부터 데이터를 입력 받고, GET이나 POST method를 이용하여 프로그램으로 데이터를 넘겨주면 프로그램에서 적절한 DBMS query를 통해 데이터 입출력을 하고 그 결과를 다시 사용자에게 보여주는 형태를 가지고 있다.
예로 ID와 암호를 입력받아 인증을 해주는 간단한 PHP 스크립트는 다음과 같은 형태를 가지고 있을 수 있다. 아래의 Login.html은 form tag를 이용하여 사용자로부터 id와 password를 입력받는 form을 출력하는 파일이고, login.php는 사용자가 입력한 데이터를 바탕으로 DBMS에 query를 하여 인증을 처리하는 프로그램이다.

- login.html
<html>
<body>
<form name="form1" method="post" action="login.php">
ID:
<input name="id" type="text" id="id">
<br>
Password:
<input name="passwd" type="text" id="passwd">
<br>
<input type="submit" name="Submit" value="Submit">
</form>
</body>
</html>

- login.php
<?php
$MySQL = mysql_connect("localhost", "user", "password");
mysql_select_db("database");

$result = mysql_query("SELECT id, passwd FROM user_table WHERE id=\'{$_POST[\'id\']}\' AND passwd=\'{$_POST[\'passwd\']}\'");
$data = mysql_fetch_assoc($result);

if (($data[\'id\'] == $_POST[\'id\']) && ($data[\'passwd\'] == $_POST[\'passwd\'])) {
/* 인증을 위해 세션 등을 설정 */
}

else {
/* 인증에 실패한 경우, 에러 출력 등의 처리 */
}
?>

위의 형태는 아주 일반적인 형태의 웹 프로그램으로 대부분의 프로그램이 이런 식으로 사용자로부터 받은 데이터를 이용하여 SQL query를 하고, 그 결과에 따라 특정한 처리를 하게 된다. 여기서는 사용자로부터 "id"와 "passwd"라는 2개의 데이터를 POST 방식으로 받아서, 프로그램 내에서는 $_POST[\'ID\'], $_POST[\'passwd\']와 같은 식으로 SQL query에 이용하고 있다(PHP에서는 이런 식으로 처리한다).
일반적인 경우에는 위와 같은 코드는 별 문제가 되지 않고 잘 작동할 것이다. 그러나 만약 php.ini 설정 파일에서 magic_quotes_gpc 옵션이 Off로 되어 있다면 아주 치명적인 문제점이 있다.


title05.gif

PHP의 설정 파일(php.ini) 중, magic_quotes_gpc 옵션이 하는 일은 사용자가 웹에서 입력한 데이터를 GET, POST으로 프로그램으로 넘겨줄 때나, 쿠키를 처리할 때에 \', ", \, 널 문자의 앞에 \를 하나 더 붙여 주어서 이러한 문자가 특수한 의미로 사용되지 않도록 escape시켜주는 것이다.

위에서 예로 든 코드 중 사용자가 입력한 id와 passwd라는 변수값을 SQL query에 넣어서 DBMS로 query를 하는 부분은 다음과 같다.

$result = mysql_query("SELECT id, passwd FROM user_table WHERE id=\'{$_POST[\'id\']}\' AND passwd=\'{$_POST[\'passwd\']}\'");

만약 사용자가 id로 chaos를, 비밀번호로도 chaos를 넣었다면 위의 코드는 사실상 다음과 같이 변환될 것이다.

$result = mysql_query("SELECT id, passwd FROM user_table WHERE id=\'chaos\' AND passwd=\'chaos\'");

원래 코드에 있었던 $_POST[\'id\']와 $_POST[\'passwd\'] 부분이 각각 chaos로 대체되면서 위와 같은 결과가 되는 것이다. 바로 이 부분에 헛점이 있다.

만약 사용자가 id로 chaos와 같은 정상적인 값을 넣지 않고 a\' or 1<2 or \'a\'<\'b라고 입력했다고 가정해보자. php.ini에서 magic_quotes_gpc 옵션이 On으로 되어 있을 경우에 원래 코드는 다음과 같이 변환될 것이다.

$result = mysql_query("SELECT id, passwd FROM user_table WHERE id=\'a\\' or 1<2 or \\'a\\'<\\'b\' AND passwd=\'chaos\'");

이런 경우에는 저런 희한한 아이디가 있을리도 없고 암호가 맞을리도 없기 때문에 이 query는 아무 row도 반환하지 않고 그 결과 인증에 실패하게 된다.

그러나 이번에는 php.ini에서 magic_quotes_gpc 옵션이 Off로 되어 있는 경우를 가정해 보자. php.ini-recommend 설정 파일을 그대로 이용하는 경우가 바로 이 경우이다. 그러면 원래 코드는 다음과 같이 변환될 것이다.

$result = mysql_query("SELECT id, passwd FROM user_table WHERE id=\'a\' or 1<2 or \'a\'<\'b\' AND passwd=\'chaos\'");

결론부터 얘기하자면 위의 SQL 문에서 WHERE clause는 항상 true이고, 모든 row를 반환하게 된다. 그렇게 되면 첫번째로 반환되는 row의 정보를 기반으로 로그인이 수행되게 된다. 왜 이 WHERE clause가 항상 true인가? 그것은 logical AND 연산이 logical OR 연산보다 우선순위가 높기 때문이다. 언뜻 보기에 위의 WHERE clause는 상당히 confusing해 보인다. 그러나 사실은 전혀 헷갈릴 것이 없이 매우 명확한 논리적 연산이다. 위의 SQL 문을 우선순위에 따라 단계적으로 괄호를 쳐보면 다음과 같다.

1: SELECT id, passwd FROM user_table WHERE id=\'a\' or 1<2 or (\'a\'<\'b\' AND passwd=\'chaos\')
2: SELECT id, passwd FROM user_table WHERE (id=\'a\' or 1<2) or (\'a\'<\'b\' AND passwd=\'chaos\')

1단계에서는 OR 연산보다 AND 연산이 우선순위가 높기 때문에 AND 쪽에 괄호를 쳤다. 그리고 나서 나머지 OR는 우선순위가 같은데, 이런 경우에는 앞에 있는 것이 더욱 우선순위가 높기 때문에 앞의 OR를 괄호로 묶었다. 이 상태에서 보면 앞의 괄호는 항상 true이다. "1<2" 연산 때문이다. 뒤에 괄호가 설사 false를 반환한다고 하더라도 이 두개의 결과가 or로 연결되었기 때문에 뒤의 괄호의 결과는 중요하지 않다. 이 WHERE clause는 항상 true인 것이다.

웹상의 로그인 form에 ID 등을 입력할 때 이런 식으로 ID를 교묘히 입력하면 SQL query 문을 비정상적으로 만들어서 아이디, 암호를 전혀 몰라도 아무 아이디로나 로그인이 가능하도록 할 수도 있고, 또한 아이디만 안다면 아이디만 적절히 지정하여 암호 없이 로그인을 통과할 수도 있다. 물론 대상 테이블 구조를 잘 알고 있다면 그 이상의 조작도 가능하다.


title06.gif

리눅스에서 인기있는 공개형 게시판 중 제로보드가 있다. 이 제로보드의 경우를 예로 들어, 이 공격을 실제로 테스트해 보겠다. 테스트 환경은 PHP 4.2.2(설정 파일로 php.ini-recommend 사용), 제로보드 버전 4.1 pl2이다.

01.gif

위와 화면이 제로보드의 관리자 로그인 화면이다. 여기서 위와 같이 a\' or 1<2 or \'a\'<\'b를 아이디 대신 입력하였다. 제로보드의 경우, 로그인을 체크하는 프로그램은 login_check.php이고 이 파일을 열어보면 인증을 하는 SQL query는 다음과 같이 적혀 있다.

$result = mysql_query("select * from $member_table where user_id=\'$user_id\' and password=password(\'$password\')") or error(mysql_error());

이런 경우에 아이디를 a\' or 1<2 or \'a\'<\'b로 입력하고 암호는 아무것이나 입력하면 위의 SQL 문은 다음과 같이 변환된다.

select * from $member_table where user_id=\'a\' or 1<2 or \'a\'<\'b\' and password=password(\'asdfasdf\');

이 SQL 문의 WHERE clause는 항상 true이며, 모든 row를 반환한다. 이런 경우에 맨 처음 반환되는 row에 의해 인증이 처리되도록 제로보드 프로그램이 만들어져 있다. 보통 게시판을 설치하면 관리자 아이디를 가장 먼저 만들게 되며, 그런 이유로 대부분 모든 row를 정렬없이 검색했을 때 관리자 아이디가 제일 먼저 반환되게 된다. 따라서 관리자로 로그인이 되는 것이다. 혹은 level=1인 row를 명시적으로 뽑아낼 수도 있을 것이다.

다음은 위와 같은 식으로 로그인을 한 결과이다.

02.gif

여기서는 관리자 페이지만 테스트를 해보았으나, 일반 사용자 계정으로도 얼마든지 불법적인 로그인이 가능하다. 그런 경우에는 아이디만 지정하고 비밀번호는 아무것이나 적어도 통과시킬 수 있다. 또한 이 문제는 제로보드만이 가지고 있는 문제는 아니다.


title07.gif

php.ini 설정에서 magic_quotes_gpc = On으로 설정하면 안전하다. 그러나 PHP 측에서는 performance 향상을 위해 이 옵션을 Off로 설정하도록 권장하고 있다. 또한 현재 PHP에서 권장하는 설정 파일에도 이 옵션이 기본적으로 Off로 되어 있다.

이 옵션이 Off로 되어 있는 상태로 프로그램을 작동시키고 싶다면 DBMS로 query를 전송하기 전에, SQL query statement를 만들기 위해 사용되는 변수들에 따옴표 등이 있는가를 확실히 체크하여 이 문자들을 escape시켜주도록 해야 한다.

한 예로, PHP에서 MySQL을 연동하는 경우를 위해 mysql_escape_string()이라는 함수를 제공한다. 이 함수는 따옴표와 같이 잘못된 MySQL query를 유발할 수 있는 문자에 대해 escape 처리를 해준다. 따라서 MySQL을 사용하는 제로보드의 경우 login_check.php 파일 앞부분을 다음과 같이 고치면 이 문제를 피할 수 있다.

$user_id = mysql_escape_string(trim($user_id));
$password = mysql_escape_string(trim($password));

위의 코드는 magic_quotes_gpc 옵션이 Off로 되어 있는 경우에만 사용해야 정상적으로 작동한다. magic_quotes_gpc 옵션이 On인 경우에는 이미 escape된 문자열에 또 escape 처리를 하게 되기 때문에 비정상적으로 작동하게 된다. 어느 경우에나 작동하도록 고치려면 get_magic_quotes_gpc()라는 함수를 이용하면 될 것이다.

오라클의 경우에는 \' 문자를 \'\'로 대체한 후에 SQL query를 작성하도록 하면 될 것이다.

제로보드의 경우, 윗 부분만 고쳤다고 해서 완전히 안전하다고 할 수는 없을 것이다. 다른 SQL query를 사용하는 수많은 부분에서 모두 잠재적인 위험이 있기 때문이다. 결국은 모든 게시판의 개발자 측에서 조치를 취해주는 것이 가장 바람직할 것이다.


title08.gif

한번 더 생각해 보면, ID 등을 입력할 때에 아얘 ; DROP TABLE user_table; 등과 같이 입력해서 완전히 독립적인 SQL 문도 실행시킬 수 있지않을까 하고 생각될 수도 있다. 그러나 이 점은 안심해도 된다. PHP에서 MySQL로 query를 전송할 때에 \';\' 문자가 포함되어 있으며 에러를 낸다.

title09.gif

PHP 언어가 버전 4.x 대로 넘어오면서 php.ini-recommend라는 설정 파일에 magic_quotes_gpc 옵션이 기본적으로 Off로 되어 있기 때문에, 기존의 설정대로라면 아무 문제없이 사용되던 게시판 프로그램 등이 보안 위험에 처하게 될 수 있다. 현재 만들어져 있는 많은 게시판 프로그램들이 PHP에서 자동으로 escape 처리를 해 줄것이라는 가정 하에 만들어졌기 때문에 이것이 더욱 위험한 것이다. 사실 이런 비슷한 유형의 문제는 예전에 perl로 웹 프로그래밍을 하던 시절부터 있던 문제였다. 다만 지금까지 PHP에서는 기본적으로 자동으로 특수 문자를 escape시켜주는 기능이 있기 때문에 보안 이슈가 되지 못했을 뿐이다.

또한 이 문제는 웹 프로그램에만 한정해서 생각할 문제는 아니다. C 언어로 system() 함수를 이용할 때에나 Perl 언어에서 `command`; 형식으로 다른 프로그램을 호출해서 사용할 때에도 항상 신중해야 한다.

만약 sendmail 프로그램을 호출하여 편지를 발송해주는 프로그램을 Perl이나 C 언어로 작성할 때에는 ; 문자를 조심해야 한다. 왜냐하면 만약 사용자가 이메일로 chaos@ecrobot.com; rm -rf / 이렇게 입력했다면 매우 심각한 문제를 가져올 수 있기 때문이다. 이런 경우에 sendmail chaos@ecrobot.com; rm -rf /과 같은 명령어가 쉘에서 실행되는 것이다.

EOF

관련자료

댓글 0
등록된 댓글이 없습니다.

공지사항


뉴스광장


  • 현재 회원수 :  60,041 명
  • 현재 강좌수 :  35,855 개
  • 현재 접속자 :  102 명