Как защитить загруженные файлы от неавторизованных пользователей?

22 дек. 2011 г., 18:46:18
Просмотры: 88.3K
Голосов: 107

Я использую WordPress для приватного сайта, где пользователи загружают файлы. Я использую "Private WordPress" для предотвращения доступа к сайту, если пользователь не авторизован.

Я хотел бы сделать то же самое для файлов, загруженных в папку uploads.

Таким образом, если пользователь не авторизован, он не сможет получить доступ к: https://xxxxxxx.com/wp-content/uploads/2011/12/xxxxxxx.pdf если они попытаются получить доступ без авторизации, они должны быть перенаправлены на страницу входа.

Я нашел плагин под названием private files, но последнее обновление было в 2009 году, и он не работает на моем WordPress.

Кто-нибудь знает какой-нибудь метод? Достаточно ли метода защиты от хотлинкинга для этого?

Я также нашел этот метод:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} ^.*uploads/private/.*
RewriteCond %{HTTP_COOKIE} !^.*wordpress_logged_in.*$ [NC]
RewriteRule . /index.php [R,L]
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

Но тогда любой пользователь, который скопирует cookie, сможет обойти эту защиту, верно? С уважением

3
Комментарии

Есть ли причина, по которой вы не можете использовать другую директорию для загрузки, например, вне корня сайта?

onetrickpony onetrickpony
22 дек. 2011 г. 19:11:33

Не совсем, но у меня уже есть множество файлов, прикрепленных к записям в этой директории. Я не против перемещения, если найду подходящее решение.

chifliiiii chifliiiii
22 дек. 2011 г. 19:37:52

Если вы автоматически перенаправляете пользователей на экран входа, один из простых способов защитить загрузки — проверять реферер. Если реферер пуст (прямой доступ) или отличается от домена, на котором размещены файлы, то доступ блокируется.

Konstantinos Konstantinos
31 янв. 2020 г. 19:55:38
Все ответы на вопрос 4
11
113

Простая проверка наличия cookie не обеспечивает строгой защиты.

Для более надежной защиты вы можете пропускать или "проксировать" все запросы к загружаемой папке (например, uploads в следующем примере) через PHP-скрипт:

RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/(.*)$ dl-file.php?file=$1 [QSA,L]

Все запросы к загруженным файлам (включая изображения в записях) будут перенаправляться в dl-file.php, который может проверить, авторизован ли пользователь.

Если пользователь не авторизован, будет показана форма входа. После успешного входа пользователь будет перенаправлен обратно к файлу и сможет его скачать.

Пример dl-file.php.

Аналогичный функционал можно найти в \wp-includes\ms-files.php вашей установки WordPress, но он предназначен для мультисайтов и без проверки авторизации и редиректов.

В зависимости от объема трафика, может быть разумнее интегрировать это решение на уровне сервера, например, используя заголовки X-Accel-Redirect или X-Sendfile.

2 янв. 2012 г. 23:50:59
Комментарии

как изменить dl-file.php, если я хочу хранить файлы в подкаталоге, например, wp-content/uploads/secure ?

User User
12 янв. 2012 г. 00:54:57

Это единственное по-настоящему безопасное решение. Все остальное, что можно найти в интернете — проверка заголовка referer, проверка cookies, запрет листинга директорий — это полумеры, поскольку заголовки HTTP-запросов легко подделать, чтобы обойти эти ограничения.

Lukasz Lukasz
20 сент. 2013 г. 11:58:28

Ребята... это казалось идеальным решением для меня... проблема в том, что я использую PDFJS от Mozilla для доступа к PDF-файлам из папки upload, а PDFJS использует заголовки partial-content для получения только тех страниц, которые ему нужны... так что это решение мне не подходит.

Есть предложения??

Otto Nascarella Otto Nascarella
28 нояб. 2014 г. 19:24:06

@OttoNascarella: Запросы частичного контента (Partial Content) в PHP были решены на сегодняшний день, это не относится к данному вопросу по WordPress. На самом деле, вопрос уже довольно старый: Возобновляемые загрузки при использовании PHP для отправки файла?

hakre hakre
5 дек. 2014 г. 12:17:49

@hakre А как насчет некоторых изображений, используемых на главной странице сайта, когда любой пользователь заходит на сайт?

Я получаю ошибку 404, если не авторизован.

Dhaval Panchal Dhaval Panchal
1 мая 2018 г. 11:30:58

Может кто-нибудь объяснить эту строку кода list($basedir) = array_values(array_intersect_key(wp_upload_dir(), array('basedir' => 1)))+array(NULL); и чем она отличается от $basedir = wp_upload_dir()['basedir']

Tomas Eklund Tomas Eklund
2 апр. 2020 г. 14:02:30

@TomasEklund: Ваш вариант, скорее всего, менее сложный. Причина оригинального решения, вероятно, в обратной совместимости версий PHP.

hakre hakre
4 апр. 2020 г. 17:08:42

Отлично. Может кто-нибудь сделать из этого решения плагин для WordPress?

Jian Chen Jian Chen
17 мая 2020 г. 07:17:34

Примечание: работает в WordPress 5.6.

long long
4 мар. 2021 г. 15:17:33

В моей Multisite-настройке у меня возникли проблемы с заполнением расположения файла данным кодом. Этот вариант сработал лучше: $file = rtrim( DIR, '/' ) . $_SERVER['REQUEST_URI'];

Talha Imam Talha Imam
29 июл. 2021 г. 22:40:29

Для nginx в конфигурационном файле сайта в блоке server{} я добавил следующее, чтобы все заработало: location /wp-content/uploads/ { rewrite ^/wp-content/uploads/(.*)$ /dl-file.php?file=$1 last; }

hax2024 hax2024
12 апр. 2022 г. 00:23:31
Показать остальные 6 комментариев
4
21

Два способа: простой в 2. с помощью правила Apache или в 1. с помощью пользовательского кода в плагине.

1. Плагин

Вы можете написать плагин, используя хук init и получение значения $_GET[ 'file' ];. Если пользователь имеет это GET-значение, перейти в функцию для проверки прав доступа к файлам: Например, с помощью чекбокса внутри Meta Box.

add_action( 'init', 'fb_init' );
function fb_init() {
    // это в функции для хука init
    if ( '' != $_GET[ 'file' ] ) {
        fb_get_file( $_GET[ 'file' ] );
    }
}

функция fb_get_file()

function fb_get_file( $file ) {

    $upload     = wp_upload_dir();
    $the_file   = $file; 
    $file       = $upload[ 'basedir' ] . '/' . $file;
    if ( !is_file( $file ) ) {
        status_header( 404 );
        die( '404 &#8212; Файл не найден.' );
    }
    else {
        $image = get_posts( array( 'post_type' => 'attachment', 'meta_query' => array( array( 'key' => '_wp_attached_file', 'value' => $the_file ) ) ) );
        if ( 0 < count( $image ) && 0 < $image[0] -> post_parent ) { // вложение найдено и родитель доступен
            if ( post_password_required( $image[0] -> post_parent ) ) { // пароль для записи недоступен
                wp_die( get_the_password_form() ); // показать форму пароля 
            }
            $status = get_post_meta( $image[0] -> post_parent, '_inpsyde_protect_content', true );

            if ( 1 == $status &&  !is_user_logged_in() ) {
                wp_redirect( wp_login_url( $upload[ 'baseurl' ] . '/' . $the_file ) );
                die();
            }
        }
        else {
            // не стандартное вложение, проверка на миниатюру
            $filename   = pathinfo( $the_file );
            $images     = get_posts( array( 'post_type' => 'attachment', 'meta_query' => array( array( 'key' => '_wp_attachment_metadata', 'compare' => 'LIKE', 'value' => $filename[ 'filename' ] . '.' . $filename[ 'extension' ] ) ) ) );
            if ( 0 < count( $images ) ) {
                foreach ( $images as $SINGLEimage ) {
                    $meta = wp_get_attachment_metadata( $SINGLEimage -> ID );
                    if ( 0 < count( $meta[ 'sizes' ] ) ) {
                        $filepath   = pathinfo( $meta[ 'file' ] );
                        if ( $filepath[ 'dirname' ] == $filename[ 'dirname' ] ) { // текущий путь миниатюры
                            foreach ( $meta[ 'sizes' ] as $SINGLEsize ) {
                                if ( $filename[ 'filename' ] . '.' . $filename[ 'extension' ] == $SINGLEsize[ 'file' ] ) {
                                    if ( post_password_required( $SINGLEimage -> post_parent ) ) { // пароль для записи недоступен
                                        wp_die( get_the_password_form() ); // показать форму пароля 
                                    }
                                    die('dD');
                                    $status = get_post_meta( $SINGLEimage -> post_parent, '_inpsyde_protect_content', true );

                                    if ( 1 == $status &&  !is_user_logged_in() ) {
                                        wp_redirect( wp_login_url( $upload[ 'baseurl' ] . '/' . $the_file ) );
                                        die();
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    $mime       = wp_check_filetype( $file );

    if( false === $mime[ 'type' ] && function_exists( 'mime_content_type' ) )
        $mime[ 'type' ] = mime_content_type( $file );

    if( $mime[ 'type' ] )
        $mimetype = $mime[ 'type' ];
    else
        $mimetype = 'image/' . substr( $file, strrpos( $file, '.' ) + 1 );

    header( 'Content-type: ' . $mimetype ); // всегда отправлять это
    if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS' ) )
        header( 'Content-Length: ' . filesize( $file ) );

    $last_modified = gmdate( 'D, d M Y H:i:s', filemtime( $file ) );
    $etag = '"' . md5( $last_modified ) . '"';
    header( "Last-Modified: $last_modified GMT" );
    header( 'ETag: ' . $etag );
    header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 100000000 ) . ' GMT' );

    // Поддержка условных GET-запросов
    $client_etag = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) : false;

    if( ! isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )
        $_SERVER['HTTP_IF_MODIFIED_SINCE'] = false;

    $client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
    // Если строка пуста, вернуть 0. Если нет, попытаться преобразовать в метку времени
    $client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;

    // Создать метку времени для нашего последнего изменения...
    $modified_timestamp = strtotime($last_modified);

    if ( ( $client_last_modified && $client_etag )
        ? ( ( $client_modified_timestamp >= $modified_timestamp) && ( $client_etag == $etag ) )
        : ( ( $client_modified_timestamp >= $modified_timestamp) || ( $client_etag == $etag ) )
        ) {
        status_header( 304 );
        exit;
    }

    // Если дошли до этого места, просто отдать файл
    readfile( $file );
    die();
}

Вы также можете добавить пользовательский URL для файлов через хук generate_rewrite_rules

add_filter( 'generate_rewrite_rules', 'fb_generate_rewrite_rules' );

function fb_generate_rewrite_rules( $wprewrite ) {
        $upload = wp_upload_dir();
        $path = str_replace( site_url( '/' ), '', $upload[ 'baseurl' ] );
        $wprewrite -> non_wp_rules = array( $path . '/(.*)' => 'index.php?file=$1' );
        return $wprewrite;
}

2. Проверка Cookie через Apache

Оставьте новый файл .htaccess внутри директории /wp-content/uploads/. Или другой определенной директории для загрузок.

Как это работает

Внутри контейнеров <IfModule> есть три правила, которые выполняют следующее:

  1. Проверяют, является ли запрос файлом
  2. Проверяют отсутствие cookie, начинающейся с wordpress_logged_in_
  3. Если эти условия выполняются, запрос файла отклоняется с ответом 403 "Запрещено"

Хитрость здесь в шаге 2 — проверка отсутствия cookie, начинающейся с wordpress_logged_in_. Когда пользователь входит в систему, WordPress добавляет cookie в ваш браузер, которая выглядит так:

wordpress_logged_in_1234567890abcdefghijklmnopqrstuvwxyz

Пример правила с проверкой типа файла

# require login for media files
<IfModule mod_rewrite.c>
    RewriteCond %{REQUEST_FILENAME} (.*)
    RewriteCond %{HTTP_COOKIE} !wordpress_logged_in_([a-zA-Z0-9_]*) [NC]
    RewriteRule .* - [F,L]
</IfModule>
3 янв. 2012 г. 12:20:25
Комментарии

У меня не сработало, кто-нибудь знает почему? Я все скопировал точно.

Ryan S Ryan S
17 февр. 2014 г. 12:40:26

Защита работает только для pdf. Для других расширений файлов не работает, например: doc, docx, jpg и т.д...

Patel Patel
23 авг. 2017 г. 13:43:48

Вариант 2 отлично сработал для всех типов файлов, спасибо.

crdunst crdunst
5 мар. 2021 г. 18:23:21

Почему условие проверки размера файла для IIS?

Scribblemacher Scribblemacher
11 мар. 2025 г. 13:34:09
1

Если вам нужен плагинный способ решения этой проблемы, вот довольно хорошее решение, которое я (наконец-то) нашёл:

  1. Установите плагин 'Download Monitor', доступный по адресу:
    https://wordpress.org/plugins/download-monitor/
  2. В панели управления WordPress перейдите в новый пункт меню 'Загрузки' и добавьте новую 'Загрузку', как описано в документации плагина здесь: https://www.download-monitor.com/kb/adding-downloads/. Запомните шорткод 'Загрузки', который вам предоставлен (например, сохраните в Блокнот). Обратите внимание, что файл сохраняется в /wp-content/uploads/dlm_uploads/
  3. В метабоксе 'Настройки загрузки' укажите 'Только для участников' (как описано здесь https://www.download-monitor.com/kb/download-options/) и нажмите 'Опубликовать'.
  4. На странице, где должна отображаться загрузка только для участников, добавьте шорткод, который вы запомнили в шаге №2, и 'Опубликуйте/Обновите' страницу, как описано здесь: https://www.download-monitor.com/kb/shortcode-download/. Вы можете изменить шаблон ссылки для загрузки, как описано здесь https://www.download-monitor.com/kb/content-templates/, или создать свой собственный (например, чтобы убрать счётчик загрузок)
  5. Перейдите на вашу страницу — вы должны увидеть ссылку для загрузки (но она не показывает реальный URL файла). Если вы откроете ту же страницу в новом окне браузера (или в режиме Инкогнито), вы обнаружите, что загрузка больше не работает.

Это означает, что любой, кто не вошёл в систему, не сможет ни скачать файл, ни увидеть его реальный URL. Если кто-то несанкционированный узнает URL файла, плагин также блокирует доступ к папке /wp-content/uploads/dlm_uploads/, предотвращая просмотр реального файла.

Бонус: если вы делаете это для сайта, где пользователи должны входить только как 'Участники' (без прав WordPress, таких как редактирование страниц или права администратора), установите плагин 'Members' https://wordpress.org/plugins/members/, создайте новую роль пользователя 'Участник' с единственным правом 'read', добавьте нового пользователя в WordPress и назначьте ему роль 'Участник'.

Если вы хотите защитить содержимое страниц, плагин 'Members' предоставляет несколько опций, либо можно использовать другие плагины. Если вам нужно оформить страницу входа для участников лучше, чем стандартная форма WordPress, используйте что-то вроде 'Theme My Login': https://wordpress.org/plugins/theme-my-login/

13 апр. 2017 г. 12:09:36
Комментарии

Описанный мной процесс также объясняется здесь, хотя, как видите, он не обязательно ограничивается только PDF-файлами: http://www.thedigitalcrowd.com/website-development/wordpress/wordpress-restrict-access-to-pdf-downloads-with-private-members-login-details/

Matty J Matty J
13 апр. 2017 г. 12:11:01
0

Как насчет подхода на основе плагинов для решения этой проблемы? Я нашел WP-плагин, созданный именно для этого:

Запретить доступ к файлам/папкам: https://wordpress.org/plugins/prevent-file-access/

8 янв. 2021 г. 16:28:32