На дво­ре вес­на, хочет­ся чего‑то лег­кого, све­жего и нового. Вот, нап­ример, све­жая идея: нель­зя ли вес­ти логи в Linux, не про­писы­вая для auditd сто тысяч раз пути к кри­тич­ным кон­фигам и исполня­емым фай­лам? Рядом девоп­серы обсужда­ли Cilium и какой‑то Tetragon, который может писать логи в JSON. Мы решили тоже поп­робовать!

Tetragon осно­ван на eBPF — это тех­нология, встро­енная в ядро Linux, которая поз­воля­ет безопас­но и эффектив­но запус­кать мини‑прог­раммы пря­мо в ядре. Бла­года­ря это­му eBPF-инс­тру­мен­ты, как пра­вило, быс­трее и пот­ребля­ют мень­ше ресур­сов, чем клас­сичес­кие решения вро­де закос­тенело­го auditd.

Тот, кто хотя бы раз нас­тра­ивал auditd для SOC, навер­няка стал­кивал­ся с самыми раз­ными, в том чис­ле безум­ными, кон­фигура­циями — нап­ример, очень популяр­ный кон­фиг от Neo23x0 нас­читыва­ет более 800 строк. Попыт­ки пре­одо­леть огра­ниче­ния и адап­тировать­ся под кон­крет­ный дис­три­бутив и архи­тек­туру — та еще морока.

info

Сра­зу ого­ворим­ся, что Falco и иные инс­тру­мен­ты и под­ходы мы не рас­смат­рива­ем, — это тема отдель­ной статьи.

Tetragon уни­вер­сален и работа­ет на мно­жес­тве плат­форм, написан на Go, а еще «из короб­ки» прис­пособ­лен к работе с кон­тей­нерами. Но информа­ции по нему (а осо­бен­но тому, как исполь­зовать его эффектив­но) край­не мало.

Да­вай вмес­те раз­бирать­ся, как мож­но играть­ся с Tetragon. Бонусом в кон­це статьи — реаль­ная исто­рия о том, почему Tetragon мес­тами лютый вин.

 

Основные понятия и схема работы

На­чать при­дет­ся с теории, но мы обой­дем­ся без душ­ных деталей. Итак, внут­ри у Tetragon тех­нология eBPF. Изна­чаль­но она была раз­работа­на для Linux, но, кста­ти, и в Microsoft сей­час тоже пилят свою реали­зацию.

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

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

Примерная схема работы ядра Linux
При­мер­ная схе­ма работы ядра Linux

eBPF, или Extended Berkeley Packet Filter, — фрей­мворк, поз­воля­ющий поль­зовате­лям заг­ружать и запус­кать прог­раммы в ядре опе­раци­онной сис­темы.

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

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

Есть готовые биб­лиоте­ки для написа­ния сво­ей ути­литы с при­мене­нием eBPF, но лишь на неболь­шом чис­ле язы­ков. Сре­ди них — C, Go, Rust и Python. Выбор язы­ка усложня­ется еще и тем, что не для всех язы­ков есть либы, реали­зующие под­дер­жку libbpf (популяр­ный вари­ант для обес­печения перено­симос­ти прог­рамм eBPF меж­ду раз­ными вер­сиями ядра). Но мы с тобой не кодеры и будем исполь­зовать готовое решение, раз­работ­чики которо­го уже обо всем позабо­тились.

Схема работы приложения Tetragon
Схе­ма работы при­ложе­ния Tetragon

Да­вай раз­берем основные фун­кци­ональ­ные ком­понен­ты сис­темы.

По­лити­ки трас­сиров­ки (tracing policy) — это для нас основной инс­тру­мент работы. Они поз­воля­ют нас­тра­ивать как реак­ции для монито­рин­га (логиро­вания), так и активные дей­ствия (бло­киро­вание дос­тупа, убий­ство про­цес­са и про­чее) на осно­ве задан­ных кри­тери­ев. Полити­ки зада­ются в фай­лах YAML и скла­диру­ются в каталог /etc/tetragon/tetragon.tp.d/. В качес­тве кри­тери­ев могут выс­тупать наз­вания сис­колов, вызовы фун­кций ядра, зна­чения вызыва­емых аргу­мен­тов. Мож­но добав­лять кас­томный текст, который добавит­ся в алерт.

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

  • LSM (Linux Security Modules) BPF, они же хуки безопас­ности SELinux/AppArmor. Для их исполь­зования необ­ходимо выпол­нение ря­да усло­вий — модули дол­жны быть заг­ружены, вклю­чены и про­чее. Наз­вания фун­кций мож­но взять из ис­ходни­ков Linux. Из пре­иму­ществ — воз­можность рас­счи­тывать хеши для фай­лов и обо­гащать ими генери­руемые события. Обыч­но в ком­пани­ях, где исполь­зуют­ся полити­ки безопас­ности, с хар­денин­гом и так все неп­лохо, поэто­му дви­жем­ся даль­ше.
  • Kprobes (Kernel probes), или зон­ды ядра. По сути, дают воз­можность соз­дания брейк‑пой­нта перед вызовом инс­трук­ции ядра с выпол­нени­ем про­изволь­ного кода. Нап­ример, мож­но заменить аргу­мен­ты — кста­ти, не толь­ко вход­ные. Но kprobes могут менять­ся меж­ду вер­сиями ядра опе­раци­онки, что сде­лает наши полити­ки нес­табиль­ными. Что­бы вывес­ти спи­сок дос­тупных kprobes, выпол­ни в тер­минале cat /proc/kallsyms. В полити­ках Tetragon для сис­темных вызовов (syscall) рекомен­дует­ся исполь­зовать обоб­щенное наз­вание вызова, нап­ример вмес­то зна­чений do_sys_open, __x64_sys_open или __ia32_sys_open в полити­ке мож­но ука­зывать прос­то sys_open. Tetragon умный, сам раз­берет­ся и адап­тиру­ется под архи­тек­туру, на которой работа­ет.
  • Uprobes. В целом ана­логич­ны kprobes, толь­ко при­меня­ются к прог­раммам в прос­транс­тве поль­зовате­ля. Здесь работу с uprobes мы рас­смат­ривать не будем, пос­коль­ку хуки ядра и так дают нам дос­таточ­но воз­можнос­тей для тон­кой нас­трой­ки политик.
  • Tracepoints. Точ­ки трас­сиров­ки ста­тичес­ки про­писа­ны в ядре. Имен­но они счи­тают­ся наибо­лее удач­ным вари­антом для отсле­жива­ния и прик­репле­ния сво­его кода для отладки. Основное пре­иму­щес­тво перед зон­дами — ста­биль­ность меж­ду релиза­ми.

Что­бы узнать дос­тупные трей­спой­нты в сво­ей сис­теме, выпол­ни такую коман­ду:

cat /sys/kernel/debug/tracing/available_events

Вы­вод будет в фор­мате «катего­рия:имя»; пос­леднее — как раз то, за что может уце­пить­ся eBPF.

В некото­рых сис­темах ряд точек недос­тупен, про­верить их спи­сок мож­но так:

cat /sys/kernel/debug/krpobes/blacklist

Из минусов трей­спой­нтов — они есть далеко не для всех фун­кций ядра, и, если нуж­ного нет, при­дет­ся смот­реть в сто­рону kprobes.

К чему в ито­ге луч­ше хукать­ся и как пос­мотреть все позиции дан­ного меню? При выборе меж­ду наз­вани­ем syscall, фун­кции ядра, kprobe и tracepoint пред­почти­тель­нее ори­енти­ровать­ся на трей­спой­нты, пос­коль­ку они име­ют более ста­биль­ный интерфейс, чем эле­мен­ты ядра. Фун­кции ядра могут пери­оди­чес­ки менять­ся, что в дол­госроч­ной пер­спек­тиве может при­вес­ти к проб­лемам. Если сов­сем лень раз­бирать­ся, мож­но прос­то гре­пать в выводе ути­литы bpftrace: bpftrace -l — она покажет прос­то всё, за что мож­но зацепить­ся.

Мы в статье будем исполь­зовать микс на осно­ве отдель­ных сис­колов и фун­кций ядра. Это обес­печит плав­ный переход от auditd (который срав­нитель­но известен) к при­мене­нию eBPF на осно­ве kprobes и tracepoints.

 

Шпаргалка по хукам

Что­бы упростить себе и кол­легам жизнь, мы сос­тавили таб­лицу по самым популяр­ным или инте­рес­ным активнос­тям, которые обыч­но хотят видеть безопас­ники. В таб­лице мы све­ли име­на отдель­ных сис­колов, как их при­вык­ли записы­вать в пра­вилах auditd. Рядом ука­зыва­ем наз­вания фун­кций ядра, сопос­тавля­емых с дан­ными сис­колов, — это уже мож­но пытать­ся исполь­зовать в eBPF-решени­ях, в том чис­ле в Tetragon.

В качес­тве бонуса при­водим некото­рые kprobes и бли­жай­шие tracepoints. Ты можешь при­атта­чить­ся поч­ти к любой фун­кции ядра — прос­то глянь, сколь­ко их (в сис­теме дол­жен быть зарегис­три­рован kprobe):

cat /sys/kernel/debug/krpobes/list

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

Как дума­ешь, как час­то прог­раммы вызыва­ют код ядра? Узнать это мож­но при помощи ути­литы strace. Что­бы вывес­ти сис­темные вызовы (трас­сиров­ку), которые дела­ет при­ложе­ние, напиши

strace ps

Что­бы прос­то пос­мотреть ста­тис­тику по сис­темным вызовам, мож­но исполь­зовать сле­дующий снип­пет:

for f in "cat /etc/passwd" ; do
strace -c $f 2>&1 > /dev/null |
grep -v ':' | cut --complement -c 42-50 |
sed '1s/^% /%_/;2d' | head -n -2 ;
done | sed -n '1p;/^%/d;p' | datamash -HW -sg5 sum 4 |
xargs printf "%-12s\t%14s\n"

Вы­вод:

GroupBy(syscall) sum(calls)
access 1
arch_prctl 1
brk 3
close 20
execve 1
fadvise64 1
fstat 19
futex 1
getrandom 1
mmap 22
mprotect 3
munmap 2
openat 31
pread64 2
prlimit64 1
read 5
rseq 1
set_robust_list 1
set_tid_address 1
write 1

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

www

Под­робнее об изме­нени­ях мож­но про­читать в раз­деле докумен­тации Use Cases.

Что при­ятно, Tetragon сам адап­тиру­ется под архи­тек­туру, на которой запущен. Для auditd при­ходи­лось писать длин­ные пас­сажи о том, какие сис­колы нуж­но вызывать при работе на раз­ных архи­тек­турах. Здесь же мож­но прос­то задать полити­ки трас­сиров­ки и ука­зать sys_ptrace, какой вызов будет зарезол­влен в одну из фун­кций ядра (__x64_sys_ptrace/__ia32_sys_ptrace/__ia32_compat_sys_ptrace или __x64_compat_sys_ptrace). За это отве­чает параметр syscall в селек­торе. Обыч­но он выс­тавлен false, то есть отсле­жива­ются обра­щения к фун­кци­ям ядра. Если пос­тавить true, будут ана­лизи­ровать­ся все сис­колы (даже те, что не дой­дут до выпол­нения фун­кции ядра).

Встро­енные в полити­ку трас­сиров­ки для отсле­жива­ния запус­ка и завер­шения про­цес­сов исполь­зуют, нап­ример, sys_execve, отме­чен­ный в таб­лице выше, — он соот­носит­ся с tracepoint sched_process_exec; со сто­роны LSM это kprobe security_bprm_committing_creds, завер­шение детек­тится через kprobe acct_process, а соз­дание фор­ка про­цес­са — через kprobe wake_up_new_task.

 

Селекторы

Про­дол­жим про полити­ки. Мы вро­де опре­дели­лись, что есть основные сис­темные хуки, от которых мож­но пля­сать. А как же нам нафиль­тро­вывать мякот­ку?

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

  • matchArgs — по зна­чению аргу­мен­тов;
  • matchReturnArgs — по воз­вра­щаемым зна­чени­ям;
  • matchPIDs — по PID про­цес­са;
  • matchBinaries — по пути к исполня­емо­му фай­лу;
  • matchNamespaces — по прос­транс­твам имен (namespaces);
  • matchCapabilities — по Linux capabilities (при­виле­гиям);
  • matchNamespaceChanges — по изме­нени­ям в прос­транс­тве имен;
  • matchCapabilityChanges — по изме­нени­ям capabilities;
  • matchActions — при­менить дей­ствие;
  • matchReturnActions — при­менить дей­ствие к выход­ным событи­ям из селек­тора.

Об­рати вни­мание, что, к сожале­нию, в зна­чени­ях для работы селек­торов нет воз­можнос­ти ука­зывать вай­лдкар­ту (звез­дочку). В ряде слу­чаев SOC инте­рес­но отсле­живать изме­нения в таких фай­лах, как /home/<username>/.bash_history, что как раз было бы удоб­но делать по мас­ке. Тут в качес­тве аль­тер­нативы мож­но юзать опе­рато­ры Prefix и Postfix в селек­торах.

В целом селек­торы доволь­но нес­ложная шту­ка. Инте­рес­но понимать, какие дей­ствия (помимо логиро­вания) мож­но делать в полити­ке (мы при­водим здесь не все):

  • убить про­цесс;
  • пос­лать сиг­нал про­цес­су;
  • из­менить вывод про­цес­са;
  • триг­гернуть URL;
  • сде­лать DNS-резолв.

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

В селек­торах дос­тупны сле­дующие опе­рато­ры, помимо при­выч­ных «рав­но», «не рав­но», «боль­ше» и «мень­ше». Это ука­зание пор­та или адре­са источни­ка и наз­начения — со сра­зу заложен­ной воз­можностью опре­деле­ния при­виле­гиро­ван­ных пор­тов. Иро­нич­но, но так­же для этих парамет­ров есть и опе­рато­ры вида «не порт наз­начения», «не сис­темный порт» и так далее. Это, вооб­ще говоря, очень полез­ные фун­кции, толь­ко стран­но, что не завез­ли прос­то отдель­ного опе­рато­ра «не». Есть опе­ратор про­вер­ки битовой мас­ки, сетево­го про­токо­ла, семей­ства IP-адре­са и сос­тояния TCP-соеди­нения (что может быть полез­но при выяв­лении раз­личных ска­ниро­ваний). Пол­ный спи­сок — опять же в докумен­тации.

По осно­вам бег­ло про­бежа­лись (мы ста­рались быть лаконич­ными), прис­тупа­ем к прак­тичес­ким упражне­ниям.

 

Установка и настройка

Ста­вить будем в Linux. Смот­рим номер пос­ледне­го релиза на GitHub, перехо­дим в бра­узе­ре. На момент написа­ния статьи акту­аль­ная вер­сия — v1.3.0. Ска­чива­ем и уста­нав­лива­ем ее:

# Скачиваем архив
curl -LO https://github.com/cilium/tetragon/releases/download/v1.3.0/tetragon-v1.3.0-amd64.tar.gz
# Распаковываем в папку tetragon-v1.3.0-amd64
tar -xvf tetragon-v1.3.0-amd64.tar.gz
# Переходим в нее
cd tetragon-v1.3.0-amd64/
# Запускаем установку
sudo ./install.sh

По­лучив завет­ное Tetragon installed successfully!, убе­дим­ся, что он запущен и акти­вен: sudo systemctl status tetragon.

 

Основные локации Tetragon

Пос­ле уста­нов­ки даль­нейшая нас­трой­ка парамет­ров Tetragon про­водит­ся в дирек­тории /etc/tetragon/tetragon.conf.d/.

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

В пер­вую оче­редь, раз мы собира­емся занимать­ся логиро­вани­ем, ука­жем путь к фай­лу, куда писать лог. Пап­ка логов — /var/log/tetragon/:

echo "/var/log/tetragon/tetragon.log" > /etc/tetragon/tetragon.conf.d/export-file
systemctl restart tetragon

В таб­лице — дефол­тные нас­трой­ки по логам (дер­жим не более пяти фай­лов раз­мером не более 10 Мбайт):

  • --export-file-max-backups — количес­тво ротиру­емых JSON-фай­лов — 5;
  • --export-file-max-size-mb — раз­мер фай­лов в мегабай­тах —  10.

Что­бы не забить логами диск, регули­руй зна­чения выше в кон­фиге.

Заг­лянем в лог, что­бы поз­накомить­ся с ним:

less /var/log/tetragon/tetragon.log

Ес­ли ты мораль­но не готов отка­зать­ся от syscall-событий auditd, где лам­пово ука­зыва­ются поль­зователь­ские и груп­повые ID (UID, GID, EUID, SUID и про­чие), ты всег­да можешь их добавить к событи­ям Tetragon.

Срав­ни обо­гащен­ное событие auditd и Tetragon о запус­ке ps. Вот вер­сия auditd:

type=SYSCALL msg=audit(1663937111.702:73702361): arch=c000003e syscall=59 success=yes exit=0 a0=7f90824cfbf8 a1=7f907a020608 a2=7f907d9de920 a3=7f90742fcb10 items=2 ppid=1736 pid=3202324 auid=4294967295 uid=996 gid=997 euid=996 suid=996 fsuid=996 egid=997 sgid=997 fsgid=997 tty=(none) ses=4294967295 comm="ps" exe="/usr/bin/ps" key="execve_example" ARCH=x86_64 SYSCALL=execve AUID="unset" UID="git" GID="git" EUID="git" SUID="git" FSUID="git" EGID="git" SGID="git" FSGID="git"

А вот Tetragon с дефол­тной полити­кой трас­сиров­ки:

{
"process_exec": {
"process": {
"exec_id": "UEFETUlOOjIwNzk2MzM1MzM0MTA4OjI2NDM4",
"pid": 26438,
"uid": 0,
"cwd": "/root",
"binary": "/usr/bin/ps",
"flags": "execve clone",
"start_time": "2025-03-15T09:16:44.617411202Z",
"auid": 4294967295,
"parent_exec_id": "UEFETUlOOjE1NzI2NzE4NDk4MzUyOjIxNzA0",
"tid": 26438,
"in_init_tree": false
},
"parent": {
"exec_id": "UEFETUlOOjE1NzI2NzE4NDk4MzUyOjIxNzA0",
"pid": 21704,
"uid": 0,
"cwd": "/root",
"binary": "/bin/bash",
"flags": "execve clone",
"start_time": "2025-03-13T18:06:21.898754560Z",
"auid": 4294967295,
"parent_exec_id": "UEFETUlOOjE1NzI2NzA5NjQ4MzE2OjIxNzAy",
"tid": 21704,
"in_init_tree": false
}
},
"node_name": "PADMIN",
"time": "2025-03-15T09:16:44.617410306Z"
}
{
"process_exit": {
"process": {
"exec_id": "UEFETUlOOjIwNzk2MzM1MzM0MTA4OjI2NDM4",
"pid": 26438,
"uid": 0,
"cwd": "/root",
"binary": "/usr/bin/ps",
"flags": "execve clone",
"start_time": "2025-03-15T09:16:44.617411202Z",
"auid": 4294967295,
"parent_exec_id": "UEFETUlOOjE1NzI2NzE4NDk4MzUyOjIxNzA0",
"tid": 26438,
"in_init_tree": false
},
"parent": {
"exec_id": "UEFETUlOOjE1NzI2NzE4NDk4MzUyOjIxNzA0",
"pid": 21704,
"uid": 0,
"cwd": "/root",
"binary": "/bin/bash",
"flags": "execve clone",
"start_time": "2025-03-13T18:06:21.898754560Z",
"auid": 4294967295,
"parent_exec_id": "UEFETUlOOjE1NzI2NzA5NjQ4MzE2OjIxNzAy",
"tid": 21704,
"in_init_tree": false
},
"time": "2025-03-15T09:16:44.636431137Z"
},
"node_name": "PADMIN",
"time": "2025-03-15T09:16:44.636429903Z"
}

info

Ло­ги Tetragon удоб­но смот­реть коман­дой tetra getevents.

Те­перь добавим обо­гаще­ние capabilities:

echo "true" > /etc/tetragon/tetragon.conf.d/enable-process-cred
systemctl restart tetragon
ps
tail -f /var/log/tetragon/tetragon.log

Вы­вод доволь­но гро­моз­дкий, на любите­ля:

{
"process_exec": {
"process": {
"exec_id": "UEFETUlOOjIxMDA3MzE3NjA4MjU5OjI2NjU2",
"pid": 26656,
"uid": 0,
"cwd": "/root",
"binary": "/usr/bin/ps",
"flags": "execve",
"start_time": "2025-03-15T09:20:15.599685868Z",
"auid": 4294967295,
"parent_exec_id": "UEFETUlOOjA6MjE3MDQ=",
"cap": {
"permitted": [
"CAP_CHOWN", "DAC_OVERRIDE", "CAP_DAC_READ_SEARCH", "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_SETGID", "CAP_SETUID", "CAP_SETPCAP", "CAP_LINUX_IMMUTABLE", "CAP_NET_BIND_SERVICE", "CAP_NET_BROADCAST", "CAP_NET_ADMIN", "CAP_NET_RAW", "CAP_IPC_LOCK", "CAP_IPC_OWNER", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_CHROOT", "CAP_SYS_PTRACE", "CAP_SYS_PACCT", "CAP_SYS_ADMIN", "CAP_SYS_BOOT", "CAP_SYS_NICE", "CAP_SYS_RESOURCE", "CAP_SYS_TIME", "CAP_SYS_TTY_CONFIG", "CAP_MKNOD", "CAP_LEASE", "CAP_AUDIT_WRITE", "CAP_AUDIT_CONTROL", "CAP_SETFCAP", "CAP_MAC_OVERRIDE", "CAP_MAC_ADMIN", "CAP_SYSLOG", "CAP_WAKE_ALARM", "CAP_BLOCK_SUSPEND", "CAP_AUDIT_READ", "CAP_PERFMON", "CAP_BPF", "CAP_CHECKPOINT_RESTORE"
],
"effective": [
"CAP_CHOWN", "DAC_OVERRIDE", "CAP_DAC_READ_SEARCH", "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_SETGID", "CAP_SETUID", "CAP_SETPCAP", "CAP_LINUX_IMMUTABLE", "CAP_NET_BIND_SERVICE", "CAP_NET_BROADCAST", "CAP_NET_ADMIN", "CAP_NET_RAW", "CAP_IPC_LOCK", "CAP_IPC_OWNER", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_CHROOT", "CAP_SYS_PTRACE", "CAP_SYS_PACCT", "CAP_SYS_ADMIN", "CAP_SYS_BOOT", "CAP_SYS_NICE", "CAP_SYS_RESOURCE", "CAP_SYS_TIME", "CAP_SYS_TTY_CONFIG", "CAP_MKNOD", "CAP_LEASE", "CAP_AUDIT_WRITE", "CAP_AUDIT_CONTROL", "CAP_SETFCAP", "CAP_MAC_OVERRIDE", "CAP_MAC_ADMIN", "CAP_SYSLOG", "CAP_WAKE_ALARM", "CAP_BLOCK_SUSPEND", "CAP_AUDIT_READ", "CAP_PERFMON", "CAP_BPF", "CAP_CHECKPOINT_RESTORE"
]
},
"tid": 26656,
"process_credentials": {
"uid": 0,
"gid": 0,
"euid": 0,
"egid": 0,
"suid": 0,
"sgid": 0,
"fsuid": 0,
"fsgid": 0
},
"in_init_tree": false
}
},
"node_name": "PADMIN",
"time": "2025-03-15T09:20:15.599686113Z"
}
 

Особенности для контейнеров

В Tetragon не нуж­ны какие‑то осо­бые дей­ствия для лов­ли событий внут­ри кон­тей­неров, подов и так далее.

Ни­же — при­мер лога пос­ле запус­ка коман­ды whoami внут­ри кон­тей­нера Docker:

{"process_exec":{"process":{"exec_id":"eGFrZXA6MjE0MjM0Nzk1NTg5MTgwNDI6MzYzNTE0NA==", "pid":3635144, "uid":0, "cwd":"/", "binary":"/usr/bin/whoami", "flags":"execve rootcwd clone inInitTree", "start_time":
"2025-03-10T08:22:46.921009521Z", "auid":4294967295, "docker":"d7a3f91a19404549b4422f5cdd4975a", "parent_exec_id":"eGFrZXA6MjE0MjM0MTQ4NzIzMzkzNjg6MzYzMTUyMQ==", "tid":3635144, "in_init_tree":true}, "paren
t":{"exec_id":"eGFrZXA6MjE0MjM0MTQ4NzIzMzkzNjg6MzYzMTUyMQ==", "pid":3631521, "uid":0, "cwd":"/", "binary":"/bin/bash", "flags":"execve rootcwd clone inInitTree", "start_time":"2025-03-10T08:21:42.234432165
Z", "auid":4294967295, "docker":"d7a3f91a19404549b4422f5cdd4975a", "parent_exec_id":"eGFrZXA6MjE0MjM0MTQ1NDk0ODU4NDg6MzYzMTUw", "tid":3631521, "in_init_tree":true}}, "node_name":"fi", "time":"2025-03-1
0T08:22:46.921008920Z"}
{"process_kprobe":{"process":{"exec_id":"eGFrZXA6MjE0MjM0Nzk1NTg5MTgwNDI6MzYzNTE0NA==", "pid":3635144, "uid":0, "cwd":"/", "binary":"/usr/bin/whoami", "flags":"execve rootcwd clone inInitTree", "start_time
":"2025-03-10T08:22:46.921009521Z", "auid":4294967295, "docker":"d7a3f91a19404549b4422f5cdd4975a", "parent_exec_id":"eGFrZXA6MjE0MjM0MTQ4NzIzMzkzNjg6MzYzMTUyMQ==", "refcnt":1, "tid":3635144, "in_init_tree"
:true}, "parent":{"exec_id":"eGFrZXA6MjE0MjM0MTQ4NzIzMzkzNjg6MzYzMTUyMQ==", "pid":3631521, "uid":0, "cwd":"/", "binary":"/bin/bash", "flags":"execve rootcwd clone inInitTree", "start_time":"2025-03-10T08:2
1:42.234432165Z", "auid":4294967295, "docker":"d7a3f91a19404549b4422f5cdd4975a", "parent_exec_id":"eGFrZXA6MjE0MjM0MTQ1NDk0ODU4NDg6MzYzMTUw", "tid":3631521, "in_init_tree":true}, "function_name":"secur
ity_file_permission", "args":[{"file_arg":{"path":"/etc/passwd", "permission":"-rw-r--r--"}}, {"int_arg":4}], "return":{"int_arg":0}, "action":"KPROBE_ACTION_POST", "policy_name":"file-change-read-moni
toring-filtered-fullpath", "return_action":"KPROBE_ACTION_POST"}, "node_name":"fi", "time":"2025-03-10T08:22:46.922387741Z"}
{"process_kprobe":{"process":{"exec_id":"eGFrZXA6MjE0MjM0Nzk1NTg5MTgwNDI6MzYzNTE0NA==", "pid":3635144, "uid":0, "cwd":"/", "binary":"/usr/bin/whoami", "flags":"execve rootcwd clone inInitTree", "start_time
":"2025-03-10T08:22:46.921009521Z", "auid":4294967295, "docker":"d7a3f91a19404549b4422f5cdd4975a", "parent_exec_id":"eGFrZXA6MjE0MjM0MTQ4NzIzMzkzNjg6MzYzMTUyMQ==", "refcnt":1, "tid":3635144, "in_init_tree"
:true}, "parent":{"exec_id":"eGFrZXA6MjE0MjM0MTQ4NzIzMzkzNjg6MzYzMTUyMQ==", "pid":3631521, "uid":0, "cwd":"/", "binary":"/bin/bash", "flags":"execve rootcwd clone inInitTree", "start_time":"2025-03-10T08:2
1:42.234432165Z", "auid":4294967295, "docker":"d7a3f91a19404549b4422f5cdd4975a", "parent_exec_id":"eGFrZXA6MjE0MjM0MTQ1NDk0ODU4NDg6MzYzMTUw", "tid":3631521, "in_init_tree":true}, "function_name":"secur
ity_file_permission", "args":[{"file_arg":{"path":"/etc/passwd", "permission":"-rw-r--r--"}}, {"int_arg":4}], "return":{"int_arg":0}, "action":"KPROBE_ACTION_POST", "policy_name":"file-change-read-moni
toring-filtered-fullpath", "return_action":"KPROBE_ACTION_POST"}, "node_name":"fi", "time":"2025-03-10T08:22:46.922425253Z"}
{"process_exit":{"process":{"exec_id":"eGFrZXA6MjE0MjM0Nzk1NTg5MTgwNDI6MzYzNTE0NA==", "pid":3635144, "uid":0, "cwd":"/", "binary":"/usr/bin/whoami", "flags":"execve rootcwd clone inInitTree", "start_time":
"2025-03-10T08:22:46.921009521Z", "auid":4294967295, "docker":"d7a3f91a19404549b4422f5cdd4975a", "parent_exec_id":"eGFrZXA6MjE0MjM0MTQ4NzIzMzkzNjg6MzYzMTUyMQ==", "tid":3635144, "in_init_tree":true}, "paren
t":{"exec_id":"eGFrZXA6MjE0MjM0MTQ4NzIzMzkzNjg6MzYzMTUyMQ==", "pid":3631521, "uid":0, "cwd":"/", "binary":"/bin/bash", "flags":"execve rootcwd clone inInitTree", "start_time":"2025-03-10T08:21:42.234432165
Z", "auid":4294967295, "docker":"d7a3f91a19404549b4422f5cdd4975a", "parent_exec_id":"eGFrZXA6MjE0MjM0MTQ1NDk0ODU4NDg6MzYzMTUw", "tid":3631521, "in_init_tree":true}, "time":"2025-03-10T08:22:46.92268996
1Z"}, "node_name":"fi", "time":"2025-03-10T08:22:46.922688945Z"}

Здесь

  • "in_init_tree":true — запуск про­изо­шел изнутри кон­тей­нера;
  • "docker":"d7a..." — иден­тифика­тор кон­тей­нера.

Ес­ли вни­матель­ней пос­мотреть на вывод, мож­но заметить, что при выпол­нении коман­ды whoami на уров­не сис­темы про­исхо­дит чте­ние фай­ла /etc/passwd (при наличии полити­ки трас­сиров­ки с фун­кци­ей "function_name":"security_file_permission"), с таким выводом дан­ных мож­но начать луч­ше понимать прин­ципы работы ОС.

 

Просмотр событий в реальном времени

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

tetra getevents -o compact

Уви­дим сле­дующий вывод (здесь ракета — старт, а взрыв — завер­шение про­цес­сов).

🚀 process xakep /usr/sbin/sshd -D -R
💥 exit xakep /usr/bin/sleep 0.125 0
🚀 process xakep /usr/bin/cat /sys/class/net/veth1752502i0/statistics/rx_packets
💥 exit xakep /usr/bin/cat /sys/class/net/veth1752502i0/statistics/rx_packets 0
🚀 process xakep /usr/bin/expr 28554688 - 28554688
💥 exit xakep /usr/bin/expr 28554688 - 28554688 1
🚀 process xakep /usr/sbin/iptables -L -vn

 

Политики трассировки

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

Пап­ка политик трас­сиров­ки — /etc/tetragon/tetragon.tp.d/. Сами полити­ки мы под­робно рас­смот­рим далее.

warning

Пос­ле добав­ления или изме­нения политик необ­ходимо переза­пус­кать служ­бу коман­дой systemctl restart tetragon!

Ес­ли служ­ба не стар­тует (или не в сос­тоянии active боль­ше десяти секунд), то с веро­ятностью, близ­кой к 100%, косяк — в син­такси­се полити­ки (очень час­то YAML пор­тят пус­тые или слу­жеб­ные сим­волы в кон­це фай­ла).

Что­бы про­читать жур­нал и убе­дить­ся, что все работа­ет как надо, выпол­няем сле­дующие коман­ды:

journalctl -xeu tetragon.service
# Или так иногда помогает
systemctl status tetragon

Ес­ли накося­чил в одной из политик, уви­дишь ошиб­ку.

Failed to start tetragon error="policy handler 'tracing' failed loading policy 'tcp-only-external-addrs': validation failed"

Без допол­нитель­но соз­данных политик в Tetragon логиру­ются события соз­дания (process_exec) и завер­шения про­цес­са (process_exit). Гля­нем логи этих событий.

При­мер завер­шения чте­ния фай­ла /etc/passwd коман­дой cat:

{
"process_exit": {
"process": {
"exec_id": "eGFrZXA6MTM4MjkyMzk2NTkyNjM2Mjc6Mjk2NDM5",
"pid": 296439,
"uid": 0,
"cwd": "/root/tetragon/tetragon-v1.2.0-amd64",
"binary": "/usr/bin/cat",
"arguments": "/etc/passwd",
"flags": "execve clone",
"start_time": "2024-11-05T11:52:47.775421543Z",
"auid": 0,
"parent_exec_id": "eGFrZXA6MTM4MTIyMDg0MjAwMDAwMDA6MjcwMTI=",
"tid": 296439,
"user": {
"name": "root"
}
},
"parent": {
"exec_id": "eGFrZXA6MTM4MTIyMDg0MjAwMDAwMDA6MjcwMTI=",
"pid": 27012,
"uid": 0,
"binary": "/usr/bin/bash",
"arguments": " " /root/tetragon/tetragon-v1.2.0-amd64"",
"flags": "procFS auidnocwd",
"start_time": "2024-11-05T07:08:56.536154067Z",
"auid": 0,
"parent_exec_id": "eGFrZXA6MTM4MTIyMDg0MjAwMDAwMDA6MjcwMTI=",
"tid": 27012,
"user": {
"name": "root"
}
},
"time": "2024-11-05T11:52:47.778558683Z"
},
"node_name": "xakep",
"time": "2024-11-05T11:52:47.778555561Z"
}

Нес­мотря на опре­делен­ную избы­точ­ность, сра­зу вид­но, что юзер root в обо­лоч­ке bash откры­вал целевой файл. При­ятно, что есть инфа о родитель­ском про­цес­се, — это упро­щает рас­сле­дова­ние инци­ден­тов.

В докумен­тации опи­саны и дру­гие полити­ки с ука­зани­ем релеван­тных сце­нари­ев безопас­ности. Нап­ример, бла­года­ря монито­рин­гу соз­дания и завер­шения про­цес­сов мож­но отсле­живать выпол­нение исполня­емых фай­лов в катало­ге /tmp, выпол­нение команд через sudo, исполь­зование бита SUID. Нас­тоятель­но рекомен­дуем озна­комить­ся с этим раз­делом справ­ки!

 

Блокировка в политике

Прос­тым монито­рин­гом дело не огра­ничи­вает­ся. Мож­но бло­киро­вать что‑то сог­ласно полити­ке (при­мер — в репози­тории Tetragon). Осо­бое вни­мание обра­ти на часть пос­ле селек­тора, в кон­це:

matchActions:
- action: Sigkill
Пример блокировки по внешнему взаимодействию
При­мер бло­киров­ки по внеш­нему вза­имо­дей­ствию

На скрин­шоте выше вид­но, что были уби­ты про­цес­сы netcat и curl из‑за домена ya.ru и пор­та 443 (HTTPS).

Вот что в логах:

{"process_exec":{"process":{"exec_id":"eGFrZXA6MjUzMTMwMDY4NTc3NTUzNTY6MzIzOTE0MA==", "pid":3239140, "uid":0, "cwd":"/root", "binary":"/usr/bin/curl", "arguments":"https://ya.ru", "flags":"execve clone", "start_time":"2025-03-18T09:51:54.520338864Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "tid":3239140, "user":{"name":"root"}, "in_init_tree":false}, "parent":{"exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "pid":3195687, "uid":0, "binary":"/usr/bin/bash", "arguments":" " /root"", "flags":"procFS auid nocwd", "start_time":"2025-03-06T11:34:35.302582913Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "tid":3195687, "user":{"name":"root"}, "in_init_tree":false}}, "node_name":"xakep", "time":"2025-03-18T09:51:54.520338132Z"}
{"process_kprobe":{"process":{"exec_id":"eGFrZXA6MjUzMTMwMDY4NTc3NTUzNTY6MzIzOTE0MA==", "pid":3239140, "uid":0, "cwd":"/root", "binary":"/usr/bin/curl", "arguments":"https://ya.ru", "flags":"execve clone", "start_time":"2025-03-18T09:51:54.520338864Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "refcnt":1, "tid":3239140, "user":{"name":"root"}, "in_init_tree":false}, "parent":{"exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "pid":3195687, "uid":0, "binary":"/usr/bin/bash", "arguments":" " /root"", "flags":"procFS auid nocwd", "start_time":"2025-03-06T11:34:35.302582913Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "tid":3195687, "user":{"name":"root"}, "in_init_tree":false}, "function_name":"tcp_connect", "args":[{"sock_arg":{"family":"AF_INET", "type":"1536", "protocol":"IPPROTO_TCP", "saddr":"10.68.85.126", "daddr":"5.255.255.242", "sport":41422, "dport":443, "cookie":"18446615535296672256", "state":"TCP_SYN_SENT"}}], "action":"KPROBE_ACTION_POST", "policy_name":"tcp-only-external-addrs", "return_action":"KPROBE_ACTION_POST"}, "node_name":"xakep", "time":"2025-03-18T09:51:54.540689266Z"}
{"process_kprobe":{"process":{"exec_id":"eGFrZXA6MjUzMTMwMDY4NTc3NTUzNTY6MzIzOTE0MA==", "pid":3239140, "uid":0, "cwd":"/root", "binary":"/usr/bin/curl", "arguments":"https://ya.ru", "flags":"execve clone", "start_time":"2025-03-18T09:51:54.520338864Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "refcnt":1, "tid":3239140, "user":{"name":"root"}, "in_init_tree":false}, "parent":{"exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "pid":3195687, "uid":0, "binary":"/usr/bin/bash", "arguments":" " /root"", "flags":"procFS auid nocwd", "start_time":"2025-03-06T11:34:35.302582913Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "tid":3195687, "user":{"name":"root"}, "in_init_tree":false}, "function_name":"tcp_sendmsg", "args":[{"sock_arg":{"family":"AF_INET", "type":"1536", "protocol":"IPPROTO_TCP", "saddr":"10.68.85.126", "daddr":"5.255.255.242", "sport":41422, "dport":443, "cookie":"18446615535296672256", "state":"TCP_ESTABLISHED"}}, {"int_arg":517}], "action":"KPROBE_ACTION_SIGKILL", "policy_name":"tcp-only-external-addrs", "return_action":"KPROBE_ACTION_POST"}, "node_name":"xakep", "time":"2025-03-18T09:51:54.580731247Z"}
{"process_exit":{"process":{"exec_id":"eGFrZXA6MjUzMTMwMDY4NTc3NTUzNTY6MzIzOTE0MA==", "pid":3239140, "uid":0, "cwd":"/root", "binary":"/usr/bin/curl", "arguments":"https://ya.ru", "flags":"execve clone", "start_time":"2025-03-18T09:51:54.520338864Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "tid":3239140, "user":{"name":"root"}, "in_init_tree":false}, "parent":{"exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "pid":3195687, "uid":0, "binary":"/usr/bin/bash", "arguments":" " /root"", "flags":"procFS auid nocwd", "start_time":"2025-03-06T11:34:35.302582913Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQyODIzNjc2NDAwMDAwMDA6MzE5NTY4Nw==", "tid":3195687, "user":{"name":"root"}, "in_init_tree":false}, "signal":"SIGKILL", "time":"2025-03-18T09:51:54.581941252Z"}, "node_name":"xakep", "time":"2025-03-18T09:51:54.581940387Z"}

Об­рати вни­мание на поле "action":"KPROBE_ACTION_SIGKILL" и пос­леду­ющее завер­шение про­цес­са в "process_exit"..."signal":"SIGKILL". По наиме­нова­нию "policy_name":"tcp-only-external-addrs" мож­но понять, какая полити­ка сра­бота­ла.

 

Написание исключений

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

В пап­ке /etc/tetragon/tetragon.conf.d/ соз­даем файл export-denylist со сле­дующим содер­жимым:

{
"event_set": [
"PROCESS_EXEC",
"PROCESS_EXIT"
],
"binary_regex": [
"^/bin/bash$",
"^/usr/bin/bash$"
],
"arguments_regex": [
"/usr/share/user/scripts/kumarez.sh"
]
}

Пос­ле пра­вок не забудь переза­пус­тить служ­бу Tetragon. Подоб­ным обра­зом мож­но исклю­чить родитель­ские про­цес­сы (исполь­зуя parent_binary или parent_arguments). Это помога­ет умень­шить шум в жур­налах.

Еще один полез­ный снип­пет для политик — выяв­ление не­удач­ной попыт­ки обра­щения к объ­екту (то есть ког­да код воз­вра­та отли­чает­ся от 0):

- matchReturnArgs:
- index: 0
operator: "NotEqual"
values:
- 0
 

Сетевая политика трассировки

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

Да­вай пос­мотрим, как мож­но написать пра­вило для выяв­ления DNS-зап­росов. Вот все, что нам извес­тно про DNS: будет про­исхо­дить сетевое вза­имо­дей­ствие через 53-й порт по про­токо­лу UDP; соеди­нение — исхо­дящее. Отлично! Для начала нам надо най­ти, какая фун­кция, сис­кол или kprobe в Linux отве­чает за сетевые соеди­нения.

Об­щий прин­цип: луч­ше все­го най­ти имен­но конеч­ную фун­кцию, выпол­няющую дей­ствие, которое мы хотим замони­торить. Пог­репав по сис­колам (при­чем мы прос­то по­иска­ли это сло­во в исходни­ках Tetragon), упо­мина­ния UDP с ходу не наш­ли. Окей, обра­тим­ся к трей­спой­нтам:

cat /sys/kernel/debug/tracing/available_events | grep udp

Но вывод тоже жид­коват — какие‑то sunrpc. Что ж, оста­ются про­бы:

cat /proc/kallsyms | grep udp | grep send

Вы­вод неболь­шой, видим в нем фун­кцию udp_sendmsg — она‑то нам и нуж­на.

Сле­дующее, с чем надо опре­делить­ся, — это аргу­мен­ты фун­кции, которые мы хотим монито­рить. Мож­но гре­пать ре­пози­тории Linux, мож­но искать в Google. Видим, что пер­вым аргу­мен­том идет sock — то есть IP-адрес и порт наз­начения.

Мы зна­ем, что порт у нас 53-й, поэто­му все необ­ходимое для сос­тавле­ния полити­ки у нас есть. Не забыва­ем, что в име­ни полити­ки недопус­тимы ниж­ние под­черки­вания, и соб­люда­ем син­таксис YAML:

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "dns-udp-tracking"
spec:
kprobes:
- call: "udp_sendmsg"
syscall: false
args:
- index: 0
type: "sock"
selectors:
- matchArgs:
- index: 0
operator: "DPort"
values:
- "53"

Сох­раня­ем полити­ку вот по такому пути:

/etc/tetragon/tetragon.tp.d/dns_udp.yaml

Пе­реза­пус­каем служ­бу:

systemctl restart tetragon

И откры­ваем прос­мотр событий:

tetra getevents

В сосед­нем тер­минале дела­ем DNS-зап­рос:

nslookup ya.ru

И видим, как сра­бота­ла наша полити­ка:

{"process_kprobe":{"process":{"exec_id":"UEFETUlOOjQ1NjAwMDAwMDA6MQ==", "pid":1, "uid":0, "cwd":"/", "binary":"/usr/lib/systemd/systemd", "flags":"procFS auid rootcwd", "start_time":"2025-03-18T14:10:14.439326884Z", "auid":4294967295, "parent_exec_id":"UEFETUlOOjE6MA==", "refcnt":36, "tid":598, "in_init_tree":false}, "parent":{"exec_id":"UEFETUlOOjE6MA==", "pid":0, "uid":0, "binary":"<kernel>", "flags":"procFS", "start_time":"2025-03-18T14:10:09.879327579Z", "auid":4294967295, "parent_exec_id":"UEFETUlOOjE6MA==", "tid":0, "in_init_tree":false}, "function_name":"udp_sendmsg", "args":[{"sock_arg":{"family":"AF_INET", "type":"SOCK_DGRAM", "protocol":"IPPROTO_UDP", "saddr":"192.168.135.3", "daddr":"8.8.8.8", "sport":34383, "dport":53, "cookie":"18446637872524733696", "state":"TCP_ESTABLISHED"}}], "action":"KPROBE_ACTION_POST", "policy_name":"dns-udp-tracking", "return_action":"KPROBE_ACTION_POST"}, "node_name":"PADMIN", "time":"2025-03-18T17:06:42.116633601Z"}

Из минусов — пос­коль­ку это уро­вень ядра (ниже некуда), то резол­ва IP-адре­сов тут не будет. Из плю­сов — ты допол­нитель­но видишь, с какими полями события мож­но работать, нап­ример добавить в полити­ку селек­тор по daddr, что­бы выбирать обра­щения не к тво­ему любимо­му DNS-сер­веру (прос­то добавь в конец полити­ки).

- index: 0
operator: "NotDAddr"
values:
- "8.8.8.8"

Ес­ли тебе нуж­но так­же учесть вари­ант с TCP, то добавь ана­логич­ное пра­вило, толь­ко ука­жи call: "tcp_connect".

Бы­вает, что тебе сра­зу известен syscall. В Tetragon мож­но без тру­да най­ти опи­сание аргу­мен­тов раз­ных сис­колов с ука­зани­ем их типа, нап­ример для socket:

"socket": [
{
"Name": "family",
"Type": "int"
},
{
"Name": "type",
"Type": "int"
},
{
"Name": "protocol",
"Type": "int"
}
]

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

За осно­ву взя­ты трас­сиров­ки из при­меров tracingpolicy и quickstart.

Мож­но нем­ного усложнить пра­вило и опи­сать трас­сиров­ку TCP- и UDP-соеди­нений, при которых про­исхо­дит обра­щение на внеш­ние IP-адре­са. Готовое пра­вило смот­ри на на­шем GitHub. В пра­виле исполь­зуют­ся сис­темные вызовы tcp_connect, tcp_close, tcp_sendmsg и udp_sendmsg.

Вот при­мер записи из жур­нала пос­ле выпол­нения коман­ды curl https://ya.ru:

{"process_kprobe":{"process":{"exec_id":"eGFrZXA6MjQxOTA0NjIyMTQ0MDg3NjA6MzE5MTQyNA==", "pid":3191424, "uid":0, "cwd":"/root", "binary":"/usr/bin/curl", "arguments":"https://ya.ru", "flags":"execve clone", "start_time":"2025-03-05T10:02:49.876992195Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQxODk4MDc0ODAwMDAwMDA6MzE5MDcyOA==", "refcnt":1, "tid":3191424, "user":{"name":"root"}, "in_init_tree":false}, "parent":{"exec_id":"eGFrZXA6MjQxODk4MDc0ODAwMDAwMDA6MzE5MDcyOA==", "pid":3190728, "uid":0, "binary":"/usr/bin/bash", "arguments":" " /root"", "flags":"procFS auid nocwd", "start_time":"2025-03-05T09:51:55.142582919Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQxODk4MDc0MzAwMDAwMDA6MzE5MDcyNw==", "tid":3190728, "user":{"name":"root"}, "in_init_tree":false}, "function_name":"tcp_connect", "args":[{"sock_arg":{"family":"AF_INET", "type":"1536", "protocol":"IPPROTO_TCP", "saddr":"10.68.85.126", "daddr":"5.255.255.242", "sport":33396, "dport":443, "cookie":"18446615511771815040", "state":"TCP_SYN_SENT"}}], "action":"KPROBE_ACTION_POST", "policy_name":"net-only-external-addrs", "return_action":"KPROBE_ACTION_POST"}, "node_name":"xakep", "time":"2025-03-05T10:02:49.889349017Z"}

При­мер бло­киров­ки обра­щения по UDP на DNS-сер­вер Google с помощью netcat:

{"process_kprobe":{"process":{"exec_id":"eGFrZXA6MjUzOTk5NzE5MTc1NjI1NDQ6MzI0MzY5Mw==", "pid":3243693, "uid":0, "cwd":"/root", "binary":"/usr/bin/nc", "arguments":"-zvw3 -u 8.8.8.8 53", "flags":"execve clone", "start_time":"2025-03-19T10:01:19.580149215Z", "auid":0, "parent_exec_id":"eGFrZXA6MjUzOTk5NzE5MTc1NjI1NDQ6MzI0MzY5Mw==", "refcnt":1, "tid":3243693, "user":{"name":"root"}, "in_init_tree":false}, "parent":{"exec_id":"eGFrZXA6MjUzOTk5NzE5MTc1NjI1NDQ6MzI0MzY5Mw==", "pid":3195474, "uid":0, "binary":"/usr/bin/bash", "arguments":" " /root"", "flags":"procFS auid nocwd", "start_time":"2025-03-06T10:16:07.862583157Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQyNzc2NjAxNDAwMDAwMDA6MzE5NTQ3Mg==", "tid":3195474, "user":{"name":"root"}, "in_init_tree":false}, "function_name":"udp_sendmsg", "args":[{"sock_arg":{"family":"AF_INET", "type":"4352", "protocol":"IPPROTO_UDP", "saddr":"10.68.85.126", "daddr":"8.8.8.8", "sport":55088, "dport":53, "cookie":"18446615534975814144", "state":"TCP_ESTABLISHED"}}, {"int_arg":1}], "action":"KPROBE_ACTION_SIGKILL", "policy_name":"net-only-external-addrs", "return_action":"KPROBE_ACTION_POST"}, "node_name":"xakep", "time":"2025-03-19T10:01:19.598368812Z"}
 

Файловая политика трассировки

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

Пер­вое пра­вило — на осно­ве опе­рато­ра Postfix, то есть окон­чания пути (некий ана­лог endswith из Python и JS). Нап­ример, у каж­дого поль­зовате­ля в Linux может быть свой файл .bashrc (/root/.bashrc или /home/user2/.bashrc), куда зло­дей может, нап­ример, попытать­ся впи­сать свой alias для коман­ды.

Вто­рое пра­вило осно­вано на опе­рато­ре Equal и содер­жит пол­ные пути до кри­тичес­ких фай­лов в Linux.

Вот при­мер записи в жур­нале об изме­нении фай­ла /etc/passwd редак­тором Vi:

{"process_kprobe":{"process":{"exec_id":"eGFrZXA6MjQwMjYwNTI2OTgzNDM0ODc6MzE3OTQxNg==", "pid":3179416, "uid":0, "cwd":"/root/test", "binary":"/usr/bin/vi", "arguments":"/etc/passwd", "flags":"execve clone", "start_time":"2025-03-03T12:22:40.360929717Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQwMjYwNTI2OTgzNDM0ODc6MzE3OTQxNg==", "refcnt":1, "tid":3179416, "user":{"name":"root"}, "in_init_tree":false}, "parent":{"exec_id":"eGFrZXA6MjQwMjYwNTI2OTgzNDM0ODc6MzE3OTQxNg==", "pid":3163368, "uid":0, "binary":"/usr/bin/bash", "arguments":" " /root/test"", "flags":"procFS auid nocwd", "start_time":"2025-02-28T07:30:40.952582904Z", "auid":0, "parent_exec_id":"eGFrZXA6MjM3NDkzMzMyMzAwMDAwMDA6MzE2MzM2Ng==", "tid":3163368, "user":{"name":"root"}, "in_init_tree":false}, function_name":"security_file_permission", "args":[{"file_arg":{"path":"/etc/passwd", "permission":"-rw-r--r--"}}, {"int_arg":2}], "return":{"int_arg":0}, "action":"KPROBE_ACTION_POST", "policy_name":"file-monitoring-filtered", "return_action":"KPROBE_ACTION_POST"}, "node_name":"xakep", "time":"2025-03-03T12:22:49.454084205Z"}

Дос­той­но ува­жения, что в событии при­водят­ся раз­решения, уста­нов­ленные целево­му фай­лу.

{"process_exit":{"process":{"exec_id":"eGFrZXA6MjQwMjYwNTI2OTgzNDM0ODc6MzE3OTQxNg==", "pid":3179416, "uid":0, "cwd":"/root/test", "binary":"/usr/bin/vi", "arguments":"/etc/passwd", "flags":"execve clone", "start_time":"2025-03-03T12:22:40.360929717Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQwMjYwNTI2OTgzNDM0ODc6MzE3OTQxNg==", "tid":3179416, "user":{"name":"root"}, "in_init_tree":false}, "parent":{"exec_id":"eGFrZXA6MjQwMjYwNTI2OTgzNDM0ODc6MzE3OTQxNg==", "pid":3163368, "uid":0, "binary":"/usr/bin/bash", "arguments":" " /root/test"", "flags":"procFS auid nocwd", "start_time":"2025-02-28T07:30:40.952582904Z", "auid":0, "parent_exec_id":"eGFrZXA6MjM3NDkzMzMyMzAwMDAwMDA6MzE2MzM2Ng==", "tid":3163368, "user":{"name":"root"}, "in_init_tree":false}, "time":"2025-03-03T12:22:49.455738131Z"}, "node_name":"xakep", "time":"2025-03-03T12:22:49.455737021Z"}

Ес­ли при редак­тирова­нии фай­ла не сох­ранить изме­нение, будет отсутс­тво­вать вызов security_file_permission и залоги­рует­ся прос­то исполне­ние коман­ды в process_exec.

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

При­мер лога, который мож­но наб­людать при соз­дании фай­ла some3.txt коман­дой touch в пап­ке /etc/profile.d/, куда потен­циаль­но может быть внед­рен скрипт авто­запус­ка:

{"process_kprobe":{"process":{"exec_id":"eGFrZXA6MjQwMjM1OTkyNDY1MjgzOTQ6MzE3Nzc0OA==", "pid":3177748, "uid":0, "cwd":"/root/test", "binary":"/usr/bin/touch", "arguments":"/etc/profile.d/some3.txt", "flags":"execve clone", "start_time":"2025-03-03T11:41:46.909112495Z", "auid":0, "parent_exec_id":"eGFrZXA6MjQwMjYwNTI2OTgzNDM0ODc6MzE3OTQxNg==", "refcnt":1, "tid":3177748, "user":{"name":"root"}, "in_init_tree":false}, "parent":{"exec_id":"eGFrZXA6MjQwMjYwNTI2OTgzNDM0ODc6MzE3OTQxNg==", "pid":3163368, "uid":0, "binary":"/usr/bin/bash", "arguments":" " /root/test"", "flags":"procFS auid nocwd", "start_time":"2025-02-28T07:30:40.952584497Z", "auid":0, "parent_exec_id":"eGFrZXA6MjM3NDkzMzMyMzAwMDAwMDA6MzE2MzM2Ng==", "tid":3163368, "user":{"name":"root"}, "in_init_tree":false}, "function_name":"fd_install", "args":[{"int_arg":3}, {"file_arg":{"path":"/etc/profile.d/some3.txt", "permission":"-rw-r--r--"}}], "action":"KPROBE_ACTION_POST", "policy_name":"file-create-open-dirs", "return_action":"KPROBE_ACTION_POST"}, "node_name":"xakep", "time":"2025-03-03T11:41:46.912969231Z"}
 

Кулстори на хостинге

На­пос­ледок рас­ска­жем про при­кол с eBPF на одном из зарубеж­ных хос­тингов.

Как‑то раз мы арен­довали вир­туал­ку, а имен­но LXC-кон­тей­нер в слав­ной Гер­мании. Запуск auditd на этой машине был зап­рещен полити­ками AppArmor. Ниже — перепис­ка с тех­ничес­кой под­дер­жкой хос­тинга.

Описываем проблему
Опи­сыва­ем проб­лему
Полученный ответ
По­лучен­ный ответ

Из‑за «секури­ти ризон» не хотят сде­лать исклю­чение для бла­город­ных донов... Что ж, мы ИБшни­ки, нам надо монито­рить свою машин­ку. И тут мы, конеч­но, вспом­нили про eBPF! Пос­тавили Tetragon, нап­равили жур­налы в SIEM, в нашем слу­чае это была KUMA (Kaspersky Unified Monitoring and Analysis Platform), и замети­ли, что видим события не толь­ко с нашей машины. Нап­ример, прод­ление сер­тифика­та Let’s Encrypt какого‑то сай­та...

За­пус­ки про­верок вир­туаль­ных машин на хос­товой ОС.

Се­тевые соеди­нения по раз­ным LXC-кон­тей­нерам...

На пос­леднем скрин­шоте вид­но, что фик­сиру­ются соеди­нения от источни­ков, вхо­дящих в чер­ные спис­ки, — в нашем слу­чае это blocklist.de (забав­но, что немец­кий хос­тер не бло­киру­ет тра­фик, помечен­ный немец­кими же чер­ными спис­ками). Репута­ция IP-адре­сов опре­деля­ется с помощью обо­гаще­ния дан­ных через опен­сор­сную плат­форму инди­като­ров угроз MISP.

В чем при­кол? Если вир­туаль­ная машина — LXC-кон­тей­нер, то все ядро общее с хос­товой машиной и мы видим, что про­исхо­дит на хос­товой ОС!

Отличие LXC-контейнеров от VM и Docker
От­личие LXC-кон­тей­неров от VM и Docker
 

Выводы

Ос­новной при­кол отсле­жива­ния сис­темных вызовов в том, что ты можешь читать файл раз­ными ути­лита­ми, но сис­темный вызов один. Это как в мат­рице ATT&CK: если зна­ешь ТТР, то тебе уже не так важ­ны хеши, IP и дру­гие инди­като­ры.

Tetragon отлично подой­дет для низ­коуров­невого отсле­жива­ния активнос­ти в Linux. Но, нес­мотря на прос­тоту уста­нов­ки, у это­го инс­тру­мен­та высокий порог вхож­дения — все‑таки мат­часть Linux знать нуж­но. Мы верим, что за eBPF-инс­тру­мен­тами в про­дук­тах безопас­ности — будущее, одна­ко победит наибо­лее дру­желюб­ный и гиб­кий инс­тру­мент, который поз­волит реали­зовать все влаж­ные фан­тазии SOC. Отно­сить ли к ним Tetragon — это воп­рос инди­виду­аль­ный.

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

Собс­твен­но, эта статья и есть пер­вый при­зыв совер­шенс­тво­вать монито­ринг безопас­ности с при­мене­нием осно­ван­ных на eBPF инс­тру­мен­тов. На на­шем GitHub мы раз­мести­ли набор пра­вил, в который вош­ли как коробоч­ные при­меры, так и кон­фиги из этой статьи.

При­зыва­ем при­соеди­нять­ся и иссле­довать увле­катель­ный мир eBPF вмес­те с нами — добав­лять новые кри­тичес­кие фай­лы для монито­рин­га, полити­ки трас­сиров­ки, исклю­чения, реали­зовы­вать инте­рес­ные сце­нарии монито­рин­га или прос­то готовить снип­петы. Там же мы раз­мести­ли кол­лекцию инте­рес­ных ссы­лок на раз­личные матери­алы по Tetragon и ePBF. При­соеди­няй­ся!

www

Дру­гие инте­рес­ные про­екты по eBPF, пра­вила и про­чие ссыл­ки:

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

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

    Подписаться

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