注意:要閱讀這篇文章,我們假設您具有最低限度的 PHP 程式設計知識。
這篇文章是關於PHP 程式碼片段的,您可能在您最喜歡的CMS 或框架的頂部看到過該程式碼片段,並且您可能已經讀過,為了安全起見,您應該始終將其包含在您所使用的所有PHP 檔案的標頭中。發展,儘管沒有非常清楚地解釋原因。我指的是這段程式碼:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
這種類型的程式碼在 WordPress 檔案中很常見,儘管它實際上出現在幾乎所有框架和 CMS 中。例如,在 CMS Joomla 的情況下,唯一改變的是使用 JEXEC,而不是 ABSPATH。否則,邏輯是相同的。這個 CMS 源自於另一個名為 Mambo 的 CMS,它也使用類似的程式碼,但使用 _VALID_MOS 作為常數。如果我們追溯到更遠的時間,我們會發現第一個使用這種類型程式碼的 CMS 是 PHP-Nuke(被一些人認為是第一個 PHP CMS)。
PHP-Nuke(以及當今大多數 CMS 和框架)的執行流程包括順序加載多個文件,這些文件一起響應用戶或訪問者在網路上執行的操作。也就是說,想像一下當時的網站,位於 example.net 網域下方並安裝了此 CMS。每次載入主頁時,系統都會有序地執行一系列檔案(這裡只是一個範例,而不是真正的序列):index.php => load_modules.php =>;模組.php。也就是說,按照這個順序,先載入index.php,然後該腳本載入load_modules.php,然後載入modules.php。
這個執行鏈並不總是從第一個檔案(index.php)開始。事實上,任何人都可以透過直接透過URL 呼叫其他PHP 檔案之一(例如http://example.net/load_modules.php 或http://example.net/modules.php)來跳過此流程的一部分,這正如我們將看到的,在許多情況下可能是危險的。
這個問題是如何解決的? 引入了安全措施,在每個文件的開頭添加代碼,類似於:
<?php if (!eregi("modules.php", $HTTP_SERVER_VARS['PHP_SELF'])) { die ("You can't access this file directly..."); }
基本上,這段程式碼位於一個名為modules.php 的檔案的標頭中,用於檢查是否可以透過URL 直接存取modules.php。如果是這樣,執行將停止,並顯示訊息:「您無法直接存取此文件...」。如果$HTTP_SERVER_VARS['PHP_SELF']不包含modules.php,那麼這意味著我們處於正常的執行流程中並被允許繼續。
但是,此程式碼有一些限制。首先,插入的每個文件的程式碼都不同,這增加了複雜性。另外,在某些情況下,PHP 並沒有為 $HTTP_SERVER_VARS['PHP_SELF'] 賦值,這限制了其有效性。
那麼開發者做了什麼?他們用更簡單、更有效率的版本取代了所有這些程式碼片段:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
在 PHP 社群中已經很常見的這段新程式碼中,驗證了常數的存在。該常數在流程的第一個檔案(index.php 或 home.php 或一些類似檔案)中定義並指派了一個值。因此,如果該常數不存在於流中的其他文件中,則表示有人跳過了 index.php 檔案並嘗試直接存取另一個檔案。
此時你一定會想,打破執行鏈一定是世界上最嚴重的事。然而,現實情況是,通常,它並不代表重大危險。
當 PHP 錯誤暴露我們檔案的路徑時,可能會出現危險。如果我們在伺服器上配置了錯誤抑制,那麼我們就不必擔心,並且即使沒有隱藏錯誤,暴露的資訊也很少,只為可能的攻擊者提供一些線索。
也可能有人存取包含 HTML 片段(來自視圖)的文件,從而洩露其部分內容。在大多數情況下,這也不應該令人擔憂。
最後,開發人員可能會因為疏忽或缺乏經驗而在執行流程中插入沒有外部依賴的危險程式碼。這是非常不尋常的,因為通常框架或 CMS 的程式碼依賴其他類別、函數或外部變數來執行。因此,如果您嘗試直接透過 URL 運行腳本,它將無法找到這些依賴項,並且不會繼續執行。
那麼,如果幾乎沒有任何值得擔心的地方,為什麼要加入常數程式碼呢?原因是這樣的:“此方法還可以防止通過攻擊 註冊全局變量 進行意外的變量注入,從而防止 PHP 文件假設它實際上不在應用程序內部。”
自 PHP 誕生以來,所有透過 URL (GET) 或表單 (POST) 傳送的變數都會自動轉換為全域變數。也就是說,如果檔案download.php?filepath=/etc/passwd 被訪問,則在download.php 檔案中(以及在執行流程中依賴於該檔案的檔案中)可以使用echo $filepath ;結果將是/ etc/passwd。
在 download.php 中,無法判斷 $filepath 變數是否是由執行流程中的前一個檔案建立的,或者是否有人透過 URL 或 POST 欺騙了它。這產生了很大的安全漏洞。讓我們來看一個例子,假設 download.php 檔案包含以下程式碼:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
開發人員可能考慮使用前端控制器模式來實現他的程式碼,也就是讓所有 Web 請求都通過單一輸入檔(index.php、home.php 等)。該檔案將負責初始化會話、載入公共變量,最後將請求重定向到特定腳本(在本例中為 download.php)以下載檔案。
但是,攻擊者可以透過簡單地呼叫 download.php?filepath=/etc/passwd 來繞過計劃的執行序列,如前所述。因此,PHP 會自動建立全域變數 $filepath,其值為 /etc/passwd,從而允許攻擊者從系統下載該檔案。嚴重錯誤。
這只是冰山一角,因為甚至可以用最小的努力進行更危險的攻擊。例如,在以下程式碼中,程式設計師可以將其保留為未完成的腳本:
<?php if (!eregi("modules.php", $HTTP_SERVER_VARS['PHP_SELF'])) { die ("You can't access this file directly..."); }
攻擊者可以使用遠端檔案包含 (RFI) 攻擊執行任何程式碼。因此,如果攻擊者在自己的網站https://mysite.net 上建立了一個My.class.php 文件,其中包含他想要執行的任何程式碼,他就可以透過將其網域傳遞給易受攻擊的腳本來呼叫該腳本:codigo_inutil.php?base_path= https://mysite.net,攻擊完成。
另一個範例:在名為remove_file.inc.php 的腳本中,包含以下程式碼:
<?php if (!defined('MODULE_FILE')) { die ("You can't access this file directly..."); }
攻擊者可以使用像remove_file.inc.php?filename=/etc/hosts這樣的URL直接呼叫這個文件,從而嘗試從系統中刪除/etc/hosts文件(如果系統允許的話,或者其他)您有刪除權限的檔案)。
在像 WordPress 這樣內部也使用全域變數的 CMS 中,這種類型的攻擊是毀滅性的。然而,由於不斷的技術,這些和其他 PHP 腳本受到了保護。讓我們來看最後一個例子:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
現在,如果有人嘗試存取remove_file.inc.php?filename=/etc/hosts,該常數將阻止存取。它必須是一個常數,因為如果它是一個變量,攻擊者當然可以注入它。
此時您可能想知道如果 PHP 如此危險,為什麼還要保留此功能。另外,如果你了解其他腳本語言(JSP、Ruby 等),你會發現它們沒有類似的東西(這就是為什麼它們也不使用常量技術)。讓我們記住,PHP 是作為 C 語言的模板系統誕生的,這種行為促進了開發。好消息是,看到它造成的問題,PHP 維護者決定在 php.ini 中引入一個名為 register_globals 的指令(預設啟動)以允許停用此功能。
但由於問題仍然存在,他們默認禁用它。即便如此,許多主機仍然繼續啟用它,因為擔心客戶的專案會停止運作,因為當時的大部分程式碼沒有使用建議的 HTTP_*_VARS 變數來存取 GET/POST/... 值,但而是全域變數。
最後,看到情況沒有改變,他們做出了一個重大決定:在 PHP 5.4 中消除這個功能,以避免所有這些問題。因此,如今,像我們看到的腳本(不使用常數)不再通常構成危險,除了在某些情況下出現一些無害的警告/通知。
時至今日,持續技術仍然很常見。然而,令人悲傷的是——也是導致這篇文章的原因——很少有開發人員知道其使用的真正原因。
與過去的其他良好實踐一樣(例如將函數中的參數複製到局部變量以避免調用中引用的危險,或者在私有變量中使用下劃線來區分它們),許多人仍然應用它只是因為有人曾經告訴他們這是一個很好的做法,而沒有考慮它是否真的在當今時代增加了價值。現實情況是,在大多數情況下,不再需要此技術。
這種做法失去相關性的一些原因如下:
*register 全域變數的消失:從 PHP 5.4 開始,將 GET 和 POST 變數註冊為 PHP 全域變數的功能不再存在。正如我們所看到的,如果沒有 *register globals,單一腳本的執行就變得無害,消除了這種做法的主要原因。
當前程式碼中更好的設計:即使在PHP 5.4 之前的版本中,現代程式碼也經過更好的設計,在類別和函數中結構化,這使得透過外部變數進行存取或操作變得複雜。即使是經常使用全域變數的 WordPress,也可以將這些風險降到最低。
*front-controllers的使用:如今,大多數Web應用程式都使用精心設計的*front-controllers,這確保了類別和函數的程式碼僅當執行鏈從主入口點開始時才執行。因此,如果有人嘗試單獨上傳檔案並不重要:如果流程沒有從正確的點開始,邏輯將不會啟動。
類別自動加載:由於目前開發中使用了類別自動加載,因此大大減少了 include 或 require 的使用。這意味著,除非您是新手開發人員,否則不應存在可能帶來風險的includes 或requires(例如遠端檔案包含 或本地文件包含)。
公用程式碼的分離:在許多現代CMS和框架中,公有程式碼(例如資產)與私有程式碼(程式碼)分開。此措施特別有價值,因為它可以確保如果 PHP 在伺服器上失敗,PHP 檔案中的程式碼(無論它們是否使用常數技術)不會暴露。儘管這並不是專門為了緩解註冊全域變數而實施的,但它有助於避免其他安全問題。
友善 URL 的擴充使用:如今,將伺服器配置為使用友善 URL 很常見,這總是強制使用單一入口點進行程式設計。這使得任何人幾乎不可能單獨上傳 PHP 檔案。
抑制生產中的錯誤輸出:大多數現代CMS 和框架預設都會阻止錯誤輸出,因此攻擊者無法找到有關應用程式內部工作原理的線索,這可能會促進其他類型的錯誤輸出攻擊。
儘管在大多數情況下不再需要此技術,但這並不意味著它永遠沒有用處。作為專業開發人員,必須分析每種情況並確定持續技術是否與您工作的特定環境相關。這是您應該始終遵循的標準,即使在您認為良好的實踐中也是如此。
如果您仍不確定何時應用持續技術,這些建議可以引導您:
對於其他一切,如果您有疑問,請應用它。在大多數情況下,它不應該是有害的,並且可以在意外情況下保護您,特別是如果您剛開始。隨著時間和經驗的積累,您將能夠更好地評估何時應用此技術和其他技術。
以上是框架和 CMS 中那些奇怪的 PHP 程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!