[Codegate 2018] WEB – rbSql
해당 문제는 소스를 공개하는 오픈소스 형태의 문제입니다. 가장 먼저 문제 페이지에 접속해보면..
ㅗㅜㅑ… 익숙한 페이지가 눈 앞에 나타납니다. 최근 몇년간의 국내 CTF를 참가해 웹 분야의 문제를 풀어보았다면 많이 봤을 크리스탈입니다. 딱 봤을 때 있는 기능이라면 Photo와 M/V가 있으며, Join과 Login 기능이 존재합니다. 문제 이름에 SQL이 들어가기 때문에 SQL Injection이라고 생각되지만, 자세한 것은 소스코드를 확인해봐야 알 것 같습니다.
문제 설명에 첨부되어있는 파일을 다운받아 압축을 해제하면 크게 두가지 메인 소스코드로 나뉘어져 있다는 것을 알 수 있습니다.
index.php와 dbconn.php입니다.
일반적으로 dbconn.php에는 해당 웹서버에서 사용하고 있는 데이터베이스에 대한 설정(아이디, 패스워드, 호스트, 포트, 사용할 DB명 등)을 담고 다른 페이지에서 import하여 사용하는 형태이지만, 이 문제는 달랐습니다. rbSql이라는 제목이 허투루 나온 것이 아니라 출제자 본인이 직접 생각하여 구현한 데이터베이스 형태인 것 같습니다. dbconn.php 내에는 저장할 데이터에 대한 파싱 방법, 패킹 방법, 그리고 데이터를 삽입/삭제/조회할 수 있는 기능들을 구현해 놓았습니다.
다음은 index.php와 dbconn.php의 소스코드입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | <?php session_start(); include("5f0c2baaa2c0426eed9a958e3fe0ff94.php"); $page = $_GET['page']; if($page == "login"){ ?> <h3>Login</h3> <p> <form action="./?page=login_chk" method="POST"> <table> <tr><td>ID</td><td><input type="text" name="uid" id="uid"></td> <td rowspan="3"><img src="./images/login.jpg" width="270" style="margin-left: 20px; margin-top: -38px; position:fixed;"></td></tr> <tr><td>PW</td><td colspan="2"><input type="text" name="upw" id="upw"></td></tr> <tr><td colspan="2"><input type="submit" value="Login" style="width: 100%;"></td></tr> </table> </form> </p> <?php } elseif($page == "join"){ ?> <h3>Join</h3> <p> <form action="./?page=join_chk" method="POST"> <table> <tr><td>ID</td><td><input type="text" name="uid" id="uid"></td> <td rowspan="3"><img src="./images/login.jpg" width="270" style="margin-left: 20px; margin-top: -38px; position:fixed;"></td></tr> <tr><td>MAIL</td><td colspan="2"><input type="text" name="umail" id="uid"></td></tr> <tr><td>PW</td><td colspan="2"><input type="text" name="upw" id="upw"></td></tr> <tr><td colspan="2"><input type="submit" value="Join" style="width: 100%;"></td></tr> </table> </form> </p> <?php } elseif($page == "login_chk"){ $uid = $_POST['uid']; $upw = $_POST['upw']; if(($uid) && ($upw)){ include "dbconn.php"; $result = rbSql("select","member_".$uid,["pw",md5($upw)]); if(is_string($result)) error("login fail"); $_SESSION['uid'] = $result['0']; $_SESSION['lvl'] = $result['4']; exit("<script>location.href='./';</script>"); } else error("login fail"); } elseif($page == "join_chk"){ $uid = $_POST['uid']; $umail = $_POST['umail']; $upw = $_POST['upw']; if(($uid) && ($upw) && ($umail)){ if(strlen($uid) < 3) error("id too short"); if(strlen($uid) > 16) error("id too long"); if(!ctype_alnum($uid)) error("id must be alnum!"); if(strlen($umail) > 256) error("email too long"); include "dbconn.php"; $upw = md5($upw); $uip = $_SERVER['REMOTE_ADDR']; if(rbGetPath("member_".$uid)) error("id already existed"); $ret = rbSql("create","member_".$uid,["id","mail","pw","ip","lvl"]); if(is_string($ret)) error("error : create"); $ret = rbSql("insert","member_".$uid,[$uid,$umail,$upw,$uip,"1"]); if(is_string($ret)) error("error : insert"); exit("<script>location.href='./?page=login';</script>"); } else error("join fail"); } elseif($page == "photo"){ ?> <h3>Photo</h3> <p><img src="./images/1.jpg" width="430"></p> <p><img src="./images/2.jpg" width="430"></p> <p><img src="./images/3.png" width="430"></p> <p><img src="./images/4.gif" width="430"></p> <?php } elseif($page == "video"){ ?> <h3>Music Video</h3> <p><iframe width="520" height="293" src="//www.youtube.com/embed/iv-8-EgPEY0?rel=0" frameborder="0" allowfullscreen></iframe></p> <p><iframe width="520" height="293" src="//www.youtube.com/embed/xnku4o3tRB4?rel=0" frameborder="0" allowfullscreen></iframe></p> <p><iframe width="520" height="293" src="//www.youtube.com/embed/n8I8QGFA1oM?rel=0" frameborder="0" allowfullscreen></iframe></p> <p><iframe width="520" height="293" src="//www.youtube.com/embed/kKS12iGFyEA?rel=0" frameborder="0" allowfullscreen></iframe></p> <?php } elseif($page == "me"){ echo "<p>uid : {$_SESSION['uid']}</p><p>level : "; if($_SESSION['lvl'] == 1) echo "Guest"; elseif($_SESSION['lvl'] == 2) echo "Admin"; echo "</p>"; include "dbconn.php"; $ret = rbSql("select","member_".$_SESSION['uid'],["id",$_SESSION['uid']]); echo "<p>mail : {$ret['1']}</p><p>ip : {$ret['3']}</p>"; if($_SESSION['lvl'] === "2"){ echo "<p>Flag : </p>"; include "/flag"; rbSql("delete","member_".$_SESSION['uid'],["id",$_SESSION['uid']]); } } elseif($page == "logout"){ session_destroy(); exit("<script>location.href='./';</script>"); } else{ ?> <h3>ㅋrystal :/</h3> <p><img src="./images/k_03.jpg" width="430" style="position:fixed;"></p> <?php } include("4bbc327f5b0fd076e005961bcfc4a9ee.php"); ?> |
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | <?php /* Table[ tablename, filepath [column], [row], [row], ... rbSqlSchema[ rbSqlSchema,/rbSqlSchema, ["tableName","filePath"], ["something","/rbSql_".substr(md5(rand(10000000,100000000)),0,16)] ] */ define("STR", chr(1), true); define("ARR", chr(2), true); define("SCHEMA", "../../rbSql/rbSqlSchema", true); function rbSql($cmd,$table,$query){ switch($cmd){ case "create": $result = rbReadFile(SCHEMA); for($i=3;$i<count($result);$i++){ if(strtolower($result[$i][0]) === strtolower($table)){ return "Error6"; } } $fileName = "../../rbSql/rbSql_".substr(md5(rand(10000000,100000000)),0,16); $result[$i] = array($table,$fileName); rbWriteFile(SCHEMA,$result); exec("touch {$fileName};chmod 666 {$fileName}"); $content = array($table,$fileName,$query); rbWriteFile($fileName,$content); break; case "select": /* Error1 : Command not found Error2 : Column not found Error3 : Value not found Error4 : Table name not found Error5 : Column count is different Error6 : table name duplicate */ $filePath = rbGetPath($table); if(!$filePath) return "Error4"; $result = rbReadFile($filePath); $whereColumn = $query[0]; $whereValue = $query[1]; $countRow = count($result) - 3; $chk = 0; for($i=0;$i<count($result[2]);$i++){ if(strtolower($result[2][$i]) === strtolower($whereColumn)){ $chk = 1; break; } } if($chk == 0) return "Error2"; $chk = 0; for($j=0;$j<$countRow;$j++){ if(strtolower($result[$j+3][$i]) === strtolower($whereValue)){ $chk = 1; return $result[$j+3]; } } if($chk == 0) return "Error3"; break; case "insert": $filePath = rbGetPath($table); if(!$filePath) return "Error4"; $result = rbReadFile($filePath); if(count($result[2]) != count($query)) return "Error5"; $result[count($result)] = $query; rbWriteFile($filePath,$result); break; case "delete": $filePath = rbGetPath($table); if(!$filePath) return "Error4"; $result = rbReadFile($filePath); $whereColumn = $query[0]; $whereValue = $query[1]; $countRow = count($result) - 3; $chk = 0; for($i=0;$i<count($result[2]);$i++){ if(strtolower($result[2][$i]) === strtolower($whereColumn)){ $chk = 1; break; } } if($chk == 0) return "Error2"; $chk = 0; for($j=0;$j<$countRow;$j++){ if(strtolower($result[$j+3][$i]) === strtolower($whereValue)){ $chk = 1; unset($result[$j+3]); } } if($chk == 0) return "Error3"; rbWriteFile($result[1],$result); break; default: return "Error1"; break; } } function rbParse($rawData){ $parsed = array(); $idx = 0; $pointer = 0; while(strlen($rawData)>$pointer){ if($rawData[$pointer] == STR){ $pointer++; $length = ord($rawData[$pointer]); $pointer++; $parsed[$idx] = substr($rawData,$pointer,$length); $pointer += $length; } elseif($rawData[$pointer] == ARR){ $pointer++; $arrayCount = ord($rawData[$pointer]); $pointer++; for($i=0;$i<$arrayCount;$i++){ if(substr($rawData,$pointer,1) == ARR){ $pointer++; $arrayCount2 = ord($rawData[$pointer]); $pointer++; for($j=0;$j<$arrayCount2;$j++){ $pointer++; $length = ord($rawData[$pointer]); $pointer++; $parsed[$idx][$i][$j] = substr($rawData,$pointer,$length); $pointer += $length; } } else{ $pointer++; $length = ord(substr($rawData,$pointer,1)); $pointer++; $parsed[$idx][$i] = substr($rawData,$pointer,$length); $pointer += $length; } } } $idx++; if($idx > 2048) break; } return $parsed[0]; } function rbPack($data){ $rawData = ""; if(is_string($data)){ $rawData .= STR . chr(strlen($data)) . $data; } elseif(is_array($data)){ $rawData .= ARR . chr(count($data)); for($idx=0;$idx<count($data);$idx++) $rawData .= rbPack($data[$idx]); } return $rawData; } function rbGetPath($table){ $schema = rbReadFile(SCHEMA); error($schema); for($i=3;$i<count($schema);$i++){ if(strtolower($schema[$i][0]) == strtolower($table)) return $schema[$i][1]; } } function rbReadFile( |