참고: 이 게시물을 진행하려면 PHP 프로그래밍에 대한 기본 지식이 있다고 가정합니다.
이 기사에서는 즐겨 사용하는 CMS 또는 프레임워크 상단에서 본 적이 있는 PHP 코드 조각에 대해 설명합니다. 비록 이유에 대한 명확한 설명은 없지만 보안상의 이유로 개발하는 모든 PHP 파일의 시작 부분에 항상 이를 포함해야 한다는 내용을 읽었을 것입니다. 저는 다음 코드를 언급하고 있습니다:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
이러한 유형의 코드는 WordPress 파일에서 매우 일반적이지만 거의 모든 프레임워크와 CMS에 나타납니다. 예를 들어 Joomla CMS의 경우 유일한 변경 사항은 ABSPATH 대신 JEXEC를 사용한다는 것입니다. 그 외에는 논리는 동일하게 유지됩니다. 이 CMS는 유사한 코드를 사용했지만 상수가 _VALID_MOS인 Mambo라는 이전 시스템에서 발전했습니다. 더 거슬러 올라가면 이런 종류의 코드를 사용한 최초의 CMS는 PHP-Nuke였습니다(일부에서는 최초의 PHP 기반 CMS로 간주함).
PHP-Nuke(및 오늘날 대부분의 CMS 및 프레임워크)의 실행 흐름은 사용자 또는 방문자가 웹사이트에서 수행한 작업에 응답하기 위해 여러 파일을 순차적으로 로드하는 것으로 구성되었습니다. 예를 들어, 이 CMS가 설치된 example.net에서 호스팅되는 그 시대의 웹사이트를 상상해 보세요. 홈페이지가 로드될 때마다 시스템은 일련의 파일을 순서대로 실행했습니다(이것은 단지 예일 뿐이며 실제 순서는 아닙니다): index.php => load_modules.php => module.php. 이 체인에서는 index.php가 먼저 로드되었고, 그 다음 load_modules.php가 로드되었고, 차례로 module.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..."); }
기본적으로 module.php라는 파일 상단에 있는 이 코드는 URL을 통해 module.php에 직접 액세스하고 있는지 확인합니다. 그렇다면 실행이 중지되고 "이 파일에 직접 액세스할 수 없습니다..."라는 메시지가 표시됩니다. $HTTP_SERVER_VARS['PHP_SELF']에 module.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을 통해 직접 스크립트를 실행하려고 하면 이러한 종속성을 찾을 수 없어 오류가 발생하고 실행이 진행되지 않습니다.
그렇다면 걱정할 이유가 거의 없는데 상수 코드를 추가하는 이유는 무엇입니까? 대답은 이렇습니다. "이 방법은 또한 register globals 공격을 통해 실수로 변수를 삽입하는 것을 방지하여 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 }
개발자는 코드에 프런트 컨트롤러 패턴을 사용하려고 했을 것입니다. 즉, 모든 웹 요청이 단일 항목 파일(index.php, home.php 등)을 통과한다는 의미입니다. 이 파일은 세션 초기화를 처리하고, 공통 변수를 로드하고, 마지막으로 요청을 특정 스크립트(이 경우 download.php)로 리디렉션하여 파일 다운로드를 수행합니다.
그러나 공격자는 앞서 언급한 것처럼 download.php?filepath=/etc/passwd를 호출하기만 하면 의도된 실행 순서를 우회할 수 있습니다. PHP는 /etc/passwd 값을 사용하여 전역 변수 $filepath를 자동으로 생성하여 공격자가 시스템에서 해당 파일을 다운로드할 수 있도록 합니다. 심각한 문제입니다.
최소한의 노력으로 훨씬 더 위험한 공격을 실행할 수 있으므로 이는 빙산의 일각에 불과합니다. 예를 들어, 프로그래머가 미완성 스크립트로 남겨둔 다음과 같은 코드에서는:
<?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 파일을 생성한 경우 도메인(useless_code.php?base_path=https)을 전달하여 취약한 스크립트를 호출할 수 있습니다. //mysite.net, 그러면 공격이 완료됩니다.
또 다른 예: 다음 코드를 사용하는 Remove_file.inc.php라는 스크립트에서:
<?php if (!defined('MODULE_FILE')) { die ("You can't access this file directly..."); }
공격자는 제거_file.inc.php?filename=/etc/hosts와 같은 URL을 사용하여 이 파일을 직접 호출하여 시스템에서 /etc/hosts 파일을 삭제하려고 시도할 수 있습니다(시스템이 허용하는 경우 또는 다른 파일을 삭제 권한이 있습니다.
내부적으로 전역 변수를 사용하는 WordPress와 같은 CMS에서는 이러한 유형의 공격이 치명적이었습니다. 그러나 지속적인 기술 덕분에 이러한 스크립트와 기타 PHP 스크립트는 보호되었습니다. 마지막 예를 살펴보겠습니다.
<?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; }
이제 누군가가 Remove_file.inc.php?filename=/etc/hosts에 액세스하려고 하면 상수가 액세스를 차단합니다. 논리적으로 변수라면 공격자가 주입할 수 있으므로 상수여야 합니다.
지금쯤이면 PHP가 이 기능을 유지한 이유가 그렇게 위험하다면 궁금할 것입니다. 또한 다른 스크립팅 언어(JSP, Ruby 등)를 알고 있다면 유사한 것이 전혀 없다는 것을 알 수 있습니다(이것이 바로 상수 기술을 사용하지 않는 이유입니다). PHP는 처음에 C 기반 템플릿 시스템으로 만들어졌으며 이러한 동작으로 인해 개발이 더 쉬워졌습니다. 좋은 소식은 PHP 관리자가 이 기능을 비활성화할 수 있도록 Register_globals(기본적으로 활성화됨)라는 php.ini 지시문을 도입했다는 것입니다.
그러나 문제가 지속되면서 기본적으로 비활성화되었습니다. 그럼에도 불구하고 많은 호스트는 당시 클라이언트의 프로젝트가 작동을 멈출 것이라는 두려움 때문에 이 기능을 계속 활성화했습니다. 당시 코드의 대부분은 권장 HTTP_*_VARS 변수를 사용하여 GET/POST/... 값에 액세스하지 않고 오히려 사용했기 때문입니다. 전역 변수.
마침내 상황이 호전되지 않는다는 것을 확인하고 이러한 모든 문제를 피하기 위해 PHP 5.4에서 이 기능을 제거하는 과감한 결정을 내렸습니다. 따라서 오늘날 우리가 본 것과 같은 스크립트(상수를 사용하지 않음)는 보통 특정 경우에 무해한 경고/알림을 제외하고는 더 이상 위험하지 않습니다.
오늘날에도 꾸준한 기술이 일반적입니다. 그러나 불행한 현실이자 이 기사를 쓴 이유는 이 기능을 사용하는 진정한 이유를 이해하는 개발자가 거의 없다는 것입니다.
과거의 다른 모범 사례(예: 참조 문제를 피하기 위해 매개 변수를 함수 내부의 로컬 변수에 복사하거나 개인 변수에 밑줄을 사용하여 구분)와 마찬가지로 많은 사람들이 누군가가 한 번 그것이라고 말했기 때문에 계속해서 적용합니다. 오늘날에도 여전히 가치를 더하는지 여부는 의심하지 않고 좋은 습관입니다. 사실 대다수의 경우에는 이 기술이 더 이상 필요하지 않습니다.
이 관행이 관련성을 잃은 몇 가지 이유는 다음과 같습니다.
*register 전역 변수 제거: PHP 5.4부터 PHP에서 GET 및 POST 변수를 전역 변수로 자동 등록하는 기능이 제거되었습니다. *전역 등록 없이 개별 스크립트를 직접 실행하는 것은 해롭지 않으며 이 기술의 주요 이유를 제거합니다.
더 나은 코드 디자인: PHP 5.4 이전 버전에서도 최신 코드는 일반적으로 클래스와 함수에서 더 잘 구조화되어 외부 변수를 통한 액세스 또는 조작이 더 어려워집니다. 전통적으로 전역 변수를 사용했던 WordPress도 이러한 위험을 최소화합니다.
*전면 컨트롤러 사용: 요즘 대부분의 웹 애플리케이션은 잘 설계된 *전면 컨트롤러를 사용하여 클래스 및 함수 코드가 실행되는 경우에만 실행되도록 합니다. 체인은 주 진입점에서 시작됩니다. 따라서 누군가가 분리된 파일을 로드하려고 시도하는 경우 흐름이 올바른 진입점에서 시작되지 않으면 논리가 트리거되지 않습니다.
클래스 자동 로딩: 현대 개발에서 클래스 자동 로딩이 널리 사용되면서 include 또는 require 사용이 크게 줄었습니다. 이를 통해 숙련된 개발자들 사이에서 이러한 방법(예: 원격 파일 포함 또는 로컬 파일 포함)과 관련된 위험을 줄일 수 있습니다.
공개 코드와 비공개 코드의 분리: 많은 최신 CMS 및 프레임워크에서 공개 코드(예: 자산)는 비공개 코드(논리)와 분리됩니다. 이 조치는 서버에서 PHP가 실패하는 경우 PHP 코드(상수 기술 사용 여부에 관계없이)가 노출되지 않도록 보장하므로 특히 중요합니다. 이러한 분리는 전역 등록을 완화하기 위해 특별히 구현되지는 않았지만 다른 보안 문제를 방지하는 데 도움이 됩니다.
친숙한 URL의 광범위한 사용: 요즘에는 친숙한 URL을 사용하도록 서버를 구성하는 것이 일반적이며 애플리케이션 로직에 대한 단일 진입점을 보장합니다. 이로 인해 누구든지 PHP 파일을 따로 로드하는 것이 거의 불가능해졌습니다.
프로덕션 오류 억제: 대부분의 최신 CMS 및 프레임워크는 기본적으로 오류 출력을 비활성화하므로 공격자는 애플리케이션의 내부 작동에 대한 단서를 찾지 못합니다. 이는 다른 유형의 공격을 용이하게 할 수 있습니다.
이 기술이 대부분의 경우 더 이상 필요하지 않더라도 이것이 결코 유용하지 않다는 의미는 아닙니다. 전문 개발자로서 각 상황을 분석하고 지속적인 기술이 작업 중인 특정 상황과 관련이 있는지 결정하는 것이 중요합니다. 이러한 비판적 사고는 소위 모범 사례라 할지라도 항상 적용되어야 합니다.
지속적인 기법을 언제 적용해야 할지 아직 확실하지 않은 경우 다음 권장 사항을 참고하세요.
그 외 다 궁금하다면 적용해 보세요. 대부분의 경우 해롭지 않으며 예상치 못한 상황에서 보호할 수 있습니다. 특히 시작하는 경우에는 더욱 그렇습니다. 시간과 경험을 통해 이 기술과 기타 기술을 보다 효과적으로 적용할 시기를 평가할 수 있을 것입니다.
위 내용은 프레임워크와 CMS의 이상한 PHP 코드의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!