Limitowanie zasobów użytkowników przy suPHP z wykorzystaniem stosu PAM

Nowy pracownik naszej firmy, administrator Linux Mateusz Małek, w ramach prowadzonych testów podatności przeprowadził udany, a zarazem trywialny atak mający wpływ na stabilność pracy jednego z naszych serwerów hostingowych.

Poniżej prezentujemy sposób częściowy zabezpieczenia serwera przed lokalnymi atakami DoS wykonywanymi przy pomocy funkcji shell_exec() w PHP, jeżeli wykorzystywany jest suPHP.

W przypadku wykorzystywania suPHP, uruchamiane z UID+GID użytkownika procesy nie przechodzą przez stos PAM, tym samym nie dotyczą ich limity pam_limits.so z /etc/security/limits.conf. Opisane poniżej kroki zmieniają to zachowanie.

Patchowanie, rekompilacja i rekonfiguracja suPHP

  1. Nakładamy przygotowany patch na suPHP:

    cd /usr/local/directadmin/custombuild
    cp $(find -type d -name \’suphp-0*\’) suphp-patched;
    cd suphp-patched;
    wget http://mkwm.zielonki.info/suphp_patch.diff -O – | patch -p1

  2. Tworzymy nową usługę PAM – np. \”suphp\”, uwzględniającą moduł pam_limits z dedykowaną konfiguracją – wykorzystanie globalnego pliku konfiguracyjnego stwarza pewne problemy (o czym za chwilę):

    cat << EOF > /etc/pam.d/suphp
    auth sufficient pam_permit.so
    session required pam_limits.so conf=/usr/local/suphp/etc/security/limits.conf
    session required pam_permit.so
    EOF

  3. Konfigurujemy limity dotyczące liczby procesów (tak, serwer uległ \”trzynastu znakom\”). Dedykowany plik konfiguracyjny rozwiązuje problem z sytuacją, w której konta użytkowników nie posiadają żadnej wspólnej grupy – w przypadku ustawienia limitów dla wszystkich (*), nie ma możliwości podniesienia ich dla konkretnego użytkownika/grupy (co powoduje problemy z działaniem usług czy administrowaniem serwerem):

    cat << EOF > /usr/local/suphp/etc/security/limits.conf
    * soft nproc 20
    * hard nproc 20
    EOF

  4. Rekompilujemy suPHP i dodajemy \”pam_service=suphp\” do sekcji global w jego pliku konfiguracyjnym.

Instalacja certyfikatu SSL w DirectAdmin

Domyślnie DirectAdmin nie korzysta z SSL przy połączeniach do panelu administracyjnego.

Aby włączyć obsługę SSL oraz wygenerować certificate signing request, zastąpić samopodpisane certyfikaty wystawionymi np. przez StartSSL.com. Na serwerze s5.aboo.pl użyty został certyfikat Comodo Essential SSL:

  1. Tworzymy nowy klucz prywatny oraz CSR:

    openssl req -nodes -newkey rsa:2048 -keyout s5.aboo.pl.key -out s5.aboo.pl.csr

  2. Używając CSR generujemy certyfikat na stronie wystawcy. Jeżeli jesteśmy pytani o typ serwera, wybieramy Apache + mod_ssl, dzięki czemu otrzymamy w formacie PEM
  3. Otrzymane od wystawcy pliki umieszczamy w /usr/local/directadmin/conf wraz z wcześniej wygenerowanym kluczem (-keyout …)
  4. Edytujemy plik directadmin.conf, dodając lub modyfikując linie:

    SSL=1 <- włączamy SSL ssl_redirect_host=s5.aboo.pl <- ważne! ustawiamy adres na który klient jest odsyłany, jeżeli połączy się z http:// zamiast https:// cacert=/usr/local/directadmin/conf/cacert.pem <- certyfikat (\"nasz\") cakey=/usr/local/directadmin/conf/cakey.pem <- klucz prywatny carootcert=/usr/local/directadmin/conf/caroot.pem <- certyfikaty pośrednie (CA)

  5. Bardzo, bardzo ważne – ustawiamy odpowiednie uprawnienia na plik z kluczem:

    chown diradmin:diradmin cakey.pem
    chmod 400 cakey.pem

  6. Restartujemy usługę DirectAdmin:

    service directadmin restart

Kompilacja Custombuild PHP z Percona Server

O Percona Server

Używamy jest Percona Server w wersji 5.5 jako serwera baz danych MySQL rozszerzając tym samym funkcjonalność standardowego serwera MySQL od Oracle o NoSQL-owy sposób komunikacji przez HandlerSocket oraz silnik składowania XtraDB.
Zoptymalizowany do korzystania z dysków SSD dla cache pozwala na znaczne zwiększenie wydajności bazy danych oraz dzięki dodatkowym INFORMATION_SCHEMA nadzorowanie wydajności i obciążenia per użytkownik bazy danych. W czasie restartów po aktualizacji oprogramowania Percona przechowuje indeksy i cache nie pozwalając nawet na tymczasową utratę wydajności.

Instalacja Percona Server

  1. Konfiguracja repozytorium
    Na Debianie Squeeze serwer został zainstalowany z pakietów pochodzących z repozytorium Percona:

    deb http://repo.percona.com/apt/ squeeze main

    Dostępne są również pakiety pod inne wersje systemu – Hardy, Lenny – jak i inne dystrybucje – np. Ubuntu.

  2. Zatrzymanie i wyłączenie obecnego serwera MySQL

    service mysqld stop
    mv /etc/init.d/mysqld /etc/init.d/mysqld-dis

  3. Instalacja pakietów, w tym pakietu deweloperskiego na potrzeby kompilacji PHP

    apt-get install percona-server-server-5.5 libmysqlclient18 libmysqlclient-dev

  4. Podmiana baz danych na pochodzące ze starego serwera MySQL

    service mysql stop
    rm -Rf /var/lib/mysql/*
    echo \”/home/mysql /var/lib/mysql none bind\” >> /etc/fstab
    mount /var/lib/mysql
    service mysql start

  5. Stworzenie lokalnej kopii konfiguracji pakietu custombuild

    cd /usr/local/directadmin/custombuild
    mkdir -p custom/suphp
    cp configure/suphp/configure.php5 custom/suphp/configure.php5

  6. Zmiana przełączników konfiguracji PHP
    /usr/local/directadmin/custombuild/custom/suphp/configure.php5

    \”–with-mysql\” \\
    \”–with-mysqli=/usr/bin/mysql_config\” \\
    \”–with-pdo-mysql=/usr/bin/mysql\” \\

  7. Rekompilacja PHP

    ./build php n

  8. Ustawienie danych debian-sys-maint. Tworzymy w bazie użytkownika z pełnymi uprawnieniami o poświadczeniach z /etc/mysql/debian.cnf

Konfiguracja Vyatta na serwerach OVH

Wykorzystując serwery OVH do wirtualizacji sprzętowej VMware ESXi pojawia się problem konfiguracji sieci na maszynach wirtualnych korzystających z IP failover. Prezentujemy kilka podejść, przy czym obecnie stosujemy równolegle dwa ostatnie z nich.

Podejście 1 – routing OVH bezpośrednio na maszynę

  1. Tworzymy adres MAC wirtualny w Usługi -> MAC wirtualny ustawiając typ \”vmware\” i ustawiamy go konfigurując adres sprzętowy interrface\’u sieciowego maszyny w vSphere Client.
  2. Konfigurujemy go z maską /32 jako adres IP hosta, dodajemy statyczne trasy do oraz przez gw serwera fizycznego (jego IP z końcówką .254)

    ip addr add 178.32.231.141/32 dev eth0
    route add  172.31.223.254 via eth0
    route add default gw 172.31.223.254

Podejście 2 – routing przez Vyatta, DNAT+SNAT

  1. Podobnie jak w 1, przypisujemy IP failover, ale do istniejącego adresu MAC interface\’u WANowego Vyatty (eth0).
  2. Konfigurujemy podsieć NATowaną na interface\’ie LANowym, NAT i DNS forward.

    set interfaces ethernet eth1 address 192.168.0.1/24
    set service nat rule 1 type destination
    set service nat rule 1 inbound-interface eth0
    set service nat rule 1 destination address 178.32.231.141
    set service nat rule 1 inside-address address  192.168.0.100
    set service nat rule 2 type source
    set service nat rule 2 outside-address 178.32.231.141
    set service nat rule 2 source address 192.168.0.100
    set service dns forwarding system

  3. Konfigurujemy sieć na serwerze wirtualnym podpiętym do LAN

    ip addr add 192.168.0.100/24 dev eth0
    ip route add default gw 192.168.0.1
    echo \”nameserver 192.168.0.1\” >> /etc/resolv.conf

Podejście 3 – routing przez Vyatta, static route

  1. Podobnie jak w 1, przypisujemy IP failover, ale do istniejącego adresu MAC interface\’u WANowego Vyatty (eth0).
  2. Konfigurujemy na obu interface\’ach Vyatty ten sam IP routera

    set interfaces ethernet eth0 address 178.33.137.225/32
    set interfaces ethernet eth1 address 178.33.137.225/32

  3. Ustawiamy domyślną trasę dla routera

    set protocols static route 0.0.0.0/0 next-hop 176.31.233.254

  4. Tworzymy trasę statyczną do serwera wirtualnego (directly connected)

    set protocols static interface-route 178.32.231.141/32 next-hop-interface eth1

  5. Konfigurujemy sieć na serwerze wirtualnym podpiętym do LAN

    ip addr add 178.32.231.141/32 dev eth0
    ip route add 178.33.137.225/32 dev eth0
    ip route add default via 178.33.137.225

Podejście 4 – routing przez Vyatta, failover RIPE

  1. Podobnie jak w 1, przypisujemy IP failover, ale do istniejącego adresu MAC interface\’u WANowego Vyatty (eth0).
  2. Konfigurujemy na obu interface\’ach Vyatty ten sam IP routera – z zewnątrz z maską /32, wewnątrz z maską podsieci

    set interfaces ethernet eth0 address 178.33.137.225/32
    set interfaces ethernet eth1 address 178.33.137.225/29

  3. Ustawiamy domyślną trasę dla routera

    set protocols static route 0.0.0.0/0 next-hop 176.31.233.254

  4. Konfigurujemy sieć na serwerze wirtualnym podpiętym do LAN

    ip addr add 178.33.137.226/29 dev eth0
    ip route add default via 178.33.137.225

Wiele wersji PHP w DirectAdmin używając mod_suphp i mod_macro

Integracja z Directadmin

Od strony DirectAdmina użyliśmy modułu phpsv specjalnie zaadaptowanego dla potrzeb używanej konfiguracji.
Pozwala on na ustawianie osobno wersji PHP dla każdej domeny.

Jeżeli taka możliwość okaże się dla klienta niewystarczająca, możliwe jest ustawienie dla każdej subdomeny, a nawet katalogu osobnej wersji używając opisanego poniżej makra.

 Instalacja wielu wersji PHP

Poszczególne wersje PHP zostały ręcznie skompilowane z różnymi prefiksami, np.:

\”–prefix=/usr/local/php52\”
\”–with-config-file-path=/usr/local/etc/php52/cgi\”

oraz podlinkowane z wygodnymi nazwami w celu uproszczenia używania zadań cron

/usr/local/bin/php-5.2 -> /usr/local/php52/bin/php

Dostępne są php-4.4.9, php-5.2.17, php-5.3.14, php-5.4.4, php-6.0.0.

Każda z nich została zlinkowana z bibliotekami libmysql dla Percona Server oraz w miarę możliwości wyposażona w Zend Optimizer albo Zend Guard Loader oraz ionCube Loader.

Instalacja mod_macro

Dla Apache/2.2.22 używamy modułu w wersji 1.1.11.
Instalacja ogranicza się do pobrania pliku ze strony mod_macro Fabiena Coelho i kompilacji przez apxs zgodnie z plikiem INSTALL.

Dla potrzeb konfiguracji wersji PHP udostępniamy dla użytkowników globalnie następujące macro.

/home/.htaccess:

<Macro php $version>
<FilesMatch \”\\.(inc|php|php3|php4|php5|php6|phtml|phps)$\”>
AddHandler x-httpd-php$version .inc .php .php3 .php4 .php5 .phtml
</FilesMatch>
</Macro>

Którego można użyć w pliku .htaccess wybierając stosowną wersję PHP:

Use php 4
Use php 52
Use php 53
Use php 54
Use php 6

 Konfiguracja mod_suphp

Po standardowej instalacji mod_suphp z custombuild dodajemy stosowne handlery.
W pliku /usr/local/suphp/etc/suphp.conf:

[handlers]
;Handler for php-scripts
x-httpd-php4=\”php:/usr/local/php4/bin/php\”
x-httpd-php52=\”php:/usr/local/php52/bin/php-cgi\”
x-httpd-php53=\”php:/usr/local/php53/bin/php-cgi\”
x-httpd-php54=\”php:/usr/local/php54/bin/php-cgi\”
x-httpd-php6=\”php:/usr/local/php6/bin/php-cgi\”

/etc/httpd/conf/extra/httpd-suphp.conf:

<IfModule mod_suphp.c>
Use php 53
<Location />
suPHP_Engine on
#suPHP_ConfigPath /usr/local/etc/php5/cgi/
suPHP_AddHandler x-httpd-php4
suPHP_AddHandler x-httpd-php52
suPHP_AddHandler x-httpd-php53
suPHP_AddHandler x-httpd-php54
suPHP_AddHandler x-httpd-php6
</Location>
</IfModule>