Lucene search

K
rdotNameSpaceRDOT:3052
HistoryMar 11, 2014 - 12:00 a.m.

PHP: Теория выражений

2014-03-1100:00:00
NameSpace
rdot.org
479

Всем привет! Сегодня хотелось бы поговорить о выражениях; о том, что они из себя представляют. Нет, это не невнятная публикация представляющая из себя выдержки c манов и ссылки. Сначала небольшой экскурс, а затем, новая и полезная информация.

Примечание: Если у вас отключен тег img, перейдите на <https://www.rdot.org/forum/showthread.php?p=34833&gt;

Что это?

В конце прошлого месяца был опубликован лист будущих докладов PHDays IV. Конечно, это не программа, однако общие тенденции уже прослеживаются - выставлено все самое новое и современное, но, где классика? Неужели все изжито и не о чем говорить?

Например PHP -PHP 5.3,5.4,5.5… Прежде уязвимое ПО становится безопасными:register_globalsиexpose_phpбольше не существуют,NULL-байт и методы с 4096-слэшами больше не работают,headerне принимает символы переноса строки, расширениеMySQLвытесняетсяMySQLi

Это начало конца? Это конец начала!

Я, конечно, не претендую на уровень докладов, однако, мне важно полное понимание между мной и читателями - следующая часть текста именно об этом. Затем на арену выходит аудит проекта по стандартной методике, и, напоследок - самое вкусненькое.

Сегодня на наковальне PHP; исследование PHP ≠ исследование PHP Source: работа будет вестись только на уровне самого языка, более не требуется - есть еще много интересного, что можно найти не спускаясь ниже

Поехали!

PHP код:

mysql_query(“SELECT * FROM users WHERE id = {$_GET[‘id’]}”) or print(mysql_error());

На этом, возможно, наиболее-простом примере, который еще понадобится, можно вспомнить краткий набор главных терминов.

1. Уязвимость — недостаток системы, используя который, можно намеренно нарушить её целостность и вызвать неправильную работу.

Примечательно, что в случае корректных значений $_GET**[‘id’]** система будет исправно функционировать.

2. Эксплуатация уязвимости — процесс операций, ведущих к нарушению целостности системы вследствие данных недостатков.

3. Результативность — «Коэффициент» эффективности эксплуатации(без учета усилий, затраченных на разработку метода).

Возьмем трех любопытных аналитиков и посмотрим, что они будут «делать» с примером на последних шагах.
Боб:

Код:

http://127.0.0.1/test.php?id=if(ascii(substring((SELECT concat(user, 0x3B, password) FROM mysql.user LIMIT 1), 1, 1)) = 0 — 255, 5, 10)

Алиса:

Код:

http://127.0.0.1/test.php?id=1 or 1 group by concat((mid((SELECT concat(user, 0x3B, password) FROM mysql.user LIMIT 1), 1, 64), floor(rand(0) * 2)) having min(0) or 1

Мэлори:
Мэлори запускает громоздкий, тяжелый и не очень удобный скрипт, использующий одни из самых результативных возможностей (RDot).

Боб, сразу видно, «совсем не в теме», и использует самый тупой способ. Можно сказать, что он «крутит» «не по теории».

Алиса взяла MySQL Manual, быстрые и эффективные «векторы атаки ✎»BlackFan,Dr.Z3r0, воспользовалась ими и уже успешно «залилась ✎» на «сервак ✎».

Мэлори использует передовые достижения теории. Большинство их реализаций(если таковые вообще имеются), как правило, являются прототипами и в настоящее время непригодны для повседневного использования.

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

Результат забега:

> 1. Алиса> 2.Мэлори> 3.Боб

Векторы (возм. Вектора) — направление исследований, группа методов.

Векторы перекрываются, особенности векторов одной области могут встречаться в другой. Один из случаев векторов — векторы атаки. Их часто используют не по назначению — для классификации уязвимостей, из-за этого возникает множество проблем. Пример с форума:
<https://rdot.org/forum/showthread.php?t=2911&gt;

Цитата:

A: <script>alert(‘xss’);</scipt>

B: <?=readfile($_GET[‘xss’])?>

Сервер,Сервак- Удаленный компьютер.Сервер — Одна из сторон в клиент-серверной архитектуре.

Заливка — загрузка web-shell’а на отдельный сервер. Исторически сложилось, что WEB-ресурсы стали строить по технологии CGI; это казалось наиболее-простым решением, и сейчас, большинство подходов схожи, однако, например при использованииNode.JS, так делать сомнительно.

Заливку шелла часто считают целью, однако, цель — получение RCE, возможноPHP-Injection на данном сервере. Почему на данном? WEB-приложение не всегда набор скриптов на целевом хосте, часто это инфраструктура.

Аудит мечты: „Метод исключения“

Стандартный взгляд на распределение уязвимостей

Проверим данную схему на примере одного из файлов?

PHP код:

<?php
set_time_limit(30);
error_reporting(E_ALL);
ini_set(‘memory_limit’, ‘64M’);
ini_set(‘display_errors’, ‘1’);

const CMS_VERSION = 5.0;

$pepper = “\xfd\x2b\xd0\x6f\xe6\x13\x7c\xe1\x58\xc3\xf\x98\xeb\xc6\xc5\x91”;

if(!isset($_POST[‘username’]) && !isset($_POST[‘password’]))
die(‘Hacking attemp!’);

function pass_test($password) {
if((string)$password != $password || is_array($password) || in_array($password, [‘’], 1) || preg_match(“/^[0-9a-f]{32}$/D”, $md5 = md5($password)) !== 1)
die(‘Hacking attemp!’);

$md5 = md5(preg_replace_callback(‘/./’, function ($matches) use (&$password) {
global $pepper, $user;

for($i = strlen($user) - 1; $i > 0; $i--) {
$chr = chr(ord($user[$i - 1]) ^ ord($pepper[$i % strlen($pepper)]));

if(!isset($str)) {
$str = $matches[0];

if(empty($user[$i - 1]) && !isset($password))
$user .= $chr and $password;
} else {
$str .= $chr . $password . $matches[0];
}
}

$password = md5($password);

return $str;
}, $md5));

return " AND pass = ‘" . addslashes($md5) . "’";
}

$user = “name = '” . addslashes((string) @$_POST[‘username’]) . “'”;

$res = mysql_query(
‘SELECT pass FROM ’ .
(CMS_VERSION >= 3.1 ? ‘user’ : ‘users’) .
’ WHERE ’ .
(CMS_VERSION >= 3.1 ? $user . pass_test(@$_POST[‘password’]) : “{$user} AND pass = '” . md5(@$_POST[‘password’]) . ‘'’) .
’ AND group != 'ban' AND useragent = '’ . addslashes(strip_tags(urldecode((string) @$_SERVER[‘HTTP_USER_AGENT’]))) . ‘' LIMIT 1;’
);

$pass = mysql_result($res, 0);
$hash = md5($pass . $pepper . $pass . $pepper);

if($hash === “…”) {
// …
} else {
// …
}

Задача сформулирована в цитате ниже. Попробуйте свои силы и вы, на все должно уйти не более 5-ти минут. Время пошло

За дело, как и прежде, берется Боб. Боб - сертифицированный специалист, он производит действия согласно строгим правилам, ему важно качественно и в наименьшие сроки написать отчет, дать точную оценку.

Цитата:

Сообщение от Боб

Задача: Произвести проверку админ-панели на критические уязвимости. Учесть, что функция хэширования устойчива к коллизиям, брутфорсу и другим возможным атакам на алгоритм. Используется кодировка UTF-8.

Аудит: Рассмотрим два вектора атаки:

  • Изменение предпологаемой логики скрипта (Обход авторизации / etc)
  • Небезопасная обработка данных (XSS / SQL Injection / RCE / etc)
    Сократим код таким образом:

PHP код:


function pass_test($password) {

return " AND pass = ‘" . addslashes($md5) . "’";
}
$user = “name = '” . addslashes((string) @$_POST[‘username’]) . “'”;

$res = mysql_query(
‘SELECT pass FROM ’ .
(CMS_VERSION >= 3.1 ? ‘user’ : ‘users’) .
’ WHERE ’ .
(CMS_VERSION >= 3.1 ? $user . pass_test(@$_POST[‘password’]) : “{$user} AND pass = '” . md5(@$_POST[‘password’]) . ‘'’) .
’ AND group != 'ban' AND useragent = '’ . addslashes(strip_tags(urldecode((string) @$_SERVER[‘HTTP_USER_AGENT’]))) . ‘' LIMIT 1;’
);
… // Обработка данных

Все строки, включаемые в SQL-запрос обрабатываются функцией addslashes. Избавиться от одной из проверок можно только тогда, когдаpass_test()вернет некорректное значение, ноreturn в его составе вызывается только 1 раз, что исключает данную возможность.

Вывод: Данный скрипт не содержит критических уязвимостей, проверка входных данных осуществляется должным образом. В часть обработки данных поступление произвольного содержимого исключено.


Да, вот он - правильный, корректный и краткий аудит. Произведен быстро, несмотря на сложность кода. Браво!

Контрразведчик должен знать всегда, как никто другой, что верить в
наше время нельзя никому. Порой даже самому себе.
Мне можно.

Вы верите Бобу? Я - нет.

Цитата:

Сообщение от Алиса

Аудит, как ни странно, начинается с первого байта.

PHP код:

if(!isset($_POST[‘username’]) && !isset($_POST[‘password’]))
die(‘Hacking attemp!’);

Оператор && перепутан с**||**. Выхода, когда только одна из переменных не инициализированна, не происходит.

Не отправлять $_POST[‘username’] смысла не имеет, имя юзера используется только в один раз и сразу же приводится к строке. С**$_POST[‘password’]** все заметно интереснее.

Куда идёт пароль, большой секрет,
Большой секрет, большой секрет.
А мы всегда идём ему вослед.

Пароль, проходя через pass_test() проходит все проверки, однако, как таковая уязвимость отсутствует - хэш будет сформирован, обойти авторизацию не удасться.

_Тип уязвимости: _ Опечатка
_Уровень: _ Несущественный
_Патч: _ _if(!isset($_POST[‘username’], $POST[‘password’]))


Двое аналитиков не могут выжать из кода даже пути - все, крышка. Или нет, может быть Мэлори превратит брюки в элегантные шорты?

Цитата:

Сообщение от Мэлори

Если автор решил реализовать мощный криптовелосипед, то он обязан где-то ошибиться. И он это сделал.

_Уязвимость: _ SQL Injection

_Описание: _ Рассмотрим данную строку кода:

PHP код:

(CMS_VERSION >= 3.1 ? $user . pass_test(@$_POST[‘password’]) : “{$user} AND pass = '” . md5(@$_POST[‘password’]) . ‘'’) .

Так как CMS_VERSION = 5.0, можно избавиться от лишнего:

PHP код:

($user . pass_test($_POST[‘password’])) .

Функция pass_test() выполняется раньше, чем происходит чтение**$user**, вследствие чегоpass_test()имеет возможность заменить подготовленное содержимое$user на желаемое. Принимая во внимание аудит Алисы, добиться этого не сложно. Изменение переменной может произойти только здесь:

PHP код:

$user .= $chr and $password;

На первый взгляд, к строе добавляется булевый результат, однако, рассматривая таблицу приоритетов нетрудно заметить ошибку.

Используя логин ABCDEFGHJK0 получаем следующий запрос:

PHP код:

SELECT * FROM user WHERE name = ‘ABCDEFGHJK0’# AND password = ‘17b247299450baeccffd6a2a043b5953’ AND group != ‘ban’ AND useragent = ‘Mozilla/5.0’;

Теперь вместо User-Agent можно передать перенос строки и инжектируемый текст.


Да! Остался вопрос классификации: вывода нет, при некорректном запросе mysql_result броситWarning, этоBoolean-based Blind SQL Injection.

Стоп. Никогда не выполняйте шаблонные действия, не бросайте рассматривать код. Классификация — загон уязвимости в жесткие критерии. Критериев меньше — возможностей больше. Если поискать хорошо, вывод найдется.

Цитата:

Сообщение от https://www.linux.org.ru/forum/talks/8039022#comment-8039103


А вот нормальные люди, которые пишут на PHP. У них всегда все как-то работает, но никто не знает, что будет в пограничных случаях: возможно, сообщение будет отправлено случайному пользователю, возможно, на сервере закончится место. Что стрясется — не знает никто, но все делают вид, что все в порядке.


Здесь кончается не место, здесь кончается память. Если memory-limit = 128M, вы можете наблюдать такую картину:

Код:

$len = 62914559;
$pass = str_repeat('a', $len);

$hash = md5($pass . "salt" . $pass . "salt");

PHP Fatal error:  Allowed memory size of **67108864** bytes exhausted (tried to allocate**62914564** bytes)

Выводимое значение зависит от $len.

Итого: В безопасном коде обнаружилась SQL-Injection, имеющая вывод в виде числа определенного диопазона.

Технический минимум

Для дальнейшего восприятия материала вам необходимо знать, успешно определять и разделять следующие составляющие языка:

Выражения — совокупности операндов и операторов, используемые в конструкциях. Выражения могут не иметь явного смысла, но обязаны быть синтаксически-корректными. Точка с запятой— конструкция разделения исполняемых последовательно выражений. Скобки (пользовательские) — оператор приоритета.

Пример (Зеленым цветом выделены выражения):

Код:

&lt;?php
$test = (125 + 3) * 2;
$test * 2;

for($i=1; $test, false; 3 * 3) {
     $test /= ++$i;;;;;;;;;;
}

echo $i;

echo является языковой конструкцией, такой же, как иempty,include,isset,__halt_compiler… Каждая из них требует индивидуального рассмотрения.

Почти все в PHP строится из этого, для объяснения уязвимостей корректнее ссылаться на данные понятия. Пример:

PHP код:

$eval = (string) $GET[‘eval’];
$regexp = "/^[a-zA-Z0-9\(\)\{\}$\[\]&amp;\#\
\`'\\\\]+$/D";

if(preg_math($regexp, $eval) !== 1)
exit;

eval(‘$test = "’ . $eval. ‘";’);

Получить RCE, а точнее PHP-injection здесь просто: $_GET[‘eval’] = ‘{${eval($_COOKIE[code])}}’. Объянить - сложнее, но возможно. Что есть текст в кавычках, к примеру “abc”? Строка? Нет, строка это abc, $regexp имеет строковый тип и содержит строку, текст в кавычках - строковый литерал.

Строковый литерал преобразовывает в нем указанное в строку. Двойные кавычки позволяют использовать экранирование и вставлять переменные. Обратите внимание, для инициализации $regexp слэш пришлось проэкранировать дважды - один раз на литерал, второй - для парсера регулярных выражений. Скобки - нет, их экранирование не поддерживается.

При задании $test в литерал попадает произвольный текст. Сложный синтаксис задания переменных выглядит как**{$var_name}** и позволяет использовать все их возможности, в том числе, обращение таким образом:

PHP код:

${“title_default_” . $title} = “selected”;

Где и можно выполнить произвольный код.

Интерпретация бинарных операторов

Итак, вернемся к аудиту Мэлори. По таблице приоритетов оператор . (точка, объединение строк) имеет левую ассоциативность.

PHP код:

$a = 0;

echo ($a) . ($a++); // Выведет 10

Таблица врет? Таблица не говорит правду! Эта ситуация часто называется неявным или неопределенным поведением. Впрочем, оно, как и все остальное, определено.

Процесс интерпретации выражений:

Вроде верно, так и есть. Выполнение также идет по порядку - сначала 1-ый оператор, затем 2-й, 3-ий, …

N-ный оператор в качестве аргументов принимает результат исполнения предыдущего оператора, следующий операнд; результат исполнения идет в первый аргумент следущего оператора.

Первому опертору негде брать результат исполнения предыдущего - он работает с двумя аргументами. Дабы не выполнять лишние действия, там, где это возможно, передача осуществляется по ссылке. Порядок исполнения бинарного оператора очень похож на порядок работы функций:

  1. Получение аргументов
  2. Вычисление
  3. Возврат результата

Аргументы исполняются до процесса объединения, из-за ссылочной передачи первый будет равен 1, второй -0(его возвратит выражение**$a++**).

Примеры:

PHP код:

$a = 0;

function &test() {
global $a;

$a++;

return $a;
}

echo test() + test(); // 4, оба аргумента - ссылки

$a = “abc”;
function &test() {
global $a;

return $a;
}
test()[0] = ‘X’;
var_dump($a); // string(3) “Xbc”

$a = 1;
echo ++$a + $a++; // 4, оба аргумента - выражения

$a = 1;
echo $a + ++$a + $a++; // $a + ++$a + $a++ = ($a + ++$a) + $a++ = 6

$a = 1;
$arr[$a] = $a + $a++; // [2 => 3], так как = имеет правую ассоциативность

function x() {
static $x = 0;

return $x++;
}

$arr = array(x() => x(), x() => x()); // [0 => 1, 2 => 3], так как => не является оператором.

Строковые операторы

Строковые операторы (.,.=) имеют одну небольшую, но в тоже время значительную особенность, отличающую их от остальных - работу с объемными величинами.

Как правило, числа не отнимают много памяти у скрипта - более того, занимаемый ими объем всегда фиксирован. Строковые значения могут содеражть мегабайты, или даже гигабайты данных. Благодоря этому, в скрипте выше стало возможно получить вывод, однако, вспомнили ли вы про Max_allowed_packet?

Max_allowed_packet по умолчанию в Debian / Ubunutu / Kali Linux равен16M, это значит, что более16 * 1024 * 1024 байтов с сервера передать невозможно (строка данного объема пересылается без ухищрений).

PHP код:

$pass = mysql_result($res, 0);
$hash = md5($pass . $pepper . $pass . $pepper);

На $pass приходится16M, на аргумент md5 -32M. Скрипт на момент получения записи отнимает не более1M, вычисление хэша требует не более того же.memory_limit = 64M - недобор.

Авторский просчет? Нет, достаточно помнить, что операторы универсальны. В качестве аргументов всегда передаются ссылки на области памяти. Память, если не требуется обратного, оператор не в праве изменять. Оператор не будут подстрачиваться под конкретный вариант аргумента, будь то результат выполнения, литерал или переменная.


Схема работы оператора . (точка). Обе линии - копирование.

PHP код:

$len = 1024 * 1024;

$pass = str_repeat(‘a’, $len);

$pass; // 1 M

$pass . ‘salt’;
/*
Первый аргумент передается по ссылке, он записывается в новую
область памяти, где после него из дописывается salt из
своей области памяти.

Ссылка на эту область передается следующему оператору.

Размер возвращаемой строки(=): 1M + 4B
Максимальный размер занимаемой памяти(≈): 1M (Переменная $pass) + 1M + 8B (Выражение) = 2M + 8B
*/

$pass . ‘salt’ . ‘salt’; // Кажется, ничего не изменилось, однако …
($pass . ‘salt’) . ‘salt’;
/*
Первый аргумент - выражение, результат его выполнения передается
по ссылке.

Размер возвращаемой строки(=): 1M + 4B + 4B = 1M + 8B
Максимальный размер занимаемой памяти(≈): 1M (Переменная $pass) + 1M + 4B (Первый аргумент) + 4B (Второй аргумент) + 1M + 8B (Результат) = 3M + 16B

Чудо!
*/

Уязвимый код:

PHP код:

$pass = mysql_result($res, 0);
$hash = md5($pass . $pepper . $pass . $pepper);

// Текст ошибки
Fatal error: Allowed memory size of 67108864 bytes exhausted (tried to allocate 33554449 bytes) in /var/www/index.php on line X

Первое значение в ошибке - memory_limit (64 * 1024 * 1024 = 67108864 = 64M).
Второе - размер области памяти, при попытки выделении которой произошла ошибка (pass + pepper + pass + 1 = 16M + 16B + 16M + 1 = 33554449).

Если принять длину $pass за 16M, то для совершения операции понадобится как минимум 16M (pass) + (16M + 16B + 16M (pass + pepper + pass))*2 + 16B (pepper) ≈ 81M

Да, 81, а не32 + 16 + 0,…, как могло показаться изначально.

Exploiting

Для эксплуатации уязвимости необходимы вычисления: в первую очередь следует определить нижнюю границу длины строки, при передаче которой гарантрированно возникнет ошибка. Она зависит только от memory_limit и текущего использования памяти (memory_get_usage()), на которое, в свою очередь, влияетHTTP-запрос. Запрос - палка о двух концах, больший даст увеличение диопазона, меньший - уменьшение загрузки канала, времени для примема/передачи.

В примере memory_limit = 64M.81M, как было выяснено, хватит всем. Что будет в случае80M, останется 2-3 бита?

Нижняя граница равна 16646124, верхняя граница -@@max_allowed_packet,16777216. Диопазон -131092,log[2, 131092]≈ 17.205, результативность - 17 бит за запрос. Для получения18следует нагрузить сервер еще на0.3M(установлено экспирементальным путем), делать это или нет - решать вам.

Цитата:

В самом деле, для увеличения объема занимаемой памяти, к примеру, на 0.3M, необязательно отправлять0.3M. Для простейшего варианта, POST переменной со строкой, достаточно лишь0.1M - и это притом, что к ней нет обращений.

SSL же имеет возможность двусторонего сжатия запросов, где, используяgzip, можно передавать сотни мегабайт за секунды.

Оптимизация логических выражений

Еще одна вещь, которую потребуеться знать - оптимизация логических выражений. Она применена в первом примере:

Код:

mysql_query("SELECT * FROM `users` WHERE id = {$_GET['id']}") or print(mysql_error());

Как только логическое выражение становится определенным - выполнение прекращается. Исполнение идет по порядку - сначала выполняется mysql_query, еслиmysql_query(…) == true - результат выражения определен и выполнять второй операнд смысла не имеет.

Важно, что это работает только для логических операторов, для других - никогда, несмотря на то, что результат также един:

PHP код:

function x() {
echo ‘OK’;

return 65535;
}

0 && x(); // ‘’
0 & x(); // ‘OK’

Аргументы логических операторов выполняются как независимые:

PHP код:

$x = 1;

var_dump(–$x || $x + $x++); // 0 || (1 + 0) == 0 || 1 == TRUE

Главное, не запутаться с приоритетами и рассматривать всё выражение целиком. Пример из MySQL практики:

PHP код:

mysql> SELECT 1 FROM dual WHERE 1 or sleep(1) – …
±–+
| 1 |
±–+
| 1 |
±–+
1 row in set (0.00 sec)

mysql> SELECT 1 FROM dual WHERE 1 = 2 && 1 or sleep(1) – …
Empty set (1.00 sec)

// 1 = 2 && 1 or sleep(1) == ((1 = 2) && 1) or sleep(1)

FAQ

Q: md5(‘240610708’) == md5(‘QNKCDZO’) = TRUE, каким образом? Это имеет отношение к вышесказанному? A:Это особенность работы оператора==, к содержимому это отношения не имеет.

Конец

PHP код:

<?php
$a = ‘A’;
$b = ‘B’;

var_dump(print($a)); // Aint(1)

if(print($a) === $b) { // Пример 1
var_dump(‘ABC’); // etc
// …
}

Вопрос: Что будет выведено в результате исполнения примера 1?

Продолжение следует

Полезные ссылки:

>
<http://3v4l.org/&gt; - online PHP shell, execute code in 100+ different PHP versions
http://www.php.net/manual/ru/faq.usi…shorthandbytes - сокращение едениц измерений