Dreamhack File-Download Write-up
File Download Vulnerability
파일 다운로드 취약점(File Download Vulnerability) 은 웹 서비스를 통해 서버의 파일 시스템에 존재하는 파일을 내려받는 과정에서 발생하는 보안 취약점이다. 이용자가 다운로드할 파일의 이름을 임의로 정할 수 있을 때 발생한다.
웹 서비스는 이용자가 업로드한 파일을 다운로드받거나 이미지를 불러올 때 특정 디렉토리에 있는 파일만 접근하도록 해야 한다. Path Traversal을 이용한 파일 다운로드 취약점은 파일 이름을 직접 입력받아 임의 디렉토리에 있는 파일을 다운로드받을 수 있는 취약점이다.
파일 다운로드 취약점이 자주 발생하는 URL 패턴:
# 정상 요청
https://vulnerable-web.dreamhack.io/download/?filename=notes.txt
# Path Traversal 공격
https://vulnerable-web.dreamhack.io/download/?filename=../../../../../etc/passwd
방어할 때는 ..뿐 아니라 파일 시스템에 대해 부적절한 접근을 시도할 수 있는 문자열도 함께 차단해야 한다.
문자열 역할 및 설명
| / | 파일 경로에서 디렉토리 구분자 |
| \ | Windows에서 디렉토리 구분자 |
| : | Windows에서 드라이브와 파일 경로 구분자 |
| ~ | 홈 디렉토리를 나타냄 |
문제 개요
메모 업로드/다운로드 기능을 제공하는 웹 서비스에서 /upload와 /read 엔드포인트의 검사 수준이 달라 발생하는 File Download 취약점을 이용해 flag.py 파일을 읽어 플래그를 획득하는 문제.
엔드포인트 분석
/upload
@APP.route('/upload', methods=['GET', 'POST'])
def upload_memo():
if request.method == 'POST':
filename = request.form.get('filename')
content = request.form.get('content').encode('utf-8')
if filename.find('..') != -1: # '..'이 있으면 차단!
return render_template('upload_result.html', data='bad characters,,')
with open(f'{UPLOAD_DIR}/{filename}', 'wb') as f:
f.write(content)
return redirect('/')
return render_template('upload.html')
파일명에 ..이 포함되어 있는지 검사하여 상위 디렉토리로 이동하는 시도를 방지한다. 따라서 ../flag.py로 파일명을 입력하면 bad characters,, 메시지와 함께 차단된다.
/read
@APP.route('/read')
def read_memo():
error = False
data = b''
filename = request.args.get('name', '')
# filename에 대한 특별한 검사 진행하지 않음!
try:
with open(f'{UPLOAD_DIR}/{filename}', 'rb') as f:
data = f.read()
except (IsADirectoryError, FileNotFoundError):
error = True
return render_template('read.html',
filename=filename,
content=data.decode('utf-8'),
error=error)
/upload와 달리 ..에 대한 검사를 전혀 하지 않아 파일 다운로드 공격에 취약하다.
취약점 분석
/upload와 /read의 검사 수준이 다르다는 점이 핵심 취약점이다.
엔드포인트 .. 필터링 결과
| /upload | 있음 | ../flag.py 업로드 시도 → 차단 |
| /read | 없음 | ../flag.py 읽기 요청 → 통과! |
/read는 name 파라미터를 검증 없이 그대로 경로에 사용하므로, ../flag.py를 입력하면 uploads/../flag.py = flag.py가 열린다.
풀이 단계
Step 1. /upload에서 ../flag.py로 업로드 시도
Filename: ../flag.py
Content: Give me the flag
→ bad characters,, 반환 — .. 필터링에 차단됨
Step 2. ..이 없는 정상 파일명으로 업로드
Filename: test
Content: Give me the flag
→ uploads/test로 저장 성공
Step 3. /read 엔드포인트에서 ../flag.py로 직접 접근
/read?name=../flag.py
실제로 열리는 파일:
uploads/../flag.py = flag.py ← uploads 탈출!
→ flag.py 내용 출력:
FLAG = 'DH{...}'
Flag

방어 방법
특정 페이지 또는 기능에 대해 충분히 검사하지 않거나, 페이지 간 검사 수준이 다르면 공격자는 해당 취약점을 악용하여 파일 다운로드 공격을 시도할 수 있다. 따라서 모든 페이지에서 일관되게 상위 디렉토리 이동 시도를 차단해야 한다.
# 취약한 코드 - /read에서 검사 없음
filename = request.args.get('name', '')
with open(f'{UPLOAD_DIR}/{filename}', 'rb') as f:
data = f.read()
# 안전한 코드 - 모든 엔드포인트에서 일관된 검사
filename = request.args.get('name', '')
if filename.find('..') != -1:
return 'bad characters'
# 추가로 경로가 허용된 디렉토리 안인지 확인
filepath = os.path.abspath(os.path.join(UPLOAD_DIR, filename))
if not filepath.startswith(os.path.abspath(UPLOAD_DIR)):
return '접근 거부!'
with open(filepath, 'rb') as f:
data = f.read()
'CTF' 카테고리의 다른 글
| pwntools - 1 (0) | 2026.06.17 |
|---|---|
| SSRF (0) | 2026.06.03 |
| Image-Storage (0) | 2026.06.02 |
| Command Injection (0) | 2026.06.02 |
| Mango (0) | 2026.06.01 |