Как работать с боль­шими объ­ема­ми дан­ных, не имея под рукой ничего, кро­ме коман­дной стро­ки? Про­ана­лизи­ровать боль­шие объ­емы дан­ных час­то помога­ет поиск, который по мере пос­тупле­ния новых дан­ных сле­дует пос­тоян­но уточ­нять и огра­ничи­вать. В этой гла­ве в качес­тве вход­ных дан­ных для наших сце­нари­ев мы возь­мем информа­цию из жур­налов веб‑сер­вера. Одна­ко сце­нарии и методы мож­но лег­ко изме­нить для работы с дан­ными прак­тичес­ки любого типа.

О книге

Пе­ред тобой седь­мая гла­ва из кни­ги «Bash и кибер­безопас­ность: ата­ка, защита и ана­лиз из коман­дной стро­ки Linux», которую мы пуб­лику­ем по догово­рен­ности с изда­тель­ством «Питер». Ее авто­ры — Пол Трон­кон и Карл Олбинг, а ори­гинал был издан O’Reilly.

Нес­мотря на то что пер­вые две гла­вы этой кни­ги зна­комят читате­ля с прин­ципами коман­дных интер­пре­тато­ров в целом и bash в час­тнос­ти, это ско­рее пособие для тех, кто уже зна­ком с осно­вами. Если чувс­тву­ешь, что пла­ваешь в теме, то луч­ше пос­мотри, к при­меру, «Прог­рам­мное окру­жение Unix» Брай­ана Кер­нигана и Роба Пай­ка.

За­то если ты уже име­ешь некото­рый опыт работы с bash, то эта кни­га поможет зак­репить и усо­вер­шенс­тво­вать навыки. В ней ты най­дешь мно­жес­тво при­емов, которые не толь­ко хороши сами по себе, но и учат поль­зовать­ся стан­дар­тны­ми ути­лита­ми Unix и Linux для дос­тижения самых раз­нооб­разных целей.

Че­го ты здесь не най­дешь — это отсы­лок к готовым прог­рам­мкам и скрип­там, которые рас­ширя­ют воз­можнос­ти сис­темы и во мно­жес­тве водят­ся как на GitHub, так и в репози­тори­ях пакетов Linux. Вмес­то это­го авто­ры кни­ги намерен­но исполь­зуют стан­дар­тные ути­литы, которые вхо­дят в любую Unix-образную сис­тему еще с вось­мидеся­тых годов. Нап­ример, мало кто сей­час зна­ет, как поль­зовать­ся дедов­ским awk. Но в этом одновре­мен­но и зак­люча­ется глав­ная пре­лесть этой кни­ги — она опи­рает­ся на про­верен­ные вре­менем мощ­ные средс­тва, которые ты точ­но най­дешь в любой сис­теме.

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

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

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

 

Используемые команды

Для сор­тиров­ки и огра­ниче­ния отоб­ража­емых дан­ных вос­поль­зуем­ся коман­дами sort, head и uniq. Работу с ними про­демонс­три­руем на фай­ле из при­мера 7.1.

Пример 7.1. file1.txt

12/05/2017 192.168.10.14 test.html
12/30/2017 192.168.10.185 login.html

 

sort

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

Общие параметры команды

  • -r — сор­тировать по убы­ванию.
  • -f — игно­риро­вать регистр.
  • -n — исполь­зовать чис­ловой порядок: 1, 2, 3 и до 10 (по умол­чанию при сор­тиров­ке в алфа­вит­ном поряд­ке 2 и 3 идут пос­ле 10).
  • -k — сор­тировать на осно­ве под­мно­жес­тва дан­ных (клю­ча) в стро­ке. Поля раз­деля­ются про­бела­ми.
  • -o — записать вывод в ука­зан­ный файл.

Пример команды

Для сор­тиров­ки фай­ла file1.txt по стол­бцу, в котором ука­зано имя фай­ла, и игно­риро­вания стол­бца с IP-адре­сом необ­ходимо исполь­зовать сле­дующую коман­ду:

sort -k 3 file1.txt

Мож­но так­же выпол­нить сор­тиров­ку по под­мно­жес­тву поля. Для сор­тиров­ки по вто­рому окте­ту IP-адре­са напиши­те сле­дующее:

sort -k 2.5,2.7 file1.txt

Бу­дет выпол­нена сор­тиров­ка пер­вого поля с исполь­зовани­ем сим­волов от 5 до 7.

 

uniq

Ко­ман­да uniq поз­воля­ет отфиль­тро­вать пов­торя­ющиеся стро­ки с дан­ными, которые встре­чают­ся друг рядом с дру­гом. Что­бы уда­лить в фай­ле все пов­торя­ющиеся стро­ки, перед исполь­зовани­ем коман­ды uniq файл нуж­но отсорти­ровать.

Общие параметры команды

  •  — вывес­ти, сколь­ко раз пов­торя­ется стро­ка.
  • -f — перед срав­нени­ем про­игно­риро­вать ука­зан­ное количес­тво полей. Нап­ример, параметр -f 3 поз­воля­ет не при­нимать во вни­мание в каж­дой стро­ке пер­вые три поля. Поля раз­деля­ются про­бела­ми.
  • -i — игно­риро­вать регистр букв. В uniq регистр сим­волов по умол­чанию учи­тыва­ется.
 

Ознакомление с журналом доступа к веб-серверу

Для боль­шинс­тва при­меров в этой гла­ве мы исполь­зуем жур­нал дос­тупа к веб‑сер­веру Apache. В жур­нал это­го типа записы­вают­ся зап­росы стра­ницы, сде­лан­ные к веб‑сер­веру, вре­мя, ког­да они были сде­ланы, и имя того, кто их сде­лал. Обра­зец типич­ного фай­ла ком­биниро­ван­ного фор­мата жур­нала (Combined Log Format) Apache мож­но уви­деть в при­мере 7.2. Пол­ный лог‑файл, который был исполь­зован в этой кни­ге, называ­ется access.log.

Пример 7.2. Фрагмент файла access.log

192.168.0.11 - - [12/Nov/2017:15:54:39 -0500] "GET /request-quote.html HTTP/1.1" 200 7326 "http://192.168.0.35/support.html" "Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"

info

Жур­налы веб‑сер­вера исполь­зуют­ся прос­то в качес­тве при­мера. Методы, опи­сан­ные в этой гла­ве, мож­но при­менять для ана­лиза раз­личных типов дан­ных.

По­ля жур­нала веб‑сер­вера Apache опи­саны в таб­лице.

Су­щес­тву­ет вто­рой тип жур­нала дос­тупа Apache, извес­тный как обыч­ный фор­мат жур­нала (Common Log Format). Фор­мат сов­пада­ет с ком­биниро­ван­ным, за исклю­чени­ем того, что не содер­жит полей для ссы­лающей­ся стра­ницы или аген­та поль­зовате­ля. Допол­нитель­ную информа­цию о фор­мате и кон­фигура­ции жур­налов Apache мож­но получить на сай­те про­екта Apache HTTP Server.

Ко­ды сос­тояния, упо­мяну­тые в таб­лице (поле 9), поз­воля­ют узнать, как веб‑сер­вер отве­тил на любой зап­рос.

www

Пол­ный спи­сок кодов мож­но най­ти в реес­тре кодов сос­тояния про­токо­ла переда­чи гипер­тек­ста (HTTP).

 

Сортировка и упорядочение данных

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

Что­бы управлять рас­положе­нием и отоб­ражени­ем дан­ных, ука­жите в кон­це коман­ды sort, head и tail:

... | sort -k 2.1 -rn | head -15

При этом выход­ные дан­ные сце­нария переда­ются коман­де sort, а затем отсорти­рован­ный вывод нап­равля­ется коман­де head, которая напеча­тает 15 вер­хних (в дан­ном слу­чае) строк. Коман­да sort в качес­тве сво­его клю­ча сор­тиров­ки (-k) исполь­зует вто­рое поле, начиная с его пер­вого сим­вола (2.1). Более того, эта коман­да выпол­нит обратную сор­тиров­ку (-r), а зна­чения будут отсорти­рова­ны в чис­ловом поряд­ке (-n). Почему чис­ловой порядок? Потому что 2 отоб­ража­ется меж­ду 1 и 3, а не меж­ду 19 и 20 (как при сор­тиров­ке в алфа­вит­ном поряд­ке).

Ис­поль­зуя коман­ду head, мы зах­ватыва­ем пер­вые стро­ки вывода. Мы мог­ли бы получить пос­ледние нес­коль­ко строк, переда­вая вывод из коман­ды sort коман­де tail вмес­то head. Исполь­зование коман­ды tail с опци­ей -15 выведет пос­ледние 15 строк. Дру­гой спо­соб отсорти­ровать дан­ные по воз­раста­нию, а не по убы­ванию — уда­лить параметр -r.

 

Подсчет количества обращений к данным

Ти­пич­ный жур­нал веб‑сер­вера может содер­жать десят­ки тысяч записей. Под­счи­тывая, сколь­ко раз стра­ница была дос­тупна, и узна­вая, по какому IP-адре­су она была дос­тупна, вы можете получить луч­шее пред­став­ление об общей активнос­ти сай­та. Далее при­водят­ся записи, на которые сле­дует обра­тить вни­мание.

  • Боль­шое количес­тво зап­росов, воз­вра­щающих код сос­тояния 404 («Стра­ница не най­дена») для кон­крет­ной стра­ницы. Это может ука­зывать на нерабо­тающие гипер­ссыл­ки.
  • Мно­жес­тво зап­росов с одно­го IP-адре­са, воз­вра­щающих код сос­тояния 404. Это может озна­чать, что выпол­няет­ся зон­дирова­ние в поис­ках скры­тых или нес­вязан­ных стра­ниц.
  • Боль­шое количес­тво зап­росов, в час­тнос­ти с одно­го и того же IP-адре­са, воз­вра­щающих код сос­тояния 401 («Не авто­ризо­ван»). Это может сви­детель­ство­вать о попыт­ке обхо­да аутен­тифика­ции, нап­ример о перебо­ре паролей.

Что­бы обна­ружить такой тип активнос­ти, нам нуж­но иметь воз­можность извле­кать клю­чевые поля, нап­ример исходный IP-адрес, и под­счи­тывать, сколь­ко раз они появ­ляют­ся в фай­ле. Поэто­му для извле­чения поля мы вос­поль­зуем­ся коман­дой cut, а затем переда­дим вывод в наш новый инс­тру­мент, файл countem.sh, показан­ный в при­мере 7.3.

При­мер 7.3. countem.sh
declare -A cnt # assoc. array # <1>
while read id xtra # <2>
do
let cnt[$id]++ # <3>
done
# now display what we counted
# for each key in the (key, value) assoc. array
for id in "${!cnt[@]}" # <4>
do
printf '%s %d\n' "$id" "${cnt[$id]}" # <5>
done

<1> Пос­коль­ку мы не зна­ем, с какими IP-адре­сами (или дру­гими стро­ками) можем стол­кнуть­ся, будем исполь­зовать ассо­циатив­ный мас­сив (так­же извес­тный как хеш‑таб­лица или сло­варь). В этом при­мере мас­сив задан с парамет­ром –A, который поз­волит нам исполь­зовать любую стро­ку в качес­тве нашего индекса.

Фун­кция ассо­циатив­ного мас­сива пре­дус­мотре­на в bash вер­сии 4.0 и выше. В таком мас­сиве индекс не обя­затель­но дол­жен быть чис­лом и может быть пред­став­лен в виде любой стро­ки. Таким обра­зом, вы можете индекси­ровать мас­сив по IР‑адре­су и под­счи­тывать количес­тво обра­щений это­го IР‑адре­са. В слу­чае если вы исполь­зуете вер­сию прог­раммы стар­ше, чем bash 4.0, аль­тер­нативой это­му сце­нарию будет сце­нарий, показан­ный в при­мере 7.4. Здесь вмес­то ассо­циатив­ного мас­сива исполь­зует­ся коман­да awk.

В bash для ссы­лок на мас­сив, как и для ссы­лок на эле­мент мас­сива, исполь­зует­ся син­таксис ${var[index]}. Что­бы получить все воз­можные зна­чения индекса (клю­чи, если эти мас­сивы рас­смат­рива­ются как пара «ключ/зна­чение»), ука­жите ${!cnt[@]}.

<2> Хотя мы ожи­даем в стро­ке толь­ко одно сло­во вво­да, добавим перемен­ную xtra, что­бы зах­ватить любые дру­гие сло­ва, которые появят­ся в стро­ке. Каж­дой перемен­ной в коман­де read прис­ваивает­ся соот­ветс­тву­ющее сло­во из вход­ных дан­ных (пер­вая перемен­ная получа­ет пер­вое сло­во, вто­рая перемен­ная — вто­рое сло­во и т. д.). При этом пос­ледняя перемен­ная получа­ет все оставши­еся сло­ва. С дру­гой сто­роны, если в стро­ке вход­ных слов мень­ше, чем перемен­ных в коман­де read, этим допол­нитель­ным перемен­ным прис­ваивает­ся пус­тая стро­ка. Поэто­му в нашем при­мере, если в стро­ке вво­да есть допол­нитель­ные сло­ва, они все будут прис­воены перемен­ной xtra. Если же нет допол­нитель­ных слов, перемен­ной xtra будет прис­воено зна­чение null.

<3> Стро­ка исполь­зует­ся в качес­тве индекса и уве­личи­вает его пре­дыду­щее зна­чение. При пер­вом исполь­зовании индекса пре­дыду­щее зна­чение не будет уста­нов­лено и он будет равен 6.

<4> Дан­ный син­таксис поз­воля­ет нам переби­рать все раз­личные зна­чения индекса. Обра­тите вни­мание: нель­зя гаран­тировать, что при сор­тиров­ке мы получим алфа­вит­ный или какой‑то дру­гой кон­крет­ный порядок. Это объ­ясня­ется при­родой алго­рит­ма хеширо­вания зна­чений индекса.

<5> При выводе зна­чения и клю­ча мы помеща­ем зна­чения в кавыч­ки, что­бы всег­да получать одно зна­чение для каж­дого аргу­мен­та, даже если оно содер­жит один или два про­бела. Мы не дума­ем, что такое про­изой­дет при работе это­го сце­нария, но подоб­ная прак­тика кодиро­вания дела­ет сце­нарии более надеж­ными при исполь­зовании в раз­личных ситу­ациях.

В при­мере 7.4 показа­на дру­гая вер­сия сце­нария, с исполь­зовани­ем коман­ды awk.

Пример 7.4. countem.awk

awk '{ cnt[$1]++ }
END { for (id in cnt) {
printf "%d %s\n", cnt[id], id
}

Оба сце­нария будут хорошо работать в кон­вей­ере команд:

cut -d' ' -f1 logfile | bash countem.sh

Ко­ман­да cut на самом деле здесь не нуж­на ни для одной из вер­сий. Почему? Потому что сце­нарий awk явно ссы­лает­ся на пер­вое поле ($1), а то, что коман­да cut в сце­нарии обо­лоч­ки не нуж­на, объ­ясня­ется кодиров­кой коман­ды read (см. <5>). Так что мы можем запус­тить сце­нарий сле­дующим обра­зом:

bash countem.sh < logfile

Нап­ример, что­бы под­счи­тать, сколь­ко раз IP-адрес делал НТТР‑зап­рос, на который воз­вра­щалось сооб­щение об ошиб­ке 404 («Стра­ница не най­дена»), нуж­но ввес­ти такую коман­ду:

$ awk '$9 == 404 {print $1}' access.log | bash countem.sh

1 192.168.0.36
2 192.168.0.37
1 192.168.0.11

Вы так­же можете исполь­зовать коман­ду grep 404 access.log и передать дан­ные сце­нарию countem.sh. Но в этом слу­чае будут вклю­чены стро­ки, в которых сочета­ние цифр 404 будет най­дено и в дру­гих мес­тах (нап­ример, чис­ло бай­тов или часть пути к фай­лу). Коман­да awk ука­зыва­ет под­счи­тывать толь­ко те стро­ки, в которых воз­вра­щаемый ста­тус (поле 9) равен 404. Далее будет выведен толь­ко IР‑адрес (поле 1), а вывод нап­равит­ся в сце­нарий countem.sh, с помощью которо­го мы получим общее количес­тво зап­росов, сде­лан­ных IР‑адре­сом и выз­вавших ошиб­ку 404.

Сна­чала про­ана­лизи­руем обра­зец фай­ла access.log. Начать ана­лиз сле­дует с прос­мотра узлов, которые обра­щались к веб‑сер­веру. Вы можете исполь­зовать коман­ду cut опе­раци­онной сис­темы Linux, с помощью которой будет извле­чено пер­вое поле фай­ла жур­нала, где содер­жится исходный IР‑адрес. Затем сле­дует передать выход­ные дан­ные сце­нарию countem.sh. Пра­виль­ная коман­да и ее вывод показа­ны здесь:

$ cut -d' ' -f1 access.log | bash countem.sh | sort -rn

111 192.168.0.37
55 192.168.0.36
51 192.168.0.11
42 192.168.0.14
28 192.168.0.26

Ес­ли у вас нет дос­тупно­го сце­нария countem.sh, для дос­тижения ана­логич­ных резуль­татов мож­но исполь­зовать коман­ду uniq с парамет­ром -c. Но для кор­рек­тной работы пред­варитель­но пот­ребу­ется допол­нитель­но отсорти­ровать дан­ные.

$ cut -d' ' -f1 access.log | sort | uniq -c | sort -rn

111 192.168.0.37
55 192.168.0.36
51 192.168.0.11
42 192.168.0.14
28 192.168.0.26

Вы можете про­дол­жить ана­лиз, обра­тив вни­мание на хост с наиболь­шим количес­твом зап­росов. Как вид­но из пре­дыду­щего кода, таким хос­том явля­ется IP-адрес 192.168.0.37, номер которо­го — 111. Мож­но исполь­зовать awk для филь­тра­ции по IP-адре­су, что­бы затем извлечь поле, содер­жащее зап­рос, передать его коман­де cut и, наконец, передать вывод сце­нарию countem.sh, который и выдаст общее количес­тво зап­росов для каж­дой стра­ницы:

$ awk '$1 == "192.168.0.37" {print $0}' access.log | cut -d' ' -f7 | bash countem.sh

1 /uploads/2/9/1/4/29147191/31549414299.png?457
14 /files/theme/mobile49c2.js?1490908488
1 /cdn2.editmysite.com/images/editor/theme-background/stock/iPad.html
1 /uploads/2/9/1/4/29147191/2992005_orig.jpg
...
14 /files/theme/custom49c2.js?1490908488

Ак­тивность это­го кон­крет­ного хос­та не впе­чат­ляет и напоми­нает стан­дар­тное поведе­ние бра­узе­ра. Если вы пос­мотри­те на хост со сле­дующим наиболь­шим количес­твом зап­росов, то уви­дите неч­то более инте­рес­ное:

$ awk '$1 == "192.168.0.36" {print $0}' access.log | cut -d' ' -f7 | bash countem.sh

1 /files/theme/mobile49c2.js?1490908488
1 /uploads/2/9/1/4/29147191/31549414299.png?457
1 /_/cdn2.editmysite.com/.../Coffee.html
1 /_/cdn2.editmysite.com/.../iPad.html
...
1 /uploads/2/9/1/4/29147191/601239_orig.png

Этот вывод ука­зыва­ет, что хост 192.168.0.36 получил дос­туп чуть ли не к каж­дой стра­нице сай­та толь­ко один раз. Дан­ный тип активнос­ти час­то ука­зыва­ет на активность веб‑ска­нера или кло­ниро­вание сай­та. Если вы пос­мотри­те на стро­ку поль­зователь­ско­го аген­та, то уви­дите допол­нитель­ное под­твержде­ние это­го пред­положе­ния:

$ awk '$1 == "192.168.0.36" {print $0}' access.log | cut -d' ' -f12-17 | uniq

"Mozilla/4.5 (compatible; HTTrack 3.0x; Windows 98)

Агент поль­зовате­ля иден­тифици­рует себя как HTTrack. Это инс­тру­мент, который мож­но исполь­зовать для заг­рузки или кло­ниро­вания сай­тов. Хотя этот инс­тру­мент не обя­затель­но при­носит вред, во вре­мя ана­лиза сто­ит обра­тить на него вни­мание.

 

Суммирование чисел в данных

Что делать, если вмес­то того, что­бы под­счи­тывать, сколь­ко раз IP-адрес или дру­гие эле­мен­ты обра­щались к опре­делен­ным ресур­сам, вы хотите узнать общее количес­тво бай­тов, отправ­ленных по IP-адре­су, или то, какие IP-адре­са зап­росили и получи­ли боль­ше все­го дан­ных?

Ре­шение мало чем отли­чает­ся от сце­нария countem.sh. Вне­сите в этот сце­нарий нес­коль­ко неболь­ших изме­нений. Во‑пер­вых, вам нуж­но так нас­тро­ить вход­ной филь­тр (коман­да cut), что­бы из боль­шого количес­тва стол­бцов извле­кались два стол­бца: IP-адрес и счет­чик бай­тов, а не толь­ко стол­бец с IP-адре­сом. Во‑вто­рых, сле­дует изме­нить вычис­ление с при­раще­нием (let cnt[$id]++) на прос­той счет, что­бы сум­мировать дан­ные из вто­рого поля (let cnt[$id]+= $data).

Те­перь кон­вей­ер будет извле­кать два поля из фай­ла жур­нала — пер­вое и пос­леднее:

cut -d' ' -f 1,10 access.log | bash summer.sh

Сце­нарий summer.sh, показан­ный в при­мере 7.5, чита­ет дан­ные из двух стол­бцов. Пер­вый стол­бец сос­тоит из зна­чений индекса (в дан­ном слу­чае IP-адре­сов), а вто­рой стол­бец — это чис­ло (в дан­ном слу­чае количес­тво бай­тов, отправ­ленных по IP-адре­су). Каж­дый раз, ког­да сце­нарий находит в пер­вом стол­бце пов­торя­ющий­ся IP-адрес, он добав­ляет зна­чение из вто­рого стол­бца к обще­му количес­тву бай­тов для это­го адре­са, сум­мируя таким обра­зом количес­тво бай­тов, отправ­ленных этим IP-адре­сом.

Пример 7.5. summer.sh

declare -A cnt # ассоциативный массив
while read id count
do
let cnt[$id]+=$count
done
for id in "${!cnt[@]}"
do
printf "%-15s %8d\n" "${id}" "${cnt[${id}]}" # <1>
done

<1> Обра­тите вни­мание, что в фор­мат вывода мы внес­ли нес­коль­ко изме­нений. К раз­меру поля мы добави­ли 15 сим­волов для пер­вой стро­ки (дан­ные IP-адре­са, в нашем при­мере), уста­нови­ли вырав­нивание по левому краю (с помощью зна­ка минус) и ука­зали восемь цифр для зна­чений сум­мы. Если сум­ма ока­жет­ся боль­ше, то будет выведе­но боль­шее чис­ло, если же стро­ка ока­жет­ся длин­нее, то она будет напеча­тана пол­ностью. Это сде­лано для того, что­бы выров­нять дан­ные по соот­ветс­тву­ющим стол­бцам: так стол­бцы будут акку­рат­ными и более читабель­ными.

Для получе­ния пред­став­ления об общем объ­еме дан­ных, зап­рашива­емых каж­дым хос­том, мож­но в сце­нарии summer.sh запус­тить файл access.log. Для это­го исполь­зуйте коман­ду cut, которая извле­чет IP-адрес и передан­ные бай­ты полей, а затем передай­те вывод в сце­нарий summer.sh:

$ cut -d' ' -f1,10 access.log | bash summer.sh | sort -k 2.1 -rn

192.168.0.36 4371198
192.168.0.37 2575030
192.168.0.11 2537662
192.168.0.14 2876088
192.168.0.26 665693

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

 

Отображение данных в виде гистограммы

Мож­но выпол­нить еще одно дей­ствие, обес­печив более наг­лядное отоб­ражение получен­ных резуль­татов. Вы можете взять вывод сце­нария countem.sh или summer.sh и передать его в дру­гой сце­нарий, который будет соз­давать гис­тограм­му, отоб­ража­ющую резуль­таты.

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

Пример 7.6. histogram.sh

function pr_bar () # <1>
local -i i raw maxraw scaled # <2>
raw=$1
maxraw=$2
((scaled=(MAXBAR*raw)/maxraw)) # <3>
# гарантированный минимальный размер
((raw > 0 && scaled == 0)) && scaled=1 # <4>
for((i=0; i<scaled; i++)) ; do printf '#' ; done
printf '\n'
} # pr_bar
#
# "main"
#
declare -A RA
declare -i MAXBAR max # <5>
max=0
MAXBAR=50 # размер самой длинной строки
while read labl val
do
let RA[$labl]=$val # <6>
# сохранить наибольшее значение; для масштабирования
(( val > max )) && max=$val
done
# масштабировать и вывести
for labl in "${!RA[@]}" # <7>
do
printf '%-20.20s ' "$labl"
pr_bar ${RA[$labl]} $max # <8>
done

<1> Мы опре­деля­ем фун­кцию, с помощью которой нарису­ем один стол­бец гис­тограм­мы. Опре­деле­ние дол­жно находить­ся перед самой фун­кци­ей, поэто­му име­ет смысл помес­тить все опре­деле­ния фун­кций в начале нашего сце­нария. Дан­ная фун­кция в будущем сце­нарии будет исполь­зована пов­торно, поэто­му ее мож­но помес­тить в отдель­ный файл и под­клю­чать с помощью коман­ды source. Но мы сде­лали по‑дру­гому.

<2> Мы объ­явля­ем все эти перемен­ные локаль­ными, так как не хотим, что­бы они мешали опре­деле­нию имен перемен­ных в осталь­ной час­ти дан­ного сце­нария (или любых дру­гих, если мы копиру­ем/встав­ляем этот сце­нарий для исполь­зования в дру­гом мес­те). Мы объ­явля­ем все эти перемен­ные целыми чис­лами (это параметр -i), потому что будем вычис­лять толь­ко целые зна­чения и не ста­нем исполь­зовать стро­ки.

<3> Вычис­ление выпол­няет­ся в двой­ных скоб­ках. Внут­ри них не нуж­но исполь­зовать сим­вол $ для ука­зания зна­чения каж­дого име­ни перемен­ной.

<4> Это опе­ратор if-less. Если выраже­ние внут­ри двой­ных ско­бок рав­но true, то тог­да и толь­ко тог­да выпол­няет­ся вто­рое выраже­ние. Такая конс­трук­ция гаран­тиру­ет, что если исходное зна­чение не рав­но нулю, то мас­шта­биро­ван­ное зна­чение никог­да не будет рав­но нулю.

<5> Основная часть сце­нария начина­ется с объ­явле­ния RA как ассо­циатив­ного мас­сива.

<6> Здесь мы ссы­лаем­ся на ассо­циатив­ный мас­сив, исполь­зуя мет­ку стро­ки в качес­тве его индекса.

<7> Пос­коль­ку мас­сив не индекси­рует­ся по чис­лам, мы не можем прос­то счи­тать целые чис­ла и исполь­зовать их в качес­тве индексов. Эта конс­трук­ция опре­деля­ет все раз­личные стро­ки, которые исполь­зовались в качес­тве индекса мас­сива, по одно­му индексу в цик­ле for.

<8> Мы еще раз исполь­зуем мет­ку как индекс, что­бы получить счет­чик и передать его как пер­вый параметр нашей фун­кции pr_bar.

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

При­мер 7.7 пред­став­ляет собой вер­сию сце­нария для пос­тро­ения гис­тограм­мы — в нем сох­раня­ется пос­ледова­тель­ность вывода и не исполь­зует­ся ассо­циатив­ный мас­сив. Это так­же может быть полез­но для ста­рых вер­сий bash (до 4.0), в которых ассо­циатив­ный мас­сив еще не исполь­зовал­ся. Здесь показа­на толь­ко основная часть сце­нария, так как фун­кция pr_bar оста­ется преж­ней.

Пример 7.7. histogram_plain.sh

declare -a RA_key RA_val # <1>
declare -i max ndx
max=0
maxbar=50 # размер самой длинной строки
ndx=0
while read labl val
do
RA_key[$ndx]=$labl # <2>
RA_value[$ndx]=$val
# сохранить наибольшее значение; для масштабирования
(( val > max )) && max=$val
let ndx++
done
# масштабировать и вывести
for ((j=0; j<ndx; j++)) # <3>
do
printf "%-20.20s " ${RA_key[$j]}
pr_bar ${RA_value[$j]} $max
done

Эта вер­сия сце­нария поз­воля­ет избе­жать исполь­зования ассо­циатив­ных мас­сивов (нап­ример, в более ста­рых вер­сиях bash или в сис­темах macOS). Здесь мы при­меня­ем два отдель­ных мас­сива: один для индек­сно­го зна­чения и один — для счет­чиков. Пос­коль­ку это обыч­ные мас­сивы, мы дол­жны исполь­зовать целочис­ленный индекс и будем вес­ти прос­той под­счет в перемен­ной ndx.

<1> Здесь име­на перемен­ных объ­явля­ются как мас­сивы. Строч­ная a ука­зыва­ет, что они явля­ются мас­сивами, но это не ассо­циатив­ные мас­сивы. Это не обя­затель­ное тре­бова­ние, зато рекомен­дуемая прак­тика. Ана­логич­но в сле­дующей стро­ке мы зада­ем параметр -i для объ­явле­ния этих перемен­ных целыми чис­лами, что дела­ет их более эффектив­ными, чем необъ­явленные перемен­ные обо­лоч­ки (которые хра­нят­ся в виде строк). Пов­торим­ся: как вид­но из того, что мы не объ­явля­ем maxbar, а прос­то исполь­зуем его, это не обя­затель­ное тре­бова­ние.

<2> Пары «ключ/зна­чение» хра­нят­ся в отдель­ных мас­сивах, но в одном и том же мес­те индекса. Это ненадеж­ный под­ход — изме­нения в сце­нарии в какой‑то момент могут при­вес­ти к тому, что два мас­сива не син­хро­низи­руют­ся.

<3> Цикл for, в отли­чие от пре­дыду­щего сце­нария, исполь­зует­ся для прос­того под­сче­та целых чисел от 0 до ndx. Здесь перемен­ная j выс­тупа­ет пре­пятс­тви­ем для индекса в цик­ле for внут­ри сце­нария pr_bar, нес­мотря на то что внут­ри фун­кции мы дос­таточ­но акку­рат­но объ­явля­ем эту вер­сию i как локаль­ную фун­кцию. Вы доверя­ете этой фун­кции? Изме­ните здесь j на i и про­верь­те, работа­ет ли цикл (а он работа­ет). Затем поп­робуй­те уда­лить локаль­ное объ­явле­ние и про­верить, успешно ли завер­шится цикл.

Та­кой под­ход с дву­мя мас­сивами име­ет одно пре­иму­щес­тво. Исполь­зуя чис­ловой индекс для хра­нения мет­ки и дан­ных, мож­но получить их в том поряд­ке, в котором они были про­чита­ны, — в чис­ловом поряд­ке индекса.

Те­перь, извле­кая соот­ветс­тву­ющие поля из access.log и перено­ся резуль­таты в summer.sh, а затем — в histogram.sh, мож­но наг­лядно уви­деть, какие хос­ты переда­ли наиболь­шее количес­тво бай­тов:

$ cut -d' ' -f1,10 access.log | bash summer.sh | bash histogram.sh

192.168.0.36 ##################################################
192.168.0.37 #############################
192.168.0.11 #############################
192.168.0.14 ################################
192.168.0.26 #######

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

По­мимо количес­тва бай­тов, переда­ваемых через IP-адрес или хост, час­то инте­рес­но прос­мотреть дан­ные, отсорти­рован­ные по дате и вре­мени. Для это­го мож­но исполь­зовать сце­нарий summer.sh, но из‑за фор­мата фай­ла access.log, преж­де чем передать его в сце­нарий, его нуж­но допол­нитель­но обра­ботать. Если для извле­чения передан­ных полей с датой/вре­менем и бай­тов исполь­зует­ся коман­да cut, оста­ются дан­ные, которые могут выз­вать некото­рые проб­лемы для сце­нария:

$ cut -d' ' -f4,10 access.log

[12/Nov/2017:15:52:59 2377
[12/Nov/2017:15:52:59 4529
[12/Nov/2017:15:52:59 1112

Как вид­но из это­го вывода, необ­работан­ные дан­ные начина­ются с сим­вола [. Из‑за него в сце­нарии появ­ляет­ся проб­лема, так как он обоз­нача­ет начало мас­сива в bash. Что­бы эту проб­лему устра­нить, мож­но исполь­зовать допол­нитель­ную ите­рацию коман­ды cut с парамет­ром -c2, с помощью которо­го сим­вол будет уда­лен.

Этот параметр ука­зыва­ет коман­де cut извле­кать дан­ные по сим­волам, начиная с позиции 2 и перехо­дя к кон­цу стро­ки (-). Вот исправ­ленный вывод с уда­лен­ной квад­ратной скоб­кой:

$ cut -d' ' -f4,10 access.log | cut -c2-

12/Nov/2017:15:52:59 2377
12/Nov/2017:15:52:59 4529
12/Nov/2017:15:52:59 1112

info

Вмес­то того что­бы вто­рой раз исполь­зовать коман­ду cut, мож­но добавить коман­ду tr. Параметр -d уда­ляет ука­зан­ный сим­вол — в дан­ном слу­чае квад­ратную скоб­ку.

cut -d' ' -f4,10 access.log | tr -d '['

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

Сце­нарий histogram.sh может быть осо­бен­но полезен при прос­мотре дан­ных, свя­зан­ных с датами. Нап­ример, если в орга­низа­ции име­ется внут­ренний веб‑сер­вер, дос­туп к которо­му осу­щест­вля­ется толь­ко в рабочее вре­мя с 09:00 до 17:00, мож­но с помощью такой гис­тограм­мы ежед­невно прос­матри­вать файл жур­нала сер­вера, что­бы про­верить, име­ются ли всплес­ки активнос­ти пос­ле обыч­ного рабоче­го дня.

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

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

$ awk '$4 ~ "12/Nov/2017" {print $0}' access.log | cut -d' ' -f4,10 | cut -c14-15,22- | bash summer.sh | bash histogram.sh

17 ##
16 ###########
15 ############
19 ##
18 ##################################################

Здесь файл access.log пересы­лает­ся с помощью коман­ды awk для извле­чения записей с опре­делен­ной датой. Обра­тите вни­мание на исполь­зование вмес­то сим­волов == опе­рато­ра подобия (~), пос­коль­ку поле 4 так­же содер­жит информа­цию о вре­мени. Эти записи переда­ются коман­де cut сна­чала для извле­чения полей даты/вре­мени и передан­ных бай­тов, а затем для извле­чения дан­ных о вре­мени. Пос­ле это­го с помощью сце­нария summer.sh дан­ные сум­миру­ются по вре­мени (часам) и с помощью histogram.sh пре­обра­зуют­ся в гис­тограм­му. Резуль­татом ста­новит­ся гис­тограм­ма, которая отоб­ража­ет общее количес­тво бай­тов, переда­ваемых каж­дый час 12 нояб­ря 2017 года.

info

Что­бы получить вывод в чис­ловом поряд­ке, передай­те его из сце­нария гис­тограм­мы коман­де sort -n. Зачем нуж­на сор­тиров­ка? Сце­нарии summer.sh и histogram.sh, прос­матри­вая спи­сок индексов сво­их ассо­циатив­ных мас­сивов, генери­руют свои выход­ные дан­ные. Поэто­му их вывод вряд ли будет осмыслен­ным (ско­рее дан­ные будут выведе­ны в поряд­ке, опре­деля­емом внут­ренним алго­рит­мом хеширо­вания). Если это объ­ясне­ние оста­вило вас рав­нодуш­ными, прос­то про­игно­рируй­те его и не забудь­те исполь­зовать сор­тиров­ку на выходе.

Ес­ли вы хотите, что­бы вывод был упо­рядо­чен по объ­ему дан­ных, добавь­те сор­тиров­ку меж­ду дву­мя сце­нари­ями. Необ­ходимо так­же исполь­зовать histogram_plain.sh — вер­сию сце­нария гис­тограм­мы, в которой не при­меня­ются ассо­циатив­ные мас­сивы.

 

Поиск уникальности в данных

Ра­нее IP-адрес 192.168.0.37 был иден­тифици­рован как сис­тема, которая име­ла наиболь­шее количес­тво зап­росов стра­ницы. Сле­дующий логичес­кий воп­рос: какие стра­ницы зап­рашива­ла эта сис­тема? Отве­тив на него, мож­но получить пред­став­ление о том, что сис­тема делала на сер­вере, и клас­сифици­ровать это дей­ствие как безопас­ное, подоз­ритель­ное или вре­донос­ное. Для это­го мож­но исполь­зовать коман­ду awk и cut и передать вывод в countem.sh:

$ awk '$1 == "192.168.0.37" {print $0}' access.log | cut -d' ' -f7 | bash countem.sh | sort -rn | head -5

14 /files/theme/plugin49c2.js?1490908488
14 /files/theme/mobile49c2.js?1490908488
14 /files/theme/custom49c2.js?1490908488
14 /files/main_styleaf0e.css?1509483497
3 /consulting.html

Хо­тя извле­чение и обрезка дан­ных могут быть реали­зова­ны путем кон­вей­ерной переда­чи команд и сце­нари­ев, для это­го пот­ребу­ется переда­вать дан­ные нес­коль­ко раз. Такой метод мож­но при­менить ко мно­гим наборам дан­ных, но он не под­ходит для очень боль­ших наборов. Метод мож­но опти­мизи­ровать, написав сце­нарий bash, спе­циаль­но раз­работан­ный для извле­чения и под­сче­та количес­тва дос­тупов к стра­ницам, — для это­го тре­бует­ся толь­ко один про­ход дан­ных. В при­мере 7.8 показан такой сце­нарий.

Пример 7.8. pagereq.sh

declare -A cnt # <1>
while read addr d1 d2 datim gmtoff getr page therest
do
if [[ $1 == $addr ]] ; then let cnt[$page]+=1 ; fi
done
for id in ${!cnt[@]} # <2>
do
printf "%8d %s\n" ${cnt[$id]} $id
done

<1> Мы объ­явля­ем cnt как ассо­циатив­ный мас­сив и в качес­тве индекса можем исполь­зовать стро­ку. В дан­ной прог­рамме в качес­тве индекса мы будем исполь­зовать адрес стра­ницы (URL).

<2> ${!cnt[@]} выводит спи­сок всех зна­чений индекса, которые были обна­руже­ны. Обра­тите вни­мание: они не будут перечис­лены в удоб­ном поряд­ке.

В ран­них вер­сиях bash ассо­циатив­ных мас­сивов нет. Под­счи­тать количес­тво раз­личных зап­росов стра­ниц с опре­делен­ного IP-адре­са вы можете с помощью коман­ды awk, потому что в ней есть ассо­циатив­ные мас­сивы (при­мер 7.9).

Пример 7.9. pagereq.awk

# подсчитать количество запросов страниц с адреса ($1)
awk -v page="$1" '{ if ($1==page) {cnt[$7]+=1 } }
END { for (id in cnt) { # <1>
printf "%8d %s\n", cnt[id], id # <2>
}
}'

<1> В этой стро­ке есть две перемен­ные $1, раз­ница меж­ду которы­ми очень боль­шая. Пер­вая перемен­ная $1 явля­ется перемен­ной обо­лоч­ки и ссы­лает­ся на пер­вый аргу­мент, пре­дос­тавлен­ный это­му сце­нарию при его вызове. Вто­рая перемен­ная $1 — это awk. В каж­дой стро­ке эта перемен­ная отно­сит­ся к пер­вому полю вво­да. Пер­вая перемен­ная $1 была наз­начена перемен­ной page awk, что­бы ее мож­но было срав­нить с каж­дой перемен­ной $1 awk (с каж­дым пер­вым полем вход­ных дан­ных).

<2> Прос­той син­таксис при­водит к тому, что перемен­ная id переби­рает зна­чения индекса в мас­сиве cnt. Это гораз­до более прос­той син­таксис, чем син­таксис обо­лоч­ки ${!cnt[@]}, но такой же эффектив­ный.

Мож­но запус­тить сце­нарий pagereq.sh, ука­зав IP-адрес, который тре­бует­ся най­ти и перенап­равить access.log в качес­тве вход­ных дан­ных:

$ bash pagereq.sh 192.168.0.37 < access.log | sort -rn | head -5

14 /files/theme/plugin49c2.js?1490908488
14 /files/theme/mobile49c2.js?1490908488
14 /files/theme/custom49c2.js?1490908488
14 /files/main_styleaf0e.css?1509483497
3 /consulting.html

 

Выявление аномалий в данных

В Интерне­те стро­ка аген­та поль­зовате­ля пред­став­ляет собой неболь­шой фраг­мент тек­сто­вой информа­ции, отправ­ляемый бра­узе­ром на веб‑сер­вер, который иден­тифици­рует опе­раци­онную сис­тему кли­ента, тип бра­узе­ра, вер­сию и дру­гую информа­цию. Обыч­но исполь­зует­ся веб‑сер­верами для обес­печения сов­мести­мос­ти стра­ниц с бра­узе­ром поль­зовате­ля. Вот при­мер такой стро­ки:

Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0

Эта стро­ка иден­тифици­рует сис­тему как Windows NT вер­сии 6.3 (она же Windows 8.1) с 64-раз­рядной архи­тек­турой и с бра­узе­ром Firefox. Стро­ка аген­та поль­зовате­ля может нас заин­тересо­вать по двум при­чинам. Во‑пер­вых, зна­читель­ный объ­ем информа­ции, которую эта стро­ка переда­ет, мож­но при­менять для иден­тифика­ции типов сис­тем и бра­узе­ров, обра­щающих­ся к сер­веру. Во‑вто­рых, эта стро­ка нас­тра­ивает­ся конеч­ным поль­зовате­лем и может быть исполь­зована для иден­тифика­ции сис­тем, в которых не уста­нов­лен стан­дар­тный бра­узер или вооб­ще нет бра­узе­ра (поис­ковых роботов, web crawler).

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

Пример 7.10. useragents.txt

Firefox
Chrome
Safari
Edge

За­тем вы можете про­читать жур­нал веб‑сер­вера и срав­нить каж­дую стро­ку со спис­ком популяр­ных поль­зователь­ских аген­тов (бра­узе­ров), пока не будет получе­но сов­падение. Если сов­падения не будет, стро­ка дол­жна рас­смат­ривать­ся как ано­малия и печатать­ся в стан­дар­тном выводе вмес­те с IP-адре­сом сис­темы, выпол­няющей зап­рос. Такое срав­нение дает нам допол­нитель­ную информа­цию, свя­зан­ную с рас­смат­рива­емы­ми дан­ными, — с ее помощью мы смо­жем иден­тифици­ровать сис­тему с необыч­ным поль­зователь­ским аген­том и получим еще один путь для даль­нейше­го изу­чения.

Пример 7.11. useragents.sh

# несовпадение поиск по массиву известных имен
# возвращает 1 (false), если совпадение найдено
# возвращает 0 (true), если совпадений нет
function mismatch () # <1>
{
local -i i # <2>
for ((i=0; i<$KNSIZE; i++))
do
[[ "$1" =~ .*${KNOWN[$i]}.* ]] && return 1 # <3>
done
return 0
}
readarray -t KNOWN < "useragents.txt" # <4>
KNSIZE=${#KNOWN[@]} # <5>
# предварительная обработка лог-файла (stdin),
# чтобы выбрать IP-адреса и пользовательские агенты
awk -F'"' '{print $1, $6}' | \
while read ipaddr dash1 dash2 dtstamp delta useragent # <6>
do
if mismatch "$useragent"
then
echo "anomaly: $ipaddr $useragent"
fi
done

<1> Сце­нарий будет осно­ван на фун­кции несов­падения. Если обна­ружит­ся несо­ответс­твие, будет воз­вра­щено успешное зна­чение (или true). Это зна­чит, что сов­падение со спис­ком извес­тных поль­зователь­ских аген­тов не най­дено. Дан­ная логика может показать­ся нес­тандар­тной, но так удоб­нее читать опе­ратор if, содер­жащий вызов mismatch.

<2> Объ­явле­ние нашего цик­ла for в качес­тве локаль­ной перемен­ной — хорошая идея. Дан­ный шаг в сце­нарии не явля­ется обя­затель­ным.

<3> Здесь пред­став­лены две стро­ки для срав­нения: вход­ные дан­ные из фай­ла жур­нала и стро­ка из спис­ка извес­тных поль­зователь­ских аген­тов. Для гиб­кого срав­нения исполь­зует­ся опе­ратор срав­нения регуляр­ных выраже­ний (=~). Зна­чение .* (ноль или более вхож­дений любого сим­вола), раз­мещен­ное по обе сто­роны ссыл­ки мас­сива $KNOWN, говорит о том, что сов­падение извес­тной стро­ки может быть най­дено в любом мес­те дру­гой стро­ки.

<4> Каж­дая стро­ка фай­ла добав­ляет­ся как эле­мент к ука­зан­ному име­ни мас­сива. Это дает нам мас­сив извес­тных поль­зователь­ских аген­тов. В bash сущес­тву­ет два спо­соба добавить стро­ки к мас­сиву: исполь­зовать либо readarray, как сде­лано в этом при­мере, либо mapfile. Опция -t уда­ляет завер­шающий сим­вол новой стро­ки из каж­дой про­читан­ной стро­ки. Здесь ука­зан файл, содер­жащий спи­сок извес­тных поль­зователь­ских аген­тов; при необ­ходимос­ти его мож­но изме­нить.

<5> Здесь вычис­ляет­ся раз­мер мас­сива. Получен­ное зна­чение исполь­зует­ся внут­ри фун­кции mismatch для цик­личес­кого перебо­ра мас­сива. Вне нашего цик­ла мы вычис­ляем его один раз, что­бы при каж­дом вызове фун­кции избе­жать пов­торно­го вычис­ления.

<6> Вход­ная стро­ка пред­став­ляет собой слож­ное сочета­ние слов и кавычек. Что­бы зах­ватить стро­ку аген­та поль­зовате­ля, в качес­тве раз­делите­ля полей мы ука­зыва­ем двой­ные кавыч­ки. Одна­ко это озна­чает, что наше пер­вое поле содер­жит боль­ше чем прос­то IP-адрес. Исполь­зуя коман­ду read для получе­ния IP-адре­са, мы можем про­ана­лизи­ровать про­белы. Пос­ледний аргу­мент read при­нима­ет все оставши­еся сло­ва, что­бы мож­но было зах­ватить все сло­ва стро­ки поль­зователь­ско­го аген­та.

При запус­ке сце­нария useragents.sh будут выведе­ны любые стро­ки поль­зователь­ско­го аген­та, не най­ден­ные в фай­ле useragents.txt:

$ bash useragents.sh < access.log

anomaly: 192.168.0.36 Mozilla/4.5 (compatible; HTTrack 3.0x; Windows 98)
anomaly: 192.168.0.36 Mozilla/4.5 (compatible; HTTrack 3.0x; Windows 98)
anomaly: 192.168.0.36 Mozilla/4.5 (compatible; HTTrack 3.0x; Windows 98)
anomaly: 192.168.0.36 Mozilla/4.5 (compatible; HTTrack 3.0x; Windows 98)
...
anomaly: 192.168.0.36 Mozilla/4.5 (compatible; HTTrack 3.0x; Windows 98)

 

Выводы

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

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    4 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии