PHP Composer 的安全性問題

ComposerPHP 上新一代的套件管理軟體。

Composer 與之前各種方案不同的地方在於,後面掛了 Packagist 這個 PHP package archivist,讓使用者很方便的取得套件 (以及更新)。

其中有一個功能是 replace,表示我所開發的這個套件「宣稱」可以取代其他套件 (通常是 API compatible)。

看到這個功能的時候,以為是要讓 Packagist 能夠串起連結。畢竟偶而會發生「這個套件最新版是 2008 年了,到底有沒有後續維護啊」的情況。如果 Packagist 官方網站可以串起連結,可以節省開發者時間,而且也可以利用 Packagist 上的熱門度來決定要用哪一個後繼者。

但實際上與預期的不同,Composer 的預設值是會「自動」尋找更新並且真的使用,也就是前幾天被丟出來的議題:「Composer is wide open with a massive security vulnerability」。

攻擊者只要在 Packagist 上面上傳一堆含有惡意程式碼的套件 (並且利用 replace 宣稱可以取代 ZF2 或是 Laravel),受害者在 composer update 的時候就有機會抓到這些有惡意程式碼的套件。

而最糟糕的是,主要開發者認為這不是問題,還寫了一大篇文章顧左右而言他之後說「錯覺啦~」:「Composer: Replace, Conflict & Forks Explained」:

Replace is not a bug. Don’t run composer update in automated systems. Forks are allowed on Packagist. Don’t be an idiot when publishing a fork. Got an unexpected fork on update? Your dependencies conflict with the original package. Use conflict (syntax like require) in your composer.json to blacklist the fork and see an explanation of the dependency issue.

於是發現問題的人就爆炸了。

依照往例,vendor 不覺得是問題的安全漏洞,只能把事情弄大爆出來逼 vendor 修。

修正在「Limit Replace / Provides to packages required by name in root package or any dep」這裡。最新版的 Composer 裡已經納入這個修正了。

PSR-4

剛剛看到「Composer now supports PSR-4」才發現有 PSR-4,而且出了好幾個禮拜了...

PSR-4

This PSR describes a specification for autoloading classes from file paths. It is fully interoperable, and can be used in addition to any other autoloading specification, including PSR-0. This PSR also describes where to place files that will be autoloaded according to the specification.

在已經有 PSR-0 的情況下,設計 PSR-4 讓人感覺有點怪,因為功能跟 PSR-0 是衝突的。

不過仔細看看以後發現 PSR-4 規則比較「乾淨」,有種想要汰換 PSR-0 的想法... 而且 Composercomposer.json 的設計也允許針對不同的 prefix 使用不同的邏輯,看起來是把 PSR-0 當作過渡期的設計,希望大家換到 PSR-4?

PSR-0 與 PSR-4 最明顯的差異是對底線 (underscore) 解讀不同,在 PSR-4 中的底線沒有任何意義:

Underscores have no special meaning in any portion of the fully qualified class name.

在 PSR-0 裡則是說 class name 的底線必須換成 DIRECTORY_SEPARATOR:

Each _ character in the CLASS NAME is converted to a DIRECTORY_SEPARATOR. The _ character has no special meaning in the namespace.

來想看看要怎麼轉換現有的系統...

Zend Framework 1 的 Zend_View 與 Zend Framework 2 的 Zend\View 的差異...

Zend Framework 1 的 Zend_View 實踐了 Rasmus Lerdorf (PHP 發明人) 的想法「PHP 本身就是 template language,不應該在 PHP 裡面再發明一套 template language」。

Zend_View 是可以獨立拿出來用的,使用方式很簡單:

$view = new Zend_View();
$view->setBasePath(realpath(__DIR__ . '/../application/view'));
echo $view->render('index/index.phtml');

這樣對應的 template 檔案是 application/view/scripts/index/index.phtml

Zend Framework 2 除了改用 namespace 外 (所以名稱從原來的 "_" 改成 "\"),還把 Zend\View 深度整合到 Zend\Mvc 裡,這使得要獨立拿出來用的手續就變得... 超... 麻... 煩...

透過 Composer 安裝的人除了在 composer.json 裡面要設定 zendframework/zend-view 外,還要設定 zendframework/zend-filter 才會動 (不知道為什麼沒被放進 dependency),像是這樣:

    "require": {
        "zendframework/zend-filter": "2.2.*",
        "zendframework/zend-view": "2.2.*"
    }

另外 ZF 2 的 Zend\View 則是透過 resolver 物件設定要讀哪個目錄,而非之前用 setBasePath 打發:

$resolver = new \Zend\View\Resolver\TemplatePathStack(
    array(
        'script_paths' => array(
            realpath(__DIR__ . '/../webdata/view/')
        )
    )
);

$renderer = new \Zend\View\Renderer\PhpRenderer();
$renderer->setResolver($resolver);

echo $renderer->render('index/index.phtml');

這樣對應的 template 檔案是 webdata/view/index/index.phtml,跟 ZF1 相比少了一個 scripts

ZF 的文件一如往常的難讀,還是要去翻 source code 才知道要這樣用,所以也跟往常一樣,寫下來避免之後又忘記...