注意:为了阅读这篇文章,假设您具有一些 PHP 编程的基本知识。
本文讨论了您可能在您最喜欢的 CMS 或框架顶部看到的 PHP 代码片段。您可能已经读过,出于安全原因,您应该始终将它包含在您开发的每个 PHP 文件的开头,尽管没有非常清楚地解释原因。我指的是这段代码:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
这种类型的代码在 WordPress 文件中很常见,尽管它出现在几乎所有框架和 CMS 中。例如,对于 Joomla CMS,唯一的变化是它使用 JEXEC,而不是 ABSPATH。除此之外,逻辑保持不变。这个 CMS 是从之前的一个名为 Mambo 的系统演变而来的,它也使用了类似的代码,但使用 _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(例如,http://example.net/load_modules.php 或 http://example.net/modules.php)直接访问其他 PHP 文件之一来绕过部分流程。 ,正如我们将看到的,这在许多情况下可能存在风险。
这个问题是如何解决的?引入了安全措施,在每个文件的开头添加类似的代码:
<?php if (!eregi("modules.php", $HTTP_SERVER_VARS['PHP_SELF'])) { die ("You can't access this file directly..."); }
本质上,这段代码放置在名为modules.php的文件的顶部,检查是否可以通过URL直接访问modules.php。如果是,则停止执行,显示消息:“You can't access this file direct...” 如果 $HTTP_SERVER_VARS['PHP_SELF'] 不包含modules.php,则意味着正常执行流程处于活动状态,允许脚本继续。
但是,此代码有一些限制。首先,插入代码的每个文件的代码都不同,这增加了复杂性。另外,在某些情况下,PHP 没有为 $HTTP_SERVER_VARS['PHP_SELF'] 赋值,这限制了其有效性。
So, what did the developers do? They replaced all those code snippets with a simpler and more efficient version:
<?php if (!defined('MODULE_FILE')) { die ("You can't access this file directly..."); }
In this new code, which had become quite popular in the PHP community, the existence of a constant was checked. This constant was defined and assigned a value in the first file of the execution flow (index.php, home.php, or a similar file). Therefore, if this constant didn’t exist in any other file in the sequence, it meant that someone had bypassed index.php and was attempting to access another file directly.
At this point, you may be thinking that breaking the execution chain must be extremely serious. However, the truth is that, usually, it doesn’t pose a major threat.
The risk might arise when a PHP error exposes the path to our files. This shouldn’t concern us if the server is configured to suppress errors; even if errors weren’t hidden, the exposed information would be minimal, providing only a few clues to a potential attacker.
It could also happen that someone accesses files containing HTML fragments (views), revealing part of their content. In most cases, this should not be a cause for concern either.
Finally, a developer, either by mistake or lack of experience, might place risky code without external dependencies in the middle of an execution flow. This is very uncommon since framework or CMS code generally depends on other classes, functions, or external variables for its execution. So, if an attempt is made to execute a script directly through the URL, errors will arise as these dependencies won’t be found, and the execution won’t proceed.
So, why add the constant code if there is little reason for concern? The answer is this: "This method also prevents accidental variable injection through a register globals attack, preventing the PHP file from assuming it's within the application when it’s actually not."
Since the early days of PHP, all variables sent via URLs (GET) or forms (POST) were automatically converted into global variables. For example, if the file download.php?filepath=/etc/passwd was accessed, in the download.php file (and in those depending on it in the execution flow), you could use echo $filepath; and it would output /etc/passwd.
Inside download.php, there was no way to know if the variable $filepath was created by a prior file in the execution chain or if it was tampered with via the URL or POST. This created significant security vulnerabilities. Let’s look at an example, assuming the download.php file contains the following code:
<?php if(file_exists($filepath)) { header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.basename($filepath).'"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($filepath)); flush(); // Flush system output buffer readfile($filepath); exit; }
The developer likely intended to use a Front Controller pattern for their code, meaning all web requests would go through a single entry file (index.php, home.php, etc.). This file would handle session initialization, load common variables, and finally redirect the request to a specific script (in this case, download.php) to perform the file download.
However, an attacker could bypass the intended execution sequence simply by calling download.php?filepath=/etc/passwd, as mentioned before. PHP would automatically create the global variable $filepath with the value /etc/passwd, allowing the attacker to download that file from the system. Serious problem.
This is only the tip of the iceberg since even more dangerous attacks could be executed with minimal effort. For example, in code like the following, which the programmer might have left as an unfinished script:
<?php require_once($base_path."/My.class.php");
An attacker could execute any code by using a Remote File Inclusion (RFI) attack. If the attacker created a file My.class.php on their own site https://mysite.net containing any code they wanted to execute, they could call the vulnerable script by passing in their domain: useless_code.php?base_path=https://mysite.net, and the attack would be complete.
Another example: in a script named remove_file.inc.php with the following code:
<?php if(file_exists($filename)) { if( unlink($filename) ) { echo "File deleted"; } }
an attacker could call this file directly with a URL like remove_file.inc.php?filename=/etc/hosts, attempting to delete the /etc/hosts file from the system (if the system allows it, or other files they have permission to delete).
In a CMS like WordPress, which also uses global variables internally, these types of attacks were devastating. However, thanks to the constant technique, these and other PHP scripts were protected. Let’s look at the last example:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } if(file_exists($filename)) { if( unlink($filename) ) { echo "File deleted"; } }
Now, if someone attempted to access remove_file.inc.php?filename=/etc/hosts, the constant would block the access. It is essential that this is a constant because, logically, if it were a variable, an attacker could inject it.
By now, you may wonder why PHP kept this functionality if it was so dangerous. Also, if you know other scripting languages (JSP, Ruby, etc.), you’ll see they have nothing similar (which is why they also don’t use the constant technique). Recall that PHP was initially created as a C-based templating system, and this behavior made development easier. The good news is that, seeing the issues it caused, PHP maintainers introduced a php.ini directive called register_globals (enabled by default) to allow this functionality to be disabled.
But as problems persisted, they disabled it by default. Even so, many hosts kept enabling it out of fear that their clients’ projects would stop working, as much of the code at the time did not use the recommended HTTP_*_VARS variables to access GET/POST/... values but rather used global variables.
Akhirnya, memandangkan keadaan tidak bertambah baik, mereka membuat keputusan drastik: untuk mengalih keluar fungsi ini dalam PHP 5.4 untuk mengelakkan semua masalah ini. Oleh itu, hari ini, skrip seperti yang telah kita lihat (tanpa menggunakan pemalar) biasanya tidak lagi berisiko, kecuali beberapa amaran/notis yang tidak berbahaya dalam kes tertentu.
Hari ini, teknik berterusan masih biasa. Walau bagaimanapun, realiti malang — dan sebab untuk artikel ini — ialah beberapa pembangun memahami sebab sebenar di sebalik penggunaannya.
Seperti amalan terbaik yang lain dari masa lalu (seperti menyalin parameter ke dalam pembolehubah tempatan di dalam fungsi untuk mengelakkan isu dengan rujukan atau menggunakan garis bawah dalam pembolehubah peribadi untuk membezakannya), ramai yang terus menggunakannya hanya kerana seseorang pernah memberitahu mereka bahawa ia adalah amalan yang baik, tanpa mempersoalkan sama ada ia masih menambah nilai hari ini. Sebenarnya, dalam majoriti kes, teknik ini tidak lagi diperlukan.
Berikut ialah beberapa sebab mengapa amalan ini hilang kaitan:
Pengalihan keluar *register globals: Sejak PHP 5.4, pendaftaran automatik pembolehubah GET dan POST sebagai global dalam PHP telah dialih keluar. Tanpa *register globals, melaksanakan skrip individu secara langsung adalah tidak berbahaya, menghapuskan sebab utama teknik ini.
Reka Bentuk Kod yang Lebih Baik: Walaupun dalam versi pra-PHP 5.4, kod moden lebih berstruktur, umumnya dalam kelas dan fungsi, menjadikan akses atau manipulasi melalui pembolehubah luaran lebih mencabar. Malah WordPress, yang secara tradisinya menggunakan pembolehubah global, meminimumkan risiko ini.
Penggunaan *pengawal hadapan: Pada masa kini, kebanyakan aplikasi web menggunakan *pengawal hadapan yang direka bentuk dengan baik untuk memastikan kod kelas dan fungsi hanya dilaksanakan jika pelaksanaan rantaian bermula di pintu masuk utama. Oleh itu, jika seseorang cuba memuatkan fail secara berasingan, logiknya tidak akan dicetuskan melainkan aliran bermula dari titik masuk yang betul.
Pemuatan Auto Kelas: Dengan penggunaan meluas automuat kelas dalam pembangunan moden, penggunaan termasuk atau memerlukan telah berkurangan dengan ketara. Ini mengurangkan risiko yang berkaitan dengan kaedah ini (seperti Kemasukan Fail Jauh atau Kemasukan Fail Tempatan) dalam kalangan pembangun yang lebih berpengalaman.
Pengasingan Kod Awam dan Peribadi: Dalam kebanyakan CMS dan rangka kerja moden, kod awam (seperti aset) diasingkan daripada kod peribadi (logik). Langkah ini amat berharga, kerana ia memastikan bahawa, jika PHP gagal pada pelayan, kod PHP (sama ada ia menggunakan teknik malar atau tidak) tidak terdedah. Walaupun pemisahan ini tidak dilaksanakan secara khusus untuk mengurangkan mendaftar global, ia membantu mencegah isu keselamatan yang lain.
友好 URL 的广泛使用:如今,将服务器配置为使用友好 URL 是常见做法,以确保应用程序逻辑的单一入口点。这使得任何人几乎不可能单独加载 PHP 文件。
生产中的错误抑制:大多数现代 CMS 和框架默认禁用错误输出,因此攻击者无法找到有关应用程序内部工作原理的线索,这可能会促进其他类型的攻击。
尽管在大多数情况下不再需要此技术,但这并不意味着它永远没有用处。作为一名专业开发人员,必须分析每种情况并确定持续的技术是否与您工作的特定环境相关。这种批判性思维应该始终被应用,即使是所谓的最佳实践。
如果您仍然不确定何时应用持续技术,这些建议可能会指导您:
对于其他一切,如果您有疑问,请应用它。在大多数情况下,它不会有害,并且可以在意外情况下保护您,尤其是在您刚开始时。随着时间和经验的积累,您将能够评估何时更有效地应用此技术和其他技术。
以上是框架和 CMS 中奇怪的 PHP 代码的详细内容。更多信息请关注PHP中文网其他相关文章!