Czyli jak opanować las reguł w firewallu, recenzja kawałka fajniego softu ;]. Oraz jak zacząć pisać podstawowy firewall na serwer.

Long long time ago, in a a galaxy far away napisałem sobie skrypt w perlu którego zadaniem było generowanie regułek dolinuksowego firewalla.  Skrypt był napisany paskudnie ale o dziwo był całkiem funkcjonalny, tylko że wtedy byłem w jeszcze w liceum nie miałem zbytnio potrzeby pisania skomplikowanych firewalli.

Parę lat później, jakieś 2 miesiące po przyjęciu mnie do pierwszej pracy w branży miałem potrzebę napisania firewalla trochę dłuższego niż “udostępnij to, zablokuj resztę i włącza masquerading”. Wtedy nie szukałem zbytnio softu do tego więc skończyło się na użyciu CPP z GCC, co samo w sobie nie było złe ale generalnie nie było zbyt przejrzyste.

Wpadłem więc na pomysł żeby zrobić coś na kształt mojego kawałka softu sprzed lat, tyle że napisanego ładniej. To co mój soft robił to było przerabianie configa takiego jak:
table filter {
chain INPUT {
rule {src x.y.o iif eth1 DROP}
rule {proto tcp dstport 80 ACCEPT)
}
}

na odpowiadające temu regułki iptables. Ale pomyślałem że może ktoś wpadł na coś podobnego lepszego więc zacząłem szukać. Wiedziałem że jest sporo pakietów typu “własny język opisu + generator dla danego firewalla” ale po pierwsze ich składnia mi zwykle wogóle nie pasowała i po drugie ,zwykle bardziej zaawansowane moduły iptables nie były obsługiwane. Lecz po parunastu minutach szukania trafiłem na FERM.

FERM – For Easy Rule Making

ferm is a tool to maintain complex firewalls, without having the trouble to rewrite the complex rules over and over again. ferm allows the entire firewall rule set to be stored in a separate file, and to be loaded with one command. The firewall configuration resembles structured programming-like language, which can contain levels and lists.

Tak opisują go autorzy. Ale co ten zwierz potrafi ? Po lekturze manuala widać że ten soft ma obsługę większości matchów i targetów wymienionych w man iptables. So far so good.

Miłą rzeczą jest że ten soft już spaczkowany w debianie wieć po szybkim aptitude install ferm zacząłem się nim bawić. Lektura man’a ujawnia pierwsze interesujące opcje:

--interactive
Apply the firewall rules and ask the user for confirmation.
Reverts to the previous ruleset if there is no valid user
response within 30 seconds. This is useful for remote
firewall administration: you can test the rules without
fearing to lock yourself out.

każdy z was który przez przypadek “odciął” się grzebiąc zdalnie przy firewallu doceni tą opcję ;]. Soft wyglądał interesująco więc zacząłem w nim grzebać i wygenerowałem taki o to config:

@def $tcp_ports = (ssh http https);
@def $udp_ports = (domain);
table filter {

chain bad-tcp {
    proto tcp tcp-flags (SYN ACK) SYN mod length length 100: mod comment comment "long SYN" DROP;
    proto tcp tcp-flags (SYN ACK) SYN
        mod hashlimit
            hashlimit 10/second
            hashlimit-burst 30
            hashlimit-mode srcip
            hashlimit-name synflood
            ACCEPT;
    mod comment comment "syn storm" DROP;
}

    chain INPUT {
        policy ACCEPT;
        mod state state INVALID mod comment comment "bad things" DROP ;
        mod state state (ESTABLISHED RELATED) ACCEPT;

        # allow local packages
        interface lo ACCEPT;

        # respond to ping

	saddr 192.168.0.0/16 ACCEPT;
#	proto tcp jump bad-tcp;
        proto tcp mod multiport destination-ports $tcp_ports jump bad-tcp;
	proto tcp mod comment comment 'bad tcp port' DROP;
       proto udp mod multiport destination-ports $udp_ports ACCEPT;
       proto udp DROP;

        proto icmp {
	    icmp-type (echo-reply echo-request) ACCEPT;
	    icmp-type (ttl-exceeded fragmentation-needed port-unreachable host-unreachable) ACCEPT;
	     mod comment comment "icmp junk" DROP;
	}
    }
    chain OUTPUT {
        policy ACCEPT;
    }
    chain FORWARD {
        policy DROP;
        # connection tracking
        mod state state INVALID DROP;
        mod state state (ESTABLISHED RELATED) ACCEPT;
    }
}

Widać że składnia wygląda w miarę czytelnie. Podany wyżej przykład jest początkiem firewalla na serwer webowy:

  • Puszcza tylko ruch na porty http,https ssh i UDP na dnsy
  • Dropuje ruch na złe porty
  • dropuje wszystkie pakiety SYN większe niż 100 bajtów (właściwie można ustawić większe niż 64)
  • Pozwala na max 10 pakietów SYN na sekundę, powyżej tego dropuje (tyle dlatego że czasami pod 1 IP chowa się cała sieć osiedlowa), + burst ustawiony na 30 (jak ktoś robi SYN flood to i tak szybko wyczerpie)
  • Dropuje wszystkie pakiety które iptables zakwalifikuje jako INVALID
  • Puszcza pingi
  • Puszcza parę innych przydatnych ICMP (do tracerouta, żeby PMTU discovery działało)
  • Puszcza wszystko z sieci lokalnej (192.168.0.0/16)

w porównaniu do (to jest wynik odpalenia ferma):

iptables -A FORWARD --match state --state INVALID --jump DROP
iptables -A FORWARD --match state --state ESTABLISHED,RELATED --jump ACCEPT
iptables -A INPUT --match state --state INVALID --match comment --comment "bad things" --jump DROP
iptables -A INPUT --match state --state ESTABLISHED,RELATED --jump ACCEPT
iptables -A INPUT --in-interface lo --jump ACCEPT
iptables -A INPUT --source 192.168.0.0/16 --jump ACCEPT
iptables -A INPUT --protocol tcp --match multiport --destination-ports ssh,http,https,8888 --jump bad-tcp
iptables -A INPUT --protocol tcp --match comment --comment "bad tcp port" --jump DROP
iptables -A INPUT --protocol udp --match multiport --destination-ports domain --jump ACCEPT
iptables -A INPUT --protocol udp --jump DROP
iptables -A INPUT --protocol icmp --icmp-type echo-reply --jump ACCEPT
iptables -A INPUT --protocol icmp --icmp-type echo-request --jump ACCEPT
iptables -A INPUT --protocol icmp --icmp-type ttl-exceeded --jump ACCEPT
iptables -A INPUT --protocol icmp --icmp-type fragmentation-needed --jump ACCEPT
-A INPUT --protocol icmp --icmp-type port-unreachable --jump ACCEPT
-A INPUT --protocol icmp --icmp-type host-unreachable --jump ACCEPT
-A INPUT --protocol icmp --match comment --comment "icmp junk" --jump DROP
-A bad-tcp --protocol tcp --tcp-flags SYN,ACK SYN --match length --length 100: --match comment --comment "long SYN" --jump DROP
-A bad-tcp --protocol tcp --tcp-flags SYN,ACK SYN --match hashlimit --hashlimit 10/second --hashlimit-burst 30 --hashlimit-mode srcip --hashlimit-name synflood --jump ACCEPT
-A bad-tcp --match comment --comment "syn storm" --jump DROP

wygląda troche czytelniej ;]. Oczywiście, chwilę zajmie uczenie sie składni ale że jest bardzo podobna do iptables to nie powinno to trwać długo. Parę rzeczy na które warto zwrócić uwagę:

1. nawiasy ( ) pozwalają podawać parę wartości i w zależności od kontekstu utowrza jedną lub pare reguł np:
icmp-type (ttl-exceeded fragmentation-needed port-unreachable host-unreachable) ACCEPT;
Utworzy 4 regułki, każda z innym icmp-type ale np:
proto tcp tcp-flags (SYN ACK) SYN
Utworzy jedną regułe z --tcp-flags SYN,ACK SYN
tu mała porada, jeżeli chcesz sprawdzić czy parę flag TCP jest włączona musisz zrobić tak:
proto tcp tcp-flags (SYN ACK FIN) (SYN ACK)
Inne przykłady (trcohę bezsensowne ale ;p):
proto tcp tcp-flags ALL (URG FIN)
proto tcp tcp-flags (SYN ACK URG) NONE

2. nawiasy { } normalnie służą głównie do formatowania np.

proto tcp {
dport http ACCEPT;
dport 21 ACCEPT;
}

to to samo co:

proto tcp dport http ACCEPT;
proto tcp dport 21 ACCEPT;

ale gdy podamy@subchain (z opzjonalna “nazwa) {} to ferm stworzy nam dodatkowy chain z tym co jest w nawiasie. Przydatnie przy większej ilości regul.
I tu jest mały bug bo gdy zrobimy coś w stylu

proto tcp sport 1234 {
proto tcp dport 5342 ACCEPT;
}

to się nam zbuguje i wygeneruje –protocol tcp x2 a tego iptables nie przyjmie, wiec póki tego nie naprawią, watch out ;]

3. Zmienne – definiujemy je przez @def $zmienna = cos, podstawa.

Soft potrafi tez zrobić inlcude innych plików i jeszzce pare drobiazgów ale nie mam zamiaru przepisywać dokumentacji na polski więc odsyłam do strony www po więcej info. I tyle, fajny kawał softu do klepania firewalli ;]

Related Posts: