نصب و راه اندازی فایرول PF در FreeBSD[این قسمت: packet filtering] (شماره دوم)

pf-iconدر شماره قبل یک FreeBSD-Box را برای استفاده از PF که مخفف Packet Filter می‌باشد آماده کردیم. این شماره کلاً به این Firewall می‌پردازیم. اگر از iptables به این سمت اومدید یا هر گونه فایروال دیگری، دنبال این نباشید که آنچه را در فایروال مبدأ آموختید اینجا بدنبالش باشید، بلکه دنبال آن باشید که خود PF را هر بهتر یاد بگیرید. قرار هم نیست PF را جلوی iptables قرار دهیم و benchmark کنیم. زیرا هر شخصی که سروری خاص نصب می‌کند،‌ باید از فایروال آن ماشین بهره جوید. پس اگر بر روی یک ماشین گنو/لینوکسی است،
باید از iptables و اگر بر روی یک BSD باکس نصب می‌کند باید از یکی از فایروال‌های بر روی آن بهره گیرد.

نکته: دلیل انتخاب استفاده خودم از PF به جای سایر فایر‌وال‌های BSD قدرت بالای‌ آن می‌باشد.
نکته: هیچ‌وقت فایروال‌های appliance-base را با فایر‌وال‌هایی که همه چیز دست کاربر است مقایسه نکنید، قیاس مع‌الفارق است.
نکته: اگر قرار است با pfSense کار کنید، باید به صورت کامل بتوانید با PF کار کنید تا بتوانید مهاجرت کنید. زیرا core آن از PF استفاده می‌کند. این موضوع بعدا مشخص خواهد شد که مفاهیم همیشه جلوترند.

۱. فرمان pfctl

بگذارید کمی با این فرمان آهسته آهسته جلو رویم، اولین چیزی که باید با این فرمان یاد بگیریم این است که می‌تواند configuration فایل PF را لود کند و یا بدون لود، آن را parse نماید که بدرد Debug mode می‌خورد:

حال نیاز مند unload کردن conf فایل هستیم:

بیاید کمی اصولی و با زبان pf با یکدیگر سخن بگوییم. زبان بالا هرچند نیاز است ولی کثیف است. در rc.conf یک متغیر برای pf به نام pf_rules وجود دارد،‌شما می‌توانید مسیر conf فایل خود را به آن بدهید که صولا آن را در /etc/ می‌گذارند:

وقتی با rc.conf فایل conf مان لود شد دیگر می‌توانید با دو گزینه d- و e- آن را disbale و enable کنیم:

نکته: در مورد گزینه‌های دیگر pcftl مجوریم جلوتر توضیح دهیم زیرا محبوریم یک pf.conf هر چند ولو کم ولی آموزشی بنویسیم و در مورد آن و ساختارش بحث کنیم و دوباره برگردیم به دستور pfctl که مقداری بیشتر جای بررسی دارد. نکته: اگر iptables در سطح کلان کار کرده باشید rule های خود را یک اسکریپت می‌کردید و همه چی را به صورت یک argument دریافت می‌کردید و چندین مزیت زیر را به همراه داشت:

  • به هر چیزی به صورت یک متغیر دیده میشد.

  • برای debug کار ساده میشد.

  • حجم rule ها پایین میامد.(در اسکریپت، نه در خود iptables)

اما زیبایی conf فایل‌های pf در همین ساختارشان است که چنین عباراتی را در خورد دارند.

۱.۱ یک pf.conf

یک pf فایل به ۴ قسمت زیر تقسیم میشود:

۱.۱.۱ Macro ها: متغیر‌هایی که توسط کاربر تعریف می‌شود و ‌می‌تواند IP address ها، Interface ها و غیره را نگهداری کنند. List ها نیز به عنوان یک نوع داده در این قسمت هستند.

۱.۱.۲ Tableها : ساختاری که برای نگهداری لیست IP address ها استفاده می‌شود.

۱.۱.۳ Option ها: گزینه‌های مختلف برای کنترل اینکه چگونه pf کار می‌کند.

۱.۱.۴ Filter Rule ها: تمام rule های packet filtering در این قسمت نوشته می‌شود که در جلوتر با آن آشنا می‌شویم.
نکته: دو مفهموم دیگر در conf فایل‌ها ذکر می‌شوند که یکی از اساسی‌ترین نقاط قوت pf می‌باشند. که شامل دو مفهوم Traffic Normalization و Queueing می‌باشند و در شماره‌های بعدی مطلب اختصاصی خواهیم نوشت.
List ها: مثال زیر دقیقاً کارکرد یک لیست را بیان می‌کند:

مثال بالا یک rule است که ما با بقیه rule کاری نداریم. چیزی که برایمان مهم است داخل {} است IP address هایی که داخل یک rule در {}تعریف می‌شوند یک List نامیده می‌شوند و این قدرت فوق العاده‌ای به یک sysadmin می‌دهد. بگذارید کمی هم در مورد rule بالا صحبت کنیم، rule بالا به فارسی می‌گوید تمام بسته‌هایی را که بر روی اینترفیس em0 قرار است خارج شوند و IP مبدأ آن‌ها از دو IP درون لیست ما به هر جا یعنی به هر dest IP باشد block کن. یا به صورت متغیر هم می‌توان از آن‌ها بهره جست:

در جلوتر به تفصیل در مورد rule ها خواهیم پرداخت نگران نباشید.

۱.۱.۱ Macro ها: حداقل برای هر فایروال به دو کارت شبکه نیازمندیم. برای سناریو‌های پیچیده‌تر به بیش از ۲ کارت شبکه نیازمندیم. اما در اینجا به em0 و em1 بسنده کردیم. به دو ماکروی زیر دقت کنید:

سپس با دو متغیر ext_if$ و int_if$ می‌توانیم در کل rule هایمان به این دو interface اشاره نماییم،‌همانند rule زیر:

۱.۱.۲ Table ها: یک table در‌واقع مجموعه‌ایست از IP address ها که به همین نام (table) یک دایرکتیو وجود دارد. Table ها بر دونوعند:

  • const : محتویات این نوع table ها در طول زمانی pf بالا است تغییر ناپذیرند.

  • persist : باعث می‌شود تا کرنل محتویات جدول را درون حافظه نگهدارد.

Table ها یکی از نقطه‌های قوت pf محسوب می‌شوند در هر لحظه lookup می‌توان ۵۰,۰۰۰ آدرس را lookup کرد. به دو گونه می‌توان آن‌ها را تعیریف کرد:

عملگر ! در همه جای pf به معنی NOT عمل می‌کند. در بالا جدول internetaccess تعریف شده است که شبکه‌ای با نام 192.168.10.0/24 می‌باشد ولی قرار است آدرس 192.168.10.5 از این شبکه را یک استثناء قرار دهیم. در‌واقع با دایرکتیو table جدولی به نام internetaccess درست کردیم. تعریف بالا می‌تواند به دو صورت زیر باشد:

نکته: وقتی جدولی را نوعش را تعریف نمی‌کنید،‌pf آن را const در نظر می‌گیرد در صورتی که اکثر جداول persist تعریف می‌شوند. یک تعریف بسیار زیبا از جداول وجود دارد که مقادیر خود را فایل‌ها می‌خوانند. این برای مقادیر با تعداد زیاد کاری عقلانی و به نوعی الزامی است:

بعد از اضافه کردن خط بالا به conf فایل می‌توان از آن پرینت گرفت:

خطوط etc/pf/whitelist.pf/ به صورت زیر می‌باشد:

شما با اضافه کردن t- کوچک می‌توانید نام جدول را مشخص کنید و با T- بزرگ چهار عمل show، replace ،delete و add را بر روی آن انجام دهید:

۱.۱.۳ Option ها: option ها برای کنترل نحوه عمل pf استفاده میشوند. همه آن‌ها با دایرکتیو set شروع می‌شوند و بعد اسم option و بعد مقداری که باید set شود. به مثال زیر توجه کنید:

۱.۱.۴ Filter Rule ها: این قسمت خود یکی از بخش‌های مهم این مطلب اینکه طول و تفصیلی دارد. پس چه بهتر که از آن گذر کنیم، چون در مرحله کانفیگ فایل هستیم و بخش‌های آن را توضیح می‌دهیم. یک pf.conf را به صورت ساختاری به قطعات زیر تقسیم می‌کنیم:

نکته: علامت # همانند شل comment می‌کند.
نکته: در فایل بالا فقط ساختار یک pf.conf از بالا به پایین پرینت شد.
نکته: در بالا ۳ نوع rule می‌بینید که خود مستدات pf غیر از Queueing همه آن‌ها را یکی دانسته است.

نکته: فکر نکنید مطالب مربوط به pf در سایت من تمام شده است، بلکه ادامه‌دار است.

نکته: خود مبحث QUEUEING که شامل کلی فرامین می‌شود مطمئناً یک مطلب در همین سایت می‌شود.
بیایید یک مثال از یک pf.conf بزنیم:

حال که فایل pf.conf تمام شد، برگردیم به همان دستور pfctl و چند دستور کاربردی زیر را رونمایی کنیم:

نکته: s- یک گزینه ترکیبی می‌باشد و اگر با r ترکیب شود rule ها را نمایش می‌دهد. اگر با s ترکیب شود State های handshakeing را نمایش می‌دهد. و اگر با i ترکیب شود شمارنده‌ها و و وضعیت filter ها را نمایش می‌دهد. و در نهایت اگر با a ترکیب شود همه چیز را نمایش می‌دهد, و گفته نشد که اگر پارامتر nat را به آن بدهیم جزییات NAT را نمایش می‌دهد که بماند برای شماره‌های آتی…

۲. نوشتن Filter Rule ها:

به یاد دارم متغیر‌های رزرو شده awk رو فرمولی یاد گرفتم و حفظ نکردم. مثلاً N == Number یا F == Field یاR == Row یا O == Output یا S == Separator و غیره … و وقتی یه ترکیب صورت بگیره مثل NF یعنی تعداد فیلدها…! حالا ترجیح می‌دم خواننده این مطلب نوشتن Filter Rule برای PF رو به صورت فرمولی یاد بگیره که خیلی به نفعشه، البته اولش یه گریز به syntax اصلی rule ها می‌زنیم. در‌واقع syntax اصلی هر rule در حالت کلی همانند زیر است:

نکته‌ای که در rule نویسی برای pf رعایت شده است استفاده از کلمات انگلیسی می‌باشد و اهمیت امر موجب خواناگی و مرتب بودن rule ها می‌شود.

action: این قسمت برای بسته‌هایی است که match شده‌اند. و با اکشن pass و block می‌توان به آن فرمان داد. در‌واقع همان پارامتر جلوی j- برای iptables می‌باشد. جلوتر با مثال‌ برخورد می‌کنیم.

direction: شما با این قسمت جهت بسته را مشخص می‌کنید که می‌تواند in یا out باشد.

log: برای log گیری از pf بکار می‌رود. Logging این فایروال یکی از قدرتمندترین قسمت‌های آن محسوب می‌شود به طوری برای آن جداگانه از یک device به نام pflog0 استفاده می‌کند. مطالبی را در شماره‌های آتی برایش می‌نویسم.

quick: با این لغت تقدم rule ها تغییر می‌دهیم. بهتر است جلوتر با مثال توضیح دهم.

interface: شما می‌توانید interface مربوطه را مشخص نمایید، به عنوان مثال :

مقدار on em0 در‌واقع مشخص می‌کند تا بر روی اینترفیس em0 این فیلتر match شود.

نکته: پس یادتان باشد که قسمت interface همیشه یک on به همراه دارد.

af: این لغت خود به کار نمی‌رود و مخفف Adress Family می‌باشد و به جای آن می‌توانید از inet یا inet6 استفاده نمایید.
protocol: این لغت یک پارامتر می‌گیرد که می‌تواند یک یا چندین protocol باشد به مثال زیر توجه نمایید:

می‌دونم این مثال رو قبلاً زده بودم ولی اینجا برام این مثال چندین نکته داشت: الف) لیست‌ها فراموش نشه ب) لغت protocol رو باید به صورت proto نوشت. پروتکل‌های زیر پشتیبانی می‌کند:

  • tcp

  • udp

  • icmp

  • icmp6

  • یک پروتکل معتبر از فایل etc/protocols/

  • شماره پروتکل‌ها باید از ۰ تا ۲۵۵ باشد.

  • می‌توان از یک لیست استفاده کرد.

src_addr, dst_addr:برای src_addr از لغت from و برای dst_addr از لغت to استفاده می‌شود که در مثال‌های بالا به کار برده شد. آدرس‌ها می‌توانند به صورت‌های زیر بکار روند:

  • یک IP نسخه ۴ یا ۶

  • یک CIDR همانند 46.41.205.73/32

  • ذکر FQDN

  • نام یک interface یا گروهی از آنها. (زیرا به هنگام NAT یکی از خاصیت‌های زیبای pf می‌باشد که شما یک interface را NAT می‌کنید نه یک آدرس را! و این زمانی به درد می‌خورد که آدرس NAT شده عوض شود. در این حالت حتماً interface مربوطه باید در () باشد.کلا زمانی که IP آدرس شما update شود داخل () گذاشتن آن یک بحث اجتناب‌ناپذیر است.)

  • یک table

  • نام یک interface که با netmask/ دنبال شود.

  • نام یک interface که با یکی از ۳ لغت رزرو شده زیر دنبال شد:

    • network

    • broadcast

    • peer

    مثالی که می‌توان زد به حالت em0:broadcast است. 0: هم بعد از آن می‌تواند قرار بگیرد که نشانگر این است که شما از alias IP استفاده نمی‌کنید. همانند em0:network:0

  • لغت کلیدی any به معنی تمام IP آدرس‌ها.

  • یک list از آدرس‌ها همانند {192.168.0.3 10.0.0.1!}

  • لغت کلیدی all به معنی from any to any

  • لغت کلیدی urpf-failed به جای src_addr می‌تواند بنشیند. در‌واقع الگوریتمی وجود دارد که از روی routing table می‌فهمد IP مذکورspoof شده است یا خیر. این لغت این الگوریتم را پیاده کرده است. شما می ‌توانید با rule زیر این router خود را از دست IP های spoof شده امن کنید:

src_port, dst_port :از لایه ۴ هدر IP می‌توان src_port و dst_port را مشخص نمود.پورت‌ها به صورت زیر مشخص می‌شوند:

  • یک عددی بین 0 تا ۲۵۵

  • یک list از پورت‌ها همانند {80 443}، البته ناگفته نماند شما می‌توانید از فایل etc/services/ استفاده نمایید. درواقع خود pf این کار را انجام می‌دهد. به مثال زیر دقت نمایید:

    کلمه ssh از این فایل اخذ شد و به معنای عدد 22 می‌باشد. در زمانی که پورت مشخص می‌کنید tcp یا udp بودن آن نیز باید مشخص گردد.

  • یک محدوده پورت:

    • =! به معنای عدم تساوی

    • > به معنای کوچکتر

    • < به معنای بزرگتر

    • => به معنای کوچکتر مساوی

    • =< به معنای بزرگ‌تر مساوی

    • >< یک محدوده

    • <> معکوس یک محدوده (عملگر باینری)

    • شامل یک محدوده (عملگر باینری)

tcp_flags:فلگ‌های TCP رو بهنگامی که از proto tcp استفاده می‌شود check می‌کند. فرمت استفاده از آن به صورت flags check/mask می‌باشد. به عنوان مثال flags S/SA موچود باشد یعنی به‌ بسته‌هایی که فلگ SYN و ACK دارند بنگر و SYN آن‌ها باید on باشد. فلگ‌هایی که TCP از آنها استفاده می‌نماید در زیر لیست شده‌اند:

  • FIN : F

  • SYN : S

  • RST : R

  • PUSH : P

  • ACK : A

  • URG : U

  • ECE : E

  • CWR : W

state: این فایروال بسیار قوی می‌باشد که یکی از نقاط قوت آن در stateful بودن آن است. آخرین گزینه‌ای که در syntax نوشته می‌شود state است که می‌تواند به صورت‌های زیر نوشته شود:

  • no state: با TCP, UDP و ICMP کار می‌کند. برای connection های TCP باید flags any نیز بکار برده شود.

  • keep state: با TCP, UDP و ICMP کار می‌کند. این گزینه پیش‌فرض همه rule های می‌باشد.

  • modulate state: تنها با TCP کار می‌کند. وقتی چنین state ی را مشخص می‌کنید PF برای بسته یک سری اعداد توالی تولید می‌کند : ISNs.

  • synproxy state : وقتی handshaking صورت نگیرد و فقط یک SYN بیاید این یعنی شما مورد حمله synflood قرار گرفته‌اید. برای مبارزه با این امر راه‌هایی همچون گذاشتن timeout و غیره وجود داره ولی گریز اصلی برای مبارزه با این حمله وجود ندارد و بهترین راه دفاع چند لایه است. State مذکور شامل keep state و mudulate state نیز می‌شود. در‌واقع این state یک ضد synflood است.

طراحی فرمول: همونطور که قبل از اشاره به syntax اصلی یک rule کنم قول دادم یک فرمول برای حفظ کردن rule ها برای خواننده‌های محترم دربیارم. البته این کار واقعاً سخت‌تر از awk هست. چون awk به صورت پیش‌فرض اینجوری نوشته شده و به صورت فرومولی هست. پس بهترین کار اینه که چند تا نکته رو از دل rule ها بکشیم بیرون و اون را برای خودمون Bold کنیم و نسبت دهی ذهنی کنیم و مجموع نکات زیر یک فرمول نهایی برای خواننده خواهد شد:

  • اگر قراره رولی بنویسید مبتنی بر آدرس مبدأ و مقصد (from و to ) رو یه حلاجی کنید ببینید باید in باشن یا out.

  • آیا پورتی باید مشخص بشه؟ پس همیشه از etc/services/ استفاده کنید تا هم در رول خونی و هم در رول نویسی به مشکل نخورید.

  • به هیچ عنوان به طور مستقیم از interface ها استفاده نکنید و از ماکرو‌هایی با نام‌های مرتبط استفاده کنید.

  • از table، macro ، و list استفاده نمایید. این رو آویزه گوش خود کنید. حتی اگر یک آدرس دارید.

  • اگر not دارید طوری بنویسید که به چشم بیاد.

  • اگر برنامه خاصی هم از udp استفاده می‌کنه وهم از tcp اینجا واقعاً list ها به کمکتون میاد. خیلی از رول‌ها باید برای هر دو جهت نوشته شود که بعداً با آن برخورد می‌کنید. این را به عنوان یک گوشزد به خاطر داشته باشید.

۳. مثال‌هایی در باب Packet Filtering:

یک مدیر سیستم در دو حالت می‌تواند رول های فایروال خود را تعریف نماید. یکی بستن از بالا به پایین و دیگری بستن از پایین به بالا که اولی حالت امن‌تری دارید. در‌واقع در حالت اول به فایروال می‌گوید همه چیز بسته باشد و هر case که قرار است باز باشد باز می‌کند. طبیعتاً این حالت هم درهم ریختگی ندارد و امن‌تر می‌باشد. شما برای بستن همه چیز به دو rule ساده زیر نیازمندید:

همانطور که در بالا گفته شد همه چیز بسته شد. البته باید loopback رو برای مصارف داخلی باز بگذاریم. بدین منظور شما در قسمت option ها می‌توانید از فرمان روبرو استفاده نمایید:

نکته: آهسته آهسته فرامین optionها برایمان ملموس می‌گردند.
یه نکته اساسی: یه نکته‌ای که خیلی از کاربران با آن برخورد می‌کنند مشکل syntax error هست . خوب مسلماً ۲ نوع از این error داریم:

    • یکی درست اعمال نکردن رول که کاربر pf باید سوادش رو ببره بالا.

    • دوم قضیه مهاجرت OpenBSD 4.6 به OpenBSD 4.7 که در ۲۰۰۹ صورت گرفت و syntax خیلی از موارد PF رو عوض کرد. این مسأله مخصوصاً در NAT که در شماره‌های بعدی خواهیم پرداخت به شدت دیده میشه. البته این مهاجرت در ۳ مرحله اتفاق افتاد یکی milestone 3.x بود دیگری در x.5 بود. کاربر وقتی جستجو می‌کنه و یه documentation پیدا می‌کنه باید ببینه اون بر اساس چه syntaxی نوشته شده. من کل مستنداتم رو بر اساس PF جدید براتون نوشتم.

این رو اول مثال‌ها گفتم که نگید رولی که در اینترنت پیدا کردید یا در فلان کتاب خوندید کار نمی‌کنه.

چند مثال با عملگر‌های پورت: فرض کنید درخواست‌های ورودی مبنی بر زیر ۱۰۲۴ را می‌خواهیم بلاک کنیم ولی پورت ورودی ۲۲ باز باشد:

نکته: ممکن است بگویید که در اول همه چی مگر بسته نشد؟ قرار است در این بخش بیشتر با رول‌ها بازی کنیم تا دستمان بیاید واگر نه حق با شماست دنیای واقعی رول نویسی اینطور نیست.

نکته: نکته‌ای که در بالا وجود داشت فقط پورت tcp آن را باز کردیم و همچنان udp:22 بسته می‌باشد. سپس وقتی از آن query میگیرم چنین نتیجه‌ای میبینیم:

پروتکل ICMP: از این پروتکل به صورت جدی در رفع اشکال استفاده می‌شود، اما چیزی که مهم است سرور شماست. اینکه سرور شما کجا، برای چه کاری،‌ و در چه سطح دسترسی قرار دارد. اگر به این سؤالات بتوانید پاسخ دهید می‌توانید تمام type های ICMP را طبق دلخواه خود customize نمایید. با یک مثال ساده ICMP را در pf بیان می‌کنیم، خط روبرو ping را می‌بندد:

فقط کافیست یک مرجع type های ICMP پیدا کنید و بقیه قضایا حل است.

Transparent کردن Squid با PF: برای چنین کاری کافیست از خطوط زیر استفاده نماییم:

نکته‌ای که اینجا وجود دارد نباید از rdr on استفاده کنید بلکه حتماً باید از rdr pass استفاده نمایید. باز نکته‌ای دیگر که خیلی ضروری است شما باید rdr را قبل از هر pass یا block بنویسید. زیرا این رول قبل از Filtering باید نوشته شود.(مبحث مبوط به طبقه بندی فایل pf.conf) در مطالب بعدی بیشتر با rdr آشنا می‌شویم.

چیزی که بسیار حائز اهمیت است این صرفا یک آشنایی اولیه با PF بود. و در مطالب بعدی به صورت جدی و کاربردی و هر شماره برای قضیه‌ای خاص در PF مطلب خواهم نوشت.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *