상세 컨텐츠

본문 제목

[보안 공부] 5주 차 - 게시판 파일 업로드 구현

컴퓨터/보안 수업(Segfault)

by 디멘터 2024. 5. 22. 10:16

본문

※ 주의 : 보안 관련 학습은 자유이지만, 학습 내용을 사용한 경우 법적 책임은 행위자에게 있습니다.
(Note: Security-related learning is allowed, but the individual is solely responsible for any legal consequences arising from the use of the acquired knowledge.)

※ 공부 기록용 포스트이며, 검수를 하고 올리지만 간혹 잘 못된 정보가 있을 수도 있습니다.

※ 보안에 취약한 코드이므로 사용하시면 안 됩니다.

 코드 구현에 있어서 인공지능의 도움을 많이 받았습니다.

 

[보안 공부] 5주 차 - 게시판 파일 업로드 구현


파일 업로드 구현

 

1. DB구조에 속성 board_file_path 추가하기 (varchar 255)

DB구조

 

2. board_content_write.php에 파일 업로드 처리 코드 추가,

 DB구조 변경으로 인한 SQL 수정, 파일 업로드 버튼 추가,

 파일이 업로드되는 폴더 생성

더보기

<!-- board_content_write.php -->

<?php
require('dblogin_board.php');
require_once('logined_check_one.php'); // 로그인된 사용자 체크

if (isset($_POST['submit'])) {
    $title = $_POST['title'];
    $content = $_POST['content'];
    $writer = $logined_one; // 로그인된 사용자의 ID를 작성자로 사용
    $datetime = date('Y-m-d H:i:s'); // 현재 시간 설정
    $board_type = 1; // 게시판 유형 1 = 전체 게시판. 2는 삭제 게시판.

    // 파일 업로드 처리
    $target_dir = "uploads/";
    if (!is_dir($target_dir)) {
        mkdir($target_dir, 0777, true);
    }
    $target_file = $target_dir . basename($_FILES["file"]["name"]);
    $uploadOk = 1;
    $fileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));

    // 파일 이미 존재하는지 확인
    if (file_exists($target_file)) {
        echo "Sorry, file already exists.";
        $uploadOk = 0;
    }

    // 파일 크기 확인
    if ($_FILES["file"]["size"] > 500000) {
        echo "Sorry, your file is too large.";
        $uploadOk = 0;
    }

    // 허용된 파일 형식 확인
    $allowedTypes = array("jpg", "png", "jpeg", "gif", "pdf", "txt");
    if (!in_array($fileType, $allowedTypes)) {
        echo "Sorry, only JPG, JPEG, PNG, GIF, TXT, & PDF files are allowed.";
        $uploadOk = 0;
    }

    // 파일 업로드 시도
    if ($uploadOk == 1 && $logined_one === $board_writer) { //작성자 맞는지 한 번 더 검증
        if (move_uploaded_file($_FILES["file"]["tmp_name"], $target_file)) {
            echo "The file ". basename($_FILES["file"]["name"]). " has been uploaded.";
        } else {
            echo "Sorry, there was an error uploading your file.";
        }
    } else {
        $target_file = null; // 업로드 실패 시 파일 경로를 null로 설정
    }

    // 게시글 데이터 삽입
    $sql = "INSERT INTO board_table (board_type, board_title, board_content, board_writer, board_datetime, board_file_path) VALUES (?, ?, ?, ?, ?, ?)";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("isssss", $board_type, $title, $content, $writer, $datetime, $target_file);

    if ($stmt->execute()) {
        echo "New record created successfully";
        // 글 작성 후 리다이렉션
        header("Location: board_1.php");
        exit();
    } else {
        echo "Error: " . $sql . "<br>" . $conn->error;
    }

    $stmt->close();
    $conn->close();
}
?>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>글쓰기</title>
    <link href="./style.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div class="container-content">
        <h2>글쓰기</h2>
        <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post" enctype="multipart/form-data">
            <label for="title">제목:</label><br>
            <input type="text" id="title" name="title" style="width:100%" required><br>
            <label for="content">내용:</label><br>
            <textarea id="content" name="content" style="width:100%; height:50%;" required></textarea><br>
            <label for="file">파일 업로드:</label><br>
            <input type="file" name="file" id="file"><br><br>
            <button type="submit" name="submit">작성 완료</button>
        </form>
    </div>
</body>
</html>

 

3. board_content.php 게시글 보는 화면에 첨부파일이 있을 경우 첨부파일 이름추가

더보기
<!-- board_content.php-->
<?php
require('dblogin_board.php');
require_once('logined_check_one.php');

if(isset($_GET['board_idx'])) {
    $board_idx = $_GET['board_idx'];
   
    // 해당 게시물의 내용을 데이터베이스에서 가져오기
    $sql = "SELECT * FROM board_table WHERE board_idx = $board_idx";
    $result = $conn->query($sql);

    if($result->num_rows > 0) {
        $row = $result->fetch_assoc();
        $title = htmlspecialchars($row['board_title']);
        $content = htmlspecialchars($row['board_content']);
        $file_path = $row['board_file_path']; // 파일 경로 가져오기

        // 파일 이름 추출
        $file_name = basename($file_path);
    } else {
        echo "게시물이 존재하지 않습니다.";
        exit();
    }
} else {
    echo "게시물 인덱스가 전달되지 않았습니다.";
    exit();
}
?>

<!DOCTYPE html>
<html lang="ko">
<head>
    <link href="./style.css" rel="stylesheet" type="text/css" />
    <meta charset="UTF-8">
</head>
<body>
    <div class="container-content">
        <h2 style="border: 2px solid #ccc; padding: 8px;"><?php echo $title; ?></h2>
        <p style="border: 2px solid #ccc; padding: 8px;"><?php echo nl2br($content); ?></p>
       
        <!-- 파일 다운로드 링크 -->
        <?php if (!empty($file_path)): ?>
            <p>첨부 파일: <a href="<?php echo htmlspecialchars($file_path); ?>" download><?php echo htmlspecialchars($file_name); ?></a></p>
        <?php endif; ?>
       
        <!-- 수정 버튼 -->
        <form action="board_content_edit.php" method="GET" style="display: inline;" >
            <input type="hidden" name="board_idx" value="<?php echo $board_idx; ?>">
            <button type="submit" style="width:100px; background-color:#b6bbff;" class="button" onclick="return confirm('수정하시겠습니까?');">수정</button>
        </form>
       
        <!-- 삭제 버튼 -->
        <form action="board_content_delete.php" method="post" style="display: inline;" >
            <input type="hidden" name="board_idx" value="<?php echo $board_idx; ?>">
            <button type="submit" style="width:100px; background-color:#ff6666;" class="button" onclick="return confirm('정말로 삭제하시겠습니까?');">삭제</button>
        </form>
       
        <div class="side-panel">
            <?php echo "<h4>로그인ID<br><br>" . htmlspecialchars($logined_one) . "<h4>"; ?><!-- 사이드바 내용 -->
            <form action="logout.php" method="post">
            <button type="submit" name="logout" style="background-color:#ff6868">로그아웃</button>
        </form>
        </div>
    </div>
</body>
</html>

 

4. board_content_edit.php로 첨부파일을 수정 및 삭제 할 수 있도록 변경

더보기
<!-- board_content_edit.php-->
<?php
require('dblogin_board.php');
require_once('logined_check_one.php');

$title = '';
$content = '';
$file_path = ''; // 파일 경로 초기화

if (isset($_GET['board_idx'])) {
    $board_idx = $_GET['board_idx'];
   
    // 해당 게시물의 내용을 데이터베이스에서 가져오기
    $sql = "SELECT * FROM board_table WHERE board_idx = ?";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("i", $board_idx);
    $stmt->execute();
    $result = $stmt->get_result();

    if ($result->num_rows > 0) {
        $row = $result->fetch_assoc();
        $title = htmlspecialchars($row['board_title']);
        $content = htmlspecialchars($row['board_content']);
        $file_path = $row['board_file_path']; // 파일 경로 가져오기

        // 파일 이름 추출
        $file_name = basename($file_path);
    } else {
        echo "게시물이 존재하지 않습니다.";
        exit();
    }
} else if (isset($_POST['board_idx'])) {
    $board_idx = $_POST['board_idx'];
} else {
    echo "게시물 인덱스가 전달되지 않았습니다.";
    exit();
}

// 파일 삭제 요청 처리
if (isset($_POST['delete_file'])) {
    $board_idx = $_POST['board_idx'];
    $sql = "SELECT board_file_path FROM board_table WHERE board_idx = ?";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("i", $board_idx);
    $stmt->execute();
    $result = $stmt->get_result();

    if ($result->num_rows > 0) {
        $row = $result->fetch_assoc();
        $file_path = $row['board_file_path'];

        if (file_exists($file_path)) {
            unlink($file_path); // 파일 삭제
            $sql = "UPDATE board_table SET board_file_path = NULL WHERE board_idx = ?";
            $stmt = $conn->prepare($sql);
            $stmt->bind_param("i", $board_idx);
            $stmt->execute();
            header("Location: board_content_edit.php?board_idx=" . $board_idx);
            exit();
        } else {
            echo "파일이 존재하지 않습니다.";
        }
    }
}

// 게시물 수정 요청 처리
if (isset($_POST['submit'])) {
    $board_idx = $_POST['board_idx'];
    $title = $_POST['title'];
    $content = $_POST['content'];
    $uploadOk = 1;
    $target_file = $file_path;

    // 파일 업로드 처리
    if (!empty($_FILES["file"]["name"])) {
        $target_dir = "uploads/";
        $target_file = $target_dir . basename($_FILES["file"]["name"]);
        $fileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));

        // 파일 이미 존재하는지 확인
        if (file_exists($target_file)) {
            echo "Sorry, file already exists.";
            $uploadOk = 0;
        }

        // 파일 크기 확인
        if ($_FILES["file"]["size"] > 500000) {
            echo "Sorry, your file is too large.";
            $uploadOk = 0;
        }

        // 허용된 파일 형식 확인
        $allowedTypes = array("jpg", "png", "jpeg", "gif", "pdf", "txt");
        if (!in_array($fileType, $allowedTypes)) {
            echo "Sorry, only JPG, JPEG, PNG, GIF, TXT, & PDF files are allowed.";
            $uploadOk = 0;
        }

        // 파일 업로드 시도
        if ($uploadOk == 1) {
            if (move_uploaded_file($_FILES["file"]["tmp_name"], $target_file)) {
                echo "The file " . basename($_FILES["file"]["name"]) . " has been uploaded.";
            } else {
                echo "Sorry, there was an error uploading your file.";
                $uploadOk = 0;
            }
        } else {
            $target_file = $file_path; // 업로드 실패 시 기존 파일 경로 유지
        }
    }

    // 게시글 데이터 업데이트
    if ($uploadOk == 1) {
        $sql = "UPDATE board_table SET board_title = ?, board_content = ?, board_file_path = ? WHERE board_idx = ?";
        $stmt = $conn->prepare($sql);
        $stmt->bind_param("sssi", $title, $content, $target_file, $board_idx);

        if ($stmt->execute()) {
            echo "Record updated successfully";
            header("Location: board_content.php?board_idx=" . $board_idx);
            exit();
        } else {
            echo "Error: " . $sql . "<br>" . $conn->error;
        }

        $stmt->close();
        $conn->close();
    }
}
?>

<!DOCTYPE html>
<html lang="ko">
<head>
    <!-- 필요한 메타 태그와 스타일 시트, 제목 등 추가 -->
    <link href="./style.css" rel="stylesheet" type="text/css" />
    <meta charset="UTF-8">
</head>
<body>
    <div class="container-content">
        <h2>글 수정</h2>
        <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post" enctype="multipart/form-data">
            <input type="hidden" name="board_idx" value="<?php echo $board_idx; ?>">
            <label for="title">제목:</label><br>
            <input type="text" id="title" name="title" style="width:100%" value="<?php echo $title; ?>" required><br>
            <label for="content">내용:</label><br>
            <textarea id="content" name="content" style="width:100%; height:50%;" required><?php echo $content; ?></textarea><br>
           
            <!-- 첨부 파일 표시 및 삭제 -->
            <?php if (!empty($file_path)): ?>
                <p>첨부 파일: <a href="<?php echo htmlspecialchars($file_path); ?>" download><?php echo htmlspecialchars($file_name); ?></a></p>
                <button type="submit" name="delete_file">첨부 파일 삭제</button><br>
            <?php endif; ?>
           
            <label for="file">새 파일 업로드:</label><br>
            <input type="file" name="file" id="file"><br><br>
            <button type="submit" name="submit">수정 완료</button>
        </form>
    </div>
</body>
</html>

 

5. board_content_delete.php에 삭제시 첨부파일의 경로를 삭제 폴더로 변경, 실제로 옮기기

더보기
<!-- board_content_delete.php-->
<?php
require('logined_check_one.php');
// 로그인 체크 및 $logined_one 으로 아이디 가져오기
require('dblogin_board.php');

// 게시글 작성자 확인
$sql = "SELECT board_writer, board_file_path FROM board_table WHERE board_idx = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $_POST['board_idx']); // 게시글 인덱스를 바인딩합니다.
$stmt->execute();
$stmt->bind_result($board_writer, $board_file_path); // 결과를 바인딩합니다.
$stmt->fetch();
$stmt->close();

// 로그인한 사용자와 게시글 작성자 비교
if ($logined_one === $board_writer) {
    // 사용자가 게시글 작성자인 경우, 첨부 파일 uploads_delete폴더로 경로 변경
    if (!empty($board_file_path) && file_exists($board_file_path)) {
        // uploads_delete 폴더가 없으면 생성
        $delete_dir = "uploads_delete/";
        if (!is_dir($delete_dir)) {
            mkdir($delete_dir, 0777, true);
        }

        // 파일 이동
        $file_name = basename($board_file_path);
        $new_file_path = $delete_dir . $file_name;
        if (rename($board_file_path, $new_file_path)) {
            // 파일이 삭제되면 경로 업데이트 uploads_delete폴더로 경로 변경
            $sql_update_path = "UPDATE board_table SET board_file_path = ? WHERE board_idx = ?";
            $stmt_update_path = $conn->prepare($sql_update_path);
            $stmt_update_path->bind_param("si", $new_file_path, $_POST['board_idx']);
            $stmt_update_path->execute();
            $stmt_update_path->close();

            echo "첨부파일이 삭제되었습니다: " . htmlspecialchars($new_file_path);
        } else {
            echo "첨부파일 삭제 중 오류가 발생했습니다.";
        }
    }

    // 게시글의 board_type을 2로 업데이트 2=삭제게시판
    $sql_update = "UPDATE board_table SET board_type = 2 WHERE board_idx = ?";
    $stmt_update = $conn->prepare($sql_update);
    $stmt_update->bind_param("i", $_POST['board_idx']);
    $stmt_update->execute();
    $stmt_update->close();

    // 업데이트 후 전체 게시판으로 리다이렉션
    header("Location: board_1.php");
    exit();
} else {
    // 권한이 없는 경우에 대한 처리
    echo '<script>alert("삭제 권한이 없습니다.")</script>';
    header("refresh:0;url=board_1.php");
}
?>

 

6.구현 화면

content.php에 첨부파일 링크 표시
DB에서 첨부파일 경로 생성

 

7. 문제점

→ 파일이 보관되는 폴더가 같아서, 다른 게시글에서 같은 첨부 파일명으로 첨부하면 첨부가 되지 않는다.

게시판 고유 번호인 idx를 첨부 파일명 앞에 붙이는 방법이 괜찮을 것 같다.

관련글 더보기