Iptables: dopasowanie ruchu wychodzącego do conntrack i właściciela. Działa z dziwnymi kroplami

11

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
arcX
źródło
Czy możesz podać wszystkie zasady, w tym łańcuch FORWARD i tablicę nat, jeśli ten host kieruje również ruch z sieci LAN.
Serge
Ten host nie wykonuje routingu, ruch pochodzi z komputera z tymi regułami. Opublikowałem tylko odpowiednie zasady dla określonego ruchu wychodzącego.
arcX

Odpowiedzi:

16

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żytkownika x.

Dłuższa historia.

Aby zrozumieć ten problem, pomaga zrozumieć, w jaki sposób wgeti żądania HTTP działają ogólnie.

W

wget http://cachefly.cachefly.net/10mb.test

wgetustanawia połączenie TCP do cachefly.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 wgetuzyska nagłówki dla odpowiedzi, wie, że jej zadanie jest wykonane, gdy pobierze 10 MB danych.

Zasadniczo wgetodczytuje 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 wgetzamyka ( closewywołanie systemowe) deskryptor pliku dla gniazda. Następnie closesystem kończy potwierdzanie danych wysyłanych przez serwer i wysyła komunikat FIN: „Nie będę więcej wysyłać danych”. W tym momencie closewraca i wgetwychodzi. Ż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 tego FINserwer 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 wgetdawno 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 NEWpakiety.

Jeśli zezwolisz na NEWpakiety od użytkownika, xale nie pakiety od użytkownika y, xprzejdą inne pakiety dla ustanowionych połączeń przez użytkownika , a ponieważ nie można ustanowić połączeń przez użytkownika y(ponieważ blokujemy NEWpakiety, które nawiązałyby połączenie), nie będzie żadnego pakietu yprzechodzącego przez połączenia użytkowników .

Stéphane Chazelas
źródło
3

Pozwala to na wyjście z portu 80 tylko dla konta „konto użytkownika”

- 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.

poige
źródło
1
Dzięki za radę. Zwykle jestem za prostotą, ale nie o to chodzi w tym konkretnym ćwiczeniu.
arcX
1
@arcX, ćwiczenie to może się nie powieść z powodu zbyt złożonego i niespójnego - IOW, zachowanie, które widzisz, może nie wynikać z wewnętrznych elementów netfiltera (błędów, dziwactw), ale ze sposobu, w jaki go używasz. Najpierw spróbuj uprościć
zestaw