자습

프로젝트 2, 3주차 파일 다운로드 실험

whydontyoushovel 2025. 3. 27. 21:51

 

목차

♪  a 태그로 파일 다운로드

  • 기존 구현 방식
  • 기존 방식의 특징
  • a 태그 적용 방식
  • 정리

 

 

   a태그로 파일 다운로드 구현      

▷기존 파일 다운로드 구현 방식

다운로드 버튼 누르면 게시글의 id를 download.php에 전달

 

<?php
    $idx = intval($_GET['id']);

    $db_conn = mysqli_connection();

    $sql = "SELECT * FROM upload_table WHERE idx = $idx";
    $result = mysqli_query($db_conn,$sql);
    $row = mysqli_fetch_array($result);

    if(!$row) {
        die('파일 정보를 찾을 수 없습니다.');
    }

    $image_path = $row['image_url'];
    $name = explode("/",$image_path);
    $file_name = end($name);
    $file_size = filesize($image_path);

    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime = finfo_file($finfo,$image_path);
    finfo_close($finfo);

    if (file_exists($image_path)){ //파일 다운로드 지시를 위한 헤더
        header("Content-Type:{$mime}"); 
        header("Content-Disposition:attachment;filename={$file_name}");
        header("Content-Length:{$file_size}");
        header("Cashe-Control:cache,must-revalidate");
        header("Pragma:no-cache");
        header("Expires:0");

        $fp = fopen($image_path,"r");

        while(!feof($fp)) {
            $buf = fread($fp,$file_size);
            $read = strlen($buf);
            print($buf);
            flush();
        }

        fclose($fp);

    } else {
        die("파일이 존재하지 않습니다.");
    }
    
  ?>

다운로드 프로세스 진행

 

 

▷기존 방식의 특징

  • id 데이터에 intval 함수 사용으로 SQLi 위험도 낮춤
  • 악성파일 업로드 취약점 + XSS 이용해서 강제 다운로드(CSRF) 시킬 수 있다고 생각했는데 어찌된 영문인지 이미지 파일을 불러오고 있음. 이미지 태그에 넣어서 그런 듯함. (브라우저는 이미지 태그를 다운로드하도록 하지 않음)
<img src="http://__ip__/download.php?id=20">

(그러고보니 파일 읽기 페이지로 img 소스 불러오기가 이런 식이었음.)

  • 만약 SQL Injection에 대한 대비가 없다면 파일 다운로드 공격으로 서버 소스코드 다운로드 가능

ㅎㄷㄷ

 

<강제 다운로드시키는 법>

1. 스크립트로 다운로드 페이지 강제 리다이렉션

근데 게시글 출력 페이지에서 nl2br 적용했을 땐 스크립트 실행이 안되더라...스크립트 내부에 <br/> 태그가 포함돼서 작동이 안되는 듯함

2. iframe으로 다운로드 페이지 불러오기

첫번째는 iframe 태그로 다운로드시킨 거, 두번째는 window 스크립트로 다운로드시킨 거.

 

어차피 브라우저에서 다운로드했다고 알려주긴 하지만...

그래도 자동+페이지 변화없이 다운로드시키기엔 2,3번 방법이 적절한 듯함.

**근데 모든 파일 시그니처를 png로 하고 올리기도 하고 download.php에서 MIME타입을 지정해서 그런지 png파일로 다운받아짐.. 그래서 내부에 서버 측 스크립트를 작성해도 실행되지 않은 채 다운로드됨. 소스코드를 받을 수 없다면 어떻게 악용할 수 있을까 생각하다가 떠오른 게.. ↓

 

≫ 실행파일을 올려볼까?

실습 파일

 

파일 업로드
(이때 확장자 및 파일 시그니처 검사는 생략함)

다른 게시글의 내용 위치에 강제 다운로드 스크립트 작성 후 수정

게시글 읽기 페이지 접근하여 실행파일 강제 다운로드

 

실행 성공

 

(파일 다운로드 공격..이라기보다 파일 업로드 +  CSRF (+ XSS) 짬뽕인 것 같음)

 

 

 

▷a태그로 파일 다운로드 구현

//현재 코드
<a href="<?=$row['image_url']?>" download>다운로드</a>
  • DB에서 이미지 저장 경로 불러와서 href 속성에 넣음
  • download 속성 추가해서 해당 이미지 다운로드 가능 (인터넷 익스플로러 브라우저에서는 구현 못한다는 듯)
  • download 속성은 SOP의 영향을 받아서 외부 파일은 다운받지 못하게 하는 듯함.

브라우저 화면

html 태그
  • 저 조잡한 파란 글자를 누르면 이미지 파일을 다운받을 수 있음.
  • href 속성을 수정해서 어디까지 할 수 있을까.

 

>>소스코드 요청

SQLi로 서버가 download.php 파일을 넣게 만듦 

다운받은 파일을 열었을 때

SQLi로 서버가 login_case5.php (로그인 처리) 파일을 넣게 만듦

다운로드한 파일을 열어보니 login_case5.php 파일이 아니라 login.php 파일이 나옴
이번에도 php 코드는 보이지 않음 

요청 기록

 

 

※ 모든 요청을 intercept하여 다시 확인한 결과 ※

 id 파라미터에 SQLi를 시도하여 파라미터 수정 후 요청 전송

→  post.php 요청 전송되고 download.php?id=1 요청도 전송됨

→  다운로드 버튼 클릭

→  login_case5.php 요청 전송 (여기서 login.php로 리다이렉션)

→  login.php 요청 전송

→  login.php 파일 다운로드 됨

Q1.  post.php 파일을 파라미터 수정 없이 요청하면 download.php?id=1 요청이 안 생김
 SQLi을 통한 파라미터 수정과 download.php는 무슨 연관이 있길래 갑자기 생긴 거지?
A1. 내가 지금 게시글 읽기 페이지에 출력되는 이미지 파일을 download.php로 불러오고 있음.
(<img src="/download.php?id=어쩌구">)
근데 이때 union select 1,2,...8 limit 1,1의 "1"이 download.php?id 파라미터 위치여서 download.php?id=1을 요청한 거임.

그러면 SQLi 없이 정상적으로 요청할 땐 download.php를 왜 요청하지 않느냐! 할 수 있음.
이해함! 버프스위트 http history에 안 떴으니까!!
근데 intercept 켜고 새로고침하면 post.php?id=47 이거 보려고 할 때 download.php?id=47도 뜸!

그냥 내가 버프스위트 설정을 그렇게 했던 거임!!! 젠장
download.php?id=47은 png 형식이지만 download.php?id=1은 html 형식이어서 필터링되지 않은 거였음!!

 

Q2.  login_case5.php를 다운받으려고 했지만 리다이렉션된 페이지인 login.php 페이지가 다운받아짐
 왜 다운로드 기능에 리다이렉션이 적용되는 거지?
A2. a태그 다운로드는 브라우저가 처리하는 방식이라 그런 듯함.

html 페이지를 구성할 때 
브라우저가 서버로 해당 파일(login_case5.php) 요청. 

서버에서 login_case5.php 파일 요청 받고 코드 처리 후 전달하는데 이때 로그인 데이터가 없어서 login.php 페이지로 리다이렉션 시켜버린 듯함.

결국 브라우저에 전달된 파일은 login.php 파일이고 다운로드되는 것도 그 파일.

 

이런 고로 소스코드를 받긴 힘들 듯함.

그럼 쉘 명령어를 파일에 넣어서 요청하면 실행된 결과를 알 수 있지 않을까?

 

 

>>서버 측 스크립트 파일 업로드 후 다운로드

/etc/passwd 파일을 읽는 명령어 삽입 후 업로드

파일 다운로드

 

파일 열었더니 /etc/passwd 파일 내용을 볼 수 있었음

 

 

소스코드도 볼 수 있을까?

 

소스코드 출력시키는 명령어를 삽입하고 업로드

다운받아서 확인했더니 소스코드 안 나옴
아 현재 업로드되는 디렉토리가 upload.php보다 하위 디렉토리라서 그런가?

코드 수정 후 업로드

다운로드 받아서 읽어보니 소스코드 출력되는 중
디렉토리 위치가 문제였던 거임

 

지금은 다운로드를 파일 저장 경로를 통해 하는 중
서버 측 스크립트 업로드 가능 + 저장 경로 앎 -> 파일 업로드 조건 충족
이건 그냥 웹쉘도 가능한 경우였음...
만약 download.php를 통해 다운받게 하면 그래도 명령어가 실행돼서 저장될까?

 

내 생각엔 안될 것 같음. download.php로 받으면 png 형식으로 저장되어서 서버 측 스크립트를 실행할 거 같지가 않음.

 

<a href="download.php?id=<?=$row['idx'];?>">다운로드</a>
위와 같이 소스코드를 수정하고 동일한 게시글의 파일을 받았을 때

 

역시나 png 파일로 저장되고 있음
저걸 php 파일로 변경한 뒤 파일을 열어봄

 

코드는 실행되지 않았음
응답을 intercept해서 MIME 타입 text/html로 수정해도 저장만 phtml로 될 뿐 스크립트가 실행되지 않음
당연함 파일은 이미 서버의 손을 떠난 상태이기 때문임.....

 

요청과 응답 사이에서 내가 어떻게 조작할 틈이 없어 보임..

그래서 SQLi를 활용해봄

 

현재 DB에서 idx값을 가져와 download.php의 파라미터로 사용하고 있기 때문에 파라미터에 SQLi를 시도함

idx값이 출력되는 1번 컬럼에 a 태그 스크립트를 삽입하여 다운로드되는 파일을 변경

 

다운로드한 파일을 읽어봤지만 역시나 소스코드는 출력되지 않음
대신 아까처럼 이미지 저장 경로로 삽입해봄

 

idx 출력 위치에 a태그와 서버 측 스크립트가 포함된 업로드 파일의 저장 경로를 추가

 

다운로드하여 읽어보니 역시나 명령어 실행 결과가 출력되고 있음
저장 경로를 알아야 가능한 방법임..
근데 이것도 그냥 악성파일 업로드 취약점이라 웹쉘 올려서 요청하면 끝나는 겨..

 

 

 

≫ download.php가 id값이 아닌 파일 경로 값을 받는다면

<?php
    $image_path = $_GET['file']; //file경로 GET파라미터로 전달
    $name = explode("/",$image_path);
    $file_name = end($name);
    $file_size = filesize($image_path);
?>

이거 완전 내 세상

다 받을 수 있음

 

파일 경로로 하더라도 저렇게 정직하게 표현하는 곳이 있을까 싶긴 하지만..

 

 

 

정리

1. 다운로드 방식

ㄱ) 별도의 파일 읽기/다운로드 페이지 이용 (download.php)

ㄴ) a 태그 download 속성

 

2. 공격 시나리오

ㄱ) 소스코드 다운로드

ㄴ) 서버 측 스크립트 실행 (악성파일 업로드)

 

3. download.php?id=10 방식

-> 서버 측 스크립트 업로드하고 해당 파일 다운로드 시도

-> 기본적으로 소스코드 단순 읽기(실행x) 후 다운로드

장점 파일 저장 경로 숨길 수 있음 (-> 파일 업로드 공격 어느정도 대응 가능)
서버에서 접근 권한 통제할 수 있음
단점 SQLi 대비하지 않으면 소스코드 털림

 

4. a 태그 방식 

-> 서버 측 스크립트 업로드하고 해당 파일 다운로드 시도

-> 기본적으로 소스코드 실행 후 다운로드

장점 사용자가 업로드한 파일의 저장경로 노출 안되면 소스코드 숨길 수 있음
단점 저장경로 노출되면 웹쉘 등 서버 측 스크립트 실행 결과 받을 수 있는데 그럴 거면 아예 파일 경로 URL로 요청하는 게 편리 
다운로드 관련 인증/인가 쪽 통제하기 어렵다..?

 

5. a태그랑 download.php 결합

<a href="download.php?id=10" download>다운로드</a>

 

  • 저장경로 숨길 수 있음
  • 만약 download.php에 sqli가 있어도 소스코드는 이미 실행된 뒤에 다운로드됨.
  • download.php에서 다운로드나 파일 접근 관련 인증/인가 통제 코드 추가하기 편함. 
  • ***근본적인 대응은 아님