Disassembler

Artificial intelligence is no match for natural stupidity.
19říjen2012

Fail2Banem střežené MySQL


Běží-li vám na vašem serveru špatný vtip jménem MySQL, je z bezpečnostního hlediska poměrně vhodné nepouštět jej mimo ohrádku localhostu. Tedy, od dob co existují SQLite, pgSQL a NoSQL databáze je lepší jej nepouštět vůbec nikam, ale lepičům písmenek co si říkají „webmasteři“ se to těžko vysvětluje. Pokud se však ocitnete v situaci, kdy MySQL ven prostě musí, pak by bylo dobré podniknout pár kroků, díky kterým budete mít lepší orientaci v tom, kdo se k vám dobývá, a případně i v tom, kdo se k vám už nikdy dobývat nebude, poněvadž bude obdarován krásným IP banem.

Fail2Log


Perfektním hlídacím a samobanovacím nástrojem je Fail2Ban. Ten funguje tak, že si sedne na logy určené v konfiguraci a kouká, kdo v nich tropí nepřístojnosti. Problém ovšem nastane, když hlídaná služba při pokusech o prolomení zatvrzele mlčí a místo křiku na celé kolo nevypíše do logu ani čárku. A přesně takhle se bohužel MySQL ve výchozím nastavení chová. Vezměme si výchozí konfiguraci z repozitářů Debianu/Ubuntu. Z ní se dá vyčíst, že mysqld_safe, tedy startovací proces MySQL, loguje do syslogu. Samotné běžící MySQL pak loguje do souboru… hmm... loguje do... eee... neloguje vůbec? No jasně, vždyť je to jenom nějaká databáze, tak na co logování, že jo? Prvním krokem tedy bude logování direktivou log-error v oddílu [mysqld] vůbec zapnout. To samotné ale zdaleka nestačí, protože si MySQL stále myslí, že si přejeme logovat pouze databázové chyby. K tomu, abychom jej donutili logovat i nezdařená připojení je třeba ještě direktiva log-warnings nastavená na hodnotu vyšší než 1. Celý kus konfigurace, který MySQL rozmluví sebevražedné sklony, bude tedy vypadat třeba takto:

[mysqld]
log-error       = /var/log/mysql/mysql.log
log-warnings    = 2

A po restartu mysql nebo reloadu jeho nastavení, se při neúspěšných přihlášeních v logu konečně začnou zobrazovat alespoň trochu užitečné hlášky

121018 23:35:33 [Warning] Access denied for user 'root'@'192.168.12.34' (using password: YES)

Hodinářská práce


Fail2Ban se z řádku logu snaží vyčíst i informaci o čase, aby věděl, jestli těch padesát neúspěšných pokusů o spojení proběhlo během poslední vteřiny nebo posledního týdne, na základě čehož porovná časový úsek se svým pravidlem a provede patřičnou akci, je-li třeba. Přechroustání informace o času se ovšem neděje kdovíjakým kouzlem. Fail2Ban prostě a jednoduše zná několik formátů času a snaží se odhadnout, který z nich je v logu použit. Zde opět přichází na scénu MySQL a jejich moto „Doprasíme, co se dá!“. Z řádku logu uvedeného výše je patrné, že časové razítko je v jakémsi obskurním formátu YYMMDD hh:mm:ss a není proto překvapením, že zrovna takový formát Fail2Ban není schopen identifikovat, poněvadž se s ním prostě nepočítalo. Naštěstí je Fail2Ban psán v sympatickém jazyku nesoucím jméno „Python“, díky čemuž je relativně jednoduché jej MySQLí formát data naučit. K tomu je třeba modifikovat soubor /usr/share/fail2ban/server/datedetector.py a do funkce addDefaultTemplate přidat

@@ -117,6 +117,12 @@
                        template.setRegex("\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}")
                        template.setPattern("%d-%m-%Y %H:%M:%S")
                        self.__templates.append(template)
+                       # MySQL 121018 23:53:33
+                        template = DateStrptime()
+                        template.setName("YearMonthDay Hour:Minute:Second")
+                        template.setRegex("\d{2}\d{2}\d{2} \d{2}:\d{2}:\d{2}")
+                        template.setPattern("%y%m%d %H:%M:%S")
+                        self.__templates.append(template)
                        # TAI64N
                        template = DateTai64n()
                        template.setName("TAI64N")

Filtrovaný kriminál


Dalším krokem je pak vytvoření filtru, tedy souboru s regulárním výrazem, podle kterého bude Fail2Ban vyhodnocovat řádky logu a vytahovat z nich IP adresy nebo hostname. Můj /etc/fail2ban/filter.d/mysql.conf vypadá následovně

[Definition]

failregex = \[Warning\] Access denied for user \'.*\'@\'<HOST>\'

ignoreregex = localhost

Výraz můžete před nasazením do ostrého provozu otestovat příkazem

# fail2ban-regex /var/log/mysql/mysql.log "\[Warning\] Access denied for user \'.*\'@\'<HOST>\'"

který vám vysype všechny řádky odpovídající výrazu a počty identifikovaných vzorů časových razítek.

Na závěr už jen strčíte filtr do nového banovacího pravidla v /etc/fail2ban/jail.conf

[mysql]
enabled  = true
port     = mysql
filter   = mysql
logpath  = /var/log/mysql/mysql.log
bantime  = 600
maxretry = 10

a pomocí

# fail2ban-client start mysql

jej nahodíte.

Bezpečnost MySQL sice bude stát za starou bačkoru pořád dál, ale vy alespoň budete mít v iptables hezkou sbírku čínských a brazilských IP adres.