본문 바로가기

개발과제/보안추가

[노말틱 모의 해킹 취업반 개발과제 (2)] 보안 관련해서 보완 하기

안녕하세요! 이태까지 제가 만든 웹 사이트에 보안을 좀 더 보완하겠습니다!

 

오늘은 파일 업로드에 보안을 좀 더 강화하겠습니다

 

저는 지정한 디렉터리에 저장을 하게 했는데 이는 적절한 보안 조치 없이 그냥 업로드된 파일에 대한 완전한 제어를 허용합니다! 그래서 보안에 취약하겠죠?? 더군다나 텍스트 파일, 압축파일 형식이니 악의적인 파일이 업로드될 수 있죠

 

그러니 저는 이걸 방지하기 위해 파일 확장자 검증 하겠습니다.

 

여기서 의문점이 있을 수 있습니다.

 

-따로 MIME 형식에서 허락된 것만 변수로 모은 게 있잖아요??

맞아요! 저희는 $allowed_mime_types에다가 형식자를 저장했죠? 근데 클라이언트는 파일 형식을. php을 바꿔서

.exe로 바꾸던가 .txt로 바꿔서 올릴 수가 있습니다! 이러면 문제점이 생기게 됩니다.

 

 

그러니깐 저는 확장자 검증을 하고 또 추가로 검증을 하는 겁니다

 

php에 좋은 내장 함수가 있습니다 finfo_file() 함수입니다.

finfo_file() 함수는 파일의 MIME 타입을 반환합니다. 근데 MIME 타입이 특정하지 않은 경우에는 false을 반환합니다!

 

즉! 클라이언트가 파일 확장자를 조작하여 업로드하더라도 finfo_file() 함수는 파일 내용을 기반으로 실제 MIME 타입을

검사하기에 더욱 정확한 검증이 가능합니다!!

 

그러니 저희가 파일 올릴 때 쓰이던 write_process_board.php로 가겠습니다

 

그리고 코드 추가 하겠습니다

//파일 업로드가 정상적으로 처리되었는지 확인

밑에 if 절에 //파일 MIME 타입 확인, 조작 됐는지 확인

한 줄에 3줄 추가하겠습니다

        $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);

 

이렇게 변경하겠습니다!

 

그리고 MIME 검사를 했으니 확장자도 같이 검사하겠습니다.

 

굳이.. 할 필요는 없지만 제가 찾기론 모든 공격을 막을 수 없다고 합니다.

그러니 MIME랑 확장자도 같이 검사하게 코딩 추가 하겠습니다.

 

        // 파일 업로드가 정상적으로 처리되었는지 확인
        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;

                    // 파일 이동 및 저장
                    if (move_uploaded_file($file_tmp_name, $upload_path)) {
                        $_SESSION['file_error'] =  '파일 업로드 성공';
                    } else {
                        $_SESSION['file_error'] =  '파일 업로드 실패';
                    }
                } else {
                    $_SESSION['file_error'] =  '잘못된 파일 형식입니다';
                }
            } else {
                $_SESSION['file_error'] =  '잘못된 파일 형식입니다';
            }
        }

 

strtolower는 문자열을 모두 소문자로 변환해 주는 역할을 합니다

pathinfo 함수는 파일 경로의 정보를 배열 형태로 반환해 주는 함수입니다!

 

 

 

이러면 파일이 조작되어도 함수 덕분에 확실히 알 수가 있겠죠 한번 실험해보겠습니다

 

수정했습니다

원래 happy.txt에 php구문을 넣어서 파일이. php형식으로 되게 했습니다

 

그리고 txt 확장자인 채로 보내봤습니다

 

 

원래 파일은 happy.php인데 제가 happy.txt.로 바꾸고 올리는 중입니다

 

그러고 나면??

 

너 안되!

 

이렇게 막히는 것을 볼 수 있습니다!

 

이렇게 해서 파일 업로드 할 때 보안을 강화해봤습니다. 물론 이게 만능까지는 아닐 겁니다.

그래도 이 정도면 파일 업로드 할 때 검증은 확실한 거죠!

 

이제 총 코드 보여드리고 글을 마치겠습니다

 

 

-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 = mysqli_real_escape_string($conn,$_POST['board_id']);
    } else {
        $board_id = null;
    }
    $title = mysqli_real_escape_string($conn,$_POST['title']);
    $detail = mysqli_real_escape_string($conn,$_POST['detail']);

    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;

                    // 파일 이동 및 저장
                    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'];
    echo $board_id;
    if(isset($board_id)) {
        //board_id가 있다는 것은 수정을 의미
        $sql = "UPDATE $db_board SET title = '$title', detail='$detail', file_name='$new_file_name' WHERE board_id='$board_id' ";
    }
    else {
        //board_id가 없으니 새로 만드는 sql문
        $sql = "INSERT INTO $db_board (id, title, detail,file_name) VALUES ('$user_id', '$title', '$detail','$new_file_name')";
    }

    //sql문 실행
    if(mysqli_query($conn,$sql)) {
        //수정인 경우 바로 게시판 보이게 함
        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");
    }

    exit();
?>

 

감사합니다