1. DB에 파일을 저장하는 이유
2. 파일 저장 방법
2.1. 디렉토리에 파일 저장
2.2. DB에 파일 저장
3. 파일 다운로드 방법
1. DB에 파일을 저장하는 이유
File Upload 및 Download 취약점을 막는 가장 좋은 방법중 하나는 File 데이터를 DB에 업로드하는 것이다.
DB에 PHP등을 까는 등 매우 특수한 경우를 제외하면 DB는 별도의 공간이므로 파일이 실행되지 않기 때문이다.
따라서 DB에 데이터를 CLOB/BLOB 형식으로 저장 할 것이다.
2. 파일 저장하는 방법
2.1. 디렉토리에 파일 저장
이미 구축해놓은 file업로드 코드는 다음과 같다.
<!-- file_up1.php -->
<?php
$targetDir = "uploads/"; // 업로드된 파일이 저장될 디렉토리 경로
// require_once "tool/chack_er.php";
// 파일이 업로드되었는지 확인
if(empty($_FILES["fileToUpload"]['name'])) {
}elseif(!empty($_FILES["fileToUpload"]['name'])) {
$targetFile = $targetDir . basename($_FILES["fileToUpload"]["name"]); // 업로드된 파일의 경로와 이름, basenaem함수는 파일경로에서 파일명만 가져옴.
$uploadOk = 1; // 파일 업로드가 성공적으로 이루어졌는지 여부를 나타내는 변수
// 파일 유형 검사 (예시: 이미지 파일만 업로드 가능하도록 제한)
$imageFileType = strtolower(pathinfo($targetFile,PATHINFO_EXTENSION)); // 확장자 추출 및 소문자 변환
if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg" && $imageFileType != "gif" ) {
echo "<script> alert('JPG, JPEG, PNG & GIF파일만 업로드 가능합니다.');
window.history.back();
</script>";
$uploadOk = 0;
}
// NULL byte 탐지
if(strpos($targetfile, "\0")){
$uploadOk = 0;
}
//파일 크기 제한
$maxFileSize = 1048576; // 1MB 제한
if ($_FILES['fileToUpload']['size'] > $maxFileSize) {
echo "<script> alert('파일 크기가 제한을 초과했습니다.');
window.history.back();
</script>";
}
// 파일 업로드 실행여부 검사
if ($uploadOk == 0) {
echo "<script> alert('error [0]');
window.history.back();
</script>";
}
// 업로드될 파일의 정보를 MySQL 데이터베이스에 저장하는 코드 작성
$filename = basename($_FILES["fileToUpload"]["name"]);
$filesize = $_FILES["fileToUpload"]["size"];
$uploader = $_SESSION["name"];
$boardNO = "1";
$contentNO = $id; //upload_content_1.php에서 정의
if(strlen($filename) >= "25"){
echo "<script> alert('파일 이름의 길이는 확장자명 포함 25자 이내여야 합니다.');
window.history.back();
</script>";
}
// MySQL 데이터베이스 연결 및 파일 정보 저장
require_once "tool/db_conn.php";
if ($con->connect_error) {
die("Connection failed: " . $con->connect_error);
}
// 파일 정보를 데이터베이스 테이블에 삽입하는 SQL 쿼리 실행
$sql = "INSERT INTO board1_file (filename, filesize, uploader, uploadDate, boardNO, contentNO) VALUES (?, ?, ?, NOW(), ?, ?)";
$stmts = $con -> prepare($sql);
$stmts -> bind_param('sssii', $filename, $filesize, $uploader, $boardNO, $contentNO);
if ($stmts -> execute()) {
$file_id = mysqli_insert_id($con); // AUTO_INCREMENT로 생성된 primary key 값을 가져옴
$saveFile = $targetDir . $file_id . "." . pathinfo($targetFile,PATHINFO_EXTENSION); //저장될 파일명 및 디렉토리
} else {
echo "<script> alert('error [0]');
window.history.back();
</script>";
}
// MySQL 연결 종료
$con->close();
//파일 업로드 실행
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $saveFile)) {
} else {
// 파일 업로드 실패
echo "<script> alert('파일 업로드에 실패하였습니다.');</script>";
}
}
?>
위 코드의 저장 원리는 다음과 같다
- 파일 데이터를 post로 받음
- 받은 파일이 임시 디렉토리에 저장
- 파일의 정보 (이름, 업로더, id)를 DB에 업로드
- 파일업로드 실행. 이때 파일명을 id값으로 변경하여 업로드.
2.2. DB에 파일 저장
CLOB/BLOB형식의 데이터로 파일을 업로드하는 코드는 다음과 같다.
<!-- file_up1.php -->
<?php
$filename = basename($_FILES["fileToUpload"]["name"]);
// require_once "../tool/chack_er.php";
// 파일이 업로드되었는지 확인
if(empty($_FILES["fileToUpload"]['name'])) {
}elseif(!empty($_FILES["fileToUpload"]['name'])) {
$uploadOk = 1; // 파일 업로드가 성공적으로 이루어졌는지 여부를 나타내는 변수
// 파일 유형 검사 (예시: 이미지 파일만 업로드 가능하도록 제한)
$imageFileType = strtolower(pathinfo($filename,PATHINFO_EXTENSION)); // 확장자 추출 및 소문자 변환
if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg" && $imageFileType != "gif" ) {
echo "<script> alert('JPG, JPEG, PNG & GIF파일만 업로드 가능합니다.');
window.history.back();
</script>";
$uploadOk = 0;
}
// NULL byte 탐지
if(strpos($targetfile, "\0")){
$uploadOk = 0;
}
//파일 크기 제한
$maxFileSize = 1048576; // 1MB 제한
if ($_FILES['fileToUpload']['size'] > $maxFileSize) {
echo "<script> alert('파일 크기가 제한을 초과했습니다.');
window.history.back();
</script>";
$uploadOk = 0;
}
// 파일 업로드 실행여부 검사
if ($uploadOk == 0) {
echo "<script> alert('error [0]');
window.history.back();
</script>";
} else{
// 업로드될 파일의 정보를 MySQL 데이터베이스에 저장하는 코드 작성
$filecontent = file_get_contents($_FILES["fileToUpload"]["tmp_name"]);
$filesize = $_FILES["fileToUpload"]["size"];
$uploader = $writer;
$boardNO = $board_num;
$contentNO = $id; //upload_content.php에서 정의
if(strlen($filename) >= "25"){
echo "<script> alert('파일 이름의 길이는 확장자명 포함 25자 이내여야 합니다.');
window.history.back();
</script>";
}
// MySQL 데이터베이스 연결 및 파일 정보 저장
require_once "../tool/db_conn.php";
if ($con->connect_error) {
die("Connection failed: " . $con->connect_error);
}
// 파일 정보를 데이터베이스 테이블에 삽입하는 SQL 쿼리 실행
$sql = "INSERT INTO board1_file (filename, filesize, uploader, uploadDate, boardNO, contentNO, filecontent) VALUES (?, ?, ?, NOW(), ?, ?, ?)";
$stmts = $con -> prepare($sql);
$stmts -> bind_param('sssiis', $filename, $filesize, $uploader, $boardNO, $contentNO, $filecontent);
$stmts -> execute();
// MySQL 연결 종료
$con->close();
}
}
?>
이 코드의 작성 이전에 파일 내용을 업로드할 filecontent라는 BLOB컬럼을 추가하였다.
일반적으로 CLOB와 BLOB는 엄연히 다른 데이터형식이므로 각각의 컬럼을 지정해주고 데이터의 형식에 따라서 다른 컬럼에 저장을 해주어야한다.
다만 MySQL의 경우 CLOB의 데이터를 BLOB컬럼에 저장하는 기능을 지원한다. 따라서 이 글에서는 BLOB컬럼에 두 형식의 데이터를 모두 저장하도록 하겠다.
위 코드의 저장 방법에 대한 내용은 아래와같다.
- 파일의 데이터를 post로 받는다
- 받은 파일이 임시디렉토리에 저장된다
- 임시디렉토리에 저장된 파일의 정보(파일명, 파일 내용 등)를 변수에 정의한다.
- 정의한 변수를 통해 데이터를 DB에 업로드한다.
3. 파일 다운로드
파일 다운로드 방법은 다음 두가지 방식이 있다.
1. 파일데이터를 바로 출력
2. 파일을 디렉토리에 저장 후 출력
이중에서 파일 데이터를 바로 출력하는 방법을 사용하도록 하겠다.
<!-- download_file.php -->
<?php
$board_id = $_GET['board_id'];
$boardNO=$board_num;
//db 연결
require_once "../tool/db_conn.php";
// require_once 'tool/chack_er.php';
$file_query = "SELECT * FROM board1_file WHERE boardNO=? AND contentNO=?";
$stmt = $con -> prepare($file_query);
$stmt -> bind_param('ii', $boardNO, $board_id);
$stmt -> execute();
$file_result = $stmt -> get_result();
$file_row = $file_result -> fetch_assoc();
// 다운로드할 파일의 경로 및 파일명
$filename = $file_row['filename'];
$filecontent = $file_row['filecontent'];
// 파일이 존재하는지 확인
if ($file_result->num_rows === 1) {
// 다운로드할 파일의 정보 설정
header('Content-Description: File Transfer'); //컨텐츠 설명. 전송데이터가 파일임.
header('Content-Type: application/octet-stream'); //컨텐츠가 MIME타입을 설정. 이진파일을 나타냄.
header('Content-Disposition: attachment; filename="' . $filename . '"'); //다운로드될 파일명설정. 첨부(파일을 열지않고 다운.)
header('Expires: 0'); //만료시간. 즉시만료.
header('Cache-Control: must-revalidate'); //캐시 강제 재검사. 만약 변경이 있을경우 캐시를 업데이트. 아니면 캐시 사용.
header('Pragma: public'); //캐시저장 허용
header('Content-Length: ' . strlen($filecontent)); //컨텐츠의 길이 설정. 효율, 진행상황 표시, 변조나 공격 탐지등.
ob_clean(); //출력 버퍼 제거
flush(); //출력 버퍼를 비우고 즉시 출력. 이를 클라이언트에게 전송. 전송지연최소화.
echo $filecontent;
// 파일 다운로드 후 종료
exit;
} else {
// 파일이 존재하지 않는 경우 처리할 내용
echo "<script> alert('파일을 찾을 수 없습니다.');
window.history.back();
</script>";
exit;
}
?>
위 코드는 다음과 같이 이루어져 있다.
- 파일 데이터 가져오기
- HTTP 헤더 설정하기
- 데이터 출력하기
이러한 파일 다운로드 방식의 경우 대용량 파일의 경우 메모리에 로드되어 메모리 부담이 발생 할 수 있다. 따라서 컬럼 데이터형식을 MIDIUMBLOB로 약 16M가량의 데이터 크기만 저장할 수 있도록 설정해두었다.
'프로그래밍 및 코딩 > PHP' 카테고리의 다른 글
게시판 추천(좋아요) 기능 만들기 (0) | 2023.06.17 |
---|---|
CLOB / BLOB (0) | 2023.06.14 |
서버시간 가져오기 (0) | 2023.06.14 |
웹 개발 프로젝트_파일 업로드 취약점 Secure coding (0) | 2023.06.03 |
비관계형 데이터베이스 (0) | 2023.05.31 |