Disable Functions Bypass - dl Function

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

dl() lets PHP load a shared extension at runtime. If you can make it load an attacker-controlled module, you can register a new PHP function that internally calls execve, system, or any other native primitive and therefore bypass disable_functions.

이것은 실제 프리미티브이지만, 현대 대상에서는 오래된 문서들이 암시하는 것보다 훨씬 드뭅니다.

Why this bypass is uncommon today

The main blockers are:

  • dl() must exist and must not be disabled
  • enable_dl must still allow dynamic loading
  • The target SAPI must support dl()
  • The payload must be a valid PHP extension compiled for the same target ABI
  • The extension must be reachable from the configured extension_dir

The official PHP manual is the most important reality check here: dl() is only available for CLI and embed SAPIs, and for the CGI SAPI when run from the command line. That means the technique is usually not available in normal PHP-FPM/mod_php web requests, so check the SAPI before spending time building a payload.

Also note that enable_dl is an INI_SYSTEM setting and, as of PHP 8.3.0, PHP documents it as deprecated, so you usually cannot flip it at runtime from attacker-controlled PHP code.

If dl() is not viable, go back to the broader list of module/version dependent bypasses.

Fast triage from a foothold

Before building anything, collect the exact parameters that the module must match:

<?php
phpinfo();
echo "PHP_VERSION=" . PHP_VERSION . PHP_EOL;
echo "PHP_SAPI=" . php_sapi_name() . PHP_EOL;
echo "ZTS=" . (PHP_ZTS ? "yes" : "no") . PHP_EOL;
echo "INT_BITS=" . (PHP_INT_SIZE * 8) . PHP_EOL;
echo "enable_dl=" . ini_get("enable_dl") . PHP_EOL;
echo "extension_dir=" . ini_get("extension_dir") . PHP_EOL;
echo "disabled=" . ini_get("disable_functions") . PHP_EOL;
?>

What you care about:

  • PHP_SAPI: 이 값이 fpm-fcgi 또는 apache2handler인 경우, 웹 익스플로잇에서 dl()은 보통 막다른 길이다
  • extension_dir: 페이로드는 여기에서 로드되어야 한다
  • PHP 버전, 아키텍처, debug/non-debug, 그리고 ZTS/non-ZTS: 모듈이 이들과 일치해야 한다
  • disable_functions: dl가 비활성화되어 없어졌는지, 아니면 SAPI가 이를 지원하지 않아 없는지 확인하라

Practical exploitation constraints

1. You normally need write access to extension_dir

이것이 가장 큰 병목이다.

dl()확장 파일명을 받아 extension_dir에서 로드한다. 실무적으로 이는 /var/www/html/uploads 같은 일반적인 임의 파일 업로드만으로는 충분하지 않음을 의미한다. PHP가 실제로 확장 모듈을 로드하는 경로에 .so/.dll을 놓을 수 있는 경로가 필요하다.

실제로 이 취약점이 이용될 수 있는 상황:

  • extension_dir가 쓰기 가능한 CTF 또는 고의로 취약한 실습 환경
  • 쓰기 가능한 extension 경로를 노출하는 공유 호스팅 또는 컨테이너 구성 실수
  • 이미 extension_dir에 도달하는 별도의 임의 파일 쓰기 primitive
  • 이미 충분히 권한을 상승시켜 그곳에 파일을 배치할 수 있는 post-exploitation 시나리오

2. The module must match the target build

PHP_VERSION만 일치하는 것으로는 부족하다. 확장 모듈은 또한 다음과 일치해야 한다:

  • OS 및 CPU 아키텍처
  • libc/toolchain 요구사항
  • ZEND_MODULE_API_NO
  • debug vs non-debug 빌드
  • ZTS vs NTS

이 항목들이 일치하지 않으면 dl()는 실패하거나 프로세스를 크래시시킨다.

3. open_basedir is not the main defense here

모듈을 extension_dir에 놓고 dl()를 호출할 수 있게 되면, 확장 모듈 코드는 PHP 프로세스 내부에서 실행된다. 그 시점에서 실질적인 장벽은 open_basedir가 아니라 확장 로딩 경로에 유효한 공유 객체를 배치할 수 있느냐이다.

Building the malicious extension

고전적인 방법은 여전히 유효하다:

  1. 가능한 한 타깃 빌드를 가깝게 재현한다
  2. phpize, ./configure, 그리고 make를 사용해 shared extension을 빌드한다
  3. 네이티브 명령 실행을 감싸는 bypass_exec($cmd)와 같은 PHP 함수를 export한다
  4. 컴파일된 모듈을 extension_dir에 업로드한다
  5. dl()로 로드하고 export한 함수를 호출한다

이 공격은 오래되었지만 여전히 관련성이 있다. PHP 8.x 변경 로그에 dl() 관련 크래시 수정이 계속 포함되고 있기 때문이다. 해당 primitive는 여전히 존재한다; 어려운 부분은 접근 가능한 배포 환경과 일치하는 모듈을 배치할 수 있는 곳을 찾는 것이다.

Minimal workflow

On the attacker box

mkdir bypass && cd bypass
phpize
./configure
make

생성된 공유 객체는 일반적으로 modules/ 아래에 위치합니다.

만약 빌드하는 환경이 target과 다르다면, 생성된 파일은 ABI가 victim과 일치하는지 확인할 때까지 초안으로 취급하세요.

확장 기능 로드 및 사용

만약 target이 실제로 dl()을 지원하고 모듈이 extension_dir 안에 있다면, 런타임 측은 간단합니다:

<?php
if (!extension_loaded('bypass')) {
dl('bypass.so'); // use the correct filename for the target platform
}
echo bypass_exec($_GET['cmd']);
?>

Windows에서는 파일명이 일반적으로 .dll이고, Unix 계열 대상에서는 보통 .so입니다.

공격자 메모

  • 일부 문서나 오래된 작성물에서 function_exists("dl")가 true를 반환한다고 해서 원격에서 작동한다고 가정하지 마세요; 실행 중인 SAPI를 검증하세요
  • 모듈이 호환되지 않을 경우 dl() 시도가 실패하면 PHP 워커가 종료될 수 있습니다
  • PHP 8부터는 비활성화된 함수가 함수 테이블에서 제거되므로, 유저랜드 열거 결과가 이전 게시물들과 다를 수 있습니다
  • extension_dir에 쓸 수 없다면, 이 기법은 보통 FPM/FastCGI, LD_PRELOAD 또는 이 섹션에서 이미 다룬 모듈별 우회 기법들보다 실용성이 낮습니다

참고자료

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기