Summary
|
Challenge 40은 다음과 같습니다.
webhacking.kr에서 처음보는 Black Box Testing인 것 같습니다. 실제 모의해킹을 하는 것 처럼 신나게 풀었습니다 :)
우선 GET 방식으로 Query를 전송하고 있으며, "no", "id", "pw" 3가지 모든 Parameter를 보내야만 정상적으로 작동합니다.
PHP 코드를 예상해보면 제일 처음에 다음과 같이 시작할 것 입니다.
<?if($_GET[no] && $_GET[id] && $_GET[pw]) { ... }?> | cs |
그 다음 실제 SELECT 구문을 예상해 봅니다.
SELECT id FROM Challenge40 WHERE no=$_GET[no] and id='$_GET[id]' and pw='$_GET[pw]'; | cs |
"no" Column이 Int 타입으로 예상됨으로, 이 곳을 Injection Vector로 선정합니다. 그리고 공격을 시도하기 전에 중요한 것이 WHERE 이하 절의 Column 순서를 맞추는 것입니다. 예전에 이 작업을 소홀히 하여 굉장한 삽질을 한 적이 있었습니다. 그렇기 때문에 정확하게 WHERE 이하 절을 분석한 뒤에 공격을 진행해야 합니다.
WHERE 이하 절을 위의 SELECT 구문에서와 똑같이 생각해보고 다음과 같은 Query를 전송해 봅니다.
1 2 3 | ?no=1%23&id=guest&pw=guest => Success - Guest ?no=1%23&id=unknown&pw=guest => Failure ?no=1%23&id=guest&pw=guest => Failure | cs |
1번 라인에 의하면 WHERE 절에서 "no" Column이 제일 첫번째 온다고 가정하고 "no" 파라미터 뒤에 "#" 주석문을 붙였더니 "Success"가 되었습니다. 하지만 2번 라인에서와 같이 "id"를 "unknown"으로 바꾸자 "Failure" 되었습니다. 즉 "no"의 주석문이 "id"에는 영향을 끼치지 않으므로 "id"가 "no"보다 앞에 있다는 것을 나타냅니다. 그 다음 "pw"도 "unknown"으로 바꾸자 "Failure" 되었습니다. 즉 "pw"도 "no"보다 앞에 있다는 것을 나태냅니다. 따라서 다시 SELECT 구문을 예상해 보면 다음과 같습니다.
SELECT id FROM Challenge40 WHERE id='$_GET[id]' and pw='$_GET[pw]' and no=$_GET[no]; | cs |
"id"와 "pw"의 Column 순서는 어찌됬든, "no" Column의 위치가 제일 마지막 이라는 것을 알아냈습니다. 그러므로 "#" 주석문은 이제 필요 없습니다.
그러면 이제 실제 공격 Query를 전송하여 "admin"을 출력해 보겠습니다. 먼저 실제 MySQL에서 어떻게 동작하는지 살펴보겠습니다.
no=1 다음에 or id='admin'을 입력하면 "or" 이전 구문에 의해 "guest"가 출력되고, 그 다음 구문에 의해 "admin"이 각각 출력되는 것을 보실 수 있습니다.
그 다음 no=2, 즉 "or" 이전 구문을 False로 만들고 or id='admin'을 입력하면 "admin"만 출력되게 됩니다.
이 방법을 이용하여 다음과 같은 Query를 만들 수 있습니다. ("or"과 "char"는 필터링되어 있습니다.)
1 | ?no=2||id=0x61646D696E&id=guest&pw=guest | cs |
Query를 전송하면 다음과 같은 Page가 나타납니다.
실제 모의해킹이라면 Administration Page라고 봐도 될 것 같습니다.
이제 이 Page의 admin password를 찾아야 합니다. 현재 SELECT 구문에서 id가 "admin"이면 Admin Page가 출력되고, id가 "guest"이면 "Success - guest"가 출력되고, SELECT 구문에 False이면 Failure가 출력이 되고 있습니다. 그러므로 Boolean을 이용하여 Blind SQL Injection 방식으로 Password를 하나씩 찾아내면 될 것 같습니다.
Blind SQL Injection은 Challenge 21(http://limjunyoung.tistory.com/79), 55(http://limjunyoung.tistory.com/99)에서 살펴보았기 때문에 자세한 설명은 해당 글을 참고하시기 바랍니다. Blind를 할 때 제일 먼저 length()를 이용하여 길이를 알아내는 것이 편리합니다. Query는 다음과 같습니다.
1 | ?no=2||length(pw)<20&id=guest&pw=guest | cs |
no=2를 지정하여 "guest"가 출력되지 않게 한 다음 "or length(pw)"를 이용하여 pw의 길이를 알아냅니다.
길이를 알아냈으면, 실제 pw를 한 문자씩 짤라내어 값을 비교해 봅니다. 다행히 substr() 함수는 필터링이 되어 있지 않습니다. 그러나 char()와 ascii()는 필터링 되어 있기 때문에 Hex값을 사용하겠습니다. 실제 Query를 전송하기 전에 실제 MySQL에서 어떻게 동작하는지 살펴보겠습니다.
no=2를 입력하여 False를 만들고 or substr()을 True로 만들면 위와 같이 "guest"와 "admin" 모두 출력되게 됩니다. 그렇기 때문에 다음과 같은 Query를 만들었습니다.
or id='admin' and substr()을 입력하여 "admin"만을 출력할 수 있습니다. 이 방법을 이용하면 Blind SQL Injection을 할 때 "guest"의 password인지 "admin"의 password인지 헷갈리지 않고 편리하게 작업할 수 있습니다.
이제 실제 Blind에 사용할 Query는 다음과 같습니다.
( "%26%26" 대신 "&&"를 입력하면 진행이 되지 않는데, 이 것은 URL에서 "&&"을 파라미터 구분자로 인식하는 것 같습니다. )
1 | ?no=2||id=0x61646D696E%26%26substr(pw,1,1)>0x61&id=guest&pw=guest | cs |
Blind를 진행하시면 이상한 점을 발견하신 분들도 있을 겁니다.
1 2 | substr(pw,1,1)=0x6C => True substr(pw,1,1)=0x4C => True |
0x6C (소문자 "l"), 0x4C (대문자 "L")을 구분하지 않고 있습니다. 실제 MySQL에서는 대소문자를 구분하지 않습니다. 그렇기 때문에 이런 결과가 나오는 것입니다. 하지만 다음 구문에서 더 이상한 점이 발견됩니다.
1 2 3 | substr(pw,5,1)>0x7A => True substr(pw,5,1)>0x7B => False substr(pw,5,1)=0x7B => False | cs |
pw의 5번째 문자가 0x7A (소문자 "z")와 0x7B ("{") 사이에서 길을 잃어 버렸습니다.
실제 pw의 5번째 문자는 0x5F ("_") 입니다.이것은 0x7A를 0x5A (대문자 "Z")와 똑같이 인식하기 때문에 0x5A(0x7A)보다는 크지만 0x7B보다는 작다는 결과가 나오는 것입니다.
이제 Blind Injection을 이용하여 Password 값을 추측한 뒤, Admin Page에 입력하면 Challenge를 해결할 수 있습니다.