728x90

파일 업로드 관련으로 Javascript 와 jQuery 를 검색하고 필요한 것을 추가/수정해서 작성했다.

파일 중복 체크를 안하려고 파일명을 rename 처리했다.

$rename = md5(uniqid($tmpname)) .round(microtime(true)).'.'.$fileExt;


파일 테스트 환경 : Windows10 AutoSet9, Linux CentOS 6.5


테스트에 사용한 첨부파일

fileupload-01.zip



=== fileform.php ===

<!DOCTYPE html>
<html>
<head>
<title>Multiple File Upload with PHP</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="css/pure-min.css">
<link rel="stylesheet" type="text/css" href="css/fileupload.css">
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<!-- javascript dependencies -->
<script type="text/javascript" src="js/jquery.form.min.js"></script>
<!-- main script -->
<script type="text/javascript" src="js/fileupload.js"></script>
<script type="text/javascript">
    function viewFileList() {
        var input = document.getElementById("files");
        var ul = document.getElementById("fileList");
        while (ul.hasChildNodes()) {
            ul.removeChild(ul.firstChild);
        }
        for (var i = 0; i < input.files.length; i++) {
            var li = document.createElement("li");
            li.innerHTML = input.files[i].name;
            ul.appendChild(li);
        }
        if(!ul.hasChildNodes()) {
            var li = document.createElement("li");
            li.innerHTML = 'No Files Selected';
            ul.appendChild(li);
        }
    }
</script>
</head>
<body>
<div class="container">
    <div class="status"></div>
    <form id="form" action="fileupload.php" method="post" enctype="multipart/form-data" >
        <input type="file" id="files" name="files[]" multiple="multiple" onChange="viewFileList();" />
        <input type="submit" value="Upload" class="pure-button" />
    </form>
    <ul id="fileList"><li>No Files Selected</li></ul>
    <div class="progress">
        <div class="bar"></div>
        <div class="percent">0%</div>
    </div>

</div><!-- end .container -->

</body>
</html>


=== fileupload.js ===

$(document).ready(function() {
    $('#form').submit(function(){
        var numFiles = $("input:file", this)[0].files.length;
        if(numFiles == 0){
            alert('선택된 파일이 없습니다');
            return false; // form을 전송시키지 않고 반환
        }
    });
});


=== fileupload.php ===

<?php
// Edit upload location here
$valid_formats = array("jpg", "png", "gif", "bmp");
$max_file_size = 1024*100; //100 kb
$upload_path='uploads/';
$count = 0;
if(isset($_POST) and $_SERVER['REQUEST_METHOD'] == "POST"){
    foreach ($_FILES['files']['name'] as $i => $name) {
        if ($_FILES['files']['error'][$i] == 4) { // 파일이 전송되지 않았습니다
            continue;
        }
        if ($_FILES['files']['error'][$i] == 0) { // 오류 없이 파일 업로드 성공
            if ($_FILES['files']['size'][$i] > $max_file_size) {
                $message[] = "$name is too large!.";
                continue;
            }elseif( ! in_array(pathinfo($name, PATHINFO_EXTENSION), $valid_formats) ){
                $message[] = "$name 은 허용된 파일 포멧이 아닙니다";
                continue;
            }else{ // 에러가 없으면
                $tmpname = $_FILES['files']['tmp_name'][$i];
                $realname = $_FILES['files']['name'][$i];
                $filesize = $_FILES['files']['size'][$i];
                $fileExt = getExt($realname);
                $rename = md5(uniqid($tmpname)) .round(microtime(true)).'.'.$fileExt;
                $uploadFile = $upload_path . $rename;
                if(move_uploaded_file($tmpname, $uploadFile)){
                    @chmod($readFile,0606);

                    // DB 테이블에 저장하는 루틴은 여기에 추가하면 된다.

                    $count++; // 업로드 성공한 파일 숫자
                }
            }
        }
    }
}

echo $count;
?>

<script type="text/javascript">
var count ='<?php echo $count;?>';
alert('업로드 성공 파일수 : ' + count );
setTimeout(function() {
    window.location.replace("fileform.php");
}, 1000);
</script>

<?php
// 확장자 추출 함수
function getExt($filename){
    $ext = substr(strrchr($filename,"."),1);
    $ext = strtolower($ext);
    return $ext;
}
?>



파일 업로드 / 다운로드 취약점
- 업로드 기능에서 파일 사이즈의 제한을 주지 않을 경우
- 파일 타입의 체크가 없는 경우
- 파일이 업로드 되는 경로가 외부에서 직접적으로 접근 가능하거나 실행 권한을 가지게 되는 경우


파일 업로드할 때 @chmod($readFile,0606); 를 하는 이유를 좀 더 알고자 한다면

http://www.macinstruct.com/node/415 에 나오는 그림을 보면 도움된다.

업로드라 함은 파일을 서버에 쓰는(write)하는 것이다.

그러므로 파일을 read(4) + write(2) 권한만 부여하면 된다. 파일을 실행할 수 있게 하면 보안상에 문제가 될 수도 있다. 그래서 업로드한 파일을 읽기와 쓰기 권한만 부여했다.


보안을 위해서 몇가지 더 권고하는 걸 적어둔다.

- 업로드한 파일을 외부에서 접근할 수 없는 경로에 저장한다.

  즉 Web 서버 URL 밖에다가 저장하도록 하는 것이 좋다.

- 업로드한 파일에 대한 웹서버의 실행권한을 제거하고 저장한다.

- 업로드한 파일의 저장 경로와 파일명을 외부에서 알 수 없도록 한다.

  실제 저장되는 파일명은 난수를 이용해 유추 불가능하도록 생성하여 외부로부터 직접적인 접근이 불가능하게 구현한다.

  업로드한 파일을 위한 전용 디렉토리를 생성하여 실행권한을 제거한다.


참고

파일 업로드에 대한 개념 설명이 잘된 URL

http://blog.habonyphp.com/entry/php-POST-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C#.WHWm81z3TFA


블로그 이미지

Link2Me

,