본문 바로가기

보안지식/File Upload

file upload 공격 - 실습 페이지 만들기

안녕하세요 오늘은 간단하게 제가 만든 게시판 있었죠?

그걸 이용해서 일부러 file upload 공격 통하게 만들어 보겠습니다.

 

간단하죠 뭔가 검사하는 코드만 없애면 되겠죠

 

그리고 하이퍼링크에 download의 기능을 이용하면 되겠죠.

그럼 취약점이 바로 나올 겁니다!

 

일단 제가 만든 게시판 코드를 조금 수정을 해서 만들어보겠습니다.

 

***주석처리는 무시해도 됩니다 보안 코딩이라 취약점 위해 지운 겁니다!!***

 

 

-write_process_board.php

 

<?php
    include 'DB_INFO.php'; //데이터 베이스 정보

    session_start(); //세션 시작

    if(!isset($_SESSION['login_id'])) {
        //로그인하지 않은 사용자
        header("Location: login.php"); //login 화면으로 바꾼다
        exit(); //이 페이지를 바로 닫는다
    }

    //게시판 데이터베이스 연결
    $conn = mysqli_connect($host,$username,$password, $db_board);

    //데이터베이스 오류시 종료
    if(mysqli_connect_errno()) {
        die("데이터 베이스 오류: ". mysqli_connect_error());
    }

    //POST로 전달된 정보 받기
    if(isset($_POST['board_id'])){
        $board_id = filter_var(strip_tags($_POST['board_id']),FILTER_SANITIZE_SPECIAL_CHARS);
    } else {
        $board_id = null;
    }
    $title = filter_var(strip_tags($_POST['title']),FILTER_SANITIZE_SPECIAL_CHARS);
    $detail = filter_var(strip_tags($_POST['detail']),FILTER_SANITIZE_SPECIAL_CHARS);

    if ($_SERVER['REQUEST_METHOD'] == 'POST' && !empty($_FILES['file'])) {
        // 업로드된 파일 정보 가져오기
        $file_name = $_FILES['file']['name'];
        $timestamp = time(); // 현재 시간을 초로 반환
        $new_file_name = /*$timestamp . '_' .*/$file_name; // 현재 시간과 원래 파일 이름을 합쳐 새로운 파일 이름 생성
        $file_tmp_name = $_FILES['file']['tmp_name'];
        $file_size = $_FILES['file']['size'];
        $file_error = $_FILES['file']['error'];
        //$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif','text/plain','application/zip','application/x-hwp','application/msword','application/vnd.ms-excel','application/pdf']; //MIME 허락 된 것
        //확장자 허락 된 것
        //$allowed_extensions = array("jpg","png","gif","txt","zip","hwp","word","xls","xlsx","pdf");

        //sql공격 방지용
        $new_file_name = mysqli_real_escape_string($conn,$new_file_name);

        // 파일 업로드가 정상적으로 처리되었는지 확인
        //if ($file_error === UPLOAD_ERR_OK) {
            // 파일 MIME 타입 확인, 조작됬는지 확인
            //$file_mime_type = mime_content_type($file_tmp_name);
            //$finfo = finfo_open(FILEINFO_MIME_TYPE);
            //$file_mime_type = finfo_file($finfo, $file_tmp_name);
            //finfo_close($finfo);
            
            //if (in_array($file_mime_type, $allowed_mime_types)) {

                //파일 확장자 확인
            //    $file_ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
            //    if(in_array($file_ext, $allowed_extensions)) {
                    // 파일 저장 경로
                    //$upload_path = '/path/to/upload/directory/' . $new_file_name;

                    $upload_path = '/var/www/html/uploads/' . $new_file_name;
                    // 파일 이동 및 저장
                    if (move_uploaded_file($file_tmp_name, $upload_path)) {
                        $_SESSION['file_error'] =  '파일 업로드 성공';
                    } else {
                       $_SESSION['file_error'] =  '파일 업로드 실패';
                    }
                //} else {
                    //$_SESSION['file_error'] =  '잘못된 파일 형식입니다';
                //}
            //} else {
            //    $_SESSION['file_error'] =  '잘못된 파일 형식입니다';
            //}
       //}
    }
    //session으로 유저 이름 받기
    $user_id = $_SESSION['login_id'];

    if(isset($board_id)) {
        //board_id가 있다는 것은 수정을 의미
        $sql = "UPDATE $db_board SET title = ?, detail= ?, file_name= ? WHERE board_id= ? ";
        //preparedstatement 적용
        $stmt = mysqli_prepare($conn, $sql);
        mysqli_stmt_bind_param($stmt, 'sssi', $title, $detail, $new_file_name, $board_id);
    }
    else {
        //board_id가 없으니 새로 만드는 sql문
        $sql = "INSERT INTO $db_board (id, title, detail,file_name) VALUES (?, ?, ?,?)";
        $stmt = mysqli_prepare($conn, $sql);
        mysqli_stmt_bind_param($stmt, 'ssss', $user_id ,$title, $detail, $new_file_name);
    }

    //sql문 실행
    if(mysqli_stmt_execute($stmt)) {
        //수정인 경우 바로 게시판 보이게 함
        if(isset($board_id)) {
            $_SESSION['write_error'] = '수정되었습니다!';
            header("Location: view_board.php?index=$board_id");
        }
        else {
            $board_id = mysqli_insert_id($conn); // 새로 생성한 게시물의 id를 가져옴
            $_SESSION['write_error'] = '작성되었습니다!';
            header("Location: view_board.php?index=$board_id");
        }
    } else {
        $_SESSION['write_error'] = '작성 중 오류가 발생하였습니다.';
        header("Location: view_board.php");
    }

    mysqli_stmt_close($stmt);

    exit();
?>

 

 

-view_board.php

 

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <link rel="stylesheet" href = "/css/style_view.css">
        <title>게시물 보는중</title>
    </head>
    <body>
        <h1>VIEWING</h1>
        <p>
        <?php 

            include 'DB_INFO.php'; //데이터 베이스 정보

            //데이터베이스 연결
            $conn = mysqli_connect($host,$username,$password,$db_board);

            //데이터베이스 오류시 종료
            if(mysqli_connect_errno()) {
                die("데이터 베이스 오류: ". mysqli_connect_error());
            }

            session_start(); //세션 시작

            if(!isset($_SESSION['login_id'])) {
                //로그인하지 않은 사용자
                header("Location: login.php"); //login 화면으로 바꾼다
                exit(); //이 페이지를 바로 닫는다
            }

            //GET방식으로 전달된 index 받는다
            $board_id = filter_var(strip_tags($_GET['index']),FILTER_SANITIZE_SPECIAL_CHARS);
            //가독성 위해 다른 변수에 저장
            $last_viewed_time_for_board_id = 'last_view_time_'.$board_id;

            // 세션에 마지막 조회 시간 정보가 있는지 확인
            if (!isset($_SESSION[$last_viewed_time_for_board_id])) {
                // 세션에 마지막 조회 시간 정보가 없으면 현재 시간을 저장
                $_SESSION[$last_viewed_time_for_board_id] = time();
            } else {
                // 세션에 마지막 조회 시간 정보가 있으면 일정 시간이 지났는지 확인
                $last_view_time = $_SESSION[$last_viewed_time_for_board_id];
                $current_time = time();
                $time_diff = $current_time - $last_view_time;
                if ($time_diff >= 60) { // 60초(1시간) 이상 지났으면 조회수 증가
                    //조회수 증가하는 sql문
                    $sql_view = "UPDATE $db_board SET views = views + 1 WHERE board_id = ? ";

                    //prepared 적용
                    $stmt = mysqli_prepare($conn, $sql_view);
                    //바인딩
                    mysqli_stmt_bind_param($stmt, 'i' , $board_id);
                    //실행
                    mysqli_stmt_execute($stmt);

                    // 세션에 현재 조회 시간을 저장
                    $_SESSION[$last_viewed_time_for_board_id] = $current_time;
                }
            }

            //작성된 게시물들 조회 문
            $sql = "SELECT * FROM $db_board WHERE board_id = ? ";
            //prepared 적용
            $stmt = mysqli_prepare($conn, $sql);
            //바인딩
            mysqli_stmt_bind_param($stmt, 'i' , $board_id);
            //실행
            mysqli_stmt_execute($stmt);
            //결과값 받기
            $result = mysqli_stmt_get_result($stmt);
            
            //게시물 출력
            while($row = mysqli_fetch_assoc($result)) {

            ?>
            <div class="view-top">
            <?php
                    echo '
                        <div class="view-top-title">'.$row['title'].'</div>

                        <div class="view-top2">
                            <div>ID: '.$row['id'].'</div>
                            
                            <div> 조회수: '.$row['views'].'</div>
                            
                            <div> 좋아요: '.$row['like_value'].'</div>
                            
                            <div> Date: '.$row['date_value'].'</div>              
                        </div>
                        <hr>
                        <div class="view-mid"> '.$row['detail'].' </div>
                        <hr>
                    ';

                

                if (isset($row['file_name'])) {
                    /*$file_name = implode('_', array_slice(explode('_', $row['file_name']), 1)); // '_' 문자를 기준으로 분리 후 첫 번째 요소를 제외한 나머지 요소를 모두 합쳐서 파일명을 구성합니다.
                    $download_url = 'download_process.php?filename='.$row['file_name'];
                    echo '
                        <div class="view-bottom"> FILE: <a href="'.$download_url.'">'.$file_name.'</a> </div>
                    
                    ';*/

                    
                    $file_path = './upload/' . $row['file_name'];
                    $download_url = './uploads/' . urlencode($row['file_name']); // 다운로드 URL 생성
                
                    echo 'FILE: <a href="'.$download_url.'" download="'.$row['file_name'].'">'.$row['file_name'].'</a>';
                }
            ?>
                
                
                <div class="view-btn">
                    <!--게시물 좋아요 버튼을 보여준다-->
                    <form method='POST' action='like.php'>
                        <input type='hidden' name='board_id' value=' <?php echo $row['board_id'] ?> '>
                        <button>좋아요!</button>
                    </form>

                    <form action="write_board.php" method="POST"> 
                        <button name="write">게시판 작성</button>
                    </form>

                    <form action="board.php">
                        <button>메인페이지로</button>
                    </form>

            
            <?php


                //로그인한 사용자의 정보 가져온다
                $user_id = $_SESSION['login_id'];

                //게시물 작성자와 로그인한 ID와 일치한 경우
                if($row['id'] == $user_id) {
                    //게시물 수정 버튼을 보여준다
                    echo "<form method='GET' action='fix_board.php'>
                            <input type='hidden' name='board_id' value='".$row['board_id']."'>
                            <button>게시물 수정</button>
                          </form>";

                    //게시물 삭제 버튼을 보여준다
                    echo "<form method='POST' action='delete_board.php'>
                            <input type='hidden' name='board_id' value='".$row['board_id']."'>
                            <button>삭제</button>
                          </form>";
                }
                            
            }


            mysqli_stmt_close($stmt);
        ?>
                
            </div><!--view-btn의 div 종료점 입니다.-->

        </p>
        <p>
        <?php session_start();
                if (isset($_SESSION['write_error'])) {
                    echo $_SESSION['write_error'];
                    unset($_SESSION['write_error']);
                }
        ?>
        </p>
        <p>
        <?php session_start();
                if (isset($_SESSION['file_error'])) {
                    echo $_SESSION['file_error'];
                    unset($_SESSION['file_error']);
                }
        ?>

        </div> <!--view-top의 div 종료점 입니다.-->
    </body>
</html>

 

 

 

원래는 download.php을 따로 만들어서 다운로드 경로를 숨겼잖아요?

그걸로 대응하는 것을 잠시 실습용 페이지 위해 지웠고요

 

그리고 작성하고 파일 저장할 때 파일 확장자 검사도 하는 것을 잠시 주석 처리 했고요

 

그리고 실습할 때 저장할 파일을 따로 만들었습니다.

 

한번 올려 볼게요!

 

<?php
	echo "Script Running!";
?>

 

올려보면?

 

 

잘 올라갔네요!

 

그다음에 다운로드 링크 복사 해보겠습니다.

 

http://localhost/uploads/test2.php

 

이렇게 나오네요!

 

그 다음에 이 링크 통해 들어가 보면?

 

이렇게

 

파일 업로드 취약점이 나오는 것을 알 수 있습니다.

 

그러니깐 파일 확장자 검사, 파일 내부 코드 검사 없거나 그저 하이퍼링크로 download 기능을 이용하면 이렇게 취약점이

나오는 것을 알 수 있습니다.

 

 

다음 포스팅에 이 취약점을 이용해서 공격 시나리 오을 짜보겠습니다. 감사합니다

'보안지식 > File Upload' 카테고리의 다른 글

file download 취약점  (0) 2023.06.15
file upload 공격 - 시나리오  (0) 2023.06.05
File Upload - 실습 2번 풀이  (0) 2023.06.03
File Upload - 실습 1번 풀이  (0) 2023.06.02
파일 업로드 취약점(File upload)  (0) 2023.06.01