Provjera sigurnosnih propusta pomoću integracijskih testova
Član:
Fran Švarc
Sadržaj |
Općenito
Integracijski test je tip softverskog testiranja u kojem se testiraju veće funkcionalnosti aplikacije. Provode se nakon unit testova koji testiraju manje dijelove aplikacije.
Unutar ovog projekta prikazat ću načine testiranja web stranice koristeći javu, junit i selenide.
Za potrebe testiranja je izrađena web stranica za koju ću izraditi testove.
Stranica sadrži implementacije sa sigurnosnim propustima te sa implementiranom zaštitom kako bi demonstrirali pad i prolaz integracijskog testa.
Selenide
Selenide je java framework za testiranje baziran na Selenium frameworku.
Unutar projekta korišten je Selenide umjesto Seleniuma zbog jednostavnije sintakse koja omogućava brže pisanje testova te jednostavne provjere raznih uvjeta nad html elementima.
Sintaksa Selenida asocira na jQuery.
$(By.id("gumb")).click();
$ oznavačava funkciju za selektiranje.
Unutar funckije $ odabiremo na koji način želimo odabrati html element. Koristimo objekt By kako bi odabrali atribut pomoću kojeg želimo odabrati element.
U ovom slučaju koristimo id te šaljemo string sa vrijednosti id-a. Na kraju se odabire željena akcija na element(u ovom slučaju click).
Kao što je već rečeno iznad Selenide omogućava i provjeru različitih stanja elemenata. Npr.
$(By.id("uspjeh")).shouldNot(Condition.exist);
Ova linija koda provjerava da li element sa id-om uspjeh ne postoji. Ukoliko postoji test se zaustavlja i ne prolazi.
Page Object
Za izradu testova korišten je page object pattern. Page object je objekt koji predstavlja određenu web stranicu. Page object prikazuje programeru sve mogućnosti web stranice kojoj pripada. Page object je jedini objekt koji poznaje web stranicu i direktno s njom komunicira(generalno pravilo je 1 html stranica -> 1 page object). Ovaj način pisanja testova omogućava brzu izmjenu svih testova ukoliko dođe do promjene na web stranici budući da se sva kontrola te stranice nalazi na jednom mjestu. Također ovaj način smanjuje repeticiju koda. Selenide ćemo pronaći samo unutar page objecta dok će klasa za test samo pozivati funkcionalnosti web stranice.
Primjer:
package org.foi.fran.page; import static com.codeborne.selenide.Selenide.$; import static com.codeborne.selenide.Selenide.open; import org.openqa.selenium.By; import com.codeborne.selenide.Condition; public class AuthenticationPage { private static final String LOGIN = "username"; private static final String PASSWORD = "password"; private static final String USPJEH = "uspjesno"; private static final String SIGN_IN = "sign_in"; private static final String PODACI = "podaci"; private static final String GRESKA = "greska"; public void insertLoginValue(String value){ $(By.id(LOGIN)).click(); $(By.id(LOGIN)).sendKeys(value); } public void insertPasswordValue(String value){ $(By.id(PASSWORD)).click(); $(By.id(PASSWORD)).sendKeys(value); } public void singIn(){ $(By.id(SIGN_IN)).click(); } public void clickOnPodaci(){ $(By.id(PODACI)).click(); } public void loginShouldNotBeSuccessful(){ $(By.id(USPJEH)).shouldNot(Condition.exist); } public void changeURL(String value){ open(value); } public void checkForError(){ $(By.id(GRESKA)).should(Condition.exist); } public void goToIndex(){ open("http://46.101.243.189"); } }
Iznad vidimo primjer page objecta za stranicu u kojoj testiramo autentifikaciju. Vidimo da stranica nudi programeru upis podataka za username, password, klik na tipku za login, provjeru grešaka itd..
Sigurnosni propusti
Za primjer testiranja napravljena je web stranica sa sigurnosnim propustima. Konkretno testirat će se SQL injection, XSS i pravilna autorizacija.
SQL injection
SQL injection predstavlja tip sigurnosnog propusta u kojem je korisniku omogućeno direktno slanje SQL upita u bazu koristeći textfield inpute.
Npr. ako korisnik zna korisnicko ime nekog drugog korisnika provjeru za šifru može zakomentirati
Primjer
pperic' --
Kako bismo to spriječili koristimo tzv. pripremljene SQL upite te dodajemo podatke koje je korisnik poslao naknado pomoću naredbe execute. Na ovaj način baza podataka unešene podatke tretira kao vrijednost za usporedbu a ne kao SQL.
Primjer:
<?php if ($_SERVER['REQUEST_METHOD'] == 'POST'){ $username = $_POST["username"]; $password = $_POST["password"]; $ispis; $dbconn = pg_connect("host=localhost port=5432 dbname=postgres user=user password=password"); if(isset($username) && isset($password)){ $result = pg_prepare($dbconn, "my_query", 'SELECT * FROM korisnici WHERE username = $1 AND password=$2'); $result = pg_execute($dbconn, "my_query", array($username, hash("sha256", $password))); } if(pg_affected_rows($result)==1){ $ispis='<div id="uspjesno" class="alert alert-success" role="alert">Uspjesna prijava! </div>'; }else{ $ispis='<div id="greska" class="alert alert-danger" role="alert">Ovaj korisnik ne postoji!</div>'; } } ?>
XSS
XSS ili cross site scripting je tip sigurnosnog propusta u kojem korisnik može ubaciti skriptu koja se izvršava na klijentskoj strani u web stranicu. Ovo je čest sigurnosni propust na web stranicama.
Budući da u našem primjeru za testiranje sadržaj koji upisuje korisnik stavljamo u sam sadržaj, a ne u atribute htmla ili nešto slično korištena je jednostavna zaštita u kojoj koristimo escapanje sadržaj. Escaping označava pojam koji opisuje postupak pretvorbe određenog znaka u njegov kod. Kada to napravimo browser tretira taj znak kao tekst, a ne kao skriptu(ako ju je korisnik unio).
Primjer:
$komentar = htmlspecialchars($komentar, ENT_QUOTES);
U ovom slučaju pozivamo php funkciju htmlspecialchars kojoj šaljamo varijablu za escape i ENT_QUOTES koja označava da se također moraju i escape-ati znakovi " i ' uz znakove < , > i & .
Insecure Direct Object References
Zadnji primjer je jednostavni primjer loše autorizacije. U primjeru korisnik može promjenom parametara koji su poslani GET-om pregledati osobne podatke drugih korisnika.
Ovaj propust je složen usporedbom varijable spremljene u $_SESSIONU(nakon logina) i get-u. Ako id-evi korisnika nisu isto, onemogućen je pristup podacima.
if(isset( $_GET['idkorisnik']) && $_GET['idkorisnik'] == $_SESSION['id_korisnik'])
Također sve šifre u bazi podataka su hashirane sa sha256 kako bi se zaštitili podaci u slučaju curenja podataka.
Primjer integracijskog testa
U ovom poglavlju je prikazan kompletan kod potreban za izvršavanje integracijskog testa. Ovaj test prolazi kroz sve slučajeve i provjerava je li zaštita implementirana.
Page objecti
Page object za početnu stranicu. Ovaj page object nam omogućava kretanje do svih stranica koje testiramo.
package org.foi.fran.page; import static com.codeborne.selenide.Selenide.$; import static com.codeborne.selenide.Selenide.open; import org.openqa.selenium.By; public class IndexPage { private static final String SQL_INJECTION_PAGE = "sql"; private static final String XSS_PAGE = "xss"; private static final String SESSION_PAGE = "session"; private static final String SQL_FIX_PAGE = "sqlfix"; private static final String XSS_FIX_PAGE = "xssfix"; private static final String SESSION_FIX_PAGE = "sessionfix"; public IndexPage() { open("http://46.101.243.189/"); } public SqlInjectionPage openSqlInjectionPage(){ $(By.id(SQL_INJECTION_PAGE)).click(); return new SqlInjectionPage(); } public XssPage openXssPage(){ $(By.id(XSS_PAGE)).click(); return new XssPage(); } public AuthenticationPage openAuthenticationPage(){ $(By.id(SESSION_PAGE)).click(); return new AuthenticationPage(); } public SqlInjectionPage openSqlInjectionFixPage(){ $(By.id(SQL_FIX_PAGE)).click(); return new SqlInjectionPage(); } public XssPage openXssFixPage(){ $(By.id(XSS_FIX_PAGE)).click(); return new XssPage(); } public AuthenticationPage openAuthenticationFixPage(){ $(By.id(SESSION_FIX_PAGE)).click(); return new AuthenticationPage(); } }
Page object za testiranje sql injection. Unutar ovog page objecta imamo sve potrebne funkcije za popunjavanje forme i ulogiravanje.
package org.foi.fran.page; import static com.codeborne.selenide.Selenide.$; import static com.codeborne.selenide.Selenide.open; import org.openqa.selenium.By; import com.codeborne.selenide.Condition; public class SqlInjectionPage { private static final String LOGIN = "username"; private static final String PASSWORD = "password"; private static final String USPJEH = "uspjesno"; private static final String SIGN_IN = "sign_in"; public void insertLoginValue(String value){ $(By.id(LOGIN)).click(); $(By.id(LOGIN)).sendKeys(value); } public void insertPasswordValue(String value){ $(By.id(PASSWORD)).click(); $(By.id(PASSWORD)).sendKeys(value); } public void singIn(){ $(By.id(SIGN_IN)).click(); } public void loginShouldNotBeSuccessful(){ $(By.id(USPJEH)).shouldNot(Condition.exist); } public void goToIndex(){ open("http://46.101.243.189"); } }
Page object za testiranje XSS-a. ovaj page object sadrži funkcije za unos podataka u formu komentari i slanje komentara.
package org.foi.fran.page; import static com.codeborne.selenide.Selenide.$; import static com.codeborne.selenide.Selenide.confirm; import static com.codeborne.selenide.Selenide.open; import org.openqa.selenium.By; public class XssPage { private static final String KOMENTAR = "komentar"; private static final String SEND_BTN = "send"; public void insertCommentValue(String value){ $(By.id(KOMENTAR)).click(); $(By.id(KOMENTAR)).sendKeys(value); } public void sendComment(){ $(By.id(SEND_BTN)).click(); } public boolean isAlertPresent() { try{ confirm("hello"); return true; }catch(Exception ex){ return false; } } public void goToIndex(){ open("http://46.101.243.189"); } }
Page object za testiranje autentifikacije i autorizacije u kojem pokušavamo pristupiti podacima kojima nebi smjeli. Također sadrži sve funkcije za logiranje i klikanje po elementima stranice.
package org.foi.fran.page; import static com.codeborne.selenide.Selenide.$; import static com.codeborne.selenide.Selenide.open; import org.openqa.selenium.By; import com.codeborne.selenide.Condition; public class AuthenticationPage { private static final String LOGIN = "username"; private static final String PASSWORD = "password"; private static final String USPJEH = "uspjesno"; private static final String SIGN_IN = "sign_in"; private static final String PODACI = "podaci"; private static final String GRESKA = "greska"; public void insertLoginValue(String value){ $(By.id(LOGIN)).click(); $(By.id(LOGIN)).sendKeys(value); } public void insertPasswordValue(String value){ $(By.id(PASSWORD)).click(); $(By.id(PASSWORD)).sendKeys(value); } public void singIn(){ $(By.id(SIGN_IN)).click(); } public void clickOnPodaci(){ $(By.id(PODACI)).click(); } public void loginShouldNotBeSuccessful(){ $(By.id(USPJEH)).shouldNot(Condition.exist); } public void changeURL(String value){ open(value); } public void checkForError(){ $(By.id(GRESKA)).should(Condition.exist); } public void goToIndex(){ open("http://46.101.243.189"); } }
Integracijski test
Unutar ovog integracijskog testa koristimo page objecte kako bi testirali postoje li sigurnosni propusti. Ovaj test konkretno pokreće stranice u kojima je implementirana zaštita te zbog toga prolazi. Na github linku se nalaze ostali testovi koji pokreću stranice na kojima nije implementirana zaštita i ne prolaze
package org.foi.fran.test; import static org.junit.Assert.assertFalse; import org.foi.fran.page.AuthenticationPage; import org.foi.fran.page.IndexPage; import org.foi.fran.page.SqlInjectionPage; import org.foi.fran.page.XssPage; import org.junit.Test; public class AllSecurityTest { private String loginValue = "pperic"; private String sqlInjectionString = "1' or '1' = '1' LIMIT '1"; private String password = "1Hxlmx"; private String xssScript = "<script>alert(\"hello\") </script>"; private IndexPage index; @Test public void mainTestMethod() { sqlInjection(); xss(); authentication(); } private void sqlInjection(){ index = new IndexPage(); SqlInjectionPage sqlInjection = index.openSqlInjectionFixPage(); sqlInjection.insertLoginValue(loginValue); sqlInjection.insertPasswordValue(sqlInjectionString); sqlInjection.singIn(); sqlInjection.loginShouldNotBeSuccessful(); sqlInjection.goToIndex(); } private void xss(){ XssPage xss = index.openXssFixPage(); xss.insertCommentValue(xssScript); xss.sendComment(); assertFalse(xss.isAlertPresent()); xss.goToIndex(); } private void authentication(){ IndexPage index = new IndexPage(); AuthenticationPage authenticationPage = index.openAuthenticationFixPage(); authenticationPage.insertLoginValue(loginValue); authenticationPage.insertPasswordValue(password); authenticationPage.singIn(); authenticationPage.clickOnPodaci(); authenticationPage.changeURL("http://46.101.243.189/pages/podaci_fix.php?idkorisnik=2"); authenticationPage.checkForError(); } }
Video testova
Link na youtube video - sve zaštite rade Uspjesan integracijski test
Link na youtube video - SQL injection prolazi i uspješno smo se ulogirali bez lozinke(test pada) Neuspjesan integracijski test
Linkovi
Repozitorij integracijskih testova [1]