[英]Prevent direct access to a php include file
我有一個 php 文件,我將把它用作包含文件。 因此,當通過輸入 URL 而不是被包含來直接訪問它時,我想拋出一個錯誤而不是執行它。
基本上我需要在php文件中進行如下檢查:
if ( $REQUEST_URL == $URL_OF_CURRENT_PAGE ) die ("Direct access not premitted");
有沒有簡單的方法來做到這一點?
將此添加到您只想包含的頁面
<?php
if(!defined('MyConst')) {
die('Direct access not permitted');
}
?>
然后在包含它的頁面上添加
<?php
define('MyConst', TRUE);
?>
對於通用的“運行在您可能完全控制或可能不完全控制的 Apache 服務器上的 PHP 應用程序”情況,最簡單的方法是將您的包含放在一個目錄中,並在您的 .htaccess 文件中拒絕對該目錄的訪問。 為了避免人們使用谷歌搜索的麻煩,如果您使用的是 Apache,請將其放在您不想訪問的目錄中名為“.htaccess”的文件中:
Deny from all
如果您實際上完全控制了服務器(這些天甚至對於小應用程序比我第一次寫這個答案時更常見),最好的方法是將您想要保護的文件粘貼在您的 Web 服務器正在提供服務的目錄之外. 因此,如果您的應用程序位於/srv/YourApp/
,請將服務器設置為提供來自/srv/YourApp/app/
並將包含項放入/srv/YourApp/includes
,因此實際上沒有任何可以訪問它們的 URL .
我有一個文件,當它被包含與直接訪問時(主要是print()
與return()
),我需要采取不同的行動,這是一些修改后的代碼:
if(count(get_included_files()) ==1) exit("Direct access not permitted.");
被訪問的文件始終是包含文件,因此 == 1。
防止直接訪問文件的最佳方法是將它們放在 Web 服務器文檔根目錄之外(通常在上一級)。 您仍然可以包含它們,但不可能有人通過 http 請求訪問它們。
我通常會一路走下去,將所有 PHP 文件放在文檔根目錄之外,除了引導文件- 文檔根目錄中的一個單獨的 index.php,它開始路由整個網站/應用程序。
if( count(get_included_files()) == ((version_compare(PHP_VERSION, '5.0.0', '>='))?1:0) )
{
exit('Restricted Access');
}
邏輯:如果不滿足最小包含計數,PHP 將退出。 請注意,在 PHP5 之前,基頁不被視為包含。
// In the base page (directly accessed):
define('_DEFVAR', 1);
// In the include files (where direct access isn't permitted):
defined('_DEFVAR') or exit('Restricted Access');
邏輯:如果沒有定義常量,那么執行不是從基頁開始,PHP 將停止執行。
請注意,為了跨升級和未來更改的可移植性,使此身份驗證方法模塊化將顯着減少編碼開銷,因為更改不需要硬編碼到每個文件。
// Put the code in a separate file instead, say 'checkdefined.php':
defined('_DEFVAR') or exit('Restricted Access');
// Replace the same code in the include files with:
require_once('checkdefined.php');
通過這種方式,可以將附加代碼添加到checkdefined.php
以用於記錄和分析目的,以及生成適當的響應。
信用應得的地方:可移植性的絕妙想法來自這個答案。
// Call the include from the base page(directly accessed):
$includeData = file_get_contents("http://127.0.0.1/component.php?auth=token");
// In the include files (where direct access isn't permitted):
$src = $_SERVER['REMOTE_ADDR']; // Get the source address
$auth = authoriseIP($src); // Authorisation algorithm
if( !$auth ) exit('Restricted Access');
這種方法的缺點是隔離執行,除非內部請求提供了會話令牌。 在單服務器配置的情況下通過環回地址進行驗證,或多服務器或負載平衡服務器基礎設施的地址白名單進行驗證。
與前一種方法類似,可以使用 GET 或 POST 將授權令牌傳遞給包含文件:
if($key!="serv97602"){header("Location: ".$dart);exit();}
一種非常凌亂的方法,但如果以正確的方式使用,同時可能也是最安全和最通用的方法。
大多數服務器允許您為單個文件或目錄分配權限。 您可以將所有包含放在此類受限目錄中,並將服務器配置為拒絕它們。
例如在 APACHE 中,配置存儲在.htaccess
文件中。 教程在這里。
但是請注意,我不推薦特定於服務器的配置,因為它們不利於跨不同 Web 服務器的可移植性。 在拒絕算法復雜或被拒絕目錄列表相當大的內容管理系統等情況下,它可能只會使重新配置會話變得相當可怕。 最后最好在代碼中處理這個問題。
由於服務器環境中的訪問限制,因此最不受歡迎,但如果您可以訪問文件系統,則是一種相當強大的方法。
//Your secure dir path based on server file-system
$secure_dir=dirname($_SERVER['DOCUMENT_ROOT']).DIRECTORY_SEPARATOR."secure".DIRECTORY_SEPARATOR;
include($secure_dir."securepage.php");
邏輯:
htdocs
文件夾之外的任何文件,因為鏈接將超出網站地址系統的范圍。請原諒我非正統的編碼約定。 任何反饋表示贊賞。
Chuck 解決方案的替代(或補充)是通過在 .htaccess 文件中放置類似的內容來拒絕訪問匹配特定模式的文件
<FilesMatch "\.(inc)$">
Order deny,allow
Deny from all
</FilesMatch>
實際上,我的建議是執行所有這些最佳實踐。
if (!defined(INCL_FILE_FOO)) {
header('HTTP/1.0 403 Forbidden');
exit;
}
這樣,如果文件以某種方式錯位(錯誤的 ftp 操作),它們仍然受到保護。
我曾經遇到過這個問題,解決了:
if (strpos($_SERVER['REQUEST_URI'], basename(__FILE__)) !== false) ...
但理想的解決方案是將文件放在 web 服務器文檔根目錄之外,如另一個 anwser 中所述。
我想直接限制對PHP文件的訪問,但也可以通過jQuery $.ajax (XMLHttpRequest)
調用它。 這對我有用。
if (empty($_SERVER["HTTP_X_REQUESTED_WITH"]) && $_SERVER["HTTP_X_REQUESTED_WITH"] != "XMLHttpRequest") {
if (realpath($_SERVER["SCRIPT_FILENAME"]) == __FILE__) { // direct access denied
header("Location: /403");
exit;
}
}
你最好用一個入口點構建應用程序,即所有文件都應該從 index.php 訪問
把它放在 index.php 中
define(A,true);
此檢查應在每個鏈接文件中運行(通過 require 或 include)
defined('A') or die(header('HTTP/1.0 403 Forbidden'));
我的答案在方法上有些不同,但包括此處提供的許多答案。 我會推薦一種多管齊下的方法:
defined('_SOMECONSTANT') or die('Hackers! Be gone!');
然而, defined or die
方法有許多失敗之處。 首先,測試和調試的假設是一個真正的痛苦。 其次,如果你改變主意,它涉及可怕的、令人麻木的無聊重構。 “找到並替換!” 你說。 是的,但你有多確定它在任何地方都寫得完全一樣,嗯? 現在乘以數千個文件...... oO
然后是.htaccess。 如果您的代碼分發到管理員不那么謹慎的站點上,會發生什么? 如果你只依靠 .htaccess 來保護你的文件,你還需要 a) 一個備份,b) 一盒紙巾擦干眼淚,c) 一個滅火器來撲滅人們所有仇恨郵件中的火焰使用您的代碼。
所以我知道這個問題要求“最簡單”,但我認為這需要更多的“防御性編碼”。
我的建議是:
require('ifyoulieyougonnadie.php');
(不include()
並作為defined or die
的替代品) 在ifyoulieyougonnadie.php
,做一些邏輯的事情——檢查不同的常量、調用腳本、本地主機測試等等——然后實現你的die(), throw new Exception, 403
等。
我正在使用兩個可能的入口點創建自己的框架——主 index.php(Joomla 框架)和 ajaxrouter.php(我的框架)——所以根據入口點,我檢查不同的東西。 如果對ifyoulieyougonnadie.php
的請求不是來自這兩個文件之一,我知道正在進行ifyoulieyougonnadie.php
!
但是如果我添加一個新的入口點呢? 不用擔心。 我只是更改ifyoulieyougonnadie.php
並且我已排序,並且沒有“查找和替換”。 萬歲!
如果我決定移動我的一些腳本來做一個不同的框架,它沒有相同的常量defined()
怎么辦? ……萬歲! ^_^
我發現這個策略讓開發變得更有趣,但更少:
/**
* Hmmm... why is my netbeans debugger only showing a blank white page
* for this script (that is being tested outside the framework)?
* Later... I just don't understand why my code is not working...
* Much later... There are no error messages or anything!
* Why is it not working!?!
* I HATE PHP!!!
*
* Scroll back to the top of my 100s of lines of code...
* U_U
*
* Sorry PHP. I didn't mean what I said. I was just upset.
*/
// defined('_JEXEC') or die();
class perfectlyWorkingCode {}
perfectlyWorkingCode::nowDoingStuffBecauseIRememberedToCommentOutTheDie();
最簡單的方法是在調用include的文件中設置一些變量,比如
$including = true;
然后在包含的文件中,檢查變量
if (!$including) exit("direct access not permitted");
debug_backtrace() || die ("Direct access not permitted");
什么Joomla! do 是在根文件中定義一個常量並檢查是否在包含的文件中定義了相同的常量。
defined('_JEXEC') or die('Restricted access');
否則
可以通過將所有文件放置在 webroot 目錄之外,如 CodeIgniter 等大多數框架推薦的那樣,將所有文件置於 http 請求范圍之外。
甚至通過在包含文件夾中放置一個 .htaccess 文件並編寫規則,您可以阻止直接訪問。
除了 .htaccess 方式,我在各種框架中看到了一個有用的模式,例如在 ruby on rails 中。 它們在應用程序根目錄中有一個單獨的 pub/ 目錄,並且庫目錄位於與pub/相同級別的目錄中。 像這樣的東西(不理想,但你明白了):
app/
|
+--pub/
|
+--lib/
|
+--conf/
|
+--models/
|
+--views/
|
+--controllers/
您將 Web 服務器設置為使用 pub/ 作為文檔根目錄。 這為您的腳本提供了更好的保護:雖然它們可以從文檔根目錄訪問以加載必要的組件,但無法從 Internet 訪問這些組件。 除了安全性之外,另一個好處是一切都集中在一個地方。
這種設置比僅僅在每個包含的文件中創建檢查要好,因為“不允許訪問”消息是攻擊者的線索,而且它比 .htaccess 配置更好,因為它不是基於白名單的:如果你搞砸了文件擴展名它在 lib/、conf/ 等目錄中不可見。
如果更准確地說,你應該使用這個條件:
if (array_search(__FILE__, get_included_files()) === 0) {
echo 'direct access';
}
else {
echo 'included';
}
get_included_files()返回包含所有包含文件名稱的索引數組(如果文件被正常執行,則它被包含並且其名稱在數組中)。 所以,當直接訪問文件時,它的名字是數組中的第一個,數組中的所有其他文件都被包含在內。
if (basename($_SERVER['PHP_SELF']) == basename(__FILE__)) { die('Access denied'); };
<?php
$url = 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
if (false !== strpos($url,'.php')) {
die ("Direct access not premitted");
}
?>
<?php
if (eregi("YOUR_INCLUDED_PHP_FILE_NAME", $_SERVER['PHP_SELF'])) {
die("<h4>You don't have right permission to access this file directly.</h4>");
}
?>
將上面的代碼放在包含的 php 文件的頂部。
例如:
<?php
if (eregi("some_functions.php", $_SERVER['PHP_SELF'])) {
die("<h4>You don't have right permission to access this file directly.</h4>");
}
// do something
?>
以下代碼用於 Flatnux CMS ( http://flatnux.altervista.org ):
if ( strpos(strtolower($_SERVER['SCRIPT_NAME']),strtolower(basename(__FILE__))) )
{
header("Location: ../../index.php");
die("...");
}
我發現了這個僅適用於 php 和不變的解決方案,它適用於 http 和 cli :
定義一個函數:
function forbidDirectAccess($file) {
$self = getcwd()."/".trim($_SERVER["PHP_SELF"], "/");
(substr_compare($file, $self, -strlen($self)) != 0) or die('Restricted access');
}
調用要防止直接訪問的文件中的函數:
forbidDirectAccess(__FILE__);
上面針對這個問題給出的大多數解決方案在 Cli 模式下都不起作用。
將包含文件存儲在 Web 可訪問目錄之外已多次被提及,這在可能的情況下當然是一個好策略。 但是,我還沒有提到另一個選項:確保您的包含文件不包含任何可運行代碼。 如果您的包含文件僅定義了函數和類,而沒有除此之外的其他代碼,則直接訪問它們時只會產生一個空白頁。
無論如何,允許從瀏覽器直接訪問這個文件:它不會做任何事情。 它定義了一些函數,但是它們都沒有被調用,所以它們都沒有運行。
<?php
function a() {
// function body
}
function b() {
// function body
}
這同樣適用於僅包含 PHP 類而沒有其他任何內容的文件。
盡可能將您的文件保存在 Web 目錄之外仍然是一個好主意。
system
的頁面,因為這會與用於代碼的路徑沖突。 我覺得這很煩人。出於安全原因,我建議不要使用$_SERVER
。
您可以使用像$root=true;
這樣的變量$root=true;
在包含另一個文件的第一個文件中。
並在包含的第二個文件的開頭使用isset($root)
。
您還可以做的是密碼保護目錄並將所有 php 腳本保存在那里,當然除了 index.php 文件,因為在包含密碼時不需要密碼,因為它僅用於 http 訪問。 它還可以為您提供訪問腳本的選項,以防萬一,因為您將擁有訪問該目錄的密碼。 您需要為該目錄設置 .htaccess 文件和一個 .htpasswd 文件來驗證用戶。
好吧,您也可以使用上面提供的任何解決方案,以防您覺得不需要正常訪問這些文件,因為您始終可以通過 cPanel 等訪問它們。
希望這有幫助
最簡單的方法是將包含的內容存儲在 Web 目錄之外。 這樣服務器可以訪問它們但不能訪問外部機器。 唯一的缺點是您需要能夠訪問服務器的這一部分。 好處是它不需要設置、配置或額外的代碼/服務器壓力。
我沒有發現 .htaccess 的建議那么好,因為它可能會阻止該文件夾中您可能希望允許用戶訪問的其他內容,這是我的解決方案:
$currentFileInfo = pathinfo(__FILE__);
$requestInfo = pathinfo($_SERVER['REQUEST_URI']);
if($currentFileInfo['basename'] == $requestInfo['basename']){
// direct access to file
}
if ( ! defined('BASEPATH')) exit('No direct script access allowed');
會順利完成工作
添加了前面提到的帶有 PHP 版本檢查的解決方案:
$max_includes = version_compare(PHP_VERSION, '5', '<') ? 0 : 1;
if (count(get_included_files()) <= $max_includes)
{
exit('Direct access is not allowed.');
}
做類似的事情:
<?php
if ($_SERVER['SCRIPT_FILENAME'] == '<path to php include file>') {
header('HTTP/1.0 403 Forbidden');
exit('Forbidden');
}
?>
您可以使用phpMyAdmin樣式:
/**
* block attempts to directly run this script
*/
if (getcwd() == dirname(__FILE__)) {
die('Attack stopped');
}
這是谷歌在其PHP示例中使用的內容, 請參見此處
if (php_sapi_name() != 'cli') {
throw new \Exception('This application must be run on the command line.');
}
您可以使用下面的方法,但它確實有一個缺陷,因為它可以被偽造,除非您可以添加另一行代碼以確保請求僅來自您的服務器或使用 Javascript。 您可以將此代碼放在 HTML 代碼的正文部分,以便在那里顯示錯誤。
<?
if(!isset($_SERVER['HTTP_REQUEST'])) { include ('error_file.php'); }
else { ?>
將您的其他 HTML 代碼放在這里
<? } ?>
像這樣結束它,因此錯誤的輸出將始終顯示在正文部分中,如果您希望它是這樣的話。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.