關於 Stream 也到第三篇文章了,倒也不是因為這個 Feature 很重要,只是因為這個主題所涵蓋的範圍太廣了,只能花這麼多篇幅來敘述。
我們已經在前兩天的文章中瞭解到:Stream 是一個操作串流的特性,無論該串流是來自於檔案、網路服務還是其它任何資訊,PHP 僅是把這些操作抽象化成為一個概念。
Filter 中文譯為「過濾器」,偍我通常不會對其進行翻譯,而會以 Stream Filter 這樣的名稱去稱呼它。
根據官方文件的定義,Stream Filter 是針對最後一塊可被讀取/寫入的 Steam 進行操作的功能。例如,我們可以利用 string.touppper 將字串轉為大寫:
<?php
$output = fopen('php://output', 'w');
stream_filter_append($output, 'string.toupper');
fwrite($output, "This is a test.\n"); // THIS IS A TEST.
在 PHP 中已經提供許多預設 Filter 可供使用,可以利用 stream_get_filters()
查閱目前可以使用的 Filter。另外,官方文件也有提供列表可供查照。
使用 Stream Filter 非常容易,只需要呼叫 stream_filter_append()
函式即可。
另外也可以在 PHP Stream 上直接使用 filter:php://filter/string.toupper/resource={檔案名稱}
除了使用預設的 Stream Filter 之外,PHP 也允許我們自定義 Stream Filter。
以下範例改寫自 PHP 官方文件
<?php
class StrToupper extends php_user_filter
{
public function filter($in, $out, &$consumed, $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$bucket->data = strtoupper($bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register('strtoupper', StrToupper::class);
$output = fopen('php://output', 'w');
stream_filter_append($output, 'strtoupper');
fwrite($output, 'this is a test.'); // THIS IS A TEST.
LFI,全名 Locale File Inclusion。
在 PHP 中,LFI 的發生通常與不正確的引入檔案有關,通常會牽涉到 include
與 require
兩個關鍵字。
舉例而言,以下程式存在 LFI 的安全風險
<?php
include file_exists($_GET['page']) ? $_GET['page'] : 'index.php';
此時,若我輸入 /?page=../../../../etc/passwd
(視目前檔案位置而定,可能需要增減 ../
的數量),就可以取得 /etc/passwd
的內容。
同時,因為 Stream 也可以被 include
,所以我也可以輸入 /?page=php://filter/convert.base64-encode/resource=index
,如此以來便可以取得一串 base64 encoded 字串,這個字串解析後就是 index.php
的原始碼。
註:這個我在 PTT PHP 版的 https://www.ptt.cc/bbs/PHP/M.1527504207.A.92A.html 一文中的第二個問題有提到,算是剛好對 Stream Filter 做一個補充。
1999 年 9 月 21 日,當時我五歲。20 年後的今天我能夠在這裡寫文,必須感謝當時人們的無私奉獻與付出。