Reboot serveru pingem

14. 8. 2012
Doba čtení: 8 minut

Sdílet

Když se server odmlčí, je to vždycky nemilá věc. Horší ale je, když je to server na sto kilometrů vzdáleném místě a nemá vzdálený management. To pak nezbývá sednout do auta a vyrazit k tlačítku reset. Linux má ale ještě jednu možnost: i ve velmi špatné kondici dokáže reagovat třeba na ping a rebootnout.

Určitě jste se už setkali s tím, že linuxový server odpovídá na ping, ale jinak už nic dělat nedokáže. Pokud běží na dospělém serveru, následuje restart přes management. Ale spoustu služeb mohou zajišťovat „běžná PC“, kde se management nevyskytuje. Případně speciální miniaturní zařízení, která mají minimální spotřebu, ale pro firewall nebo router dostatečný výkon. Ta však také nebývají vybavena managementem, jako nedávno popisované Raspberry Pi.

Nemrtvé jádro

Ani u takto jednoduchých zařízení ještě není vše ztraceno. Linuxový kernel má totiž jednu dobrou vlastnost. I když zabloudí, stále ještě reaguje na přerušení a dokáže je obsluhovat. Nás budou zajímat speciálně přerušení od síťových karet. Takové přerušení vyvoláme zasláním paketu, jádro se probudí, podívá se do paketu a ujistí, že po něm chceme něco speciálního, třeba reboot. V podstatě jednoduchý nápad a jednoduchá je i realizace.

Existuje totiž nepříliš známý, ale užitečný doplněk do iptables. Je jím skok na cíl SYSRQ. Asi není potřeba popisovat, co vše je možné udělat se systémem přes zápis písmenek do /proc/sysrq-trigger. A tento doplněk nám to umožní udělat přes síť. A co je vůbec nejlepší, můžeme to udělat i systému, který je již jinak mrtvý, stačí, pokud žijí zbytky kernelu starající se o síť, většinou to poznáme tak, že systém odpovídá na ping.

Příprava na „ping of reboot“

V první řadě si musíme připravit server, který budeme chtít později ovládat. Nejjednodušší je, pokud to podporuje daná distribuce. Např. na Debianu a Ubuntu zadáme:

# apt-get install module-assistant xtables-addons-common xtables-addons-source
# module-assistant --verbose --text-mode auto-install xtables-addons

Na přicházejícím Debianu Wheeze asi nebudete druhý řádek ani potřebovat. Na klonech systémů společnosti Red Hat budete muset využít služeb alternativního repozitáře www.rpmfusion.org, potom je instalace opět snadná:

# yum install xtables-addons akmod-xtables-addons kernel-PAE-devel

A samozřejmě vždycky se můžete obrátit na projekt samotný a instalovat ze zdrojáků získaných z domovské stránky projektu xtables-addons.

Dále si musíme připravit heslo a případně i sekvenční číslo.

options xt_SYSRQ password="tohle_nesmim_rict" seqno=123456

do souboru /etc/modprobe.d/xt_sysrq

K čemu je dobré heslo, asi tušíte, stejně tak to, že obsah souboru /etc/modprobe.d/xt_sysrq nesmí vidět běžný uživatel. Blíže popíši jen parametr seqno. Každý požadavek, který budete pro SYSRQ posílat je podepsán heslem, solí (salt) a IP adresou, navíc obsahuje pořadové číslo. (Oponent je toho názoru, že použití soli je třeba více rozvést, ale solení hesel je téma spíše na další článek.) Pokud dekódovaný paket obsahuje pořadové číslo, které je nižší nebo stejné jako minule použité, příkaz se neprovede. To brání tomu, aby někdo na síti odchytil náš paket a později jej zaslal na server znovu (replay attack).

Asi tušíte, že tady nastává problém, protože rebootnutý server nemá tušení jaké bylo poslední sekvenční číslo, a tak akceptuje jakékoliv. Proto se zavádí princip, že při zavedení modulu se sekvenční číslo nastaví na počet sekund od počátku unixového času (ledaže si parametrem nastavíte vlastní). Klient počet sekund zná také, a tak dokud posílá požadavky pomaleji než jeden za vteřinu, může sekvenční číslo snadno odvodit z aktuálního času.

Pokud máte na serveru více uživatelů, možná bude lepší heslo nastavit přes

# echo -n "tohle_nesmim_rict" >/sys/module/xt_SYSRQ/parameters/password

To aby si jej nikdo nemohl přečíst v ps  nebo etc. Heslo každopádně nastavujeme před startem iptables, takže pokud chceme heslo nastavit přes /sys, musíme modul explicitně zavést ještě před startem firewallu ( modprobe xt_SYSRQ).

Ochranu heslem a sekvenčním číslem můžete kombinovat s běžnými pravidly v iptables, takže pravidlo bude vypadat nějak takto:

# iptables -A INPUT  -s moje_pracovni_stanice -p udp --dport 9 -j SYSRQ

Samozřejmě je možné testovat i další pravidla, například MAC adresu.

Autoři navíc doporučují, aby pro případ útoku byl omezen počet paketů zaznamenaných tímto pravidlem, tedy příkaz by měl vypadat takto:

# iptables -A INPUT -s moje_stanice -m mac --mac-source aa:bb:cc:dd:ee:ff -d muj_server -p udp --dport 9 -m limit --limit 5/minute -j SYSRQ

Tím, že se používá UDP, je snadné podvrhnout IP adresu odesílatele a někdo může zkoušet server zahltit. Sice pravděpodobně neuhodne vaše heslo, ale zpracování těchto paketů je relativně náročné na CPU proti ostatním iptables pravidlům. Pokud je mezi vaší pracovní stanicí a serverem router, musíte případně použít MAC adresu toho routeru, ale to je celkem samozřejmé. Ochrana potom pomůže jen proti vedlejším serverům v síti, ne proti vzdálenému útočníkovi.

Pokud chcete dále zvýšit bezpečnost, můžete parametrem hash=sha256 použít silnější hash než výchozí SHA-1. Silnější hash ale vede k dalšímu zvýšení nároku na CPU v případě útoku.

Port můžete použít jakýkoliv (neobsazený). Většina příkladů na internetu používá port 9, který je jinak vyhrazen pro „službu“ discard, můžete ale použít jakýkoliv jiný. Pouze dejte pozor na to, že jakýkoliv paket poslaný do cíle SYSRQ je zahozen, není to tak, že by např. pakety s neplatným heslem byly poslány do dalšího zpracování.

Pro pořádek zmíním potřebu povolit sysrq na serveru, vetšinou zadáním

kernel.sysrq = 1

do /etc/sysctl.conf a nebo příkazem

# echo "1" >/proc/sys/kernel/sysrq

Jakou hodnotu máme nastavenou získáme naopak příkazem  cat /proc/sys/kernel/sysrq.

Klientská strana

Server máme připravený, a tak si musíme připravit skript na straně klienta. Lze potkat hotový skript jménem send_sysrq, ale v Debianu jej v balících nenajdeme. Jeho vytvoření je ale extrémně snadné, stačí jej okopírovat za manuálové stránky modulu, na ukázku:

#!/bin/bash
sysrq_key="s"  # the SysRq key(s)
password="nikomu_nereknu"
ipaddr="1.2.3.4"
seqno="$(date +%s)"
salt="$(dd bs=12 count=1 if=/dev/urandom 2>/dev/null | openssl enc -base64)"
req="$sysrq_key,$seqno,$salt"
req="$req,$(echo -n "$req,$ipaddr,$password" | sha1sum | cut -c1-40)"

# pokud máte rádi socat
echo "$req" | socat stdin udp-sendto:1.2.3.4:9
# pokud máte rádi netcat
echo "$req" | netcat -uw1 1.2.3.4 9

Případně můžete použít můj skript v Pythonu. Ten používá konfigurační soubor .sysrqrc, který je obdobou .netrc, je uložen v domovském adresáři uživatele a obsahuje pro každý ovládaný stroj heslo, případně položku default, která se použije pro stoje přímo neuvedené:

# machine muj.server.cz password aaaa
default password aaaaaa

A skript sám:

#!/usr/bin/env python
"""
xt_SYSRQ klient

Umoznuje zaslat na po siti prikazy do /proc/sysrq-trigger
"""
import sys, netrc, datetime, random, string, hashlib, socket, os.path

class Usage(Exception):
    def __init__(self, msg):
        self.msg = msg

def main(argv=None):
    if argv is None:
        argv = sys.argv
    try:
        try:
            hostname = sys.argv[1]
            command  = sys.argv[2]
        except:
             raise Usage('Nespravny pocet parametru')

        user_home = os.path.expanduser('~')
        sysrqrc = netrc.netrc(os.path.join(user_home,'.sysrqrc'))
        authinfo = sysrqrc.authenticators(hostname)
        seqno = datetime.datetime.now().strftime('%s')
        ip = socket.gethostbyname(hostname)
        salt = ''
        for i in range(0,15):
          salt += random.choice(string.ascii_letters)
        req = '%s,%s,%s' % (command,seqno,salt)
        req = '%s,%s' % (req,hashlib.sha1('%s,%s,%s'%(req,ip,authinfo[2])).hexdigest())
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.sendto(req,(hostname,9))

    except Usage, err:
        print >>sys.stderr, err.msg
        print >>sys.stderr, "Skript pro vzdalene volani SYSRQ. Pouziti:"
        print >>sys.stderr, "send_sysrq.py hostname prikaz"
        return 2

if __name__ == "__main__":
    sys.exit(main())

První test

Nyní můžeme provést první test. K tomu se hodí poslat písmeno h, které vypisuje nápovědu. Pošleme si tedy jedno h a příkazem dmesg se podíváme a vidíme:

SysRq : HELP : loglevel(0-9) reBoot Crash terminate-all-tasks(E) memory-full-oom-kill(F) kill-all-tasks(I) thaw-filesystems(J) saK show-backtrace-all-active-cpus(L) show-memory-usage(M) nice-all-RT-tasks(N) powerOff show-registers(P) show-all-timers(Q) unRaw Sync show-task-states(T) Unmount show-blocked-tasks(W) dump-ftrace-buffer(Z)

Pokud ano, tak máme hotovo.

Při běžném používání potom můžete posílat více písmenek najednou. Počítejte ale s tím, že písmeno s (sync) čeká na dokončení, které u systému, který má problém se zápisem na disk nikdy nenastane. Takže zaslání sekvence „sub“ nemusí vést k rebootu.

Když to nefunguje

Pokud posílání nefunguje, zapneme si ladění příkazem

ict ve školství 24

# echo 1 > /sys/module/xt_SYSRQ/parameters/debug

Pohledem do dmesg pak zjistíme, co je špatně. Kromě zjevných překlepů můžete narazit na tři problémy:

odlišná časová zóna serveru a klienta
Pokud se nám čas liší, snadno se stane, že klient použije sekvenční číslo, které je nižší, než si server odvodil z času při startu. Potom musíme na straně klienta začít od přiměřeně vyššího čísla a nebo na serveru zajistit, že se sekvenční číslo sníží (což na mrtvém serveru nebude snadné).
starší verze modulu na serveru
Modul dříve nepoužíval v „podepisovací“ části IP adresu serveru, což umožňovalo útočníkovi zopakovat zaslání paketu na jiný server používající stejné heslo. Pokud takovou verzi modulu máte, musíte upravit řádek 34 ve skriptu takto:
req = '%s,%s' % (req,hashlib.sha1('%s,%s'%(req,authinfo[2])).hexdigest())
některé příkazy prochází a jiné ne
Vetšinou projde „h“, ale reboot se nepovede. To je způsobeno tím, že parametr kernel.sysrq nemá jen hodnotu 0 a 1, ale jedná se o bitovou masku, která selektivně povoluje a zakazuje jednotlivé příkazy. Na aktuální hodnotu se podíváte příkazem:
# sysctl -ar kernel.sysrq

(ano minule jsme se dívali jiným příkazem, ale ať je to pestřejší)

a např. Debian Wheezy má ve výchozím stavu hodnotu 438, což nedovolí poslat příkazy pro dump paměti nebo zabíjení procesů. Detailnější popis hledejte v dokumentaci kernelu. xt_SYSRQ se chová, jako byste zadávali znaky na klávesnici, není ekvivalentní zápisu do /proc/sysrq-trigger, který reaguje i na příkazy touto maskou potlačené.

A to je vše, nyní již dokážeme „restartovat server pingem“.

Autor článku

Dan Ohnesorg pracuje jako systémový administrátor, správce sítí a příležitostný programátor v jazycích, pro které se nedá najít nikdo, kdo by je znal.