오늘 How to Setup Secure Media Uploads라는 글을 봤다. 주요 요지는 .htaccess 파일을 이용해서 업로드 폴더의 보안을 강화하는 거였다. 서버에서 파일을 업로드할 때 퍼미션을 777로 해야 하는 경우가 생기는데 이러면 보안은 취약해진다. 업로드 폴더에 php 스크립트를 올리고 실행하면 서버 정보가 다 털릴 수도 있다.

.htaccess 파일을 이용해서 허용된 확장자 외에는 아무 것도 읽을 수 없게 하면 방어력을 좀더 높일 수 있다. 당연히 .htaccess 파일은 소유자만 수정할 수 있게 퍼미션을 줘야 한다.

위 글에서 알려 준 방식의 .htaccess 파일은 아래와 같다.

# secure uploads directory
<Files ~ ".*\..*">
  Order Allow,Deny
  Deny from all
</Files>
<FilesMatch "\.(jpg|jpeg|jpe|gif|png|tif|tiff)$">
  Order Deny,Allow
  Allow from all
</FilesMatch>

자, 핵심은 6번째 줄에 있는 확장자 모음이다. 이렇게, 읽을 수 있는 확장자만 지정해 주는 게 이 보안 방법의 핵심이다.

나도 이 보안 방법을 사용하려고 하다가, ‘도대체 어떤 확장자들이 올라가 있는지 알 수가 있어야지!’ 하는 생각이 들어서 스크립트를 짰다. 업로드 폴더에 있는 모든 파일의 확장자를 구하는 거다. php 같은 확장자는 빼고 말이다. 그걸 자동으로 구해 주면 편하게 .htaccess 파일을 생성할 수 있겠다고 생각해서 스크립트를 짰다.

스크립트를 짜고 나서는, ‘이거 나중에 유지보수할 때 찾기 힘들 텐데’ 하는 생각이 들었고, 그래서 플러그인으로 만들면 편하겠다는 생각이 들었다. 그래서 플러그인을 만들었다. 코드는 아래와 같다.

/*
Plugin Name: 업로드 폴더 보안 .htaccess
Description: 업로드 폴더 보안을 위해서 .htaccess 텍스트를 구성해 주는 플러그인이다. 활성화하면 노티스로 뿌려 준다. 필요할 때 활성화해서 메시지를 보고, 메시지를 바탕으로 업로드 폴더의 .htaccess 파일을 구성한 뒤, 플러그인을 비활성화하면 된다.  
Author: mytory
Author URI: https://mytory.net
Version: 1.0
*/

/**
 * 이 함수의 출처는 http://php.net/manual/kr/function.readdir.php#103418
 * Finds path, relative to the given root folder, of all files and directories in the given directory and its sub-directories non recursively.
 * Will return an array of the form
 * array(
 *   'files' => [],
 *   'dirs'  => [],
 * )
 * @param string $root
 * @result array
 * @author sreekumar
 */
function gae_read_all_files($root = '.')
{
    $files = array('files' => array(), 'dirs' => array());
    $directories = array();
    $last_letter = $root[strlen($root) - 1];
    $root = ($last_letter == '\\' || $last_letter == '/') ? $root : $root . DIRECTORY_SEPARATOR;

    $directories[] = $root;

    while (sizeof($directories)) {
        $dir = array_pop($directories);
        if ($handle = opendir($dir)) {
            while (false !== ($file = readdir($handle))) {
                if ($file == '.' || $file == '..') {
                    continue;
                }
                $file = $dir . $file;
                if (is_dir($file)) {
                    $directory_path = $file . DIRECTORY_SEPARATOR;
                    array_push($directories, $directory_path);
                    $files['dirs'][] = $directory_path;
                } elseif (is_file($file)) {
                    $files['files'][] = $file;
                }
            }
            closedir($handle);
        }
    }

    return $files;
}

function gae_get_all_extension($root)
{
    $files = gae_read_all_files($root);
    $extensions = array();
    foreach ($files['files'] as $file) {
        $extension = pathinfo($file, PATHINFO_EXTENSION);
        $extension = strtolower($extension);
        if ($extension == 'php' OR $extension == '' OR $extension == 'ds_store' OR $extension == 'htaccess') {
            continue;
        }
        if (!in_array($extension, $extensions)) {
            $extensions[] = $extension;
            $extensions[] = strtoupper($extension);
        }
    }
    $extensions_string = implode('|', $extensions);
    return $extensions_string;
}

function gae_notice()
{
    $upload_dir = wp_upload_dir();
    $extensions_string = gae_get_all_extension($upload_dir['basedir']);
    $htaccess_string = '# secure uploads directory
<Files ~ ".*\..*">
  Order Allow,Deny
  Deny from all
</Files>
<FilesMatch "\.($extensions_string)$">
  Order Deny,Allow
  Allow from all
</FilesMatch>';
    $result_string = str_replace('$extensions_string', $extensions_string, $htaccess_string);
    ?>
    <div class="updated">
        <p>아래 문자열을 복사해서 업로드 폴더의 .htaccess 를 구성하세요. 이 메시지를 그만 보이게 하려면 ‘업로드 폴더 보안 .htaccess’ 플러그인을 비활성화하세요.</p>
        <p>
        <pre><?php echo htmlspecialchars($result_string) ?></pre>
        </p>
    </div>
    <?php
}

add_action('admin_notices', 'gae_notice');

플러그인 폴더 없이 파일 하나이므로 plugins 폴더에 그냥 넣으면 된다. 플러그인을 활성화하면 아래와 같은 메시지가 출력된다. 그러면 메시지의 코드 부분을 긁어서 업로드 폴더에 .htaccess 파일을 만들고 붙여 넣으면 된다.

[이미지] 이렇게, .htaccess에 넣을 문자열을 만들어서 뿌려 준다.