image Привет, Хаброжители! Командная строка может стать идеальным инструментом для обеспечения кибербезопасности. Невероятная гибкость и абсолютная доступность превращают стандартный интерфейс командной строки (CLI) в фундаментальное решение, если у вас есть соответствующий опыт.

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


Для кого эта книга


Книга «Bash и кибербезопасность» предназначена для тех, кто желает научиться работать с командной строкой в контексте компьютерной безопасности. Наша цель — не заменить существующие инструменты скриптами для командной строки, а, скорее, научить вас эффективно использовать командную строку для улучшения существующего функционала.

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

Эта книга не является введением в программирование, хотя в части I и освещены некоторые общие понятия.

Мониторинг журналов в режиме реального времени


Умение анализировать журнал после того, как событие произошло, — важный навык. Но не менее важно иметь возможность извлекать информацию из файла журнала в режиме реального времени, чтобы обнаруживать вредоносные или подозрительные действия в то время, когда они происходят. В этой главе мы рассмотрим методы чтения записей журнала по мере их создания и форматирования для вывода аналитики и создания предупреждений на основе известных показателей угрозы для работы системы или сети (indicators of compromise).

Мониторинг текстовых журналов


Самый простой способ мониторинга журнала в режиме реального времени — использовать команду tail с параметром –f — она непрерывно считывает файл и по мере добавления новых строк выводит их в stdout. Как и в предыдущих главах, для примеров будем использовать журнал доступа к веб-серверу Apache, но описанные методы актуальны для любого текстового журнала. Чтобы отслеживать журнал доступа Apache с помощью команды tail, введите следующее:

tail -f /var/logs/apache2/access.log

Вывод из команды tail может быть передан команде grep, поэтому будут выводиться только записи, соответствующие определенным критериям. В следующем примере отслеживается журнал доступа Apache и выводятся записи, соответствующие конкретному IP-адресу:

tail -f /var/logs/apache2/access.log | grep '10.0.0.152'

Можно также использовать регулярные выражения. В этом примере будут отображаться только записи, возвращающие код состояния HTTP 404 «Страница не найдена»; параметр -i добавляется для игнорирования регистра символов:

tail -f /var/logs/apache2/access.log | egrep -i 'HTTP/.*" 404'

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

$ tail -f access.log | egrep --line-buffered 'HTTP/.*" 404' | cut -d' ' -f4-7
[29/Jul/2018:13:10:05 -0400] "GET /test
[29/Jul/2018:13:16:17 -0400] "GET /test.txt
[29/Jul/2018:13:17:37 -0400] "GET /favicon.ico


Далее, чтобы убрать квадратные скобки и двойные кавычки, вы можете направить вывод в tr -d '[]"'.

Обратите внимание: здесь используется параметр --line-buffering команды egrep. Это вынуждает egrep выводить в stdout каждый раз, когда происходит разрыв строки. Без данного параметра произойдет буферизация и выходные данные не будут переданы команде cut до тех пор, пока буфер не будет заполнен. Мы не хотим так долго ждать. Данный параметр позволит команде egrep записывать каждую строку сразу по мере ее нахождения.

БУФЕРЫ КОМАНДНОЙ СТРОКИ

Что же происходит при буферизации? Представьте, что egrep находит много строк, соответствующих указанному шаблону. В этом случае у egrep будет много выходных данных. Но вывод (фактически любой ввод или вывод) намного затратнее (занимает больше времени), чем обработка данных (поиск текста). Таким образом, чем меньше вызовов ввода/вывода, тем эффективнее будет работа программы.

При обнаружении совпадения семейство программ grep копирует совпадающую строку в большую область памяти, называемую буфером, в котором достаточно места для размещения большого количества строк текста. После поиска и копирования множества совпадающих строк буфер заполнится. Затем grep делает один вызов для вывода всего буфера. Представьте себе случай, когда grep может поместить 50 совпадающих строк в буфер. В этом случае, вместо того чтобы сделать 50 выходных вызовов, по одному для каждой строки, необходимо сделать только один вызов. Это в 50 раз эффективнее!

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

Но при чтении из конвейера, особенно для нашего примера, когда tail -f вносит данные в конвейер не так часто (только когда происходят определенные события), данных для заполнения буфера может не хватить. Буфер, чтобы мы увидели его содержимое в «реальном времени», в ближайшее время не будет очищен. Нам придется подождать его заполнения. А на это может уйти несколько часов или даже дней.

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

Обнаружение вторжений с помощью журнала


Для мониторинга журнала и вывода любых записей, которые соответствуют известным шаблонам подозрительной или вредоносной деятельности, часто называемым IOC, можно использовать возможности команд tail и egrep. Вы можете создать простую систему обнаружения вторжений (IDS). Для начала создадим файл, содержащий шаблоны регулярных выражений для IOC, как показано в примере 8.1.

Пример 8.1. ioc.txt

\.\./ (1)
etc/passwd (2)
etc/shadow
cmd\.exe (3)
/bin/sh
/bin/bash

(1) Этот шаблон (../) является показателем обходной атаки каталога: злоумышленник пытается выйти из текущего рабочего каталога и добраться к файлам, доступ к которым ему закрыт.

(2) Файлы Linux etc/passwd и etc/shadow используются для аутентификации системы и никогда не должны быть доступны через веб-сервер.

(3) Обслуживание файлов cmd.exe, /bin/sh или /bin/bash является показателем наличия обратного подключения, возвращаемого веб-сервером. Обратное подключение часто говорит об успешной попытке эксплуатации.

Обратите внимание, что IOC должны быть в формате регулярного выражения, так как они позже будут использоваться командой egrep.

Файл ioc.txt можно использовать с параметром egrep -f. Данный параметр приказывает egrep выполнять поиск в шаблонах регулярных выражений из указанного файла. Это позволяет вам использовать команду tail для мониторинга файла журнала, и при добавлении каждой записи прочитанная строка будет сравниваться со всеми шаблонами в файле IOC, выводя любую соответствующую запись. Вот пример:

tail -f /var/logs/apache2/access.log | egrep -i -f ioc.txt

Кроме того, команда tee может использоваться для одновременного отображения предупреждений на экране и сохранения их для последующей обработки в собственном файле:

tail -f /var/logs/apache2/access.log | egrep --line-buffered -i -f ioc.txt |
tee -a interesting.txt


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

Мониторинг журналов Windows


Как уже говорилось, для доступа к событиям Windows нужно использовать команду wevtutil. Хотя эта команда универсальна, она не имеет такой функциональности, как tail, которую можно задействовать для извлечения новых поступающих записей. Но выход есть — использовать простой сценарий bash, который может предоставить такую же функциональность (пример 8.2).

Пример 8.2. wintail.sh

#!/bin/bash -
#
# Bash и кибербезопасность
# wintail.sh
#
# Описание:
# Выполнение функции наподобие tail для журнала Windows
#
# Использование: ./wintail.sh
#

WINLOG="Application" (1)

LASTLOG=$(wevtutil qe "$WINLOG" //c:1 //rd:true //f:text) (2)

while true
do
      CURRENTLOG=$(wevtutil qe "$WINLOG" //c:1 //rd:true //f:text) (3)
      if [[ "$CURRENTLOG" != "$LASTLOG" ]]
      then
            echo "$CURRENTLOG"
            echo "----------------------------------"
            LASTLOG="$CURRENTLOG"
      fi
done

(1) Этой переменной определяется журнал Windows, который вы хотите отслеживать. Для получения списка журналов, доступных в системе в настоящее время, можете использовать команду wevtutil el.

(2) Здесь для запроса указанного файла журнала выполняется команда wevtutil. Параметр c:1 возвращает только одну запись журнала. Параметр rd:true позволяет команде считать самую последнюю запись журнала. Наконец, f:text возвращает результат в виде обычного текста, а не в формате XML, что позволяет легко читать результат с экрана.

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

Создание гистограммы, актуальной в реальном времени


Команда tail -f обеспечивает текущий поток данных. А что делать, если требуется подсчитать количество строк, которые были добавлены в файл за определенный промежуток времени? За этим потоком данных можно понаблюдать, запустить таймер и выполнять подсчет на протяжении заданного промежутка времени; затем подсчет следует прекратить и сообщить о результатах.

Эту работу можно разделить на два процесса-сценария: один сценарий будет считать строки, а другой — наблюдать за временем. Таймер уведомляет счетчик строк с помощью стандартного механизма межпроцессной связи POSIX, называемого сигналом. Сигнал — это программное прерывание, и существуют различные виды сигналов. Некоторые из них являются фатальными — приводят к завершению процесса (например, исключение в операции с плавающей запятой). Большинство из этих сигналов могут быть как проигнорированы, так и пойманы. Действие предпринимается, когда сигнал пойман. Многие из этих сигналов имеют предопределенное назначение в операционной системе. Мы будем использовать один из двух сигналов, доступных пользователям. Это сигнал SIGUSR1 (другой — это SIGUSR2).

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

trap warnmsg SIGINT

Это приводит к тому, что команда warnmsg (наш собственный сценарий или функция) вызывается всякий раз, когда сценарий оболочки получает сигнал SIGINT, например, когда для прерывания запущенного процесса вы нажмете сочетание клавиш Ctrl+C.

В примере 8.3 показан сценарий, выполняющий подсчет.

Пример 8.3. looper.sh

#!/bin/bash -
#
# Bash и кибербезопасность
# looper.sh
#
# Описание:
# Подсчет строк в файле
#
# Использование: ./looper.sh [filename]
# filename — имя файла, который должен проверяться,
# по умолчанию: log.file
#

function interval ()                                           (1)
{
      echo $(date '+%y%m%d %H%M%S') $cnt                       (2)
      cnt=0
}

declare -i cnt=0
trap interval SIGUSR1                                          (3)

shopt -s lastpipe                                              (4)

tail -f --pid=$$ ${1:-log.file} | while read aline             (5)
do
     let cnt++
done

(1) Функция interval будет вызываться при получении каждого сигнала. Конечно, интервал должен быть определен до того, как мы сможем его назвать и использовать в нашем выражении trap.

(2) Команда date вызывается, чтобы предоставить временную метку для значения переменной cnt, которое мы распечатываем. После вывода показания счетчика мы сбрасываем это значение на 0, чтобы начать отсчет следующего интервала.

(3) Теперь, когда интервал определен, мы можем указать, чтобы функция вызывалась всякий раз, когда наш процесс получает сигнал SIGUSR1.

(4) Это очень важный шаг. Обычно, когда есть конвейер команд (например, ls-l | grep rwx | wc), части конвейера (каждая команда) выполняются в подсетях и каждый процесс заканчивается своим собственным идентификатором процесса. Это могло бы стать проблемой для данного сценария, потому что цикл while будет находиться в подоболочке с другим идентификатором процесса. Какой бы процесс ни начался, сценарий looper.sh не будет знать идентификатора процесса цикла while, чтобы отправить ему сигнал. Кроме того, изменение значения переменной cnt в подоболочке не изменяет значение cnt в основном процессе, поэтому сигнал для основного процесса каждый раз приведет к установке значения 0. Решить эту проблему можно с помощью команды shopt, которая устанавливает (-s) параметр lastpipe. Он указывает оболочке не создавать для последней команды в конвейере подоболочку, а запускать эту команду в том же процессе, в котором запущен сам сценарий. В нашем случае это означает, что команда tail будет выполняться в подоболочке (то есть в другом процессе), а цикл while станет частью основного процесса сценария. Внимание: эта опция оболочки доступна только в bash версии 4.x и выше и только для неинтерактивных оболочек (то есть сценариев).

(5) Это команда tail -f еще с одним параметром --pid. Мы указываем идентификатор процесса, который по завершении данного процесса завершит работу команды tail. Мы указываем идентификатор процесса текущего сценария оболочки $$, который нужно просмотреть. Это действие позволяет очистить процессы и не оставлять команду tail выполняться в фоновом режиме (если, скажем, этот сценарий выполняется в фоновом режиме; пример 8.4).

Сценарий tailcount.sh запускает и останавливает сценарий с секундомером (таймер) и ведет подсчет временных интервалов.

Пример 8.4. tailcount.sh

#!/bin/bash -
#
# Bash и кибербезопасность
# tailcount.sh
#
# Описание:
# Подсчет строк каждые n секунд
#
# Использование: ./tailcount.sh [filename]
#     filename: проанализировать looper.sh
#

# очистка — другие процессы на выходе
function cleanup ()
{
      [[ -n $LOPID ]] && kill $LOPID          (1)
}

trap cleanup EXIT                             (2)
bash looper.sh $1 &                           (3)
LOPID=$!                                      (4)
# даем возможность начать
sleep 3

while true
do
      kill -SIGUSR1 $LOPID
      sleep 5
done >&2                                      (5)

(1) Поскольку этот сценарий будет запускать другие сценарии, после работы он должен выполнить очистку. Если идентификатор процесса был сохранен в LOPID, переменная будет хранить значение, поэтому функция с помощью команды kill отправит этому процессу сигнал. Если в команде kill не указать конкретный сигнал, то по умолчанию будет отправлен сигнал SIGTERM.

(2) Команда EXIT не является сигналом. Это специальный случай, когда оператор trap указывает оболочке вызвать эту функцию (в данном случае cleanup), если оболочка, выполняющая данный сценарий, собирается завершить работу.

(3) Теперь начинается настоящая работа. Запускается сценарий looper.sh, который будет работать в фоновом режиме: чтобы этот сценарий работал на протяжении всего цикла (не дожидаясь команды на завершение работы), он отсоединяется от клавиатуры.

(4) Здесь сохраняется идентификатор процесса сценария, который мы только что запустили в фоновом режиме.

(5) Данное перенаправление — просто мера предосторожности. Весь вывод, поступающий из цикла while или от операторов kill/sleep (хотя их мы не ожидаем), не должен смешиваться с любыми выводами функции looper.sh, которая, хотя и работает в фоновом режиме, все равно отправляет их в stdout. Поэтому мы перенаправляем данные из stdout в stderr.

Подводя итог, мы видим, что, хотя функция looper.sh была помещена в фоновый режим, идентификатор ее процесса сохраняется в переменной оболочки. Каждые пять секунд сценарий tailcount.sh отправляет данному процессу (который выполняется в функции looper.sh) сигнал SIGUSR1, который, в свою очередь, вызывает сценарий looper.sh, чтобы распечатать зафиксированное в нем текущее количество строк и перезапустить подсчет. После выхода сценарий tailcount.sh очистится, отправив сигнал SIGTERM в функцию looper.sh для ее прерывания.

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

bash tailcount.sh | bash livebar.sh

Сценарий livebar.sh считывает данные из stdin и печатает вывод в stdout, по одной строке для каждой строки ввода (пример 8.5).

Пример 8.5. livebar.sh

#!/bin/bash -
#
# Bash и кибербезопасность
# livebar.sh
#
# Описание:
# Создание горизонтальной гистограммы «живых» данных
#
# Использование:
# <output from other script or program> | bash livebar.sh
#

function pr_bar ()                                         (1)
{
      local raw maxraw scaled
      raw=$1
      maxraw=$2
      ((scaled=(maxbar*raw)/maxraw))
      ((scaled == 0)) && scaled=1 # гарантированный минимальный размер
      for((i=0; i<scaled; i++)) ; do printf '#' ; done
      printf '\n'

} # pr_bar

maxbar=60     # наибольшее количество символов в строке    (2)
MAX=60
while read dayst timst qty
do
      if (( qty > MAX ))                                   (3)
      then
           let MAX=$qty+$qty/4    # предоставляем немного места
           echo "                      **** rescaling: MAX=$MAX"
      fi
      printf '%6.6s %6.6s %4d:' $dayst $timst $qty         (4)
      pr_bar $qty $MAX
done

(1) Функция pr_bar выводит строку хештегов, масштабированных на основе предоставленных параметров до максимального размера. Эта функция может показаться знакомой, так как мы ранее уже использовали ее в сценарии histogram.sh.

(2) Это самый длинный размер строки хештега, который мы можем допустить (чтобы обойтись без переноса строки).

(3) Насколько большими будут значения, которые необходимо отобразить? Не зная этого заранее (хотя эти данные могут быть предоставлены сценарию в качестве аргумента), сценарий будет отслеживать максимум. Если этот максимум будет превышен, значение начнет «масштабироваться» и линии, которые выводятся сейчас, и будущие линии также будут масштабированы до нового максимума. Сценарий добавляет 25 % к максимальному значению, так что ему не придется масштабировать значение, если очередное новое значение каждый раз увеличивается только на 1–2 %.

(4) printf определяет минимальную и максимальную ширину первых двух полей, которые будут выведены. Это метки даты и времени, которые при превышении значений ширины будут обрезаны. Чтобы вывести значение целиком, указываем его ширину размером четыре символа. При этом, несмотря на ограничения, будут напечатаны все значения. Если количество символов в значениях будет меньше четырех, недостающие будут дополнены пробелами.

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

$ bash  livebar.sh
201010 1020 20
201010     1020 20:####################
201010 1020 70
                       **** rescaling: MAX=87
201010     1020 70:################################################
201010 1020 75
201010 1020 75:###################################################
^C

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

$ bash livebar.sh < testdata.txt
bash livebar.sh < x.data
201010 1020 20:####################
                 **** rescaling: MAX=87
201010 1020 70:################################################
201010 1020 75:###################################################
$

» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 25% по купону — Bash

По факту оплаты бумажной версии книги на e-mail высылается электронная книга.