Maison > Article > développement back-end > Cet étrange code PHP dans les frameworks et les CMS
Remarque : Pour suivre cet article, il est supposé que vous avez des connaissances de base en programmation en PHP.
Cet article traite d'un extrait de code PHP que vous avez probablement vu en haut de votre CMS ou framework préféré. Vous avez probablement lu que vous devez toujours l'inclure au début de chaque fichier PHP que vous développez, pour des raisons de sécurité, mais sans explication très claire de pourquoi. Je fais référence à ce code :
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
Ce type de code est très courant dans les fichiers WordPress, bien qu'il apparaisse dans presque tous les frameworks et CMS. Dans le cas du CMS Joomla, par exemple, le seul changement est qu'au lieu d'ABSPATH, il utilise JEXEC. A part ça, la logique reste la même. Ce CMS a évolué à partir d'un système précédent appelé Mambo, qui utilisait également un code similaire, mais avec _VALID_MOS comme constante. Si l'on remonte encore plus loin, on constate que le premier CMS à utiliser ce genre de code était PHP-Nuke (considéré par certains comme le premier CMS basé sur PHP).
Le flux d'exécution de PHP-Nuke (et de la plupart des CMS et frameworks aujourd'hui) consistait à charger séquentiellement plusieurs fichiers pour répondre à l'action que l'utilisateur ou le visiteur avait entreprise sur le site Web. Par exemple, imaginez un site Web de cette époque hébergé sur example.net avec ce CMS installé. Chaque fois que la page d'accueil était chargée, le système exécutait une séquence de fichiers dans l'ordre (ce n'est qu'un exemple, pas une séquence réelle) : index.php => load_modules.php => modules.php. Dans cette chaîne, index.php a été chargé en premier, qui a ensuite chargé load_modules.php, qui à son tour a chargé modules.php.
Cette chaîne d'exécution ne commençait pas toujours par le premier fichier (index.php). En fait, n'importe qui pourrait contourner une partie du flux en accédant directement à l'un des autres fichiers PHP via son URL (par exemple, http://example.net/load_modules.php ou http://example.net/modules.php) , ce qui, comme nous le verrons, pourrait être risqué dans de nombreux cas.
Comment ce problème a-t-il été résolu ? Une mesure de sécurité a été introduite, ajoutant un code similaire à celui-ci au début de chaque fichier :
<?php if (!eregi("modules.php", $HTTP_SERVER_VARS['PHP_SELF'])) { die ("You can't access this file directly..."); }
Essentiellement, ce code, placé en haut d'un fichier nommé modules.php, vérifiait si modules.php était accessible directement via l'URL. Si tel était le cas, l'exécution était arrêtée, affichant le message : « Vous ne pouvez pas accéder directement à ce fichier… » Si $HTTP_SERVER_VARS['PHP_SELF'] ne contenait pas modules.php, cela signifiait que le flux d'exécution normal était actif, permettant au script pour continuer.
Ce code présentait cependant certaines limites. Premièrement, le code était différent pour chaque fichier dans lequel il était inséré, ce qui ajoutait à la complexité. De plus, dans certaines situations, PHP n'attribuait pas de valeur à $HTTP_SERVER_VARS['PHP_SELF'], ce qui limitait son efficacité.
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.
마침내 상황이 호전되지 않는다는 것을 확인하고 이러한 모든 문제를 피하기 위해 PHP 5.4에서 이 기능을 제거하는 과감한 결정을 내렸습니다. 따라서 오늘날 우리가 본 것과 같은 스크립트(상수를 사용하지 않음)는 보통 특정 경우에 무해한 경고/알림을 제외하고는 더 이상 위험하지 않습니다.
오늘날에도 꾸준한 기술이 일반적입니다. 그러나 불행한 현실이자 이 기사를 쓴 이유는 이 기능을 사용하는 진정한 이유를 이해하는 개발자가 거의 없다는 것입니다.
과거의 다른 모범 사례(예: 참조 문제를 피하기 위해 매개 변수를 함수 내부의 로컬 변수에 복사하거나 개인 변수에 밑줄을 사용하여 구분)와 마찬가지로 많은 사람들이 누군가가 한 번 그것이라고 말했기 때문에 계속해서 적용합니다. 오늘날에도 여전히 가치를 더하는지 여부는 의심하지 않고 좋은 관행입니다. 사실 대부분의 경우에는 이 기술이 더 이상 필요하지 않습니다.
이 관행이 관련성을 잃은 몇 가지 이유는 다음과 같습니다.
*register 전역 변수 제거: PHP 5.4부터 PHP에서 GET 및 POST 변수를 전역 변수로 자동 등록하는 기능이 제거되었습니다. *전역 등록 없이 개별 스크립트를 직접 실행하는 것은 해롭지 않으며 이 기술의 주요 이유를 제거합니다.
더 나은 코드 디자인: PHP 5.4 이전 버전에서도 최신 코드는 일반적으로 클래스와 함수에서 더 잘 구조화되어 외부 변수를 통한 액세스 또는 조작이 더 어려워집니다. 전통적으로 전역 변수를 사용했던 WordPress도 이러한 위험을 최소화합니다.
*전면 컨트롤러 사용: 요즘 대부분의 웹 애플리케이션은 잘 설계된 *전면 컨트롤러를 사용하여 클래스 및 함수 코드가 실행되는 경우에만 실행되도록 합니다. 체인은 주 진입점에서 시작됩니다. 따라서 누군가가 분리된 파일을 로드하려고 시도하는 경우 흐름이 올바른 진입점에서 시작되지 않으면 논리가 트리거되지 않습니다.
클래스 자동 로딩: 현대 개발에서 클래스 자동 로딩이 널리 사용되면서 include 또는 require 사용이 크게 줄었습니다. 이를 통해 숙련된 개발자들 사이에서 이러한 방법(예: 원격 파일 포함 또는 로컬 파일 포함)과 관련된 위험을 줄일 수 있습니다.
공개 코드와 비공개 코드의 분리: 많은 최신 CMS 및 프레임워크에서 공개 코드(예: 자산)는 비공개 코드(논리)와 분리됩니다. 이 조치는 서버에서 PHP가 실패하는 경우 PHP 코드(상수 기술 사용 여부에 관계없이)가 노출되지 않도록 보장하므로 특히 중요합니다. 이러한 분리는 전역 등록을 완화하기 위해 특별히 구현되지는 않았지만 다른 보안 문제
를 방지하는 데 도움이 됩니다.友好 URL 的广泛使用:如今,将服务器配置为使用友好 URL 是常见做法,以确保应用程序逻辑的单一入口点。这使得任何人几乎不可能单独加载 PHP 文件。
生产中的错误抑制:大多数现代 CMS 和框架默认禁用错误输出,因此攻击者无法找到有关应用程序内部工作原理的线索,这可能会促进其他类型的攻击。
尽管在大多数情况下不再需要此技术,但这并不意味着它永远没有用处。作为一名专业开发人员,必须分析每种情况并确定持续的技术是否与您工作的特定环境相关。这种批判性思维应该始终被应用,即使是所谓的最佳实践。
如果您仍然不确定何时应用持续技术,这些建议可能会指导您:
对于其他一切,如果您有疑问,请应用它。在大多数情况下,它不会有害,并且可以在意外情况下保护您,尤其是在您刚开始时。随着时间和经验的积累,您将能够评估何时更有效地应用此技术和其他技术。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!