Atak na wp-login.php i bardzo proste rozwiązanie

Dzisiejszy dzień zaczął się dla mnie dość dziwnie. Poranne spotkanie z klientem skończyło się przed czasem z powodu nie działania serwisu testowego! Co dziwne, czasem coś się pojawiało, czasem nic. Generalnie dość nieprzyjemna sytuacja, biorąc pod uwagę, działo się to na prezentacji serwisu, krótko przed odbiorem.

A że serwis testowy jest jedną z witryn w sieci blogów, do której należy również ta witryna, to również ona nie działa.

Szybkie logowanie na serwer pozwala ustalić przyczynę.
Do pliku

wp-login.php

leciało i leci kilka zapytań na sekundę, jako spreparowane próby zalogowania się.

Dane oczywiście słownikowe typu:

administrator cheetah
administrator older
administrator sabres
administrator some
admin wings
iworks beaches
iworks greenbay
iworks legacy
iworks pothead
iworks spice

Serwer www oczywiście bez kłopotu to przyjmował, ale niestety proces fcgi już nie dawał rady z obsługiwaniem takiej liczby zapytań.

Rozwiązanie jest bardzo proste:

Należy zmienić nazwę pliku wp-login.php na inną.

No taaaak.

Ja się zaloguję, ale co z klientami? Z idącym cały czas atakiem nie ma problemu, bo to nie jest próba logowania na adres, który otrzymujemy po próbie dostępu do zasobu wp-admin, tylko brutalne wysyłanie metodą POST.

Tutaj na pomocą przychodzi filtr site_urlFunction Reference/site_url, który można wykorzystać do zmiany docelowego pliku wykonującego logowanie.
W celu zapewnienia wyświetlania poprawnej strony po wylogowaniu skorzystać należy z filtra wp_redirectFunction Reference/wp_redirect.

Kod obsługujący:

class iWorks_WP_Login_Redirect
{
    private $login_prefix = 'vuushiep-';
    public function __construct()
    {
        add_filter( 'site_url', array( &$this, 'site_url' ), 10, 2 );
        add_filter( 'wp_redirect', array( &$this, 'wp_redirect' ), 10, 2 );
    }
    public function site_url( $url, $file )
    {
        if ( 'wp-login.php' == $file ) {
            return preg_replace( '/wp-login/', $this->login_prefix.'wp-login', $url );
        }
        return $url;
    }
    public function wp_redirect( $location, $status )
    {
        if ( 'wp-login.php?loggedout=true' == $location ) {
            return preg_replace( '/wp-login/', $this->login_prefix.'wp-login', $location );
        }
        return $location;
    }
}
new iWorks_WP_Login_Redirect();

Cały kod mikro-wtyczki możesz pobrać i używać do woli.

Bardzo ważne!

Zmień nazwę pliku wp-login.php na taki który będzie zgodny z tym co używa załączony kod, tutaj vuushiep-wp-login.php

Łukasz zwrócił uwagę na dość istotną kwestię. Jeżeli nie macie CGI, tylko standardowy serwer www, to zawartość pliku

wp-login.php

wymienić na następujący kod:

<?php
header("HTTP/1.0 404 Not Found");
[zip href="http://iworks.pl/wp-content/uploads/2013/09/iworks-wp-login.php-.zip"]iworks-wp-login.php.zip[/zip]

Aktualizacja 2013-09-25 20:30

Można dodać do serwera regułę banującą dostęp do pliku wp-login.php w celu uniknięcia dostępu do niego po aktualizacji i co za tym idzie odtworzeniu pliku.

Konfiguracja dla nginxa:

location ~ ^/wp-login\.php {
    deny all;
}

konfiguracja dla apache’a:

<files wp-login.php>
deny from all
</files>

Aktualizacja: 2014-11-21

Ja u siebie (nginx) nie kasuję pliku wp-login.php a tylko tworzę do niego symlinka. W rezultacie plik zawsze jest aktualny. Dostęp do niego jest zakazany w konfiguracji wirtuala (nginx).

Aktualizacja: 2016-09-21

Zamiast kasować/zmieniać nazwę pliku wp-login.php lepiej jest zrobić symlinka do pliki wp-login.php oraz zablokować sam plik wp-login.php w konfiguracji wirtuala lub za pomocą pliku .htaccess.

Po zalogowaniu się i przejściu do odpowiedniego folderu:

ln -s wp-login.php vuushiep-wp-login.php

Jak użyć podany kod?

Masz 3 rozwiązania:

  1. wrzuć załączony plik do folderu wp-content/plugins, a potem włącz wtyczkę
  2. wrzuć załączony plik do folderu wp-content/mu-plugins
  3. dołącz kod do pliku functions.php aktualnie używanego motywu

Poprzedni

Motyw WordPress – ASM

Następne

WordCamp Wrocław 2013 – Moja prezentacja

14 komentarzy

  1. Problem tylko taki, że wp-login.php przywróci się po aktualizacji WP…
    Poza tym – WP nie będzie serwował swojej 404 przy próbie wejścia na nieistniejący wp-login.php?

    • @Łukasz: faktycznie dla instalacji na apache’u będzie taka sytuacja jak przedstawiasz i wtedy warto zawartość pliku wp-login.php zmienić na header(„HTTP/1.0 404 Not Found”, dopiszę to.

  2. Jenny

    „iworks beaches” <3 (chociaż w wersji audio brzmi lepiej ;))

  3. DMati

    Nie lepiej zamiast tykać wp pliki ukryć url do wp-login.php?

    W efekcie zamiast:

    domena.pl/wp-login.php => np: domena.pl/kokpit

  4. Paweł

    @Marcin, zostaje jeszcze kwestia wspomnianych aktualizacji, które skutecznie rozkładają całość przywracając oryginalny wp-login, no i konieczność ręcznej zabawy plikami…

    Aby to było funkcjonalne jako wtyczka, wypadało by nieco rozwinąć -automatyzując operacje plikowe- mam nadzieję, że rozumiesz w czym rzecz. ;)
    -pewną podpowiedź do tego typu rozwinięcia może stanowić taki najprostszy przykładzik pastebin.com/P7CBttfH

  5. Nie łatwiej wyciąć dostęp do pliku przez dodatkową autoryzacje (na apache / lighttpd / nginx)? Klient będzie musiał (raz na jakiś czas) wpisać swój login i hasło dwukrotnie.

    Oczywiście boty pewnie będą dalej próbowały się dobijać, ale obciążenie serwera powinno spaść niemal do zera.

  6. @Paweł: śmiało, kod jest na GPL można pobierać i modyfikować. :D Ale! u mnie serwer www nie ma prawa zapisu w katalogach oprócz /wp-content/files (instalacja MU).

    Kod aktualizuję bezpośrednio z subversion (svn switch) co dodatkowo ma kilka zalet i wad, ale generalnie będę widział pliki, szczególnie że svn zakrzyczy że plik wp-login.php znikł trzeba go odtworzyć.

    Zawsze można dać w konfiguracji virtuala bana na /wp-login.php, więc kwestię pojawianie się pliku uważam za ogarniętą.

    @Daggerka: można, ale podstawowe logowanie dla niektórych klientów jest problemem, podwójne będzie dla nich nie do ogarnięcia.

    Chyba najlepszym rozwiązaniem jest zbanowanie tego pliku w konfiguracji vhosta.

  7. Maciej

    Jeszcze pojawia się kwestia odzyskiwania hasła.
    Po zmianie nazwy pliku wp-login.php na własną można się logować, natomiast po próbie odzyskiwania hasła (wpisanie nazwy użytkownika i akceptacja) przekierowuje na domyślny plik wp-login.php

    • @Maciej: faktycznie tak jest, już zacząłem pisać dalej (tutaj działa odzyskiwanie hasła), ale sprawa okazała się bardzo pracochłonna, bo jest bardzo dużo akcji związanych z logowaniem. Dodatkowo okazało się, że to co już zrobiłem nie działa dla WordPressa stojącego jako pojedyncza strona (ten tutaj to multisite) i jak testowałem lokalnie wyszło, że odzyskiwanie hasła nadal nie działa.

      Jak tylko znajdę troszkę czasu, postaram się całość opanować i udostępnić.

  8. Maciej Budzisz

    Najważniejsze, że już jakaś podstawa jest, która zabezpiecza przed atakiem.
    U mnie zaczęło się od jednej strony, a później kolejne zaczęły być atakowane. Klienci się rzadko sami logują do panelu, więc na razie powinno to wystarczyć.
    To i tak duża pomoc z Twojej strony, za którą dziękuję. Czekam również na zmodernizowane rozwiązanie ;)

  9. Artur Jaskólski

    Zgadzam się z DMati. Najprostszym rozwiązaniem będzie dodanie do pliku .htaccess nastepującej reguły:

    RewriteRule ^/kokpit/?$ /wp-login.php [QSA,L]

    To powinno załatwić sprawę.

  10. Łukasz

    Metoda bardzo dobrze działa dla wp-login.php, ale w momencie wejścia przez końcówkę /admin lub /login od razu przenosi nas do symlinka i odkrywa go w pasku adresu – czyli /de facto bardzo łatwo odkryć jaki jest nowy adres pliku php związanego z logowaniem i go atakować.

    • Łukasz, masz 100% racji. Wejście na /wp-admin/ też odkrywa nowy adres. Cała metoda nie ma na celu „całkowitego ukrycia” (co jest oczywiście możliwe), tylko na mocne ograniczenie automatycznych ataków. Atakujący nie sprawdzają odpowiedzi, nie podążają za przekierowaniami i nie atakują niestandardowych adres. Jeżeli tak prosta metoda może ograniczyć 99% ataków, to i tak IMVHO warto ją zastosować – u mnie się sprawdziło.

Oparte na WordPress & Theme by Anders Norén