W tym artykule dowiesz się ode mnie w jaki sposób znalazłem kilka bardzo poważnych błędów w systemie zarządzania treścią Netious CMS. Wszystkie błędy pozwalały na zalogowanie się do systemu z uprawnieniami administratora. Znalezione błędy należą do klas: SQL Injection, Auth Bypass oraz Session Hijacking. Ten ostatni błąd zasługuje na szczególną uwagę, ponieważ jest to bardzo ciekawy przypadek podatności związanej z zarządzaniem sesjami.
Session Hijacking.
Netious sprawdza identyfikator użytkownika w bazie i jeśli równy jest on 1, logujemy się jako administrator. Przyjrzyjmy się bliżej plikowi cms/log.php, który zawiera kod odpowiedzialny za proces logowania się. Poniższy kod stanowi część pliku cms/log.php. Poniżej linie od 9-29
$result=mysql_query("SELECT AdminId FROM mycmsadmin WHERE username='$username' and password='".sha1($password)."'");
$row=mysql_fetch_row($result);
$num_rows = mysql_num_rows($result);
$id=$row[0];
if ($num_rows==1){
$SID=f_ip2dec($REMOTE_ADDR);
if (!session_id($SID))
session_start();
if (!session_is_registered('signed_in'))
session_register('signed_in');
$signed_in = "indeed";
session_register('uname');
$uname = $username;
session_register('pass');
$pass = $password;
if ($id=="1"){
Header( "Location: admin.php");
}
W linii 9 widać zapytanie, które pobiera identyfikator użytkownika. Domyślnie pierwszym użytkownikiem zapisanym w tabeli jest admin. Widzimy, że skrypt nigdzie nie inicjuje zmiennej ‘username’ ani jej nie czyści. Można tutaj wstrzyknąć kod SQL, np. za ‘username’ podstawiając cokolwiek’ or 1=1/*
Zapytanie przyjmie wtedy postać:
SELECT AdminId FROM mycmsadmin WHERE username=” or 1=1/*
or 1=1 ma na celu zwrócenie zawsze wartości prawda, natomiast /* jest znakiem komentarza dalszej części zapytania. Wynikiem zapytania, gdzie zwrócona jest prawda i pobieramy pierwszy wiersz wyniku, będzie zarazem pierwszy wiersz tabeli. Jeśli pierwszym użytkownikiem zarejestrowanym w systemie jest admin, zapytanie to pozwoli na zalogowanie się z jego uprawnieniami, co de facto ma miejsce w przypadku tego systemu CMS.
Auth Bypass
Błąd SQL Injection, powoduje również możliwość autoryzacji jako administrator, czyli mamy kolejny błąd – Auth Bypass.
Ostatnim błędem jaki opiszę, jest bardzo ciekawy błąd zarządzania sesjami. Błędny kod znajduje się w pliku cms/admin.php. Linie od 11-27:
$SUID=f_ip2dec($REMOTE_ADDR);
if (!session_id($SUID))
session_start();
$username=$_SESSION['uname'];
$password=$_SESSION['pass'];
$result=mysql_query("SELECT AdminId FROM mycmsadmin WHERE username='$username' and password='".sha1($password)."'");
$row=mysql_fetch_row($result);
$num_rows = mysql_num_rows($result);
$id=$row[0];
if ($_SESSION['signed_in']!='indeed' || $num_rows!=1 || $id!=1){
Header( "Location: index.php?action=2");
}else{ ------------>admin control panel
Jak wygląda ścieżka wykorzystania podatności
Przeanalizujmy studium przypadku zwracając uwagę na powyższe fragmenty skryptów:
- Administrator znajdujący się w sieci lokalnej loguje się do systemu CMS, podając login i hasło administracyjne.
- Generowany jest identyfikator sesji, bazując na adresie IP z którym ‘wychodzi’ na zewnątrz administrator.
- Ustawiane są odpowiednie zmienne sesyjne ‘uname’ i ‘pass’ na login i hasło administratora
- Następuje przekierowanie na admin.php
- W tym momencie każdy użytkownik, którego zewnętrzny adres IP będzie taki sam jak IP administratora, wystarczy że odwiedzi stronę admin.php i dzięki mechanizmowi sesji, której zmienne już wcześniej ustawione zostały po zalogowaniu administratora, uzyskuje najwyższe uprawnienia.
Problem tkwi tutaj w tym, że zmienna sesyjna w pliku admin.php przyjmuje wartość numeryczną adresu IP użytkownika. Na jej podstawie następuje zapytanie do bazy a co za tym idzie autoryzacja użytkownika. Problem w tym, że wcześniej zalogowany administrator utworzył właśnie taki sam identyfikator. Z jego uprawnieniami będzie działał każdy użytkownik, który dzierżawi ten sam adres IP. Nie konieczne jest nawet logowanie – sesja jeśli nie była ustanowiona w momencie logowania, to i tak zostanie zainicjalizowana po wejściu na stronę admin.php, a jak wiemy wszystkie zmienne ‘administracyjne’ są już w niej wcześniej ustawione.