app.py

경로 필터링이 전혀 없는 Path Traversal / LFI(Local File Inclusion) 취약점 발견

<path:file>은 슬래시(/)도 그대로 받기 때문에 /etc/passwd 같은 절대경로나 ../../ 상대경로를 그대로 넣을 수 있음

@app.route('/<path:file>')
def file(file):
	return open(file).read()

 

 

 

Dockerfile

/flag가 chmod 111(실행 전용, r-x가 아니라 --x--x--x)로 설정돼 있음

즉 읽기 권한이 없어서 open('/flag').read()로는 못 읽음 실행은 되지만 읽을 수 없는 파일

→ 이걸 "실행시켜서 출력을 받아내는" RCE가 필요

RUN gcc /app/flag.c -o /flag \
    && chmod 111 /flag && rm /app/flag.c

 

 

 

 

Werkzeug 1.0.1은 알려진 Directory Traversal 취약점이 존재

응답 해더에 노출된 정보

 

 

 

 

Flask를 debug=True로 실행하면 에러 발생 시 브라우저에 Werkzeug 인터랙티브 디버거가 뜨는데

이 콘솔에 PIN만 뚫으면 임의 파이썬 코드 실행이 가능함

app.run(host='0.0.0.0', port=8000, threaded=True, debug=True)

 

 

 

Flask 앱을 debug=True로 켜면, 코드에서 처리되지 않은 예외(에러)가 발생했을 때 브라우저에 Werkzeug 인터랙티브 디버거라는 화면이 뜸

이건 원래 개발자 편의 기능 — 에러가 난 그 스택 트레이스 위에서 직접 파이썬 코드를 실행해보면서 변수값을 확인하고 디버깅할 수 있게 해주는 콘솔이지.

그런데 이건 완전한 파이썬 REPL임

os.system(), os.popen() 뭐든 실행 가능

그래서 이걸 아무나 접근하게 두면 바로 RCE(원격 코드 실행)가 되는 거고, Werkzeug는 이 콘솔에 접근하려면 **PIN 코드(보통 9자리 숫자)**를 입력해야 함

 

PIN 구하기

Werkzeug 소스에서 PIN을 계산하는 함수를 확인 가능

https://github.com/pallets/werkzeug/blob/main/src/werkzeug/debug/__init__.py

 

 

 

PIN 계산에 사용되는 값

  • username (앱 실행 유저) — Dockerfile에서 $user = dreamhack로 이미 확인됨
  • modname (보통 flask.app)
  • getattr(app, '__name__', getattr(app.__class__, '__name__')) — Flask
  • getattr(mod, '__file__', None) — Flask 라이브러리 경로 (예: /usr/local/lib/python3.8/site-packages/flask/app.py)
  • 머신 고유값들: uuid.getnode()(MAC), machine_id(/etc/machine-id 또는 /proc/self/cgroup의 값 등)

 

 

 

 

 

machine_id 관련 파일들

/etc/machine-id
/proc/sys/kernel/random/boot_id
/proc/self/cgroup

 

 

 

 

@app.route('/<path:file>')에서 URL이 /etc/machine-id면, Flask는 라우트의 구분자로 쓰인 맨 앞 / 하나를 떼어내고 <path:file>에는 etc/machine-id (맨 앞 슬래시 없이)가 들어감

그러면 코드에서 open(file)을 호출할 때:

open("etc/machine-id")

 

 

슬래시 두 개 써서 절대경로 만들기

http://host3.dreamhack.games:18629//etc/machine-id

 

 

연속된 슬래시(//)를 자동으로 슬래시 하나로 합쳐버림...

 

 

 

 

 

 

  • 요청: /..%2fetc%2fmachine-id → 디코딩되면 /../etc/machine-id
  • 라우트 /<path:file>이 맨 앞 슬래시 하나만 구분자로 소비하고, 나머지 ../etc/machine-id 전체가 file 변수에 그대로 들어감 (슬래시가 하나뿐이라 병합될 것도 없음)
  • 코드에서 open("../etc/machine-id") 실행
  • 현재 작업 디렉토리는 WORKDIR /app이고, /app은 파일시스템 루트 바로 아래 있으니까 ../로 한 번만 올라가면 / (루트)에 도달 → /etc/machine-id를 정확히 가리키게 됨
  • curl이나 브라우저는 HTTP 요청을 실제로 보내기 전에, URL 경로에 있는 .이나 .. 같은 dot-segment를 RFC 3986 규칙에 따라 알아서 정리하므로 ../를 ..%2F로 인코딩해야 함

 

 

 

 

 

 

 

확인한 PIN 함수로 PIN 구함

 

 

 

 

에러페이지에서 코드 부분에 마우스를 올리면 콘솔 아이콘이 나타남

 

 

 

 

콘솔 아이콘을 클릭하여 디버거 콘솔에 계산한 PIN을 입력하면 

콘솔 사용 가능

 

 

 

flag 파일을 읽으면 끝

 

 

 

 

 

+ Recent posts