После загрузки файла картинки на сервер через административную панель CMS (например, opencart) его невозможно скачать через браузер.
Браузер сообщает об ошибке:
1 |
403 Forbidden |
В логах веб-сервера error.log возникает ошибка:
1 |
2015/11/07 12:33:59 [error] 14206#0: *88716 open() "~/image/data/Capture.PNG" failed (13: Permission denied), client: 192.168.0.0, server: localhost, request: "GET /image/data/Capture.PNG HTTP/1.0", host: "localhost" |
На сервере файл успешно сохраняется
1 2 |
[root@srv ~]$ ls -l image/data/Capture.PNG -rw-r--r--. 1 apache apache 66075 Apr 18 2015 image/data/Capture.PNG |
И доступен веб-серверу для чтения
1 2 3 4 |
[root@srv ~]$ sudo -u nginx test -r image/data/Capture.PNG && echo 'allow' || echo 'deny' allow [root@srv ~]$ sudo -u apache test -r image/data/Capture.PNG && echo 'allow' || echo 'deny' allow |
Проблема
Проблема в том, что PHP загружая файл, сначала помещает его во временный каталог (по умолчанию /tmp), потом, как правило, в скриптах используется функция move_upload_file для переноса загруженного файла из временного каталога в каталог назначения установленный пользователем.
Когда PHP загружает файл и помещает его во временный каталог (по умолчанию /tmp), операционная система в лице SELinux устанавливает свою метку к нему, которая отражает расширенные права доступа, основываясь на каталоге размещения этого файла.
Для того чтобы посмотреть права доступа SELinux к каталогу, можно выполнить следующую команду:
1 2 |
[root@srv /]$ ls -Zd /tmp drwxrwxrwt. root root system_u:object_r:tmp_t:s0 /tmp |
Для того чтобы посмотреть права доступа SELinux к файлу, можно выполнить следующую команду:
1 2 |
[root@srv /]$ ls -Z /tmp/file -rw-------. apache apache unconfined_u:object_r:user_tmp_t:s0 /tmp/file |
Формат метки unconfined_u:object_r:user_tmp_t — user:role:type
Затем функция move_upload_file переносит этот файл к месту его фактического назначения. Но, в отличии от копирования файла, когда метка корректно меняется, при переносе файла метка с расширенными правами доступа не меняется! Это важно помнить.
Таким образом в месте фактического расположения загруженного файла находится файл с меткой unconfined_u:object_r:user_tmp_t:s0, т.е. указан type – user_tmp_t. Файлы с этим типом не доступны для чтения веб-серверу, поэтому и возникает ошибка «Permission denied» при доступе к файлу.
Если посмотреть метки с расширенными правами доступа для каталога /var/www, то можно увидеть, что там для доступа к веб-контенту используется тип httpd_sys_content_t:
1 2 3 4 5 |
[root@srv /]$ ls -Z /var/www/ drwxr-xr-x. root root system_u:object_r:httpd_sys_script_exec_t:s0 cgi-bin drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 error drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 html drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 icons |
Решение
Самое простое на мой взгляд решение — это создать отдельный временный каталог для PHP и указать для него правильную метку SELinux с расширенными правами доступа.
1 2 |
[root@srv /]$ mkdir -Z system_u:object_r:httpd_sys_rw_content_t:s0 /var/www/tmp [root@srv /]$ chmod 777 /var/www/tmp |
Далее в файле с настройками PHP, найти файл php.ini можно легко с помощью следующей команды:
1 2 3 4 5 |
[root@srv /]$ php –-ini Configuration File (php.ini) Path: /usr/local/lib Loaded Configuration File: /usr/local/lib/php.ini Scan for additional .ini files in: /usr/local/etc/php.d Additional .ini files parsed: |
Следует установить значение параметра sys_temp_dir:
1 |
sys_temp_dir = "/var/www/tmp" |
Перезагрузить сервис php-fpm или веб-сервер, если модуль php встроен в веб-сервер.
1 |
[root@srv /]$ service php-fpm restart |
И проверить, что после загрузки файла он доступен для скачивания.
Дополнение
Вместо общего временного каталога sys_temp_dir можно указать upload_tmp_dir — временный каталог только для файлов, которые загружаются по протоколу HTTP.
1 |
upload_tmp_dir = /var/www/tmp |
Дополнение 2
Чтобы исправить метки с расширенными правами доступа для файлов, которые, например, уже находятся в каталоге «image/catalog» и всех его подкаталогах, можно выполнить следующую команду:
1 |
restorecon -R -v image/catalog |