안녕하세요 제가 만든 게시판에는 제 서버에 직접 파일을 넣고
그리고 그 파일을 다운로드하게 했잖아요?
그러면 취약점이 될 수 있습니다.
아무리 화이트 리스트나 블랙리스트를 작성해도 나중에 가서 우회하는 방법이 나올 수 있죠.
그런데 그런 파일들을 DB에 저장하게 하면 걱정이 없죠 악성코드든 어느 파일이든
DB에서는 실행이 불가능하니깐요!
그러니 보안 코딩을 하겠습니다.
우선적으로 저희는 CLOB랑 BLOB을 알아야 합니다.
- CLOB(Character Large Object)
문자형 대용량 객체 고정길이와 가변 길이 문자집합 지원
이라고 하는데 쉽게 말씀드리자면 엄청 긴 문자열 데이터를 저장하기 위해 사용됩니다.
그래서 텍스트 문서, XML 데이터, 소스 코드 등 텍스트 기반 데이터를 저장하는 데 적합하다고 하네요!
- BLOB(Binary Large Object)
이진 데이터를 저장하기 위해 사용할 때 이용합니다.
그러니 이미지, 오디오, 비디오, 문서 파일 같은 이진 형식의 데이터를 저장하는 데 적합합니다!
저희는 이 BLOB 형식을 채용해서 DB에 저장을 하겠습니다.
BLOB 형식으로 저장을 하기 위해 phpmyadmin에
BOARD_INFO 에 새로운 칼럼을 추가했습니다.
그리고 DB에 올릴 때 b로 올리라는데.. 안돼서 s로 올렸더니 되네요!
주요 코드는
//파일 데이터 읽기
$file_data = file_get_contents($file_tmp_name);
$sql_file = "UPDATE $db_board SET file_upload = ? , file_name= ? WHERE board_id = ?";
$stmt_file = mysqli_prepare($conn, $sql_file);
mysqli_stmt_bind_param($stmt_file, 'ssi', $file_data, $new_file_name ,$board_id);
입니다.
총 코드 보여드릴게요
-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);
//session으로 유저 이름 받기
$user_id = $_SESSION['login_id'];
if(isset($board_id)) {
//board_id가 있다는 것은 수정을 의미
$sql = "UPDATE $db_board SET title = ?, detail= ? WHERE board_id= ? ";
//preparedstatement 적용
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 'ssi', $title, $detail, $board_id);
}
else {
//board_id가 없으니 새로 만드는 sql문
$sql = "INSERT INTO $db_board (id, title, detail) VALUES (?, ?, ?)";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 'sss', $user_id ,$title, $detail);
}
//sql문 실행
if(mysqli_stmt_execute($stmt)) {
if(isset($board_id)) {
$_SESSION['write_error'] = '수정되었습니다';
}
else {
$board_id = mysqli_insert_id($conn);
$_SESSION['write_error'] = '작성되었습니다';
}
} else {
$_SESSION['write_error'] = '작성 중 오류가 발생하였습니다.';
header("Location: view_board.php");
}
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)) {
//파일 데이터 읽기
$file_data = file_get_contents($file_tmp_name);
$sql_file = "UPDATE $db_board SET file_upload = ? , file_name= ? WHERE board_id = ?";
$stmt_file = mysqli_prepare($conn, $sql_file);
mysqli_stmt_bind_param($stmt_file, 'ssi', $file_data, $new_file_name ,$board_id);
// 파일 이동 및 저장
if (mysqli_stmt_execute($stmt_file)) {
$_SESSION['file_error'] = '파일 업로드 성공';
} else {
$_SESSION['file_error'] = '파일 업로드 실패';
}
} else {
$_SESSION['file_error'] = '잘못된 파일 형식입니다';
}
} else {
$_SESSION['file_error'] = '잘못된 파일 형식입니다';
}
}
}
header("Location: view_board.php?index=$board_id");
mysqli_stmt_close($stmt);
if (isset($stmt_file)) {
mysqli_stmt_close($stmt_file);
}
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'].'&idx='.$row['board_id'];
echo '
<div class="view-bottom"> FILE: <a href="'.$download_url.'">'.$file_name.'</a> </div>
';
}
?>
<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_process.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());
}
if(isset($_GET['filename']) && isset($_GET['idx'])) {
// 파일 이름 가져오기
$file_name = basename(filter_var(strip_tags($_GET['filename']),FILTER_SANITIZE_SPECIAL_CHARS));
$id = filter_var(strip_tags($_GET['idx']),FILTER_SANITIZE_SPECIAL_CHARS);
$sql = "SELECT file_upload, file_name FROM $db_board WHERE board_id = ? ";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, "i", $id);
mysqli_stmt_execute($stmt);
//결과 저장
mysqli_stmt_store_result($stmt);
//결과 바인딩
mysqli_stmt_bind_result($stmt, $file_data, $file_name);
// 파일이 존재하는지 확인
if(mysqli_stmt_num_rows($stmt) > 0) {
mysqli_stmt_fetch($stmt);
// 다운로드 헤더 설정
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$file_name.'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . strlen($file_data));
//파일 데이터 출력
echo $file_data;
exit;
} else {
echo '파일이 존재하지 않습니다.';
}
} else {
echo '파일 이름이 전달되지 않았습니다.';
}
?>
'개발과제 > 보안추가' 카테고리의 다른 글
[노말틱 모의 해킹 취업반 추가 개발과제 ] XSS 방지 (0) | 2023.05.13 |
---|---|
[노말틱 모의 해킹 취업반 추가 개발과제 ] PreparedStatement 적용하기 -2 (0) | 2023.05.03 |
[노말틱 모의 해킹 취업반 추가 개발과제 ] PreparedStatement 적용하기 -1 (0) | 2023.05.02 |
[노말틱 모의 해킹 취업반 개발과제 (3)] 보안 관련해서 보완 하기 (0) | 2023.04.23 |
[노말틱 모의 해킹 취업반 개발과제 (2)] 보안 관련해서 보완 하기 (2) | 2023.04.20 |