Omezení oprávnění Apache na Windows 10 pro C:/www/, direktiva open_basedir – ano/ne?

m.brecher
Generous Backer | 753
+
0
-

Ahoj,

chtěl bych zabezpečit PC kde mám vývojový Apache server s PHP + Nette proti smazání disku PC v případě chyby v parametru $filepath v metodě Nette\Utils\FileSystem::delete($filepath).

Na vývojovém PC mám Windows 10.

Webové projekty mám v:

C:/www/

PHP 8.0 nainstalováno jako modul Apache, pache běží jako služba Windows pod defaltním účtem Local System, httpd.conf:

LoadModule php_module "C:\Program Files\PHP8\php8apache2_4.dll"
PHPIniDir "C:\Program Files\PHP8"
DocumentRoot "C:\www"

php.ini:

; open_basedir
upload_tmp_dir = "C:\www\_upload_tmp_file"
session.save_path = "C:\www\_session_tmp"

Řešení pomocí konfigurační direktivy PHP open_basedir jsem kdysi zkoušel, ale narazil jsem na problémy s blokací composeru, funkce PHP is_file() vyhazovala warningy a ani odborné servery tuto cestu moc nedoporučují.

V dokumentaci Apache https://httpd.apache.org/…windows.html je doporučováno pro běh Apache jako služby Windows vytvořit jiný účet než Local System.

Se svými omezenými znalostmi v této problematice odhaduji že:

Omezení oprávnění k souborovým operacím pro účet pod kterým se Apache spouští předpokládám že stejným způsobem omezí možnosti PHP, protože PHP je spuštěno jako modul Apache. Kdyby se tento účet podařilo vytvořit (ve Windows to asi nebude snadné), byl by disk PC proti nechtěnému smazání ochráněn a PHP by mohlo smazat pouze všechny PHP/Nette projekty v C:/www/.

Když by se pomocí ini_set(‚open_basedir‘, $appDir.‚/‘) omezily oprávnění konkrétní aplikace na složku příslušného projektu, nevznikne tam nějaký problém s update projektu pomocí composeru, popř. s Nette Frameworkem (také používá PHP funkci is_file(), která může díky open_basedir vyhazovat warning). Když to promýšlím, může se tam narazit na řadu problémů. V nejhorším bych na open_basedir rezignoval a alespoň bych ochránil zbytek disku mimo C:/www/, nedávno jsem si totiž pomocí Nette\Utils\FileSystem málem smazal disk.

Nemá někdo hlubší praktické zkušenosti v této oblasti a mohl by poradit?

Díky předem

Editoval m.brecher (14. 9. 2022 3:51)

m.brecher
Generous Backer | 753
+
0
-

Ahoj,

tak jsem trochu experimentoval a zkusil jsem v Nette projektu dynamicky nastavit open_basedir v Bootstrap.php:

class Bootstrap
{
    public static function boot(): Configurator
    {
        $configurator = new Configurator;
        $appDir = dirname(__DIR__);

        ...........

        ini_set('open_basedir', implode(';',[$appDir.'/', $appDir.'/_upload_tmp_file/']));

        return $configurator;
    }

Aplikace fungovala, PHP má skutečně zamezen přístup k souborům mimo C:/www/, kvůli uploadování souborů jsem povolil ještě práva k souborům pro adresář C:/www/_upload_tmp_file/.

Takže by to možná takto mohlo fungovat. Funkce PHP is_file() (použité v mém kódu) sice vyhazuje warningy, ale v production módu to nevadí, jenom se zbytečně logují. Ale s tím se dá žít. Nette warningy z is_file() nevyhazuje, takže dobrý.

Co mne překvapilo dosti, že ačkoliv jsem v open_basedir nepovolil složku pro soubory session, která je mimo C:/www/ (C:/www/_session_tmp/), přihlašování fungovalo a aplikace bez problémů soubory session zapisovala. Usuzuji, že to má Nette pečlivě ošetřeno a samo si práva pro C:/www/_session_tmp/ nastaví.

Ještě mě napadá, že dát inicializaci open_basedir do Bootstrap.php asi není úplně Best practice a nejlepší bude to umístit do BasePresenter::startup().

Editoval m.brecher (13. 9. 2022 4:11)

Pepino
Člen | 249
+
0
-

Nebude to nějakým jiným nastavením? Zkusil jsem hodit open_basedir = c:/www/ přímo do php.ini a žádná chyba (php 8). Nemám potlačené výpisy chyb.

Jinak co se týče umístění ini_set, tak to dej přímo do index.php ještě před načítání čehokoliv. V bootu nebo startupu už je pozdě.

Marek Bartoš
Nette Blogger | 1167
+
0
-

Zda to bude na začátku bootstrapu (před inicializací containeru a debuggeru) nebo v indexu je jedno, volají se ihned po sobě.

I v indexu může být, v závislosti na nastavení php (kupříkladu když má php nastavené session auto start a je aktivní session) pozdě. open_basedir je vhodné mít v php.ini, ještě před startem aplikace.

m.brecher
Generous Backer | 753
+
0
-

Ahoj,

tak jsem dál testoval zda jak v reálných podmínkách na domácím pc s Windows použít open_basedir pro omezení práv PHP v jednom projektu právě na adresář tohoto projektu.

@MarekBartoš a @Pepino v diskusi radili použít open_basedir v php.ini. To vypadalo zajímavě – omezit všechny projekty na DocumentRoot celého serveru C:/www a uvnitř každého projektu dynamicky v index.php nebo Bootstrap.php dále omezit na podadresář projektu C:/www/path-to-project. PHP skripty by nemohly omylem smazat soubory v jiných projektech ani na zbytku PC a kdyby se v projektu na přidání kódu zapomnělo, byla by alespoň ochrana celého disku PC.

Toto by fungovalo bezvadně, kdybych nenarazil na problém, že pokud se nastaví open_basedir v php.ini, tak to omezí funkci composeru. Testoval jsem pouze composer show a s tímto výsledkem:

php.ini:
open_basedir = ‚C:/www‘

C:\www\dev\playweb>composer show

PHP Warning:  Unknown: open_basedir restriction in effect. File(C:\ProgramData\ComposerSetup\bin\composer.phar) is not within the allowed path(s): (C:\www) in Unknown on line 0
PHP Warning:  Phar::mapPhar(): open_basedir restriction in effect. File(C:\ProgramData\ComposerSetup\bin\composer.phar) is not within the allowed path(s): (C:\www) in C:\ProgramData\ComposerSetup\bin\composer.phar on line 28
PHP Warning:  require(phar://composer.phar/bin/composer): Failed to open stream: phar error: invalid url or non-existent phar "phar://composer.phar/bin/composer" in C:\ProgramData\ComposerSetup\bin\composer.phar on line 29
PHP Fatal error:  Uncaught Error: Failed opening required 'phar://composer.phar/bin/composer' (include_path='.;C:\php\pear') in C:\ProgramData\ComposerSetup\bin\composer.phar:29
Stack trace:
#0 {main}
  thrown in C:\ProgramData\ComposerSetup\bin\composer.phar on line 29

Tak povolíme také C:\ProgramData\ComposerSetup:

php.ini:
open_basedir = ‚C:/www;C:\ProgramData\ComposerSetup‘

C:\www\dev\playweb>composer show

PHP temp directory (C:\Users\milos\AppData\Local\Temp) does not exist or is not writable to Composer. Set sys_temp_dir in your php.ini

In JsonFile.php line 85:

  is_file(): open_basedir restriction in effect. File(C:/Users/milos/AppData/Roaming/Composer/config.json) is not within the allowed path(s): (C:\www;C:\ProgramData\ComposerSetup)

Dále povolíme C:\Users\milos\AppData\Local\Temp a C:/Users/milos/AppData/Roaming/Composer:

php.ini:
open_basedir = ‚C:/www;C:\ProgramData\ComposerSetup;C:\Users\milos\AppData\Local\Temp;C:/Users/milos/AppData/Roaming/Composer‘

C:\www\dev\playweb>composer show

In Factory.php line 200:

  file_exists(): open_basedir restriction in effect. File(C:/Users/milos/AppData/Local/Composer/.htaccess) is not within the allowed path(s): (C:\www;C:\ProgramData\ComposerSetup
  ;C:\Users\milos\AppData\Local\Temp;C:/Users/milos/AppData/Roaming/Composer)

Přidáme tedy ještě C:/Users/milos/AppData/Local/Composer:

php.ini:
open_basedir = ‚C:/www;C:\ProgramData\ComposerSetup;C:\Users\milos\AppData\Local\Temp;C:/Users/milos/AppData/Roaming/Composer;C:/Users/milos/AppData/Local/Composer‘

C:\www\dev\playweb>composer show

latte/latte   v2.11.5
.....

Takže composer show se sice podařilo rozchodit, ale nastalo podstatné snížení výkonu, stránky se rendrují nikoliv cca 100 ms, ale 600 ms.

Závěr:

Při standardní instalaci composeru nelze rozumným způsobem použít open_basedir v php.ini na omezení práv skriptů PHP k souborům v celém domácím Windows 10 PC. Rozumné je zůstat pouze u dynamického nastavení ini_set(‚open_basedir‘), které dle provedených testů zabrání dávkovému smazání souborů jiných Nette projektů, nebo celého disku domácího PC.

Kód jsem zatím vložil do Bootstrap.php, protože tam už mám větev pro lokální domácí stroj a protože mě šlo v podstatě hlavně o ochranu před vlastní chybou ve vlastním PHP kódu při mazání souborů, tak si myslím, že to takhle bude stačit.

Otestované řešení:

class Bootstrap
{
    public static function boot(): Configurator
    {
        $configurator = new Configurator;
        $appDir = dirname(__DIR__);

        .......
        .......

        $configurator->addConfig($appDir . '/config/common.neon');
        $configurator->addConfig($appDir . '/config/services.neon');

        if($_SERVER['HTTP_HOST'] === 'localhost'){
            $configurator->addConfig($appDir . '/config/local.neon');   // nastavení pro domácí (vývojový) server

                // povolena složka projektu + složka pro dočasné upload soubory
            ini_set('open_basedir', implode(';', [$appDir, ini_get('upload_tmp_dir')]));
        }

        return $configurator;
    }
}

Překvapivě jsem při testech zjistil, že session Nette nebylo restrikcemi open_basedir nijak na funkci omezeno, takže není potřeba adresář pro ukládání souborů session speciálně povolovat, Nette si to umí povolit samo.

Editoval m.brecher (13. 9. 2022 14:35)

Pepino
Člen | 249
+
+2
-

@mbrecher To snížení výkonu by mohlo způsobovat vypnutí realpath cache. Viz dokumentace: Using open_basedir will disable the realpath cache.
PS: Můžeš použít docker nebo nějakou jinou virtualizaci pro každý projekt.

m.brecher
Generous Backer | 753
+
0
-

@Pepino díky za komentář, mě stačí, když rozumným a jednoduchým způsobem ochráním svoje data na PC před mojí vlastní chybou při manipulaci se souborovým systémem.