Перевіряючи ваш файрвол, Безпека ПЗ, Security & Hack, статті







У цій статті описується вирішення проблем, пов’язаних з пакетним фільтром. Замість приведення готової таблиці у вигляді «проблема» – «рішення» наводяться методи системного підходу для вирішення виниклих проблем.

 

Введення

Пакетний фільтр здійснює виконання політики фільтрації, обходячи набір правил і, відповідно, блокує або пропускає пакети. У розділі дається пояснення, як перевірити, що політика фільтрації виконується коректно, і як знайти помилки, якщо це не так.

Загалом, в курсі цієї глави ми будемо порівнювати завдання написання набору правил фільтрації з програмуванням. Якщо ж у вас немає навичок програмування, то це порівняння здасться вам радше ускладнює задачу. Але ж саме по собі написання правил не вимагає наявності наукового ступеня по «computer science» або досвіду програмування, чи не так?

Відповіддю буде «ні», цього вам напевно не потрібно. Мова, використовуваний для конфігурації пакетного фільтра, зроблений схожим на людські мови. Наприклад:

block all

pass out all keep state

pass in proto tcp to any port www keep state

Насправді – не потрібно мати поруч програміста, щоб зрозуміти, що робить даний набір або навіть, скориставшись інтуїцією написати подібну політику фільтрації. Високий навіть шанс того, що створений за цього подобою набір правил фільтрації буде виконувати ті дії, які мав на увазі його автор.

На жаль, комп’ютери роблять тільки те, що ви просите їх зробити, а не те, що ви хочете самі. Гірше того, вони не зможуть відрізнити бажане від дійсного, якщо така різниця є. Значить, якщо комп’ютер невірно виконує те, чого хочете ви, навіть якщо вважаєте, що описали інструкції чітко – у ваших руках знайти відмінності і змінити інструкції. А так як це і є загальною проблемою в програмуванні, ми можемо подивитися, як же справляються з цим програмісти. Тут і виходить, що навички та методи, що використовуються для перевірки і налагодження програм і правил фільтрації дуже схожі. І все ж тут вам не знадобиться знання, якого б то не було мови програмування, для того щоб зрозуміти implications для перевірки і налагодження.

Хороша політика фільтрації.

Політика фільтрації – це неформальна специфікація того, чого ми хочемо від файрвола. А набір правил навпаки, реалізація специфікації – набір стандартних інструкцій, програма, яка виконується машиною. Відповідно, щоб написати програму, ви повинні визначити, що ж вона повинна робити.

Таким чином, першим кроком в конфігурації файрвола буде неформальне завдання того, чого необхідно досягти. Які з’єднання потрібно блокувати або пропускати?

Прикладом буде:

Є три мережі, які повинні бути відокремлені один від одного файрволом. Будь-які з’єднання з однієї мережі в іншу проходити через файрвол. У файрвола 3 інтерфейсу, кожен з яких, підключений до відповідної мережі:

$ Ext_if – в зовнішню мережу.

$ Dmz_if – DMZ з серверами.

$ Lan_if – LAN з робочими станціями.

Хости в LAN повинні вільно з’єднуватися з будь-якими хостами в DMZ або у зовнішній мережі.

Сервери в DMZ повинні вільно з’єднуватися з хостами у зовнішній мережі. Хости зовнішньої мережі можуть з’єднуватися тільки до наступних серверів в DMZ:

Web-сервер 10.1.1.1 порт 80

Mail-сервер 10.2.2.2 порт 25

Всі інші з’єднання повинні бути заборонені (наприклад, від машин у зовнішній мережі до машин в LAN).

Ця політика виражена неформально, так, щоб будь-який читає міг її зрозуміти. Вона повинна бути настільки конкретизована, щоб у читає легко формулювалися відповіді на питання виду ‘Чи повинно пропускатися сполучення від хоста X до хосту Y входить (або виходить) на інтерфейсі Z? ‘. Якщо ви задумалися про ті випадки, коли ваша політика не відповідає такій вимозі, значить вона недостатньо чітко задана.

«Туманні» політики типу «вирішувати тільки все, що життєво необхідно» або «блокувати атаки», необхідно уточнювати, чи у вас не вийде застосувати або перевірити їх. Як і в розробці програмного забезпечення, недостатньо формалізовані завдання рідко призводять до виправданим або коректним їх реалізацій. («Чому б вам не піти вже почати писати код, а я поки з’ясую, що потрібно замовнику»).

Набір правил, який реалізує політику

Набір правил записується у вигляді текстового файлу, що містить пропозиції на формальному мові. Також як вихідний код обробляється і переводиться в інструкції машинного коду компілятором, «вихідний текст» набору правил обробляється pfctl і результат інтерпретується pf в ядрі.

Коли вихідний код порушує правила формальної мови, аналізатор рапортує про синтаксичну помилку і відмовляється від подальшої обробки файлу. Ця помилка відноситься до помилок під час компіляції і зазвичай швидко виправляється. Коли pfctl не може розібрати ваш файл набору правил, він видає рядок, в якому виявлена ​​помилка і більш-менш інформативне повідомлення про те, що він не зміг розібрати. До тих пір, поки весь файл не буде оброблений без єдиної помилки, pfctl не змінить попередній набір правил в ядрі. А оскільки файл містить одну або більше синтаксичних помилок, не буде тієї «програми», яку pf може виконати.

Другий тип помилок називається «помилки часу виконання» (run-time errors), так як виникає при в синтаксично коректно записаної програмі, яка успішно транслювати і виконується. У загальному випадку, в мовах програмування, таке може трапитися, коли програмою виконується ділення на нуль, робиться спроба звернутися до неприпустимих областям пам’яті або виникає брак пам’яті. Але так як набори правил лише віддалено нагадують функціонал мов програмування, більшість з подібних помилок не може виникнути під час застосування правил, так наприклад, правила не можуть викликати т.зв. «Падіння системи », як це роблять звичайні програми. Однак набір правил може викликати подібні помилки, у вигляді блокування або навпаки, пропускання пакетів, які не відповідають політиці. Це іноді називається логічною помилкою, помилкою яка не викликає виключення і зупинки, а просто видає невірні результати.

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

Помилки аналізатора

Помилки аналізатора виникають при спробі завантаження списку правил з використанням pfctl, наприклад:

# pfctl -f /etc/pf.conf

/etc/pf.conf:3: syntax error

Це повідомлення говорить про те, що в рядку 3 файлу / etc / pf.conf синтаксична помилка і pfctl не може завантажити правила. Набір в ядрі не змінився, він залишився таким же, як і до спроби завантажити новий.

Є багато різновидів помилок, які видаються pfctl. Для початку знайомства з pfctl необхідно просто уважно їх читати. Можливо, не всі частини повідомлення відкриють свій сенс для вас відразу, але необхідно прочитати їх усі, тому що згодом це полегшить розуміння того, що ж пішло не так. Якщо в повідомленні є частина виду «назва файлу: число: текст», воно посилається на точки з відповідним номером у вказаному файлі.

Наступним кроком подивимося на видану рядок, використовуючи текстовий редактор (в vi ви можете перейти до 3 рядку ввівши 3G в режимі beep), або так:

# cat -n /etc/pf.conf

1 int_if = “fxp 0”

2 block all

3 pass out on $int_if inet all kep state

# head -n 3 /etc/pf.conf / tail -n 1

pass out inet on $int_if all kep state

Проблема може бути в простій друкарські помилки, як в цьому випадку (“kep” замість “keep”). Після виправлення спробуйте перезавантажити файл:

# pfctl -f /etc/pf.conf

/etc/pf.conf:3: syntax error

# head -n 3 /etc/pf.conf / tail -n 1

pass out inet on $int_if all keep state

Тепер всі ключові слова вірні, але, при найближчому розгляді, ми помічаємо, що розташування ключового слова “inet” перед “on $ int_if” невірно. Це ілюструє те, що одна і також рядок може містити більше однієї помилки. Pfctl виводить повідомлення про першу знайденої помилку і припиняє своє виконання. Якщо при повторному запуску був виданий той же номер рядка, значить в неї є ще помилки, або попередні не були коректно усунені.

Неправильно розташовані ключові слова також є поширеною помилкою. Це може бути виявлено при порівнянні правила з еталонним BNF-синтаксисом в кінці файлу довідки man pf.conf (5), яка містить:

pf-rule = action [ ( “in” / “out” ) ]

[ “log” / “log-all” ] [ “quick” ]

[ “on” ifspec ] [ route ] [ af ] [ protospec ]

hosts [ filteropt-list ]

ifspec = ( [ “!” ] interface-name ) / “{” interface-list “}”

af = “inet” / “inet6”

Що означає, що ключове слово “inet” має слідувати за “on $ int_if”

Підправимо і спробуємо ще раз:

# pfctl -f /etc/pf.conf

/etc/pf.conf:3: syntax error

# head -n 3 /etc/pf.conf / tail -n 1

pass out on $int_if inet all keep state

Ніяких очевидних помилок тепер не залишилося. Але нам не видно всі супутні деталі! Рядок залежить від макровизначення $ inf_if. Що ж може бути неправильно визначено?

# pfctl -vf /etc/pf.conf

int_if = “fxp 0”

block drop all


/etc/pf.conf:3: syntax error

Після виправлення помилки «fxp 0» на «fxp0» пробуємо ще раз:

# pfctl -f /etc/pf.conf

Відсутність повідомлень свідчить про те, що файл був успішно завантажений.

В деяких випадках pfctl може видавати більш специфічні повідомлення про помилки, ніж просто «syntax error»:

# pfctl -f /etc/pf.conf

/etc/pf.conf:3: port only applies to tcp/udp

/etc/pf.conf:3: skipping rule due to errors

/etc/pf.conf:3: rule expands to no valid combination

# head -n 3 /etc/pf.conf / tail -n 1

pass out on $int_if to port ssh keep state

Перший рядок повідомлення про помилку найбільш інформативна порівняно з іншими. У цьому випадку проблема в тому, що правило, вказуючи порт, не визначає протокол – tcp або udp.

У рідкісних випадках pfctl буває збентежений наявністю недрукованих символів або непотрібних прогалин у файлі, такі помилки нелегко виявити без спеціальної обробки файлу:

# pfctl -f /etc/pf.conf

/etc/pf.conf:2: whitespace after

/etc/pf.conf:2: syntax error

# cat -ent /etc/pf.conf

1 block all$

2 pass out on gem0 from any to any $

3 ^Ikeep state$

Тут проблемою є символ пробілу, після «бекслеш» та перед кінцем другого рядка, позначеним знаком “$” у висновку cat-e.

Після того, як набір правил успішно завантажений, непогано б подивитися на результат:

$ cat /etc/pf.conf

block all

# pass from any to any

pass from 10.1.2.3 to any

$ pfctl -f /etc/pf.conf

$ pfctl -sr

block drop all

«Бекслеш» в кінці рядка коментаря насправді означає, що рядок коментаря буде продовжена нижче.

Розгортання списків ув’язнених у фігурні дужки {} може видати результат, який можливо вас здивує, а заодно і покаже оброблений аналізатором набір правил:

$ cat /etc/pf.conf

pass from { !10.1.2.3, !10.2.3.4 } to any

$ pfctl -nvf /etc/pf.conf

pass inet from ! 10.1.2.3 to any

pass inet from ! 10.2.3.4 to any

Тут проблема в тому, що вираз “{! 10.1.2.3,! 10.2.3.4}” не буде означати «все адреси, за винятком 10.1.2.3 і 10.2.3.4», розгорнуте вираження саме по собі означає збіг з будь-яким можливим адресою.

Ви повинні перезавантажити свій набір правил після перманентних змін, для того, щоб переконатися, що і pfctl зможе завантажити його при перезавантаженні машини. В OpenBSD стартовий rc-скрипт з / etc / rc перший справою завантажує невеликий набір правил, встановлений за умовчанням, який блокує весь трафік, за винятком необхідного на етапі завантаження (такого як dhcp або ntp). Якщо ж скрипт не зможе завантажити реальний набір правил з / etc / pf.conf через синтаксичних помилок, внесених до перезавантаження машини без перевірки, то набір «по-замовчуванню» залишиться активним. На щастя, в цьому наборі дозволені входять ssh з’єднання, тому проблему можна буде вирішити віддалено.

Тестування

Так як ми маємо гранично точно певну політику, і набір правил, який повинен її реалізовувати, тоді термін тестування буде означати в нашому випадку відповідність отриманого набору заданої політиці.

Є лише два шляхи неправильного спрацьовування правил: блокування сполук, які повинні пропускатися і навпаки, пропускання тих сполук, які повинні блокуватися.

Тестування в загальному випадку має на увазі під собою системний підхід до впорядкованого створення різних видів з’єднань. Неможливо перевірити всі можливі комбінації джерела / приймача і відповідних портів на інтерфейсах, т.к. файрвол може теоретично зіткнутися з величезною кількістю таких комбінацій. Забезпечення початкової правильності набору правил може бути забезпечене тільки для дуже простих випадків. На практиці, найкращим рішенням буде створення списку тестових сполук, заснованого на політиці безпеки, такого, щоб кожен пункт політики був би торкнуться. Так, для нашого прикладу політики, список тестів буде наступним:

З’єднання з LAN в DMZ (повинно пропускатися)

з LAN в зовнішню мережу (повинно пропускатися)

з DMZ в LAN (повинно блокуватися)

з DMZ в зовнішню мережу (повинно пропускатися)

із зовнішньої мережі в DMZ до 10.1.1.1 на порт 80 (повинно пропускатися)

із зовнішньої мережі в DMZ до 10.1.1.1 на порт 25 (повинно блокуватися)

із зовнішньої мережі в DMZ до 10.2.2.2 на порт 80 (повинно блокуватися)

із зовнішньої мережі в DMZ до 10.2.2.2 на порт 25 (повинно пропускатися)

із зовнішньої мережі в LAN (повинно блокуватися)

Очікуваний результат повинен бути визначений в цьому переліку до початку тестування.

Це може звучати дивно, але мета кожного тесту – знайти помилки в реалізації набору правил файрвола, а не просто констатувати їхню відсутність. А вища мета процесу, це побудова набору правил без помилок, тому, якщо ви припускаєте, що тут ймовірно можуть міститися помилки, вам буде краще їх знайти, ніж пропустити. І якщо вже ви берете на себе роль тестера, ви повинні дотримуватися деструктивного стилю мислення і пробувати обійти обмеження файрвола. ТА тільки факт, що обмеження не можна зламати, стане аргументованим підтвердженням те, що набір правил не містить помилок.

TCP і UDP з’єднання можуть бути перевірені за допомогою nc. nc може використовуватися як клієнтська і серверна частина (використовуючи опцію-l). А для ICMP запитів і відповідей, найкращим клієнтом для перевірки буде ping.

Для перевірки факту блокування з’єднання можна використовувати будь-які кошти, які будуть намагатися створювати з’єднання з сервером.

Використовуючи інструменти з колекції портів, такі як nmap, ви легко зможете просканувати безліч портів навіть на декількох хостах. Якщо результати виглядають не зовсім ясно, не полінуйтеся заглянути в man-сторінку. Наприклад, для TCP порту сканер повертає значення ‘unfiltered’, коли nmap отримує RST від pf. Також pf, встановлений на одній машині зі сканером, може привносити свій вплив на коректність роботи nmap.

Більш складні інструменти проведення сканування можуть включати в себе засоби для створення фрагментованих або посилки некоректних ip пакетів.

Для перевірки того, що фільтром пропускаються з’єднання задані в політиці, найкращим методом буде перевірка з використанням тих додатків, які згодом і будуть використовуватися клієнтами. Так, перевірка проходження http-з’єднань з різних машин-клієнтів веб-сервера, а також з різних браузерів і вибірка різного контенту буде краще, ніж просто підтвердження встановлення TCP сесії до nc, що працює в якості серверної частини. Різні фактори, такі, як операційна система хоста, також можуть викликати помилки – проблеми з масштабуванням TCP-вікна (TCP window scaling) або відповідями TCP SACK між певними операційними системами.

Коли черговий пункт тестування пройдено, результати його можуть бути не завжди однаковими. Може обриватися зв’язок в процесі встановлення з’єднання, у разі, якщо файрвол повертає RST. Встановлення з’єднання може просто обірватися по таймаут. З’єднання може повністю встановлюватися, працювати, але через деякий час зависати або обриватися. З’єднання може триматися, але пропускна здатність або затримки можуть відрізнятися від очікуваних, бути вище або нижче (у випадку якщо ви використовуєте AltQ для обмеження пропускної спроможності).

В якості очікуваних результатів, крім пропуску / блокування сполуки, можна також відзначити те, логіруются чи пакети, як вони транслюються, маршрутизуються, збільшують чи потрібні лічильники, якщо це необхідно. Якщо для вас важливі ці аспекти, то їх також необхідно включити в методику тестування.

Ваша політика може включати вимоги, що стосуються продуктивності, реакції на перевантаження, відмовостійкість. А вони можуть вимагати окремих тестів. Якщо налаштовуєте відмовостійку систему з використанням CARP, ймовірно ви захочете дізнатися, що станеться при різного роду відмовах.

Коли ви спостерігаєте результат, відмінний від очікуваного, покроково відзначте ваші кроки під час тесту, чого ви очікували, чому ви цього чекали, отриманий результат і як результат відрізняється від ваших очікувань. Повторіть тест, щоб побачити, відтворюється ситуація, або відрізняється раз від разу. Спробуйте змінювати вхідні параметри перевірки (адреса джерела / приймача або порти).

З моменту, коли ви отримали відтворну проблему, необхідно приступити до налагодження, щоб з’ясувати, чому все працює не так, як ви очікували, і як всі «полагодити». З цією установкою, ви повинні змінити набір правил і повторити всі тести, включаючи ті, які не викликали помилок, тому що, змінюючи правила, ви могли ненавмисно торкнутися роботу вірно працюючих частин набору правил.

Цей же принцип стосується і до інших змін, що вносяться в набір. Така формальна процедура перевірки допоможе зробити процес менш схильним до внесення помилок. Можливо, для дрібних змін і не буде потрібно повторювати всю процедуру, але сума декількох дрібних змін може вплинути на загальний результат обробки набору. Ви можете використовувати систему контролю версій, таку як cvs, для роботи з вашим конфігураційним файлом, тому що це допоможе в дослідженні змін, які призвели до появи помилки. Якщо ви знаєте, що помилка не виникала тиждень тому, але зараз вона є, перегляд всіх зроблених змін за останній тиждень допоможе помітити проблему, або, щонайменше, відкотитися до моменту її відсутності.

Нетривіальні набори правил можна розглядати як програми, вони рідко бувають ідеальні у своїй першій версії, і потрібен час, для того щоб з упевненістю стверджувати, що в них немає помилок. Однак, на відміну від звичайних програм, які більшістю програмістів ніколи не вважаються вільними від помилок, набори правил все ж досить прості, щоб бути близькими до цього визначення.

Налагодження

Під терміном налагодження зазвичай мається на увазі пошук і усунення помилок програмування в комп’ютерних програмах. Або, в контексті наборів правил для файрвола, термін буде означати процес пошуку причини, чому набір не повертає бажаний результат. Є трохи типів помилок, які можуть проявлятися в правилах, тим не менш, методи їх відшукання схожі з програмуванням.

Перед тим, як ви почнете пошук причини, що викликала проблему, ви повинні чітко уявити собі суть цієї проблеми. Якщо ви самі помітили помилку під час тестування, це дуже просто. Але якщо інша людина повідомляє вам про помилку, постановка чіткої завдання з неточного повідомлення про помилку може бути непростим завданням. Найкраще почати з того, що ви самі відтворите помилку.

Не завжди проблеми мережі можуть бути викликані пакетним фільтром. Перед тим, як сфокусувати свою увагу на налагодженні конфігурації pf, необхідно впевнитися, що проблема викликана пакетним фільтром. Це легко зробити, а також допоможе заощадити час на пошук несправності в іншому місці. Просто вимкніть pf командою pfctl-d і перевірте, чи проявляється проблема знову. Якщо це так, включіть pf командою pfctl-e і подивіться, що відбувається. Цей метод не пройде в деяких випадках, наприклад, якщо pf не робить правильну трансляцію мережевих адрес (NAT), то вимкнення pf очевидно не позбавить вас від помилки. Але в тих випадках, коли це можливо, постарайтеся переконатися, що винен саме пакетний фільтр.

Відповідно, якщо проблема в пакетному фільтрі, перше, що необхідно зробити, це переконатися в тому, pf дійсно працює і успішно завантажений потрібний набір правив:

# pfctl -si / grep Status

Status: Enabled for 4 days 13:47:32 Debug: Urgent

# pfctl -sr

pass quick on lo0 all

pass quick on enc0 all


Налагодження по протоколам

Наступним кроком налагодження буде відображення проблеми в конкретних мережевих з’єднаннях. Якщо ви маєте посилку: «не працює обмін миттєвими повідомленнями в додатку X», потрібно з’ясувати, які мережеві з’єднання використовуються. Висновок може бути у вигляді «хост А не може з’єднатися з хостом B порту на С». Іноді це завдання становить найбільшу складність, але якщо у вас є інформація про потрібні з’єднаннях і ви знаєте, що файрвол їх не пропустить, потрібно буде всього лише змінити правила для вирішення цієї проблеми.

Є кілька шляхів для з’ясування використовуваних додатком протоколів або з’єднань. Tcpdump може відобразити пакети прибувають або залишають як реальний мережевий інтерфейс, так і віртуальні, такі як pflog і pfsync. Ви можете задати вираз для фільтрації, щоб задати пакети для відображення та виключити побічний мережевої «шум». Спробуйте встановити з’єднання з мережею в потрібному додатку і подивіться на відсилаються пакети. Наприклад:

# tcpdump -nvvvpi fxp0 tcp and not port ssh and not port smtp

23:55:59.072513 10.1.2.3.65123 > 10.2.3.4.6667: S

4093655771:4093655771(0) win 5840 (DF)

Це пакет TCP SYN, перший пакет з встановлюваного TCP з’єднання (TCP handshake).

Відправник – 10.1.2.3 порт 65123 (виглядає як випадковий непривілейований порт) а одержувач 10.2.3.4 порт 6667. Детальне пояснення формату виводу tcpdump ви знайдете на сторінках керівництва по утиліті. Tcpdump – найбільш важливий інструмент для налагодження проблем, пов’язаних з pf, і дуже важливо познайомитися з ним ближче.

Інший метод – використання функції ведення лог-файлів в pf. Вважаючи, що ви використовуйте опцію ‘log’ у всіх правилах з ‘block’, тоді всі пакети, заблоковані pf будуть відображені в балці. Можна видалити опцію ‘log’ з правил, які мають справу з відомими протоколами, тобто записуватися в лог будуть тільки ті пакети, які йдуть на невідомі порти. Спробуйте використовувати програму, що не може встановити зв’язок і загляньте в pflog:

# ifconfig pflog0 up

# tcpdump -nettti pflog0

Nov 26 00:02:26.723219 rule 41/0(match): block in on kue0:

195.234.187.87.34482 > 62.65.145.30.6667: S 3537828346:3537828346(0) win

16384 (DF)

Якщо ви використовуєте pflog – демона, який постійно прослуховує pflog0 і зберігає отриману інформацію в / var / log / pflog, збережену інформацію можна побачити так:

# tcpdump -netttr /var/log/pflog

Коли виводите збережені pf пакети, ви можете використовувати додаткові вирази для фільтрації, наприклад, переглянути пакети, які були заблоковані на вході на інтерфейсі wi0:

# tcpdump -netttr /var/log/pflog inbound and action block and on wi0

Деякі протоколи, такі як FTP, не так легко відстежити, так як вони не використовують фіксовані номери портів, або використовують кілька співіснуючих сполук. Можливо, буде неможливо пропустити їх через файрвол без відкриття широкого діапазону портів. Для окремих протоколів існують рішення, подібні ftp-proxy.

Налагодження правил

Якщо ваш набір правил блокує конкретний протокол тому що ви не відкрили потрібний порт, це більше проблема стадії проектування ніж баг в правилах. Але що якщо ви бачите, що блокується з’єднання для якого у вас є пропускає його правило?

Наприклад ваш набір містить правило

block in return-rst on $ext_if proto tcp from any to $ext_if port ssh

Але коли ви намагаєтеся під’єднатися до TCP порту 22, з’єднання приймається! Схоже, що файрвол ігнорує ваше правило. Як і в збірці «пазлів», в цих випадках, коли з ними зіштовхуєшся перші кілька раз, існує просте логічне і, як правило, тривіальне пояснення.

Перше, ви повинні перевірити всі ті кроки, про які говорилося раніше. Наприклад, припустимо, що файрвол працює і містить правило, наведене вище. Малоймовірно, що мають місце наші попередні побоювання, але це легко перевірити:

# pfctl -si / grep Status

Status: Enabled for 4 days 14:03:13 Debug: Urgent

# pfctl -gsr / grep “port = ssh”

@14 block return-rst in on kue0 inet proto tcp from any to 62.65.145.30 port = ssh

Наступне, що ми маємо: приймаються з’єднання TCP на порт 22 на kue0. Можна подумати, що це і так очевидно, але незайвим буде перевірити. Запустіть tcpdump:

# tcpdump -nvvvi kue0 tcp and port 22 and dst 62.65.145.30

Тепер повторіть SSH з’єднання. Ви повинні побачити пакети з вашого з’єднання у висновку tcpdump. Можливо ви їх не бачите, а це може бути через те, що з’єднання насправді не проходить через kue0, а проходить через інший інтерфейс, що пояснює, чому не спрацьовує правило. Або ви можливо з’єднуєтеся з іншою адресою. Якщо коротко, то якщо ви не бачите ssh пакети, то pf їх також не побачить можливо не може їх заблокувати використовуючи правило, наведене в нашого завдання.

Але якщо ви бачите пакети з допомогою tcpdump, pf їх теж «побачить» і буде їх фільтрувати. Наступним припущенням буде те, що блокує правило повинне не просто присутніми в наборі (що ми вже з’ясували), а бути останнім збігається правилом для потрібних пакетів. Якщо ж це не останнє правило, очевидно, згідно з цим не приймається рішення про затримання пакетів.

У яких випадках правило може бути не останнім збігається правилом? Можливі три причини:

А) правило не спрацьовує, тому що перегляд правил не доходить до потрібного нам.

Раніше присутнє правило також спрацьовує і викликає припинення виконання опцією ‘quick’;

Б) обробка проводиться правила, але правило не спрацьовує через розбіжності окремих критеріїв.

В) обробка правила виробляється, правило спрацьовує, але обробка продовжується і наступні правила також спрацьовують для пакета.

Щоб відкинути ці три випадки ви можете, дивлячись на завантажений набір правил подумки уявити собі оброблення гіпотетичного TCP пакету, що приходить на інтерфейс kue0 і порт 22. Виділіть відладжуваної блок. Почніть обхід з першого правила. Співпадає? Якщо так – позначте його. Чи має воно опцію ‘quick’? Якщо так, то припиняємо обхід. Якщо ж ні, продовжуємо з наступним правилом. Повторюйте перевірку, до знаходження збігу з опцією ‘quick’ або досягнення кінця набору правил. Яке правило співпало останнім? Якщо це не правило номер 14, ви знайшли пояснення проблеми.

Обхід правил вручну може здатися забавним, тим не менш, він, за наявності достатнього досвіду може бути проведений досить швидко і великим ступенем надійності. Якщо набір досить великий, ви можете тимчасово його скоротити. Збережіть копію реального списку правил і видаліть ті записи, які, на ваш погляд, не вплинуть на результат. Завантажте цей набір і повторіть перевірку. Якщо тепер з’єднання блокується, отже, що здавалися непов’язаними з шуканими пакетами правила відповідальні за випадки А або Б. Додавайте правила одне за одним, повторюючи тест, до тих пір, поки не знайдете потрібну. Якщо з’єднання все ще пропускається після видалення не впливають на нього правил – повторіть уявний обхід зменшеного набору.

Інший метод, це використання здатності pf вести логи для ідентифікації випадків А чи С. Додайте ‘log’ до всіх правилами з ‘pass quick’ перед чотирнадцятим правилом. Додайте ‘log’ до всіх правилами з ‘pass’, стоять після чотирнадцятого правила. Запустіть tcpdump для інтерфейсу pflog0 і встановлюйте ssh з’єднання. Ви побачите, які правила крім чотирнадцятий спрацьовують на ваших пакетах останнім. Якщо в балці нічого немає, значить має місце випадок Б.

Відстеження з’єднань через файрвол

Коли з’єднання проходить через файрвол, пакети приходять на один інтерфейс, а передаються назовні через другий. Відповіді приходять на другий інтерфейс, а йдуть у перший. Сполуки, таким чином, можуть обриватися в кожному з цих чотирьох випадків.

Перше, ви повинні з’ясувати, в якому з чотирьох випадків проблема. Якщо ви спробуєте встановити з’єднання, ви повинні будете побачити пакет TCP SYN на першому інтерфейсі, використовуючи tcpdump. Ви повинні також побачити Того ж TCP SYN пакет на виході з другого інтерфейсу. Якщо ви його не бачите, отже, укладаємо, що pf блокує вхідний пакет на першому інтерфейсі, або вихідний на другому.

Якщо ж SYN посилка не блокується, ви повинні будете бачити SYN + ACK, що приходить на другий інтерфейс і виходить з першого. Якщо ні – pf блокує SYN + ACK на якомусь інтерфейсі.

Додайте опцію ‘log’ до правил, які повинні пропускати SYN і SYN + ACK на обох інтерфейсах, також до правил, які повинні їх блокувати. Повторіть спробу з’єднання і перевірте pflog. Він повинен прояснити, в якому випадку відбувається блокування та яким правилом.

Налагодження станів сполук

Найбільш поширена причина блокування пакетів pf полягає в тому, що в наборі існує надлишкове блокуючу правило. Відповідне правило, спрацьовує за останнім збігом, може бути знайдено додаванням опції ‘log’ у всі потенційно впливають на результат правила і прослуховуванням інтерфейсу pflog.

В меншій кількості випадків трапляється так, що pf «мовчки» відкидає пакети грунтуючись не на правилах, і тут додавання ‘log’ в усі правила не призведе до потрапляння скинутих пакетів в pflog. Часто пакет майже, але не повністю підпадає під запис в таблиці станів (state entry).

Пам’ятайте, що для кожного оброблюваного пакета, пакетний фільтр виробляє перегляд таблиці станів. Якщо знайдена збігається запис, пакет негайно пропускається, не викликаючи для себе обробки набору правил.

Запис в таблиці станів містить інформацію, що відноситься до одного з’єднанню.

Кожен запис має унікальний ключ. Цей ключ складається з декількох значень, які обмежують constant throughout час життя з’єднання. Ось вони:

* Тип адреси (Ipv4 або Ipv6)
* Адреса джерела
* Адреса приймача
* Протокол (TCP UDP)
* Порт джерела
* Порт приймача

Цей ключ використовується для всіх пакетів, які належать до одного й того ж з’єднанню, і пакети різних з’єднань будуть завжди мати різні ключі.

Коли опцією ‘keep state’ з правила створюється запис у таблиці станів, запис о з’єднанні зберігається з використанням ключа даного з’єднання. Важливе обмеження для таблиці станів – всі ключі повинні бути унікальними. Тобто не може бути двох записів з однаковими ключами.

Можливо відразу не очевидно те, що ті ж два хоста не можуть встановити кілька співіснуючих сполук використовуючи ті ж адреси, протоколи і порти, але це є фундаментальне властивість як TCP так і UDP. Фактично, стеки TCP / IP всього лише можуть асоціювати окремі пакети з їх сокетами виконуючи вибірку, засновану на адресах і портах.

Навіть якщо з’єднання закрито, та ж пара адрес і портів не може бути наново задіяна відразу ж. Мережним устаткуванням можуть пізніше доставлятися повторно передані пакети, і якщо стеком TCP / IP одержувача вони будуть помилково прийняті за пакети новоствореного з’єднання, це завадить або зовсім розірве нове з’єднання. З цієї причини обидва хоста повинні почекати певний проміжок часу, званий 2MSL («подвійне час життя сегмента», «twice the maximum segment lifetime”) перед тим, як знову мати можливість використовувати ті ж адреси та порти для нового з’єднання.

Ви можете поспостерігати це властивість, вручну встановлюючи кілька з’єднань до одного й того ж хосту. Наприклад, маючи веб-сервер працює на 10.1.1.1 і порту 80, і двічі під’єднуючись з 10.2.2.2. використовуючи nc:

$ nc -v 10.1.1.1 80 & nc -v 10.1.1.1 80

Connection to 10.1.1.1 80 port [tcp/www] succeeded!

Connection to 10.1.1.1 80 port [tcp/www] succeeded!

Під той час, поки з’єднання відкриті, можете за допомогою netstat на клієнті або сервері вивести інформацію про ці сполуках:

$ netstat -n / grep 10.1.1.1.80

tcp 0 0 10.2.2.6.28054 10.1.1.1.80 ESTABLISHED

tcp 0 0 10.2.2.6.43204 10.1.1.1.80 ESTABLISHED

Як бачите, клієнт вибрав два різних (випадкових) порту-джерела, тому це не порушує вимоги до унікальності ключів.

Ви можете вказати nc використовувати певний порт-джерело опцією-p:

$ nc -v -p 31234 10.1.1.1 80 & nc -v -p 31234 10.1.1.1 80

Connection to 10.1.1.1 80 port [tcp/www] succeeded!

nc: bind failed: Address already in use

TCP / IP стек клієнта запобіг порушення унікальності ключів. Деякі рідкісні та помилкові реалізації TCP / IP стеків не відповідали цьому правилу, і тому, як ми скоро побачимо, pf заблокує їх з’єднання при порушенні унікальності ключів.

Повернемося назад до того місця, коли pf робить запит до таблиці станів, у той час коли пакет починає фільтрувати. Запит складається з двох кроків. Перший запит робиться для пошуку входження в таблицю записи з ключем, відповідним протоколом, адресами, і порту пакета. Пошук буде вестися для пакетів, що йдуть в будь-якому напрямках. Припустимо, що наведений нижче пакет створив запис у таблиці станів:

incoming TCP from 10.2.2.2:28054 to 10.1.1.1:80

Запит до таблиці виявить такі записи в таблиці станів:

incoming TCP from 10.2.2.2:28054 to 10.1.1.1:80

outgoing TCP from 10.1.1.1:80 to 10.2.2.2:28054

Запис в таблиці включає інформацію про направлення (вхідний або вихідний) першого пакета, який створив запис. Наприклад такі записи не викличуть збіги:

outgoing TCP from 10.2.2.2:28054 to 10.1.1.1:80

incoming TCP from 10.1.1.1:80 to 10.2.2.2:28054

Причина цих обмежень неочевидна, але досить проста. Представте, що у вас лише один інтерфейс з адресою 10.1.1.1, де веб-сервер здійснює прослуховування порту 80. Коли клієнт 10.2.2.2 приєднується, використовуючи випадково обраний вихідний порт 28054, перший пакет з’єднання приходить на ваш інтерфейс і всі ваші вихідні відповіді повинні будуть йти з 10.1.1.1:80 на 10.2.2.2:28054. Ви ж не будете пропускати вихідні пакети з 10.2.2.2:28054 до 10.1.1.1:80, так як такі пакети не мають сенсу.

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

Коли спроба відшукати пакет серед записів у таблиці станів зазнає невдачі, проводиться обхід списку правил фільтра. Ви повинні спеціально дозволити проходження пакета назовні через другий інтерфейс окремим правилом. Напевно ви використовуєте ‘keep state’ в цьому правилі, щоб другий запис в таблиці станів охоплювала всі з’єднання і на другому інтерфейсі.

Ви можете здивуватися, як же можливо створити другу запис у таблиці, якщо ми тільки що роз’яснили, що записи повинні мати унікальні ключі. Поясненням тут буде те, що запис також містить інформацію про направлення з’єднання, і комбінація цього з іншими даними повинні бути унікальна.

Тепер ми також зможемо пояснити різницю між вільним з’єднанням і з’єднанням, прив’язаним до інтерфейсу. За замовчуванням pf створює записи, які не прив’язані ні до якого інтерфейсу. Тому, якщо ви дозволяєте з’єднання на одному інтерфейсі, пакети пов’язані з з’єднанню і підпадають під запис в таблиці (включаючи інформацію про направлення пакету!) проходять через будь-який інтерфейс. У простих інсталяціях зі статичної маршрутизацією це більше теоретичні викладки. В принципі, ви не повинні бачити пакети одного з’єднання, що прибувають через кілька інтерфейсів і відповідні пакети, що йдуть також на кілька інтерфейсів. Однак при динамічної маршрутизації таке можливе. Ви можете прив’язати записи про стани до конкретного інтерфейсу використовуючи глобальну установку ‘set state-policy if-bound’ або опцією для кожного правила ‘keep state (if-bound)’. Так ви будете впевнені, що пакети будуть підпадати під записи тільки з інтерфейсу, який ці записи створив.

Якщо використовується інтерфейси для тунелів, то одне і те ж з’єднання проходить через файрвол кілька разів. Наприклад перший пакет з’єднання спочатку може пройти крізь інтерфейс A, потім через B, потім С і нарешті покинути нас через інтерфейс D. Зазвичай пакети будуть інкапсульовані на інтерфейсах A і D і декапсуліруется на B і C, тому pf бачить пакети різних протоколів і ви можете створити 4 різних записи в таблиці станів. Без інкапсуляції пакет буде незмінний на всіх чотирьох інтерфейсах і ви не зможете використовувати деякі можливості, як трансляцію адреси або модуляцію номера tcp послідовності, тому що це призведе до появи в таблиці станів конфліктуючих ключів. До тих пір, поки у вас не буде закінченою установки включає інтерфейси з тунелюванням і налагодженими ошібкмі виду “pf: src_tree insert failed “, ви не зможете вважати свою інталяцію досить успішною. Повернемося до запиту до таблиці станів, що проводить для кожного пакета перед перевіркою правил. Запит повинен повернути єдину запис з відповідним ключем, або не повернути нічого. Якщо запит нічого не повертає, проводиться обхід списку правил.

Якщо ж запис знайдена, другим кроком для пакетів TCP, перед тим як вони почнуть рахуватися належать конкретному з’єднанню і піддадуться фільтрації, буде перевірка номера послідовності.

Є велика кількість TCP атак, в яких атакуючий намагається керувати з’єднанням між двома хостами. У більшості випадків, атакуючий не знаходиться на шляху маршрутів між хостами, тому не може прослухати легітимні пакети пересилаються хостами. Однак він може відсилати пакети будь-якого з хостів, що імітують пакети його співрозмовника, шляхом спуфінга (“spoofing”) – підробки адреси відправника. Метою атакуючого може бути запобігання можливості створення з’єднань між хостами або обрив вже встановлених з’єднань (щоб викликати відмову в обслуговуванні) або для створення шкідливої ​​завантаження на з’єднання.

Для успішної атаки атакуючому необхідно вірно «вгадати» кілька параметрів з’єднання, таких як адреса / порт джерела і приймача. І для широко поширених протоколів це може бути не так вже складно, як може здатися. Якщо атакуючий знає адреси хостів і один з портів (оскільки мова йде про поширеному сервісі), йому буде потрібно тільки «вгадати» один порт. Навіть якщо клієнт використовує по-справжньому випадковий порт-джерело (що насправді не завжди вірно), атакуючому потрібно всього лише перебрати 65536 портів за короткий проміжок часу. (В більшості випадків навіть (65536-1024) портів, тобто тільки непривілейованих порти – прим. перекладача))

Але ось що по справжньому важко вгадати для нападника, так це вірний номер послідовності (і його підтвердження). Якщо обидва хоста вибирають початковий номер послідовності випадковим чином (або ви використовуєте модуляцію номера послідовності для хостів, які мають «слабкий» генератор ISN (Initial Sequence Number)), то атакуючому не вдасться підібрати відповідне значення в потрібний момент з’єднання.

Під час існування валідного TCP з’єднання номера послідовностей (і підтвердження) для окремих пакетів змінюються згідно з визначеними правилами.

Наприклад, якщо хост посилає деякий сегмент даних і його одержувач підтвердив прийом, не повинно бути причини, по якій відправник повинен надіслати дані сегмента ще раз. Але, фактично, спроба перезаписати частини інформації вже отримані хостом не є порушенням протоколу TCP, хоча і може бути різновидом атаки.

pf використовує правила, щоб визначити найменший діапазон для легітимних номерів послідовностей. У загальному випадку, pf може точно визначити справжність тільки 30000 з 4294967296 можливих номерів послідовності в будь-який момент соедіенія. Тільки якщо номер послідовності і підтвердження входить в це вікно, pf переконається в тому, що пакет легітимний і пропустить його.

Якщо під час проведення запиту до таблиці станів знайдена відповідна запис, на наступному кроці номера послідовностей пакетів, збережених в таблиці, перевіряються на входження в діапазон можливих значень. У разі невдачі при порівнянні pf згенерує повідомлення “BAD state” і відкине пакет без обчислення набору правил. Є дві причини, за якими може не відбутися порівняння з правилами: майже напевно буде являтся помилкою пропуск пакета, тому що якщо обчислення набору призведе до потрапляння в правилі на опцію “keep state” і pf не зможе винести рішення і створити новий запис бо це призведе до появи кнфліктующіх ключів в таблиці.

Для того щоб бачити і записувати в лог повідомлення “BAD state”, вам необхідно включити налагоджувальний режим використовуючи команду:

$ pfctl -xm

Налагоджувальні повідомлення за замовчуванням потрапляють на консоль, також syslogd записує їх в / var / log / messages. Шукайте повідомлення починаються на “pf”:

pf: BAD state: TCP 192.168.1.10:20 192.168.1.10:20 192.168.1.200:64828

[lo=1185380879 high=1185380879 win=33304 modulator=0 wscale=1]

[lo=1046638749 high=1046705357 win=33304 modulator=0 wscale=1]

4:4 A seq=1185380879 ack=1046638749 len=1448 ackskew=0 pkts=940:631

dir=out,fwd

pf: State failure on: 1 /

Ці повідомлення завжди йдуть парами. Перше повідомлення показує запис в таблиці станів в момент, коли пакет був заблокований і номера послідовності пакета, який привів до помилки. Другий запис відображає умови, які були порушені.

В кінці першого повідомлення ви побачите, чи була створена запис стану на вхідний (dir = in) або вихідний (dir = out) пакет, і йшов чи заблокований пакет в тому ж напрямки (dir =, fwd) або протилежному (Dir =, rev) напрямку.

Запис в таблиці містить три адреси: пари портів, два з яких завжди рівні між собою, в разі якщо з’єднання не піддалося перетворенню nat, rdr або bnat. Для вихідних з’єднань джерело виводиться ліворуч, а приймач пакета – справа. Якщо виходить з’єднання задіє перетворення адреси джерела, пара в середині показує джерело після перетворення. Для вхідних з’єднань джерело знаходиться праворуч у висновку, а адреса призначення посередині. Якщо вхідне з’єднання піддається перетворенню адреси призначення, пара ip / port ліворуч показує приймач після проведеного перетворення. Цей формат відповідає висновку pfctl-ss, ч тією лише різницею, що pfctl показує напрямок пакета скористайтесь стрілки.

У висновку ви можете бачити поточні номера послідовності у хостів у квадратних дужках. Так значення “4:4” означає, що соедіененіе встановлено повністю (менші значення більш вірогідні на етапі встановлення сполуки, великі – до моменту закриття соедіенія). “A” означає, що заблокований пакет мав встановлений прапор ACK (також як і у висновку прапорів у tcpdump), далі йдуть значення номерів послідовностей (Seq =) і (ack =) в заблокованих пакетах і довжина корисного навантаження пакета – довжина даних (len =). askskew це частина внутрішнього представлення даних у таблиці, задіюються тільки при значеннях не рівних нулю.

Запис “pkts = 930:631” обзначает, що з нею збіглося 940 пакетів, які йшли напрямки, що збігається з пакетом, що викликав створення даного запису, і 631 пакет в протилежному напрямки. Ці лічильники будуть особливо корисні при пошуку проблем на етапі встановлення з’єднання, якщо один з них дорівнює нулю, це буде суперечити вашому очікуванню того, що з даної записом збігаються пакети, що йдуть в обох напрвлениях.

Наступне повідомлення собдержіт список з однієї або декількох цифр. Кожна цифра являє собою перевірку, на якій сталася помилка:

1. розмір вікна пакета перевищує максимальний розмір в одержувача (seq + len> high)
2. пакет містить вже передані дані (seq high + win)
6. те ж, що і в (2), але (seq 10.2.3.4:80 ESTABLISHED: ESTABLISHED

[3046252937 + 58296] wscale 0 [1605347005 + 16384] wscale 1

Якщо присутній запис “wscale x” виведена в другій сходинці (навіть якщо x дорівнює нулю), pf зачит знає про те, що з’єднання використовує масштабування.

Другий простий метод для виявлення проблем пов’язаних з масштабуванням це тимчасове вимкнення підтримки масштабування і потвторное відтворення ситуації. В OpenBSD використання масштабування може керуючи опцією sysctl:

$ sysctl net.inet.tcp.rfc1323

net.inet.tcp.rfc1323=1

$ sysctl -w sysctl net.inet.tcp.rfc1323=0

net.inet.tcp.rfc1323: 1 -> 0

Подібні проблеми з’являються коли ви створюєте записи в таблиці станів по пакетах, відмінним від початкових SYN і використовуєте опцію “moulate state” або трансляцію. В обох випадках телеканал на початку з’єднання. Якщо перший пакет не транслював, транчляція наступних зазвичай бентежить приймаючу сторону і призводить до того, що надіслані відповіді блокуються pf з повідомленням “BAD state”.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*