2011-07-17 15 views
6

Próbuję wdrożyć system blokowania IP w mojej aplikacji internetowej przy użyciu MySQL, wiem, że mogę to zrobić za pomocą .htaccess, ale to nie jest dla mnie miłe.Wildcard IP Banning with MySQL

Zasadniczo moja aktualna tabela:

ip_blacklist(id, ip, date) 

w php i zajrzeć do bazy danych dla klienta IP aby sprawdzić, czy jest on blokowany czy nie:

$sql = "SELECT ip FROM ip_blacklist WHERE ip = ? LIMIT 1" 
$query = $this->db->query($sql, array($ip)); 
if($query->num_rows() > 0){ 
// Gotcha 
} 

teraz .. to działa W porządku, ale chcę mieć możliwość wprowadzania zakresów adresów wieloznacznych adresów IP w bazie danych, takich jak:

42.21.58.* 
42.21.*.* 
53.*.*.* 

Jak to zrobić?

Z góry dziękuję.

Odpowiedz

7

Jeśli zawsze będziesz sprawdzenie jednego adresu IP w tym czasie i twoje zakazane zakresy nigdy się nie przecinają, powinieneś przechowywać adresy początkowe i końcowe zakresów, aby zablokować w formacie liczbowym.

Powiedz, że chcesz zablokować 192.168.1.0 na 192.168.1.15, który jest 192.168.1.0/28.

utworzyć tabelę tak:

CREATE TABLE ban (start_ip INT UNSIGNED NOT NULL PRIMARY KEY, end_ip INT UNSIGNED NOT NULL) 

włóż zasięg tam:

INSERT 
INTO ban 
VALUES (INET_ATON('192.168.1.0'), INET_ATON('192.168.1.0') + POWER(2, 32 - 28) - 1) 

następnie sprawdzić:

SELECT (
     SELECT end_ip 
     FROM ban 
     WHERE start_ip <= INET_ATON('192.168.1.14') 
     ORDER BY 
       start_ip DESC 
     LIMIT 1 
     ) >= INET_ATON('192.168.1.14') 

W ORDER BY i LIMIT części są wymagane dla zapytanie jest skuteczne.

To, jak wcześniej stwierdzono, zakłada nie przecinające się bloki i jedno IP na raz.

Jeśli bloki się przecinają (na przykład jednocześnie blokujesz 192.168.1.0/28 i 192.168.1.0/24), zapytanie może zwrócić fałszywe negatywy.

Jeśli chcesz kwerendy więcej niż jeden adres IP w czasie (powiedzmy, aktualizować tabelę z długiej listy adresów IP), a następnie ta kwerenda będzie nieefektywne (MySQL nie zoptymalizować range w skorelowanych podkwerend dobrze)

W obu tych przypadkach należy trzeba przechowywać swoje zakresy jak LineString i używać indeksów przestrzennych dla szybkich wyszukiwań:

CREATE TABLE ban (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, range LINESTRING NOT NULL) ENGINE=MyISAM; 

CREATE SPATIAL INDEX sx_ban_range ON ban (range); 

INSERT 
INTO ban (range) 
VALUES (
     LineString 
       (
       Point(INET_ATON('192.168.1.0'), -1), 
       Point(INET_ATON('192.168.1.0') + POWER(2, 32 - 28) - 1), 1) 
       ) 
     ); 

SELECT * 
FROM ban 
WHERE MBRContains(range, Point(INET_ATON('192.168.1.14'), 0)) 
+0

Tutaj widzimy Quassnoi ucieleśniający głos doświadczenia. Jeśli nie przechowujesz adresów IP w formacie liczbowym, prawdopodobnie robisz to źle. – TehShrike

3

Jest to trudniejsze, jeśli chcą zakazać podsieci

Uwagi:

  • A "wildcard" mapowanie powinno używać 0 (np 42.21.58.0), który określa, że ​​podsieć
  • 0,0 może nie będzie z powodu podsieci CIDR (mogą 0,128, 0,192 etc)

Więc:

  • Store IP jako 4 bajty binarnej lub unsigned int
  • Store maski podsieci w postaci binarnej (lub uint) 4 dla podsieci czarnych list
  • Spójrz na INET_NTOA and INET_ATON do tłumaczenia adresów IP

Następnie WHERE klauzula staje

WHERE ip = @ip --whole IP 
     OR 
     (ip & mask = @ip) --subnet 

Jeśli się maskę 0xffffffff dla dokładnych adresów IP to zawsze można zrobić ip & mask = @ip z ip & mask jako kolumna obliczana

Również masz IPv6 myśleć zbyt

+0

Dziękuję za to, nie wiem zbyt wiele na temat adresów IP, ale co to jest oznaczanie podsieci? i jak przechowywać IP jako 4-bajtowe pliki binarne? – Ryan

+0

@David: Zacznę szturchać na Wikipedii, np. Http://en.wikipedia.org/wiki/CIDR. I właśnie updoed z funkcjami MySQL do korzystania z – gbn

+0

jestem pewien, że eksperci tutaj rozumieją twoje rozwiązanie, ale dla mnie i innych początkujących jest mało niejasne:/czy możesz dać szybki przykład php, jak przekonwertować IP do formatu, który Twój program SQL akceptuje? – Ryan

2

Quick'n'Dirty, ale nie można korzystać z odpowiednich indeksów:

SELECT ip FROM ip_blacklist WHERE ? LIKE REPLACE(ip,'*','%') LIMIT 1 
+1

Hmm. W rzeczywistości "?" Będzie adresem IP klienta i nie będzie zawierać gwiazd. – Ryan

+0

Pomysł jest dobry. Po prostu wymień ** ip ** z **? ** i voila! :) – Karolis

+0

Ah, cholera, tęsknię za tym, naprawione teraz :) – Wrikken

0

konwersji symboli wieloznacznych z 42.21.*.* do 42.21.0.0 iz powrotem podczas zapisu lub odczytu wpisy z bazy danych. W celu zwiększenia wydajności (niska pamięć i ślad dysku, wydajność) zapisz go jako liczbę całkowitą, użyj konwersji INET_NTOA and INET_ATON.

jeśli spojrzeć Utwórz a.b.c.d adresie IP:

SELECT ip FROM ip_blacklist WHERE ip=INET_ATON('a.b.c.d') or ip=INET_ATON('a.b.c.0') or ip=INET_ATON('a.b.0.0') or ip=INET_ATON('a.0.0.0') 

Ok, jest ostatni mecz prawdopodobnie głupie.

Nie zapomnij dodać indeksów.

1

Moja sugestia może sprawić pewne przykrości, ale wydaje się, że w tym projekcie dzieje się coś nietradycyjnego, więc tutaj jest: Analizowanie każdego adresu IP z bazy danych w regex, który można porównać z adresem IP użytkownika. Przykład:

<?php 
//Fetch IP's and begin to contruct regex 
$regex = array(); 
while($arr = mysql_fetch_array($result)) { 
    $regex[] = '('.$arr['ip'].')'; 
} 

$regex = implode('|', $regex); //Regex now becomes (1.1.1.1)|(2.2.2.2)|etc. 
$regex = str_replace('.', '\.', $regex); //Escape dots for regex 
$regex = str_replace('*', '((25[0-5])|(2[0-4]\d)|(1\d\d)|(\d\d?))', $regex); //Deal with wildcards 

$httpVars = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'); 
foreach($httpVars as $httpVar) { //No hiding behind proxies 
    if(isset($IP = $_SERVER[$httpVar])) { 
     break; 
    } 
} 

if(preg_match('/^'.$regex.'$/', $IP) != 0) { 
    die(header('HTTP/1.1 403 Forbidden')); //Magical regex says user should be banned 
} 
?> 

Oczywiście możesz zrobić o wiele więcej. Można buforować wyrażenie regularne, aby zapisać zapytanie do bazy danych przy każdym żądaniu lub nawet rozszerzyć opcje wieloznaczne o zakresy adresów IP.