Пишемо заштиту од ДДоС напада на КСДП. Нуклеарни део
Технологија еКспресс Дата Патх (КСДП) дозвољава произвољну обраду саобраћаја на Линук интерфејсима пре него што пакети уђу у мрежни стог кернела. Примена КСДП-а - заштита од ДДоС напада (ЦлоудФларе), сложени филтери, прикупљање статистике (Нетфлик). КСДП програме извршава еБПФ виртуелна машина, тако да имају ограничења и на њихов код и на доступне функције кернела, у зависности од типа филтера.
Чланак има за циљ да надокнади недостатке бројних материјала о КСДП-у. Прво, они обезбеђују готов код који одмах заобилази карактеристике КСДП-а: припремљен за верификацију или превише једноставан да изазове проблеме. Када касније покушате да напишете сопствени код од нуле, нема разумевања шта да радите са типичним грешкама. Друго, не покрива начине локалног тестирања КСДП-а без ВМ-а и хардвера, упркос чињеници да они имају своје замке. Текст је намењен програмерима који познају мреже и Линук који су заинтересовани за КСДП и еБПФ.
У овом делу ћемо детаљно разумети како се КСДП филтер саставља и како га тестирати, затим ћемо написати једноставну верзију добро познатог механизма СИН колачића на нивоу обраде пакета. Док не формирамо "белу листу"
верификовани клијенти, водите бројаче и управљајте филтером - довољно дневника.
Писаћемо на Ц - ово није модерно, већ практично. Сав код је доступан на ГитХуб-у на линку на крају и подељен је на урезивање према корацима описаним у чланку.
Одрицање од одговорности. У току чланка биће развијено мини решење за одбијање ДДоС напада, јер је то реалан задатак за КСДП и моју област. Међутим, главни циљ је разумевање технологије, ово није водич за креирање готове заштите. Код туторијала није оптимизован и изоставља неке нијансе.
Кратак преглед КСДП-а
Навешћу само кључне тачке да не бих дуплирао документацију и постојеће чланке.
Дакле, код филтера се учитава у кернел. Филтеру се прослеђују долазни пакети. Као резултат, филтер мора да донесе одлуку: да проследи пакет језгру (XDP_PASS), испусти пакет (XDP_DROP) или га пошаљи назад (XDP_TX). Филтер може да промени пакет, што посебно важи за XDP_TX. Такође можете срушити програм (XDP_ABORTED) и одбаците пакет, али ово је аналогно assert(0) - за отклањање грешака.
Виртуелна машина еБПФ (ектендед Берклеи Пацкет Филтер) намерно је поједностављена тако да кернел може да провери да се код не врти у петљи и да не оштети меморију других људи. Кумулативна ограничења и провере:
Лоопс (скокови назад) су забрањени.
Постоји стек за податке, али нема функција (све Ц функције морају бити уметнуте).
Приступ меморији изван стека и бафера пакета је забрањен.
Величина кода је ограничена, али у пракси то није много значајно.
Дозвољене су само посебне функције кернела (еБПФ помоћници).
Развој и инсталирање филтера изгледа овако:
изворни код (нпр. kernel.c) компајлира у објекат (kernel.o) за архитектуру виртуелне машине еБПФ. Од октобра 2019. Цланг подржава компајлирање у еБПФ и обећава га у ГЦЦ 10.1.
Ако у овом објектном коду постоје позиви структурама језгра (на пример, табелама и бројачима), уместо њихових ИД-а постоје нуле, односно такав код се не може извршити. Пре учитавања у кернел, ове нуле морају бити замењене ИД-овима одређених објеката креираних путем позива кернела (повезати код). То можете учинити помоћу спољних услужних програма или можете написати програм који ће повезати и учитати одређени филтер.
Кернел проверава програм који се учитава. Проверава одсуство циклуса и неизлазак граница пакета и стека. Ако верификатор не може да докаже да је код исправан, програм се одбацује - мора се моћи угодити њему.
Након успешне верификације, кернел компајлира објектни код еБПФ архитектуре у машински код архитектуре система (баш на време).
Програм је прикључен на интерфејс и почиње да обрађује пакете.
Пошто КСДП ради у кернелу, отклањање грешака се заснива на евиденцији праћења и, у ствари, на пакетима које програм филтрира или генерише. Међутим, еБПФ чува преузети код безбедним за систем, тако да можете експериментисати са КСДП-ом директно на вашем локалном Линук-у.
Припрема животне средине
Скупштина
Цланг не може директно да изда објектни код за еБПФ архитектуру, тако да се процес састоји од два корака:
Преведите Ц код у ЛЛВМ бајт код (clang -emit-llvm).
Претвори бајт код у еБПФ објектни код (llc -march=bpf -filetype=obj).
Приликом писања филтера добро ће доћи неколико датотека са помоћним функцијама и макроима из тестова кернела. Важно је да одговарају верзији кернела (KVER). Преузмите их на helpers/:
KDIR садржи путању до заглавља кернела, ARCH - Архитектура система. Путања и алати могу мало да се разликују између дистрибуција.
Пример разлике за Дебиан 10 (кернел 4.19.67)
# другая команда
CLANG ?= clang
LLC ?= llc-7
# другой каталог
KDIR ?= /usr/src/linux-headers-$(shell uname -r)
ARCH ?= $(subst x86_64,x86,$(shell uname -m))
# два дополнительных каталога -I
CFLAGS =
-Ihelpers
-I/usr/src/linux-headers-4.19.0-6-common/include
-I/usr/src/linux-headers-4.19.0-6-common/arch/$(ARCH)/include
# далее без изменений
CFLAGS укључују директоријум са помоћним заглављима и неколико директоријума са заглављима кернела. Симбол __KERNEL__ значи да су УАПИ (усерспаце АПИ) заглавља дефинисана за код кернела, пошто се филтер извршава у кернелу.
Заштита стека може да се онемогући (-fno-stack-protector) зато што верификатор еБПФ кода ионако проверава да није ван граница стека. Требало би одмах да омогућите оптимизације, јер је величина еБПФ бајткода ограничена.
Почнимо са филтером који пропушта све пакете и не ради ништа:
Тим make prikuplja xdp_filter.o. Где га сада можете тестирати?
Тест постоље
Сталак треба да садржи два интерфејса: на којима ће бити филтер и са којих ће се слати пакети. То морају бити потпуни Линук уређаји са сопственим ИП адресама да би се проверило како обичне апликације раде са нашим филтером.
Уређаји као што је ветх (виртуелни етернет) су погодни за нас: они су пар виртуелних мрежних интерфејса „повезаних“ директно један са другим. Можете их креирати овако (у овом одељку све команде ip изведено од root):
ip link add xdp-remote type veth peer name xdp-local
Овде xdp-remote и xdp-local — називи уређаја. на xdp-local (192.0.2.1/24) филтер ће бити прикључен, са xdp-remote (192.0.2.2/24) долазни саобраћај ће бити послат. Међутим, постоји проблем: интерфејси су на истој машини, а Линук неће слати саобраћај једном од њих преко другог. Можете га решити лукавим правилима iptables, али ће морати да мењају пакете, што је незгодно приликом отклањања грешака. Боље је користити мрежне просторе имена (мрежни простори имена, даље мреже).
Мрежни именски простор садржи скуп интерфејса, табела рутирања и НетФилтер правила која су изолована од сличних објеката у другим мрежама. Сваки процес се покреће у неком именском простору и њему су доступни само објекти ове мреже. Подразумевано, систем има један мрежни простор имена за све објекте, тако да можете да радите на Линук-у и не знате за нетнс.
Хајде да направимо нови простор имена xdp-test и преселити се тамо xdp-remote.
ip netns add xdp-test
ip link set dev xdp-remote netns xdp-test
Затим се процес покреће xdp-test, неће "видети" xdp-local (подразумевано ће остати у нетнс) и приликом слања пакета на 192.0.2.1 ће га проћи кроз xdp-remote, јер је то једини интерфејс на 192.0.2.0/24 који је доступан овом процесу. Ово такође функционише обрнуто.
Када се крећете између мрежа, интерфејс се спушта и губи адресу. Да бисте подесили интерфејс у нетнс, потребно је да покренете ip ... у овом именском простору команде ip netns exec:
ip netns exec xdp-test
ip address add 192.0.2.2/24 dev xdp-remote
ip netns exec xdp-test
ip link set xdp-remote up
Као што видите, ово се не разликује од подешавања xdp-local у подразумеваном именском простору:
ip address add 192.0.2.1/24 dev xdp-local
ip link set xdp-local up
Ако трчи tcpdump -tnevi xdp-local, можете видети да су пакети послати из xdp-test, испоручују се на овај интерфејс:
ip netns exec xdp-test ping 192.0.2.1
Погодно је покренути шкољку xdp-test. Репозиторијум има скрипту која аутоматизује рад са постољем, на пример, можете подесити штанд командом sudo ./stand up и уклоните га sudo ./stand down.
прецртавање
Филтер је причвршћен за уређај на следећи начин:
ip -force link set dev xdp-local xdp object xdp_filter.o verbose
Кључ -force потребно за повезивање новог програма ако је други већ повезан. „Ниједна вест није добра вест“ се не односи на ову команду, резултат је ионако обиман. указују verbose опционо, али са њим се појављује извештај о раду верификатора кода са листингом асемблера:
Verifier analysis:
0: (b7) r0 = 2
1: (95) exit
Одвојите програм од интерфејса:
ip link set dev xdp-local xdp off
У скрипти, ово су команде sudo ./stand attach и sudo ./stand detach.
Везивањем филтера можете се уверити у то ping наставља да ради, али да ли програм функционише? Хајде да додамо логотипе. Функција bpf_trace_printk() слично printf(), али подржава само до три аргумента осим шаблона и ограничену листу спецификација. Макро bpf_printk() поједностављује позив.
Из тог разлога, излаз за отклањање грешака у великој мери надувава резултујући код.
Слање КСДП пакета
Хајде да променимо филтер: нека шаље све долазне пакете назад. Ово је нетачно са мрежне тачке гледишта, пошто би било потребно променити адресе у заглављима, али сада је важан принцип рада.
Лансирање tcpdump на xdp-remote. Требало би да приказује идентичан одлазни и долазни ИЦМП ехо захтев и да престане да приказује ИЦМП ехо одговор. Али то се не види. Испада да ради XDP_TX у програму за xdp-localморатиза упаривање интерфејса xdp-remote програм је такође додељен, чак и ако је био празан, и он је подигнут.
Како сам знао?
Праћење путање пакета у кернелу механизам перф догађаја омогућава, иначе, коришћење исте виртуелне машине, односно еБПФ се користи за растављање са еБПФ-ом.
Од зла морате направити добро, јер од њега нема шта друго.
Ако је уместо тога приказан само АРП, потребно је да уклоните филтере (ово чини sudo ./stand detach), дозволити ping, затим инсталирајте филтере и покушајте поново. Проблем је што филтер XDP_TX утиче и на АРП, а ако стек
именских простора xdp-test успео да "заборави" МАЦ адресу 192.0.2.1, неће моћи да реши ову ИП адресу.
Проблем статемент
Пређимо на наведени задатак: написати механизам СИН колачића на КСДП.
До сада, СИН флоод остаје популаран ДДоС напад, чија је суштина следећа. Када се веза успостави (ТЦП руковање), сервер прима СИН, додељује ресурсе за будућу везу, одговара СИНАЦК пакетом и чека АЦК. Нападач једноставно шаље СИН пакете са лажних адреса у количини од хиљада у секунди са сваког хоста у више хиљада ботнета. Сервер је приморан да додели ресурсе одмах по доласку пакета, али га отпушта након дужег временског ограничења, као резултат тога, меморија или ограничења су исцрпљени, нове везе се не прихватају, услуга је недоступна.
Ако не доделите ресурсе на СИН пакет, већ само одговорите са СИНАЦК пакетом, како онда сервер може да разуме да АЦК пакет који је дошао касније припада СИН пакету који није сачуван? На крају крајева, нападач такође може да генерише лажне АЦК-ове. Суштина СИН колачића је да се кодира seqnum параметри везе као хеш адреса, портова и промена соли. Ако је АЦК успео да стигне пре промене соли, можете поново израчунати хеш и упоредити са acknum. лажан acknum нападач не може, пошто сол укључује тајну, и неће имати времена да је сортира због ограниченог канала.
СИН колачићи су већ дуго имплементирани у Линук кернел и чак се могу аутоматски омогућити ако СИН-ови стигну пребрзо и масовно.
Едукативни програм о ТЦП руковању
ТЦП обезбеђује пренос података као ток бајтова, на пример, ХТТП захтеви се преносе преко ТЦП-а. Стрим се преноси део по део у пакетима. Сви ТЦП пакети имају логичке заставице и 32-битне секвенце:
Комбинација заставица дефинише улогу одређеног пакета. Ознака СИН значи да је ово први пакет пошиљаоца на вези. Ознака АЦК значи да је пошиљалац примио све податке о вези до једног бајта. acknum. Пакет може имати неколико заставица и назван је по њиховој комбинацији, на пример, СИНАЦК пакет.
Број секвенце (секнум) одређује помак у току података за први бајт који се шаље у овом пакету. На пример, ако је у првом пакету са Кс бајтова података овај број био Н, у следећем пакету са новим подацима биће Н+Кс. На почетку везе, свака страна бира овај број насумично.
Број потврде (ацкнум) - исти помак као секнум, али не одређује број пренетог бајта, већ број првог бајта од примаоца, који пошиљалац није видео.
На почетку везе, стране морају да се договоре seqnum и acknum. Клијент шаље СИН пакет са својим seqnum = X. Сервер одговара СИНАЦК пакетом, где пише свој seqnum = Y и излаже acknum = X + 1. Клијент одговара на СИНАЦК са АЦК пакетом, где seqnum = X + 1, acknum = Y + 1. Након тога почиње стварни пренос података.
Ако саговорник не потврди пријем пакета, ТЦП га поново шаље до истека времена.
Зашто се СИН колачићи не користе увек?
Прво, ако се изгуби СИНАЦК или АЦК, мораћете да сачекате поновно слање - успостављање везе се успорава. Друго, у СИН пакету - и само у њему! - преноси се низ опција које утичу на даљи рад везе. Не памтећи долазне СИН пакете, сервер стога игнорише ове опције, у следећим пакетима клијент их више неће слати. ТЦП може радити у овом случају, али барем у почетној фази, квалитет везе ће се смањити.
Што се тиче пакета, КСДП програм треба да уради следеће:
одговорите на СИН са СИНАЦК са колачићем;
одговорите на АЦК са РСТ (прекините везу);
испусти друге пакете.
Псеудокод алгоритма заједно са рашчлањивањем пакета:
Если это не Ethernet,
пропустить пакет.
Если это не IPv4,
пропустить пакет.
Если адрес в таблице проверенных, (*)
уменьшить счетчик оставшихся проверок,
пропустить пакет.
Если это не TCP,
сбросить пакет. (**)
Если это SYN,
ответить SYN-ACK с cookie.
Если это ACK,
если в acknum лежит не cookie,
сбросить пакет.
Занести в таблицу адрес с N оставшихся проверок. (*)
Ответить RST. (**)
В остальных случаях сбросить пакет.
Једна (*) означене су тачке у којима треба да управљате стањем система – у првој фази можете без њих једноставним имплементирањем ТЦП руковања са генерисањем СИН колачића као секвенце.
На сајту (**), док немамо табелу, прескочићемо пакет.
Имплементација ТЦП руковања
Парсирање пакета и верификација кода
Потребне су нам структуре заглавља мреже: Етхернет (uapi/linux/if_ether.h), ИПв4 (uapi/linux/ip.h) и ТЦП (uapi/linux/tcp.h). Последње нисам могао да се повежем због грешака у вези са atomic64_t, морао сам да копирам потребне дефиниције у код.
Све функције које се разликују у Ц-у због читљивости морају бити уметнуте на место позива, пошто еБПФ верификатор у кернелу забрањује скокове уназад, односно, у ствари, петље и позиве функција.
Макро LOG() онемогућава штампање у верзији издања.
Програм је низ функција. Сваки прима пакет у којем је истакнуто заглавље одговарајућег нивоа, нпр. process_ether() чека да се попуни ether. На основу резултата анализе поља, функција може пренети пакет на виши ниво. Резултат функције је КСДП акција. Док СИН и АЦК руковаоци пропуштају све пакете.
Кеи стринг invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): постоје путање извршења када је тринаести бајт од почетка бафера изван пакета. Тешко је рећи из листе о којој линији говоримо, али постоји број инструкције (12) и дисамблер који приказује редове изворног кода:
што јасно ставља до знања да је проблем ether. Увек би било тако.
Одговорите на СИН
Циљ у овој фази је да се генерише исправан СИНАЦК пакет са фиксним seqnum, који ће у будућности бити замењен колачићем СИН. Све промене се дешавају у process_tcp_syn() и околина.
Провера пакета
Чудно, ево најупечатљивијег реда, тачније, коментара на њега:
Приликом писања прве верзије кода коришћено је језгро 5.1 за чији верификатор је постојала разлика између data_end и (const void*)ctx->data_end. У време писања овог текста, кернел 5.3.1 није имао овај проблем. Можда је компајлер приступао локалној променљивој другачије од поља. Морал - на великом гнежђењу, поједностављивање кода може помоћи.
Даље рутинске провере дужина за славу верификатора; О MAX_CSUM_BYTES испод.
ИПв4 и ТЦП контролни суми захтевају додавање свих 16-битних речи у заглавља, а величина заглавља је уписана у њима, односно у време компилације је непозната. Ово је проблем јер верификатор неће прескочити нормалну петљу све до граничне променљиве. Али величина заглавља је ограничена: до 64 бајта свако. Можете направити петљу са фиксним бројем итерација, које се могу завршити раније.
Напомињем да постоји РФЦ КСНУМКС о томе како делимично поново израчунати контролни збир ако се промене само фиксне речи пакета. Међутим, метода није универзална, а имплементацију би било теже одржавати.
Функција израчунавања контролне суме:
#define MAX_CSUM_WORDS 32
#define MAX_CSUM_BYTES (MAX_CSUM_WORDS * 2)
INTERNAL u32
sum16(const void* data, u32 size, const void* data_end) {
u32 s = 0;
#pragma unroll
for (u32 i = 0; i < MAX_CSUM_WORDS; i++) {
if (2*i >= size) {
return s; /* normal exit */
}
if (data + 2*i + 1 + 1 > data_end) {
return 0; /* should be unreachable */
}
s += ((const u16*)data)[i];
}
return s;
}
Мада size проверен позивним кодом, други излазни услов је неопходан да би верификатор могао да докаже крај петље.
За 32-битне речи имплементирана је једноставнија верзија:
INTERNAL u32
sum16_32(u32 v) {
return (v >> 16) + (v & 0xffff);
}
Заправо прерачунавање контролних сума и слање пакета назад:
Функција carry() прави контролни збир од 32-битног збира 16-битних речи, према РФЦ 791.
ТЦП провера руковања
Филтер исправно успоставља везу са netcat, прескачући коначни АЦК, на који је Линук одговорио са РСТ пакетом, пошто мрежни стек није примио СИН – конвертован је у СИНАЦК и послат назад – и са становишта ОС-а стигао је пакет који није везано за отворене везе.
$ sudo ip netns exec xdp-test nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peer
Важно је проверити са пуноправним апликацијама и посматрати tcpdump на xdp-remote јер нпр. hping3 не реагује на нетачне контролне суме.
СИН колачић
Са становишта КСДП-а, сама провера је тривијална. Алгоритам прорачуна је примитиван и вероватно рањив на софистицираног нападача. Линук кернел, на пример, користи криптографски СипХасх, али његова имплементација за КСДП је очигледно ван оквира овог чланка.
Појавио се за нове ТОДО-е везане за спољну интеракцију:
КСДП програм не може да сачува cookie_seed (тајни део соли) у глобалној променљивој, потребно вам је складиште кернела чија ће вредност бити периодично ажурирана из поузданог генератора.
Ако се СИН колачић у АЦК пакету подудара, не морате да штампате поруку, већ запамтите ИП верификованог клијента да бисте даље прескочили пакете са њега.
Понекад се еБПФ уопште и посебно КСДП представљају више као напредни администраторски алат него као развојна платформа. Заиста, КСДП је алатка за ометање обраде пакета кернела, а не алтернатива стеку кернела, као што је ДПДК и друге опције заобилажења кернела. С друге стране, КСДП вам омогућава да имплементирате прилично сложену логику, коју је, штавише, лако ажурирати без паузе у обради саобраћаја. Верификатор не ствара велике проблеме, лично не бих одбио такве за делове кода корисничког простора.
У другом делу, ако је тема интересантна, употпунићемо табелу проверених клијената и прекинућемо везе, имплементирати бројаче и написати услужни програм корисничког простора за управљање филтером.