LOS 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트는 succubus에 이어 nightmare 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.

nightmare은 단순한 필터링 문제가 아닙니다. 여기서는 딱히 필터링하는 것도 없고, 단지 입력 값이 6글자라는 것 뿐입니다.


6글자로 문제를 해결해보도록 합시다.



 

 문제 이해


문제 소스코드는 다음과 같이 PHP 소스를 그대로 보여주는 것을 알 수 있습니다.

<?php 
  include "./config.php"; 
  login_chk(); 
  dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)|#|-/i', $_GET[pw])) exit("No Hack ~_~"); 
  if(strlen($_GET[pw])>6) exit("No Hack ~_~"); 
  $query = "select id from prob_nightmare where pw=('{$_GET[pw]}') and id!='admin'"; 
  echo "<hr>query : <strong>{$query}</strong><hr><br>"; 
  $result = @mysql_fetch_array(mysql_query($query)); 
  if($result['id']) solve("nightmare"); 
  highlight_file(__FILE__); 
?>


위의 문제에서 6번 라인에 나타나 있는 특징은 글자가 6글자를 넘으면 안 된다는 제약사항입니다.

그 외에는 특별하게 필터링 된 건 없어보입니다.


또 여기서 특이한 점은 pw 쿼리를 괄호로 묶었다는 점입니다.

이 괄호를 이용하여 어떤 걸 할 수 있는지 알아봐야겠습니다.




 

 문제 풀이(쿼리)


GET 형태이기 때문에 다음과 같이 쿼리를 짜주었습니다.



 

 자동 형 변환(묵시적 형 변환 - Auto casting)


이 문제에서는 자동 형 변환에 대해 이해가 필요합니다.

자동 형 변환이란 조건절에 있는 데이터 타입이 다르면 '우선 순위가 있는 쪽'으로 '형 변환'이 내부적으로 발생하게 되는 것을 말합니다.


자동 형 변환의 규칙은 다음과 같습니다.


1. 묵시적 형변환으로 풀 테이블 스캔이 발생한다.(테이블의 내용을 전체 다 살펴봄)

2. 기본적으로 문자열은 0으로 치환된다. 따라서 문자와 숫자 0을 비교하면 참이다.

3. 문자열과 숫자를 비교할 때 가장 처음 글자와 숫자가 일치하는지 비교한다. 일치하면 참, 다르면 거짓이다.


자세하게 설명하기 위해 다음과 같은 예시 테이블이 있다고 가정해봅시다.


 idx(INT UNSIGNED)

name(VARCHAR) 

 id(VARCHAR)

 password(VARCHAR)

1

name1

id1

password1

2

name2

id2

password2

3

1name

1id

1password

4

2name

2id

2password

5

0

0

0

6

1

1

1

7

0name

0id

0password

8

name0

id0

password0


위의 테이블을 기준으로 다음과 같은 테스트를 수행해보도록 하겠습니다.




 

 자동 형 변환 테스트(문자열 컬럼에 숫자 넣기)


만약 문자열 컬럼에 숫자로 검색하는 쿼리를 다음과 같이 짜보았습니다.


 SELECT * FROM testtable WHERE id=0;


id 컬럼은 VARCHAR로 문자열 컬럼입니다. 문자열 컬럼에 숫자 0으로 검색 조건을 넣어주면 다음과 같은 결과가 나타나게 됩니다.

(설명을 쉽게 하기 위해서 다소 표현이 이상할 수 있습니다. ㅠㅠ)


idx(INT UNSIGNED)

name(VARCHAR)

id(VARCHAR)

password(VARCHAR)

1

name1

id1

password1

2

name2

id2

password2

5

0

0

0

7

0name

0id

0password

8

name0

id0

password0


이러한 결과가 나타나는 이유는 자동 형 변환의 규칙을 통해 알 수 있습니다.

id 컬럼의 문자열과 숫자를 비교하여 나타난 결과 중, 1,2,8번 컬럼은 첫 번째 글자가 문자이기 때문에, 문자는 0으로 변하게 된다는 규칙에 따라 참이 되었습니다. 이는 다음과 같이 풀어서 해석할 수 있습니다.


1. 컬럼의 첫 문자 'i'

2. 숫자와 비교하기 위해 이를 0으로 변환

3. 0으로 변한 'i'와 0이 같은가?

4. 같으므로 참

5. 검색 결과에 반영


그리고 5, 7번 컬럼은 첫 번째 숫자가 0이기 때문에 숫자와 숫자가 일치하므로 참이되었습니다. 이는 다음과 같이 플어서 해석할 수 있습니다.


1. 컬럼의 첫 문자 '0'

2. 첫 문자가 숫자이므로 숫자로 변경한다.

3. 첫 문자 '0'은 숫자 0으로 치환되었다.

4. 치환된 숫자와 비교할 숫자 0이 일치하므로 참

5. 검색 결과에 반영



만약 다음과 같은 쿼리를 사용하게 되면 결과가 달라지게 되겠습니다.


 SELECT * FROM testtable WHERE id=1;


idx(INT UNSIGNED)

name(VARCHAR)

id(VARCHAR)

password(VARCHAR)

3

1name

1id

1password

6

1

1

1


위의 결과가 나타나게되는 이유는 다음과 같이 풀어서 해석할 수 있습니다.


1. 컬럼의 첫 문자 '1'

2. 첫 문자가 숫자이므로 숫자로 변경한다.

3. 첫 문자 '1은 숫자 1로 치환되었다.

4. 치환된 숫자와 비교할 숫자 1이 일치하므로 참

5. 검색 결과에 반영


나머지 컬럼은 문자 == 0, 첫 번째 숫자가 1이 아님, 과 같은 이유로 거짓이 되어 검색되지 않습니다.


위의 설명은 이해하기 쉽게 하기 위해 다소 부족한 설명입니다.


정리하자면, 첫 문자가 아니라 첫 문자부터 뒤로 쭉 읽어들인 후, 숫자가 일치하는지 비교하게 된다는 것입니다.




 

 자동 형 변환 테스트(숫자 컬럼에 문자열 넣기)


만약 숫자 컬럼에 문자열로 검색하는 쿼리를 다음과 같이 짜보았습니다.


 SELECT * FROM testtable WHERE idx='1';


idx 컬럼은 UNSIGNED INT로 문자열 컬럼입니다. 여기에 문자 '1'을 검색하라, 라고 조건을 주게 되면, 여기서도 마찬가지로 풀 테이블 스캔이 발생하게됩니다.

문자 안에 있는 숫자는 숫자로 바뀌게 되고, 그와 일치하는 숫자가 있는지 확인하게 됩니다.


여기서 숫자 1의 컬럼이 결과로 나타나게 됩니다.


만약 다음과 같은 쿼리를 실행했을 경우에는 조금 다른 결과가 나오게 됩니다.


 SELECT * FROM testtable WHERE idx='a';


위의 쿼리는 문자를 삽입하였기 때문에, idx 컬럼에 숫자가 0으로 설정된 값만 반환되게 됩니다.

문자는 0으로 취급되기 때문입니다.


위와 같은 자동 형 변환(묵시적 형 변환)을 통해 SQL Injection을 수행할 수 있습니다.



 

 pw에 풀이 쿼리 삽입


여기서 이용할 자동 형 변환의 규칙은 문자열은 숫자 0과 같다, 라는 규칙입니다.

다음과 같은 방법으로 값을 넣어주면 됩니다.


https://los.rubiya.kr/chall/nightmare_be1285a95aa20e8fa154cb977c37fee5.php?pw=%27)=0;%00


위와 같은 값을 삽입하게 되면 다음과 같은 쿼리 형태가 됩니다.


 select id from prob_nightmare where pw=('')=0;') and id!='admin'


여기서 유의해야할 점은 MySQL로 넘어가야 하는 쿼리를 중간에 잘라주어야 한다는 것입니다.

위의 쿼리를 모두 보내게 되면 에러가 발생하게 됩니다.


그렇다면 우리는 select id from prob_nightmare where pw=('')=0;까지 깔끔한 문장을 전달하고 싶어집니다. 이를 위해서 %00 즉, NULL 값을 넣어줍니다.


이렇게 값을 넣어주게 되면 세미콜론 뒤에 따라오는 값들은 전달되지 않게 됩니다.


이유는, NULL 값을 정해줬으니, mysql_query() 함수에서 문자열이 끝이 세미콜론(;)까지구나, 하고 받아들이게 됩니다. 따라서 해당 함수를 통해 전달되는 값은 결국 select id from prob_nightmare where pw=('')=0; 값이 됩니다.



+ Recent posts