14번째 문제인 giant다.
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(strlen($_GET[shit])>1) exit("No Hack ~_~");
if(preg_match('/ |\n|\r|\t/i', $_GET[shit])) exit("HeHe");
$query = "select 1234 from{$_GET[shit]}prob_giant where 1";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result[1234]) solve("giant");
highlight_file(__FILE__);
?>
이번 문제는 특이하게 id나 pw같은 파라미터를 받지 않고 shit이라는 파라미터를 받아서 쿼리 중간에 삽입한다. 그런데 쿼리가 삽입되는 위치가 원래 공백이 있어야 할 위치인데 그렇다면 shit 파라미터에 공백 문자를 삽입해서 쿼리가 정상적으로 수행될 수 있도록 해야 하지 않을까? 하지만 두 가지 필터링이 이를 방해하고 있다.
먼저 shit 파라미터에 전달된 글자가 한글자를 초과할 경우 종료한다. 그리고 공백 문자(' '), 개행 문자('\n'), 리턴 문자('\r'), 탭 문자('\t') 등 해당 쿼리에 공백을 줄 수 있는 다양한 문자들을 필터링하고 있다. 그럼 이 필터링된 문자들을 제외하고 어떤 한글자짜리 값을 입력해야 쿼리에 공백을 만들어 줄 수 있을까? 문제를 해결할 수 있는 단서는 HTML URI Encoding 문서였다.
HTTP 요청, 응답에서 우리가 입력한 문자들은 알파벳이나 숫자같은 경우 그대로 URL이나 바디에 포함되어 전송되지만 요청 처리 시 헷갈릴 수 있는 문자, 예를 들어 '#' 같은 문자들은 HTML의 앵커를 의미하는 건지 아니면 정말 '#' 문자를 입력하고 싶은 건지 헷갈릴 수 있다. 그렇기 때문에 '#' 을 %23으로 URI 인코딩하여 전송하면 서버 측에서 이를 URI 디코딩하여 원래 문자를 해석할 수 있다. 이 문자들이 어떤 값으로 인코딩되고 디코딩되는지를 정리해 놓은 문서가 위의 링크인데 일반적으로 입력할 수 있는 아스키 문자나 두글자기 때문에 입력할 수 없는 유니코드를 제외하고 제일 아래쪽으로 내려가보면 옛날에 C언어에서 하드웨어를 조작하기 위해 사용하던 하드웨어 문자들을 볼 수 있다.
ASCII Character | Description | URL-encoding |
NUL | null character | %00 |
SOH | start of header | %01 |
STX | start of text | %02 |
ETX | end of text | %03 |
EOT | end of transmission | %04 |
ENQ | enquiry | %05 |
ACK | acknowledge | %06 |
BEL | bell (ring) | %07 |
BS | backspace | %08 |
HT | horizontal tab | %09 |
LF | line feed | %0A |
VT | vertical tab | %0B |
FF | form feed | %0C |
CR | carriage return | %0D |
... | ... | ... |
이들은 대부분 콘솔이나 텍스트로 관찰하기 힘든 문자들로 현재는 사용되지 않는 문자들도 포함되어 있다. 하드웨어 문자라곤 하지만 URL 내부에선 별 동작을 하지 않는다. 수십가지가 있지만 이중에서 살펴볼 것은 %09나 %0A같은 공백을 만들 수 있는 문자들이다. 현재로서는 다음과 같이 5가지의 문자들이 공백 역할을 수행할 수 있다.
-
%09: horizontal tab. 수평 탭 문자를 의미한다.
-
%0A: line feed. 줄 개행을 의미한다.
-
%0B: vertical tab. 수직 탭 문자를 의미한다.
-
%0C: form feed. 페이지 넘김을 의미한다.
-
%0D: carriage return. 커서를 줄의 처음으로 옮김을 의미한다.
그냥 엔터키나 탭키 하나만 누르면 개행이고 뭐고 다 되는게 아닌가 싶지만 옛날 타자기나 구식 프린터같은 출력 하드웨어를 조작하려면 이런저런 명령이 필요했다. 예를 들어 line feed 명령은 줄 개행만 해주고 carriage return은 커서를 처음으로 옮겨주는 역할만 수행하기 때문에 필요에 따라 각각 사용할 수 있었다. 지금 쓰고있는 이 블로그의 포스트같은 경우 엔터키 하나만 누르면 바로 다음줄로 넘어가서 맨 처음으로 커서가 이동한다. 이는 line feed와 carriage return이 동시에 적용된 형태라 할 수 있다.
어쨌든 이들은 프린터의 출력 모드를 마음대로 조작하기 위해 사용되는 문자들이다. 지금에야 워드프로세서에서 편하게 입력하고 수정할 수 있지만 옛날 일반 텍스트 파일을 서식에 맞게 정교하게 출력하려면 저런 문자들이 필요했던 것이다. 그럼 이 문자들은 지금은 필요가 없으니 URI상에서 무시될까? 그건 또 아니다.
위 테스트는 flask로 간단하게 만든 에코 서버에서 %0A, 즉 line feed 문자를 이용하여 줄 개행을 전달한 것이다. HTML상에서 출력될때는 개행이 안된것처럼 보이지만 개발자 도구에서 Network - Response 탭을 보면 정상적으로 개행된 것을 볼 수 있다. 그럼 왜 HTML에서는 개행되지 않고 그냥 공백으로 나타났을까? 아마 <br> 태그로 끊어줘야 하는 특성상 얼마나 많은 개행 문자가 있던 단순 공백으로 처리하는 것 같다. 다른 문자들도 시도해보면 마찬가지로 Response에는 잘 돌아오고 HTML에서 출력될때만 공백으로 처리된다.
그럼 지금 문제에서 필터링하고 있는 공백, 개행 문자, 캐리지 리턴, 탭을 제외하고 무엇을 입력할 수 있을까? 남은 것들은 바로 수직 탭(%0B, vertical tab)과 페이지 넘김(%0C, form feed) 문자였다. 페이지를 넘기는 건 말 그대로 프린터 용지를 한장 넘기는데 사용된다고 추측할 수 있는데 이 수직 탭은 무엇일까? 이 문자는 프린터의 커서를 수직으로 옮기는 문자로 정렬이나 여백을 생성하기 위해 사용되었다고 한다.
그럼 이 문자는 탭 명령이니까 '\t'에 필터링되는 게 아닐까? 그렇지 않다. '\t'는 수평 탭(%09, horizontal tab)에 속한다. 사실상 수직 탭은 이제와서 사용될 일이 없기 때문에 일반적인 문자('\n', '\t' 등)로도 잘 알려져 있지 않다. 그래서 이를 URI 인코딩한 값인 %0B를 shit 파라미터로 전달해주면 다음처럼 특수문자로 표시되지만 쿼리는 정상적으로 수행되어 문제를 풀 수 있었다.
마찬가지로 %0C를 전달해줘도 문제를 풀 수 있었다.
아마 mysql 자체에서 처리하는 문자와 php를 이용하여 출력되는 문자 간에 표시방법의 차이가 있는 것 같다. 현재 문제 사이트에서는 문자가 필터링되면 쿼리를 보여주지 않고 그대로 종료시켜버리기 때문에 에코 서버에서 Response를 확인해보면서 위의 5가지 문자들이 각각 어떻게 나오는지 확인해보자.
%0A(line feed), %0D(carriage return)는 둘 다 동일하게 줄넘김을 수행하고 있기 때문에 '\n' 필터링에 걸려서 해답이 되지 않는다. 현대에서는 둘 다 줄넘김을 수행하도록 변경된 게 아닐까 추측된다. %09(horizontal tab)이나 %20(whitespace) 역시 '\t', ' ' 필터링에 걸리기 때문에 남은건 %0B(vertical tab), %0C(form feed)임을 알 수 있었다.
이번 문제는 좀 특이한 문제였다. 이런 아스키 문자가 있었구나.. 하면서 옛날엔 C언어가 얼마나 하드웨어와 친숙하게 사용됐던 걸까 생각해보게 되는 계기가 되었다.
'챌린지 > los.rubiya.kr' 카테고리의 다른 글
Lord of SQLInjection - succubus (0) | 2021.01.06 |
---|---|
Lord of SQLInjection - assassin (0) | 2021.01.05 |
Lord of SQLInjection - bugbear (0) | 2021.01.03 |
Lord of SQLInjection - darkknight (0) | 2021.01.01 |
Lord of SQLInjection - golem (0) | 2020.12.31 |