Нужна помощь в понимании этой короткой программы на C++ и ее уязвимости

Я был бы рад, если бы кто-то мог объяснить мне, что именно делает код. Я знаю, что существует уязвимость, связанная с переполнением буфера и выполнением команд bash, но, поскольку я работаю в сети, а не программистом, я действительно могу использовать некоторую помощь для понимания всего кода. Заранее спасибо!

int main () {
    int status;
    char t[1024]="ps -eo lstart,cmd | grep ";
    cout << "Content-type:text/html\r\n\r\n"<<endl;
    char *value = getenv("QUERY_STRING");
    strcat(t,value);
    status = system(strcat(t," | grep -v grep | head -n 1 | awk '{ print $1\" \"$3\" \"$2\" \"$5\" \"$4}'"));

    return 0;
}

2 ответа

Решение

tl;dr: это то, что делает ваш код в виде сценария оболочки:

#!/bin/bash
echo -en "Content-type:text/html\r\n\r\n"
ps -eo lstart,cmd | grep init | grep -v $QUERY_STRING | \
head -n 1 | awk '{ print $1" "$3" "$2" "$5" "$4}'

Теперь ответ дольше.

Переписать код

Во-первых, давайте сделаем это в C++, а не в C (как показывает ваш тег, о котором вы спрашиваете) с небольшой обработкой ошибок, а затем поговорим о том, что происходит:

#include <iostream>
#include <string>
#include <string_view>

int main () {
    auto query_string = getenv("QUERY_STRING");
    if (query_string == nullptr) {
        std::cerr << "Couldn't obtain QUERY_STRING environment variable\n";
        return EXIT_FAILURE;
    }
    if (std::string_view{query_string}.empty()) {
        std::cerr << "Empty query string (QUERY_STRING environment variable)\n";
        return EXIT_FAILURE;
    }
    std::stringstream command_line;
    command_line 
        << "ps -eo lstart,cmd | grep "
        << query_string 
        << " | grep -v grep | head -n 1 | awk '{ print $1\" \"$3\" \"$2\" \"$5\" \"$4}'";
    std::cout << "Content-type:text/html\r\n\r\n";
    return system(command_line.str()); // security vulnerability, see below
}

Что мы тут делаем?

Итак, мы создаем здесь командную строку, которую затем выполняем, используя system() функция. Это вызов ps команда с некоторыми переключателями с последующей обработкой текста с grep, head а также awk - использование механизма конвейера для перемещения вывода каждой команды на следующую. Их ключевой частью является то, что мы используем переменную среды QUERY_STRING фильтровать ps результаты, т.е. мы перечисляем процессы, которые соответствуют какой-то фразе. Если мы скомпилируем эту программу, установим переменную окружения и запустим, это выглядит так:

$ export QUERY_STRING=init
$ ./the_program
Content-type:text/html


Sun 3 Jun 2018 21:48:56

Это дало нам время запуска первого процесса, командная строка которого не включает фразу "init". Так что теперь вы можете догадаться, что моя система работала со вчерашнего дня...

Наконец, как сетевой парень, вы, вероятно, понимаете "контент-тип" mumbo-jumbo, а double-newline - это заголовок MIME, так что этот вывод, вероятно, предназначен для использования в качестве ответа HTTP. Вероятно, это задумано как своего рода CGI-скрипт.

Уязвимости безопасности

  1. В исходном коде размер буфера был произвольно ограничен 1024 - хотя ничто не ограничивало QUERY_SIZE, чтобы он не был длиннее этого. Если оно длиннее, у вас может быть повреждение памяти, что может иметь последствия для безопасности; и злоумышленник, скорее всего, сможет выяснить расположение вашей памяти, так что это более опасно. Это ушло с версией C++.
  2. Вторая уязвимость связана с system команда. Мы вводим произвольную строку в строку, которую создаем; и ничто не мешает кому-то установить

    $export QUERY_STRING="dummy; rm -rf $HOME ; echo"
    

    в этом случае вы будете запускать:

    ps -eo lstart,cmd | grep dummy; rm -rf $HOME ; echo | grep -v init | head -n 1 | awk '{ print $1" "$3" "$2" "$5" "$4}'
    

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

  3. Даже если вы очистите QUERY_STRING только допустимым шаблоном grep, все равно может произойти атака типа "отказ в обслуживании", если кто-то каким-то образом предоставит сложные, сверхдлинные шаблоны grep. Так что ограничение длины тоже хорошая идея.

Это просто объявляет переменную status

int status;

Это объявило t, с C-String "ps -eo lstart,cmd | grep ".t имеет максимум 1024 байта в емкости (1023 для строки + 1 байт для \0)

char t[1024]="ps -eo lstart,cmd | grep ";

Распечатать строку ниже

cout << "Content-type:text/html\r\n\r\n"<<endl;

получить переменную среды QUERY_STRING

char *value = getenv("QUERY_STRING");

объединить значение выше, чтобы t, Здесь у вас может быть переполнение буфера, потому что вы не знаете размер строки в value и это может быть больше, чем 1024 байта

strcat(t,value);

Вызвать system() функция для другого объединения предыдущего t со всеми этими командами grep, etc, etc... здесь может произойти переполнение буфера, один раз t может также переполнить свои 1024 байта здесь.

status = system(strcat(t," | grep -v grep | head -n 1 | awk '{ print $1\" \"$3\" \"$2\" \"$5\" \"$4}'"));
Другие вопросы по тегам