W moim skrypcie iptables eksperymentowałem z pisaniem tak precyzyjnych reguł, jak to tylko możliwe. Ograniczam, którzy użytkownicy mogą korzystać z których usług, częściowo ze względów bezpieczeństwa, a częściowo jako ćwiczenie edukacyjne.
Używanie iptables v1.4.16.2 na Debianie 6.0.6 z uruchomionym jądrem 3.6.2.
Jednak natrafiłem na problem, którego jeszcze nie rozumiem ...
porty wychodzące dla wszystkich użytkowników
Działa to doskonale. Nie mam żadnych ogólnych reguł śledzenia stanu.
## Port wychodzący 81 $ IPTABLES -A WYJŚCIE -p tcp --port 81 -m conntrack --ctstate NOWOŚĆ, USTANOWIONO -j AKCEPTUJ $ IPTABLES -A WEJŚCIE -p tcp --sport 81 -s $ MYIP -m conntrack --ctstate USTANOWIONO -j AKCEPTUJ
porty wychodzące z dopasowaniem użytkownika
## port wychodzący 80 dla konta użytkownika $ IPTABLES -A WYJŚCIE - właściciel dopasowania - konto-właściciel konto użytkownika -p tcp --port 80 -m conntrack --ctstate NOWOŚĆ, USTALONY --sport 1024: 65535 -j AKCEPTUJ $ IPTABLES -A WEJŚCIE -p tcp --sport 80 --port 1024: 65535 -d $ MYIP -m conntrack --ctstate USTANOWIONO -j AKCEPTUJ
Pozwala to na port 80 tylko dla konta „konto użytkownika”, ale takie reguły dla ruchu TCP mają problemy.
## Domyślny dziennik wychodzący + reguły blokowania $ IPTABLES -A WYJŚCIE -j LOG - log-prefiks „BAD OUTGOING” --log-ip-options --log-tcp-options --log-uid $ IPTABLES -A WYJŚCIE -j DROP
Problem
Powyższe działa, użytkownik „konto użytkownika” może uzyskać pliki doskonale. Żaden inny użytkownik w systemie nie może nawiązywać połączeń wychodzących z portem 80.
useraccount @ host: $ wget http://cachefly.cachefly.net/10mb.test
Ale powyższy wget pozostawia x7 porzucone wpisy w moim syslog:
18 października 02:00:35 xxxx jądro: BAD OUTGOING IN = OUT = eth0 SRC = xx.xx.xx.xx DST = 205.234.175.175 LEN = 40 TOS = 0x00 PREC = 0x00 TTL = 64 ID = 12170 DF PROTO = TCP SPT = 37792 DPT = 80 SEQ = 164520678 ACK = 3997126942 WINDOW = 979 RES = 0x00 ACK URGP = 0
Nie dostaję tych kropli dla podobnych reguł dla ruchu UDP. Mam już zasady, które ograniczają użytkowników, którzy mogą wysyłać żądania DNS.
Porzucone wychodzące pakiety ACK wydają się pochodzić z konta głównego (URGP = 0), czego nie rozumiem. Nawet kiedy zamieniam konto użytkownika na root.
Uważam, że pakiety ACK są klasyfikowane jako nowe, ponieważ conntrack rozpoczyna śledzenie połączeń po 3 kroku uzgadniania w 3 kierunkach, ale dlaczego są one odrzucane?
Czy te krople można bezpiecznie zignorować?
Edytować
Często więc widzę takie zasady, które działają dla mnie dobrze:
$ IPTABLES -A WYJŚCIE -s $ MYIP -p tcp -m tcp --port 80 -m stan - stan NOWY, USTANOWIONY -j AKCEPTUJ $ IPTABLES -A WEJŚCIE -p tcp -m tcp --sport 80 -d $ MYIP -m stan - stan USTANOWIONO -j AKCEPTUJ
Zamieniłem „-m state --state” na „-m conntrack --ctstate”, ponieważ dopasowanie stanu jest najwyraźniej nieaktualne.
Czy najlepszą praktyką jest stosowanie ogólnych reguł śledzenia stanu? Czy powyższe zasady nie są uważane za prawidłowe?
Czy dla lepszej kontroli połączeń wychodzących użytkowników byłoby coś takiego?
$ IPTABLES -A WEJŚCIE -m conntrack --ctstate USTANOWIONO -j AKCEPTUJ $ IPTABLES -A WYJŚCIE -m conntrack --ctstate USTANOWIONO -j AKCEPTUJ $ IPTABLES -A WYJŚCIE -p tcp --port 80 -s $ SERVER_IP_TUNNEL -m conntrack --ctstate NOWOŚĆ -m właściciel - rachunek-właściciela konto użytkownika -j AKCEPTUJ $ IPTABLES -A WYJŚCIE -p tcp --port 80 -s $ SERVER_IP_TUNNEL -m conntrack --ctstate NOWOŚĆ -m właściciel --uid-właściciel inne konto -j AKCEPTUJ
Odpowiedzi:
Krótko mówiąc, wiadomość ACK została wysłana, gdy gniazdo nie należało do nikogo. Zamiast zezwalać na pakiety dotyczące gniazda należącego do użytkownika
x
, należy zezwalać na pakiety odnoszące się do połączenia zainicjowanego przez gniazdo od użytkownikax
.Dłuższa historia.
Aby zrozumieć ten problem, pomaga zrozumieć, w jaki sposób
wget
i żądania HTTP działają ogólnie.W
wget
ustanawia połączenie TCP docachefly.cachefly.net
, a po ustanowieniu wysyła żądanie w protokole HTTP, które mówi: „Proszę o przesłanie mi treści/10mb.test
(GET /10mb.test HTTP/1.1
), a przy okazji, czy możesz nie zamykać połączenia po zakończeniu (Connection: Keep-alive
). Powód robi to, ponieważ w przypadku, gdy serwer odpowie przekierowaniem dla adresu URL na ten sam adres IP, może ponownie użyć połączenia.Teraz serwer może odpowiedzieć: „oto dane, o które prosiłeś, strzeż się, że ma 10 MB dużego rozmiaru (
Content-Length: 10485760
), i tak OK, pozostawię połączenie otwarte”. Lub jeśli nie zna rozmiaru danych, „Oto dane, przepraszam, że nie mogę zostawić otwartego połączenia, ale powiem, kiedy możesz przestać pobierać dane, zamykając mój koniec połączenia”.W powyższym adresie URL jesteśmy w pierwszym przypadku.
Tak więc, jak tylko
wget
uzyska nagłówki dla odpowiedzi, wie, że jej zadanie jest wykonane, gdy pobierze 10 MB danych.Zasadniczo
wget
odczytuje dane do momentu odebrania 10 MB i kończy działanie. Ale w tym momencie pozostaje wiele do zrobienia. Co z serwerem? Powiedziano, aby pozostawić połączenie otwarte.Przed wyjściem
wget
zamyka (close
wywołanie systemowe) deskryptor pliku dla gniazda. Następnieclose
system kończy potwierdzanie danych wysyłanych przez serwer i wysyła komunikatFIN
: „Nie będę więcej wysyłać danych”. W tym momencieclose
wraca iwget
wychodzi. Żadne gniazdo nie jest już powiązane z połączeniem TCP (przynajmniej nie należy do żadnego użytkownika). Jednak to jeszcze nie koniec. Po otrzymaniu tegoFIN
serwer HTTP widzi koniec pliku podczas odczytywania następnego żądania od klienta. W HTTP oznacza to: „koniec z żądaniem, zamknę mój koniec”. Więc wysyła również FIN, mówiąc: „Ja też nie będę niczego wysyłać, połączenie zanika”.Po otrzymaniu tego FIN klient wysyła „ACK”. Ale w tym momencie
wget
dawno już minęło, więc ACK nie pochodzi od żadnego użytkownika. Dlatego jest blokowany przez twoją zaporę ogniową. Ponieważ serwer nie otrzymuje ACK, będzie wysyłać FIN w kółko, dopóki się nie podda i zobaczysz więcej upuszczonych ACK. Oznacza to również, że upuszczając te ACK, niepotrzebnie używasz zasobów serwera (które muszą utrzymywać gniazdo w stanie LAST-ACK) przez dłuższy czas.Zachowanie wyglądałoby inaczej, gdyby klient nie poprosił o „Keep-alive” lub serwer nie odpowiedział na „Keep-alive”.
Jak już wspomniano, jeśli używasz modułu do śledzenia połączeń, to co chcesz zrobić, to pozwolić każdemu pakietowi w stanach USTAWIONY i POWIĄZANY i martwić się tylko o
NEW
pakiety.Jeśli zezwolisz na
NEW
pakiety od użytkownika,x
ale nie pakiety od użytkownikay
,x
przejdą inne pakiety dla ustanowionych połączeń przez użytkownika , a ponieważ nie można ustanowić połączeń przez użytkownikay
(ponieważ blokujemyNEW
pakiety, które nawiązałyby połączenie), nie będzie żadnego pakietuy
przechodzącego przez połączenia użytkowników .źródło
- cóż, przynajmniej zasady, które pokazałeś, nie implikują tego.
Jest też miejsce na porady - nie sprawdzaj użytkowników na USTAWIONYCH strumieniach, po prostu zrób to na NOWOŚCI. Nie widzę też sensu w sprawdzaniu portu źródłowego podczas sprawdzania przychodzącego USTANOWIONEGO, jaka jest różnica, który to port, jest już w USTANOWIONYM stanie z PoV conntracka. Zapora ogniowa powinna być tak prosta, jak to możliwe, ale jeszcze wydajna, więc podejście brzytwy Occama jest najlepsze.
źródło