Русский
Русский
English
Статистика
Реклама

Скриптовые языки

Перевод Трюки с переменными среды

17.07.2020 08:05:27 | Автор: admin
Интересные переменные среды для загрузки в интерпретаторы скриптовых языков

Вступление


В недавнем хакерском проекте мы получили возможность указывать переменные среды, но не выполняемый процесс. Мы также не могли контролировать содержимое файла на диске, а брутфорс идентификаторов процессов (PID) и файловых дескрипторов не дал интересных результатов, исключив удалённые эксплоиты LD_PRELOAD. К счастью, исполнялся интерпретатор скриптового языка, который позволял нам выполнять произвольные команды, задавая определённые переменные среды. В этом блоге обсуждается, как произвольные команды могут выполняться рядом интерпретаторов скриптового языка при вредоносных переменных среды.

Perl


Беглое чтение раздела ENVIRONMENT справочной страницы perlrun(1) показывает множество переменных среды, достойных изучения. Переменная среды PERL5OPT позволяет задавать параметры командной строки, но ограничивается только принятием параметров CDIMTUWdmtw. К сожалению, это означает отсутствие -e, которая даёт загружать код perl для запуска.

Однако не всё потеряно, как показано в эксплоите для CVE-2016-1531 от Hacker Fantastic. Эксплоит записывает вредоносный модуль perl в файл /tmp/root.pm и предоставляет переменные среды PERL5OPT=-Mroot и PERL5LIB=/ tmp для выполнения произвольного кода. Однако это был эксплоит для локальной уязвимости эскалации привилегий, а общий метод в идеале не должен требовать доступа к файловой системе. Глядя на эксплоит от blasty для того же CVE, он не требовал создания файла, использовал переменные среды PERL5OPT=-d и PERL5DB=system("sh");exit;. Те же переменные были использованы для решения задачи CTF в 2013 году.

Последняя тонкость универсального метода заключается в использовании одной переменной среды вместо двух. @justinsteven обнаружил, что это возможно с помощью PERL5OPT=-M. В то время как для загрузки модуля perl можно использовать либо -m, либо -M, но опция -M позволяет добавлять дополнительный код после имени модуля.

Доказательство концепции


Пример 0: Выполнение произвольного кода с помощью переменной среды против perl, выполняющего пустой скрипт (/dev/null)
$ docker run --env 'PERL5OPT=-Mbase;print(`id`)' perl:5.30.2 perl /dev/nulluid=0(root) gid=0(root) groups=0(root)

Python


Судя по разделу ENVIRONMENT VARIABLES в манах по python(1), PYTHONSTARTUP изначально выглядит как простое решение. Он позволяет указать путь к скрипту Python, который будет выполнен до отображения приглашения в интерактивном режиме. Требование к интерактивному режиму не казалось проблемой, поскольку переменная среды PYTHONINSPECT может использоваться для входа в интерактивный режим, так же как и -i в командной строке. Однако документация для опции -i объясняет, что PYTHONSTARTUP не будет использоваться, когда python запускается со скриптом для выполнения. Это означает, что PYTHONSTARTUP и PYTHONINSPECT не могут быть объединены, а PYTHONSTARTUP имеет эффект только тогда, когда Python REPL немедленно запускается. Это в конечном счете означает, что PYTHONSTARTUP нежизнеспособен, так как не имеет никакого эффекта при выполнении обычного скрипта Python.

Многообещающе выглядели переменные среды PYTHONHOME и PYTHONPATH. Обе позволяют произвольное выполнение кода, но требуют, чтобы вы также могли создавать каталоги и файлы в файловой системе. Возможно, удастся ослабить эти требования с помощью виртуальной файловой системы /proc и/или ZIP-файлов.

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

Работа с помощью PYTHONWARNINGS


В документации для PYTHONWARNINGSговорится, что это эквивалентно указанию параметра -W. Параметр -W используется для управления предупреждениями, чтобы указать предупреждения и как часто их выводить. Полная форма аргумента action:message:category:module:line. Хотя контроль предупреждений не казался многообещающей зацепкой, это быстро изменилось после проверки реализации.

Пример 1: Python-3.8.2/Lib/warnings.py
[...]def _getcategory(category):    if not category:        return Warning    if '.' not in category:        import builtins as m        klass = category    else:        module, _, klass = category.rpartition('.')        try:            m = __import__(module, None, None, [klass])        except ImportError:            raise _OptionError("invalid module name: %r" % (module,)) from None[...]

Этот код показывает, что пока наша указанная категория содержит точку, мы можем запустить импорт произвольного модуля Python.

Следующая проблема в том, что подавляющее большинство модулей из стандартной библиотеки Python при импорте выполняют очень мало кода. Они обычно просто определяют классы, которые будут использоваться позже, и даже когда они предоставляют код для запуска, код обычно защищён проверкой переменной __main__ (чтобы определить, был ли файл импортирован или запущен напрямую).

Неожиданным исключением из этого правила является модуль antigravity. Разработчики Python в 2008 году включили пасхальное яйцо, которое можно вызвать запуском import antigravity. Этот импорт немедленно откроет в вашем браузере комикс xkcd с шуткой, что импорт антигравитации в Python даёт возможность летать.

Что касается того, как модуль antigravity открывает ваш браузер, он использует другой модуль из стандартной библиотеки под названием webbrowser. Этот модуль проверяет ваш PATH для большого разнообразия браузеров, включая mosaic, opera, skipstone, konqueror, chrome, chromium, firefox, links, elinks и lynx. Он также принимает переменную среды BROWSER с указанием, какой процесс выполнить. Процессу в переменной среды нельзя предоставить аргументы, а URL-адрес комикса xkcd является единственным жёстко закодированным аргументом для команды.

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

Использование Perl для выполнения произвольного кода


Один из подходов заключается в использовании Perl, который обычно установлен в системе и даже доступен в стандартном докеровском образе Python. Однако нельзя использовать бинарник perl сам по себе, потому что первым и единственным аргументом является URL-адрес комикса xkcd. Данный аргумент вызовет ошибку, а процесс завершится без использования переменной среды PERL5OPT.

Пример 2: PERL5OPT не оказывает никакого эффекта, когда URL передаётся в perl
$ docker run -e 'PERL5OPT=-Mbase;print(`id`);exit' perl:5.30.2 perl https://xkcd.com/353/Can't open perl script "https://xkcd.com/353/": No such file or directory

К счастью, когда Perl доступен, также часто доступны сценарии Perl по умолчанию, такие как perldoc и perlthanks. Эти скрипты также будут завершаться с ошибкой и недопустимым аргументом, но ошибка в этом случае происходит позже, чем обработка переменной среды PERL5OPT. Это означает, что вы можете использовать полезную нагрузку переменной среды Perl, подробно описанную ранее в этом блоге.

Пример 3: PERL5OPT работает как положено с perldoc и perlthanks
$ docker run -e 'PERL5OPT=-Mbase;print(`id`);exit' perl:5.30.2 perldoc https://xkcd.com/353/uid=0(root) gid=0(root) groups=0(root)$ run -e 'PERL5OPT=-Mbase;print(`id`);exit' perl:5.30.2 perlthanks https://xkcd.com/353/uid=0(root) gid=0(root) groups=0(root)

Доказательство концепции


Пример 4: Выполнение произвольного кода с использованием нескольких переменных среды с Python 2 и Python 3
$ docker run -e 'PYTHONWARNINGS=all:0:antigravity.x:0:0' -e 'BROWSER=perlthanks' -e 'PERL5OPT=-Mbase;print(`id`);exit;' python:2.7.18 python /dev/nulluid=0(root) gid=0(root) groups=0(root)Invalid -W option ignored: unknown warning category: 'antigravity.x'$ docker run -e 'PYTHONWARNINGS=all:0:antigravity.x:0:0' -e 'BROWSER=perlthanks' -e 'PERL5OPT=-Mbase;print(`id`);exit;' python:3.8.2 python /dev/nulluid=0(root) gid=0(root) groups=0(root)Invalid -W option ignored: unknown warning category: 'antigravity.x'

NodeJS


Михал Бентковски в своём блоге выложил полезную нагрузку для эксплоита Kibana (CVE-2019-7609). Прототип уязвимости с загрязнением был использован для установки произвольных переменных среды, которые приводили к произвольному выполнению команд. Полезная нагрузка от Михала использовала переменную среды NODE_OPTIONS и файловую систему proc, в частности, /proc/self/environ.

Хотя техника Михала творческая и отлично подходит в его случае, она не всегда гарантированно работает и имеет некоторые ограничения, которые было бы неплохо устранить.

Первое ограничение заключается в том, что он использует /proc/self/environ только в том случае, если содержимое можно сделать синтаксически допустимым JavaScript. Для этого необходимо иметь возможность создать переменную среды и заставить её появиться сначала в содержимом файла /proc/self/environ или знать/сбрутить имя переменной среды, которое появится первым, и перезаписать её значение.

Ещё одно ограничение в том, что значение первой переменной среды заканчивается однострочным комментарием (//). Поэтому любой символ новой строки в других переменных среды, скорее всего, вызовет синтаксическую ошибку и предотвратит выполнение полезной нагрузки. Использование многострочных комментариев (/*) не исправит проблему, так как они должны быть закрыты, чтобы быть синтаксически корректными. Поэтому в редких случаях, когда переменная среды содержит символ новой строки, необходимо знать/сбрутить имя переменной среды и перезаписать её значение на новое значение, которое не содержит новой строки.

Устранение этих ограничений оставим в качестве упражнения для читателя.

Доказательство концепции


Пример 5. Выполнения произвольного кода с переменными среды против NodeJS Михала Бентковски
$ docker run -e 'NODE_VERSION=console.log(require("child_process").execSync("id").toString());//' -e 'NODE_OPTIONS=--require /proc/self/environ' node:14.2.0 node /dev/nulluid=0(root) gid=0(root) groups=0(root)

РНР


Если запустить ltrace -e getenv php /dev/null, то вы обнаружите, что PHP использует переменную среды PHPRC. Переменная среды используется при попытке найти и загрузить конфигурационный файл php.ini. Эксплоит от neex для CVE-2019-11043 использует ряд параметров PHP, чтобы добиться выполнения произвольного кода. У Orange Tsai также есть отличный пост о создании собственного эксплоита для того же CVE, который использует немного другой список настроек. Используя эти знания, а также знания, полученные из предыдущей техники NodeJS, и некоторую помощь Брендана Скарвелла, было найдено решение для PHP с двумя переменными среды.

Для этого метода существуют те же ограничения, что и для примеров NodeJS.

Доказательство концепции


Пример 6: Выполнения произвольного кода с переменными среды против PHP
$ docker run -e $'HOSTNAME=1;\nauto_prepend_file=/proc/self/environ\n;<?php die(`id`); ?>' -e 'PHPRC=/proc/self/environ' php:7.3 php /dev/nullHOSTNAME=1;auto_prepend_file=/proc/self/environ;uid=0(root) gid=0(root) groups=0(root)

Ruby


Универсальное решение для Ruby пока не найдено. Ruby действительно принимает переменную среды RUBYOPT для указания параметров командной строки. На man-странице говорится, что RUBYOPT может содержать только -d, -E, -I, -K, -r, -T, -U, -v, -w, -W, --debug, --disable-FEATURE и --enable-FEATURE. Наиболее перспективным вариантом является -r, который заставляет Ruby загружать библиотеку с помощью require. Однако это ограничивается файлами с расширением .rb или .so.

Найденный пример относительно полезного файла .rb это tools/server.rb из gem'а json, который доступен после установки Ruby в системах Fedora. Когда требуется этот файл, запускается веб-сервер, как показано ниже:

Пример 7: Использование переменной среды RUBYOPT для запуска процесса ruby и старта веб-сервера

$ docker run -it --env 'RUBYOPT=-r/usr/share/gems/gems/json-2.3.0/tools/server.rb' fedora:33 /bin/bash -c 'dnf install -y ruby 1>/dev/null; ruby /dev/null'Surf to:http://27dfc3850fbe:6666[2020-06-17 05:43:47] INFO  WEBrick 1.6.0[2020-06-17 05:43:47] INFO  ruby 2.7.1 (2020-03-31) [x86_64-linux][2020-06-17 05:43:47] INFO  WEBrick::HTTPServer#start: pid=28 port=6666

Другой подход в Fedora заключается в том, чтобы использовать тот факт, что /usr/bin/ruby на самом деле является скриптом Bash, который запускает /usr/bin/ruby-mri. Скрипт вызывает функции Bash, которые могут быть перезаписаны переменными среды.

Доказательство концепции


Пример 8: Использование экспортированной функции Bash для выполнения произвольной команды
$ docker run --env 'BASH_FUNC_declare%%=() { id; exit; }' fedora:33 /bin/bash -c 'dnf install ruby -y 1>/dev/null; ruby /dev/null'uid=0(root) gid=0(root) groups=0(root)

Заключение


В этом посте были рассмотрены интересные случаи использования переменных среды, которые могли бы помочь в достижении произвольного выполнения кода с помощью различных интерпретаторов скриптового языка, не записывая файлы на диск. Надеюсь, вам понравилось читать и вам захотелось найти и поделиться улучшенные полезные нагрузки для этих и других скриптовых языков. Если найдёте универсальную технику, которая работает против Ruby, будет очень интересно услышать о ней.
Подробнее..

Интерпретатор скрипта на С

24.12.2020 20:04:36 | Автор: admin
Всем привет.

Написал простой интерпретатор, конечно не конкурент lua, но тоже может пригодиться.
Кому интересно прошу.

Сразу пример, что получилось:

stringstream ss;ss << "$a = 5;"      "$b = 2;"      "while($a > 1){"      "  $a -= 1;"      "  $b = summ($b, $a);"      "  if($a < 4){"      "    break;"      "  }"      "}"      "$b";string res = ir.cmd(ss.str()); // 9

Что хотелось


Идея была в том, чтобы полностью отделить функционал (пользовательские операторы и функции) от самого языка скрипта, ограничиться минимумом ключевых слов (всем известных), ну и сделать интерпретатор и язык компактным и удобным для использования.

Что получилось


Скриптовый язык вышел простой и ограниченный конечно.

Состоит из трех компонентов переменные, выражения и функции, и нескольких базовых ключевых слов. Тип значений для всех компонентов строка.

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

Как это работает


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

Явно АСД не создается, но косвенно появляется (можно сказать в плоскости массива) в виде очереди операций, которые должны быть выполнены последовательно, то есть другими словами, все встречающиеся сущности попадают в массив операций сразу в нужном порядке выполнения.

Все ошибки написания скрипта находятся на этом этапе.

Второй этап выполнение скрипта. Здесь идет проход по массиву операций, с последовательным выполнением каждой.

Внутри все построено на рекурсивном вызове функций и проверках условий вызова.

Основные компоненты скрипта:

  • Переменная. Любая последовательность символов в коде скрипта начинающаяся с '$', считается переменной.

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

    Объявляются и используются только в коде скрипта, сразу использовать без объявления можно (значение по умолчанию пустая строка):

    $c = 5 + 6;summ($c, 6);
    

    Ко всем переменным в скрипте можно обращаться (и изменять их при необходимости) из основного кода, например, в функции:

    Intrerpreter ir;ir.addFunction("summScriptVars", [&ir](const vector<string>& args) ->string {    int res = 0;    for (auto& v : ir.allVariables()) {      if (isNumber(v.second)) res += stoi(v.second);    }    return to_string(res);  });
    
  • Выражение. Состоит из переменных, операторов и вызовов функций.

    Обязательно должно заканчиваться символом ';'.

    Может быть параметром функции, в этом случае его не нужно закрывать символом ';'.

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

    $b = 4; $c = 5 + $b + 3 - 7; $a = $b * (3 + $c) + summ($a, $b, $c + 1);
    
  • Функция. Любые функции создаются на уровне основного кода, в скрипте только используются. Функция принимает массив параметров, возвращает строку как результат работы.
    Сначала функцию нужно определить и добавить в основном коде:

    Interpreter ir;ir.addFunction("summ", [](const vector<string>& args) ->string {    int res = 0;    for (auto& v : args) {      if (isNumber(v)) res += stoi(v);    }    return to_string(res);  });
    

    В скрипте функция вызывается по имени, параметры передаются в скобочках, как обычно:

    $b = summ($b, $a);
    

    Функция может принимать другие функции и выражения:

    $b = 1;$c = summ($b, summ($b + 5, $b + $b - 1), 4);$a = $c - summ($b, 3);
    
  • Оператор. Любая последовательность символов в коде скрипта, заранее определенная в основном коде, считается оператором.

    Сначала оператор нужно определить и добавить в основном коде:

     Interpreter ir; ir.addOperator("+", [](string& leftOpd, string& rightOpd) ->string {    if (isNumber(leftOpd) && isNumber(rightOpd))      return to_string(stoi(leftOpd) + stoi(rightOpd));    else      return leftOpd + rightOpd;  }, 1);   ir.addOperator("==", [](string& leftOpd, string& rightOpd) ->string {    return leftOpd == rightOpd? "1" : "0";  }, 2);  ir.addOperator("=", [](string& leftOpd, string& rightOpd) ->string {    leftOpd = rightOpd;    return leftOpd;  }, 17);
    

    При создании оператора помимо определения нужно задать приоритет.

    Приоритет работает так же как в С++: нулевой наивысший, далее чем больше значение приоритета, тем позже будет выполнен оператор. Порядок выполнения операторов с одинаковым приоритетом слева направо.

    Операторы используются в выражениях.

    $c = 5 + 6;$b = 2;$a = $c + 5; $c = summ($a + 5 / $b);
    


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

  • while(condition){body}. Выполняет циклически последовательность выражений (далее, тело цикла) в зависимости от результата выполнения условия.

    Условие заключается в скобочки '()' и, как и в любом языке, рассчитывается на каждой итерации цикла.

    Условие считается выполненным, если результат расчета условия для численного значения не равен 0, для строкового значения не пустая строка (численное значение значит, что строка может быть преобразована в целое число).

    Тело цикла заключается в фигурные скобки '{}', и состоит из неограниченной последовательности выражений и управляющих конструкций (то есть в теле цикла могут быть другие циклы).

    $c = 1;$b = 4; while($b > 0){  $c *= $b;   $b -= 1;}
    

  • if(condition){body}. Выполняет однократно последовательность выражений в зависимости от результата выполнения условия.

    $c = 1;$b = 4; if(($b - 4) == 0){  $c = $b;}
    

  • else{body}. Выполняет однократно последовательность выражений, если не было выполнено предыдущее условие.

    $c = 1;$b = 4; if(($b - 3) == 0){  $c = $b;}else{  $b = $c;}
    

  • elseif(condition){body}. Выполняет однократно последовательность выражений, если не было выполнено предыдущее условие и выполняется текущее условие.

    $c = 1;$b = 4; if(($b = $b - 3) == 0){  $c = $b;}elseif($c == summ($b)){  $b = $c;}
    

  • break;. Выполняет прерывание текущего цикла.
    continue;. Начинает заново текущий цикл.

    $b = 4; while($b > 0){  $b = rand(10);  if ($b == 3){    continue;  }  if ($b == 2){    break;   }}
    

  • #macro name{body}. Объявление макроса.

    #name;. Вставка тела макроса далее в коде.

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

    #macro myMc{ $c = 1; $b = 4; };$d = 5;#myMc;
    

  • goto l_name;. Перемещение на метку вверх или вниз по скрипту. Должен быть единственным оператором в выражении.

    l_name:. Метка, на которую можно переместиться.

    Метка обязательно должна начинаться с 'l_' (элл и нижнее подчеркивание) и заканчиваться ':'.

    $a = 5; while($a > 0){  $a -= 1;  if ($a == 2){    goto l_myLabel;  }  }l_myLabel: $a;
    

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

    Interpreter ir;ir.addFunction("myJump", [&ir](const vector<string>& args) ->string {    if (!args.empty())      ir.gotoOnLabel(args[0]);    }    return "";  });
    


Как использовать и где может быть полезен


Предлагается использовать как код, то есть добавлять в свой проект файл исходного кода интерпретатора, он всего один. Заголовочный файл тоже единственный.

Может использоваться в простых случаях, когда не хочется подключать что-то внешнее, но нужно дать пользователю возможность интерактивно влиять на ход выполнения ПО.

Либо в случаях, когда не хочется давать пользователю в руки весь арсенал скриптовых языков, а ограничиться простым набором команд.

Еще можно попробовать построить RPC на его основе.

Что дальше, что планируется нового


Если коротко, то ничего.

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

Только поддержка, правка багов, возможно, стоит добавить несколько заранее определенных пользовательских функций, отдельно.

Распространяется свободно, лицензия MIT

Спасибо.

P.S.:

Я писал его ранее когда-то давно, там получилось не очень. Тут после одного письма пользователя, решил все это дело переписать по нормальному.

Вот думал, публиковать-нет статью на эту поделку, по сути тривиальный баян в принципе, и есть уже мастодонты всякие в этой нише.

Нажал все-таки кнопочку, может еще кому-то пригодится когда.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru