ASP.NET Security
Član:
- Nikola Majcen
Poveznice:
--Nikola Majcen 22:35, 9. studenog 2015. (CET)
Sadržaj |
Uvod
Za razvijanje web aplikacija postoje razna rješenja i mogućnosti. Jedna od tih mogućnosti je ASP.NET, odnosno Microsoftov razvojni okvir (engl. framework) za izradu web aplikacija. ASP.NET sadrži razne elemente i načine za razvijanje web aplikacija, a to su WebForms, MVC, WebAPI i slično. Ono što je važno napomenuti jest da svaka vrsta web aplikacije, bilo da se radi o MVC-u, WebForms itd. donosi određene prednosti i nedostatke.
U ovom radu ću se orijentirati na sigurnost ASP.NET MVC-a pošto je to trenutno preporučeni način izrade web aplikacija, sadrži i mnogo sigurnosnih elemenata koji su već automatski implementirani.
SQL Injection
Kao što je poznato, većina web aplikacija omogućuje korisniku da unese određene podatke u formu za unos podataka. Ukoliko zanemarimo osnovne radnje koje korisnik može napraviti koristeći navedene forme, postoji opasnost da se takve funkcionalnosti aplikacije zloupotrijebe. Primjer toga je SQL injection. SQL injection je tehnika napada kojom se želi umetnuti određen SQL programski kod zbog određene ranjivosti web aplikacije, kako bi se došlo do određenih podataka ili bi se izmjenili podaci u bazi podataka. Naravno, važno je da se ovom tehnikom zapravo želi promijeniti kod originalnog upita nad bazom podataka i tako izmijeniti sam upit, što se manifestira radnjom koja nije predviđena.
Primjer
Da bih konkretnije prikazao problem SQL injection-a, slijedi vrlo jednostavan primjer. Zamislimo da imamo sljedeći upit:
private List<Result> GetByQuery(String input) { String query = "SELECT FirstName, LastName, Country FROM Employees " + "WHERE FirstName LIKE '" + input + "' OR LastName LIKE '" + input + "'"; List<Result> results = new List<Result>(); using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString)) { connection.Open(); using (SqlCommand command = new SqlCommand(query, connection)) { SqlDataReader data = command.ExecuteReader(); while(data.Read()) { Object[] row = new Object[data.FieldCount]; data.GetValues(row); results.Add(new Result(row)); } } } return results; }
Ukoliko unesemo neko ime ili prezime (primjerice "Marko"), dobivamo sljedeći upit:
SELECT FirstName, LastName, Country FROM Employees WHERE FirstName Like 'Marko' OR LastName LIKE 'Marko'
Ukoliko se unese bilo koje ime ili prezime, bit će ispisana navedena osoba. Međutim, problem koji se javlja je u tome što se unos korisnika konkatenira odnosno spaja u string te se nakon toga izvršava kao konačni upit. Da bi se na jednostavan način napravio SQL injection, potrebno je samo umjesto imena ili prezimena unijeti sljedeće:
' OR 'a'=='a'--
Na taj način upit postaje sljedeći:
SELECT FirstName, LastName, Country FROM Employees WHERE FirstName Like OR 'a'=='a'-- OR LastName LIKE 'Marko'
Ono što vidimo jest da imamo upit koji će nam vratiti sve vrijednosti iz razloga jer je 'a'=='a' uvijek točno, dok je ostali dio upita odnosno koda zanemaren radi znaka "--".
Isto tako, primjer koji se kasnije nalazi u videu jest ukoliko se u polje za unos unese sljedeće: "%'--", pa je sve nakon znaka "--" opet zanemareno. Konačni upit izgleda ovako:
SELECT FirstName, LastName, Country FROM Employees WHERE FirstName Like '%'--' OR LastName LIKE 'Marko'
Prevencija SQL injection-a
Prevencija ovakve tehnike napada može se izbjeći vrlo lako, a dane su i određene upute:
U nastavku ću prikazati na koji način se SQL injection može izbjeći u ASP.NET MVC-u.
Parametrizirani upit
Prvo ću objasniti i prikazati korištenje parametriziranog upita. Ovaj način je jedan od preporučenih načina kako spriječiti SQL injection. Opet ću krenuti od primjera:
private List<Result> GetByParams(String input)
{ String query = "SELECT FirstName, LastName, Country FROM Employees WHERE FirstName LIKE @Name OR LastName LIKE @Name"; List<Result> results = new List<Result>(); using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString)) { connection.Open(); using (SqlCommand command = new SqlCommand(query, connection)) { command.Parameters.Add(new SqlParameter("@Name", input)); SqlDataReader data = command.ExecuteReader(); while(data.Read()) { Object[] row = new object[data.FieldCount]; data.GetValues(row); results.Add(new Result(row)); } } } return results; }
Ovdje vidimo da nakon što korisnik unese određeni unos, taj unos se ne konkatenira u sam upit. To je vrlo važno, jer ukoliko bismo načinili tu grešku, onda bismo opet imali propust kao i kod primjera SQL injection-a. Prema tome, potrebno je samo u upitu naznačiti naziv parametra (parametri se definiraju na način "@ImeParametra") te ga kasnije dodati prije samog izvođenja upita što se radi na sljedeći način:
command.Parameters.Add(new SqlParameter("@Name", input));
Na ovakav način uspješno se može spriječiti SQL injection napad, iako se i tu mogu potkrasti određeni propusti. U video primjeru je prikazan sličan kod koji radi isto, međutim, vidimo da je ipak uspješno prikazao sve elemente odnosno korisnike iz tablice.
String query = @"SELECT FirstName, LastName, Country FROM Employees WHERE FirstName LIKE '%' + @Name + '%' OR LastName LIKE '%' + @Name + '%'";
Upravo zbog konkatenacije bilo je moguće provesti SQL injection, ali ne uz sve mogućnosti koje je bilo moguće uz "obični" upit. Ipak, to je i dalje određeni propust pa je preporučljivo da se izbjegava konkatenacija.
Stored procedures
Sljedeći način je zaštita od SQL injection-a pomoću procedura. To je zapravo način gdje također proslijeđujemo parametre u unaprijed definirane procedure koje će izvršiti upit, ali ovdje postoje određene prednosti. Naime, moguće je definirati zaštitu na razini korisnika, odnosno, koji korisnici mogu koristiti određene tablice, tj. koje akcije mogu raditi, što je definirano u samoj bazi podataka. Na ovakav način vrlo je teško pribaviti podatke, ako korisnik nema mogućnost čitanja ili pisanja za određenu tablicu u bazi podataka. Ipak, potrebno je reći da u ponekim slučajevima nije moguće mijenjati ovlasti za korisnike u bazi podataka, pa ovakva vrsta zaštite nije uvijek moguća.
Primjer pozivanja procedure:
using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString)) { using (SqlCommand command = new SqlCommand("ADD_NEW", connection)) { command.CommandType = CommandType.StoredProcedure; command.Parameters.AddWithValue("@FirstName", firstName); command.Parameters.AddWithValue("@LastName", lastName); connection.Open(); command.ExecuteNonQuery(); } }
Upotreba ORM-a
Posljednji način koji ću navesti je uz korištenje ORM-a. ORM je kratica za Object-relation maping, a Microsoft ga je nazvao Entity Framework. Entity Framework olakšava rad samom programeru pošto se u ovom slučaju ne mora brinuti o samom upitu i na koji način je on strukturiran, već pomoću definiranih metoda na temelju unosa korisnika pretražuje bazu za određni objekt.
Da bi Entity Framework uopće mogao raditi, mora biti instaliran u sam projekt, što je u slučaju ASP.NET MVC-a automatski podešeno. Kako bi se moglo pristupiti određenim elementima odnosno objektima iz tablice, potrebno je napraviti sljedeće:
Za početak ću prikazati primjer jedne klase:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Web; namespace AspnetSecurity.Models { public class Employee { [Key] public int EmployeeId { get; set; } public string Username; public string FirstName { get; set; } public string LastName { get; set; } public string City { get; set; } public string Country { get; set; } public virtual IEnumerable<Account> Accounts { get; set; } } }
Ovdje vidimo standardni prikaz klase, gdje imamo anotaciju za primarni ključ, odnosno za atribut koji će poprimiti vrijednost primarnog ključa u bazi. Ukoliko se prate konvencije Entity Framework-a, dovoljno je samo definirati atribut Id
ili nazivKlaseId
bez anotacije [Key]
. Isto tako, ovdje se nalazi i atribut Accounts
dok to uz virtual
predstavlja navigacijsko svojstvo, odnosno, na taj način je moguće pristupiti drugoj klasi u Entity Frameworku.
Da bi ta klasa bila zapisana u bazi podataka, odnosno da se kreira tablica na temelju ove klase, potrebno je još u klasu ApplicationDbContext
koju možemo naći u datoteci "IdentityModels.cs" unijeti sljedeće:
public DbSet<Account> Accounts { get; set; }
Sada smo definirali klasu koja se može koristiti uz Entity Framework te je potrebno pokrenuti Rebuild aplikacije. Kada je to učinjeno, mogu prikazati na koji način je napravljen upit uz pomoć ORM-a:
private List<Result> GetByOrm(String input) { List<Result> results = new List<Result>(); using (ApplicationDbContext db = new ApplicationDbContext()) { List<Employee> employees = db.Employee .Where(o => o.FirstName == input || o.LastName == input).ToList<Employee>(); foreach (Employee employee in employees) { results.Add(new Result { Column1 = employee.FirstName, Column2 = employee.LastName, Column3 = employee.Country }); } } return results; }
Ovdje je vidljivo da se ne koristi sintaksa niti SQL jezik, već je sve obavljeno pomoću metoda objekta. Naravno, potrebno je reći da se sve obavlja pomoću konteksta, tj. pomoću njega je omogućen pristup bazi podataka. Tako da on vraća objekt koji na temelju određenih metoda vraća vrijednosti iz baze podataka. Ono što je zgodno jest to da se zapravo u samoj pozadini Entity Frameworka koriste parametrizirani upiti, ali je svakako dobro za programera što taj dio njega ne treba zanimati. U video primjeru bit će prikazano korištenje i ove metode koja se pokazala veoma sigurna, a i sam Microsoft preporučuje da se ista koristi.
Video prikaz
U nastavku se nalazi kratki video prikaz SQL injection-a i prevencije istog: "SQL Injection Demonstration"
Parameter Tampering
Parameter tampering je vrlo jednostavan napad koji pomoću modifikacije parametara pokušava promijeniti podatke u sustavu i/ili naštetiti istom. Ovaj napad je vrlo raširen i često ga je vrlo lako provesti jer većina web aplikacija sadrži skrivena polja u kojima su pohranjene vrijednosti kao što su ID korisnika i slično. Postoje određene zaštite od ovakvog napada, ali prije toga ću prikazati kako se isti može realizirati.
Primjer i video prikaz napada
Ovdje ću prikazati vrlo jednostavan način parameter tampering-a, gdje je omogućeno promijeniti podatke jednog korisnika, iz forme za promjenu podataka drugog korisnika. Upravo se ranjivost može vidjeti u sakrivanju ID-a, ali i vidljivosti istog u adresnog traci. Iako te vrijednosti mogu ostati vidljive, naišao sam na zanimljivu biblioteku koja rješava problem ovakve ranjivosti.
Biblioteka se zove MVC Security Extensions, a moguće ju je preuzeti sa sljedećeg linka: "MVC Security Extensions". Da bi se ista uključila u projekt, potrebno je samo dodati referencu u projekt.
Ova biblioteka funkcionira na način da enkriptira ključ (ID) asimetričnim ključem, te se i taj ključ pojavljuje kao skriveni parametar. Međutim, promjenom vrijednosti ID-a (bilo u adresnoj traci ili u skrivenom polju), kada se podaci pošalju natrag na server, taj ID ne odgovara dekriptiranom ključu, pa je onemogućen tampering.
Da bi se ovo omogućilo, potrebno je za početak dodati anotaciju [ValidateAntiModelInjectionAttribute("EmployeeId")]
iznad određene metode u kontroleru sa identifikatorom kao stringom koji predstavlja naziv ID-a entiteta koji se koristi u formi:
[HttpPost] [ValidateAntiForgeryToken] [ValidateAntiModelInjectionAttribute("EmployeeId")] public ActionResult Edit([Bind(Include = "EmployeeId,FirstName,LastName,City,Country")] Employee employee) { if (ModelState.IsValid) { db.Entry(employee).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(employee); }
Isto tako, potrebno je unutar forme unijeti HtmlHelper koji će dio koji je dohvaćen sa servera prikazati na samoj formi, a kasnije ga vratiti serveru:
@Html.AntiModelInjectionFor(o => o.EmployeeId)
Video prikazu moguće je pristupiti sa sljedeće poveznice: "Parameter Tampering Part I"
Iako je ovo samo jedan od načina prevencije, postoje i razni drugi. Jedan od načina je korištenje Identity-a ili Membership-a koji omogućuju da se na temelju kolačića (engl. cookie) vidi koji je korisnik trenutno prijavljen te se vidi da li je zapravo on korisnik koji može raditi određene promjene. Prikaz koda je u nastavku:
UserManager.FindById(User.Identity.GetUserId<int>());
Isto tako, za sam kraj, vrlo je korisno, iako ne i presudno da se na početku metode uvijek provjeri da li je model validan, odnosno, da li sadrži sve podatke koje bi trebao, kako ne bi podaci koji su dobiveni bili krivi, odnosno ne bi zadovoljavali definiranu formu podataka koje smo trebali dobiti. Primjer koda je u nastavku:
if (!ModelState.IsValid) { return View(); }
Prevencija i video prikaz
U prethodnom video primjeru bio je prikazan jedan način na koji je Parameter Tampering vrlo lako rješen. Naravno, to je samo jedno rješenje.
Druga rješenja su sljedeća; jedan od načina jest korištenje Regex
-a odnosno validiranja korisničkog unosa koji može dovesti do SQL Injection-a ili XXS-a. Ovakvu vrstu obrane je vrlo lako napraviti, a moguće je tako što nakon prihvata podataka iz forme jednostavno iskoristimo sljedeće:
input = Regex.Replace(input, "[^a-zA-Z0-9]", "");
Ovdje vidimo da se korisnikov unos filtrira na način da su omogućeni samo znakovi od a do z i od A do Z. Prema tome, vrlo lako je isfiltriran unos korisnika, koje je u ranijim primjerima doveo do SQL Injection-a. Naravno, važno je reći da se ovakve stvari mogu definirati samo onda ukoliko ste sigurni da su potrebni samo znakovi u određenom intervalu.
Drugi primjer također se tiče Regex
-a, ali u ovom slučaju je on definiran na modelu. Na ovaj način definiramo kako će se ponašati određen property, primjerice FirstName
kojem će biti onemogućen unos bilo kojeg drugog znaka osim malog ili velikog slova. Primjer koda je u nastavku, dok na sljedećoj poveznici možete vidjeti video primjer: "Parameter Tampering Part II"
[RegularExpression("^[a-zA-Z]*", ErrorMessage = "Characters only!")]
Information Leakage
Information leakage odnosno curenje informacija predstavlja odavanje informacija o sustavu ili informacija o nekim grešaka sustava. Na ovakav način vrlo je lako moguće dobiti opće informacije o sustavu koji se koristi za pogonjenje određene web aplikacije i/ili koji se elementi koriste, odnosno koji dijelovi sustava koriste. Način na koji najčešće dolazi do curenja informacija jest ukoliko ostanu nepromijenjeni modovi za ispravljanje pogrešaka (engl. debug mode), pa ukoliko dođe do pogreške ne pojavi se stranica koja je namijenjena korisnicima aplikacije, već se pojavi stranica sa svim detaljima koji su uzrokovali pogrešku te što je pošlo po zlu.
Primjeri curenja informacija
Ovakvih primjera ima mnogo, a jedan od njih može se vidjeti u nastavku, gdje je tražilica Google indeksirala određene web aplikacije, a s njima i prikaz stranica o određenoj pogrešci koja uključuje curenje informacija:
Isto tako, moguće je pomoću određenih alata kao što je "Fiddler" vidjeti određene informacije zaglavlja, što u slućaju uključenog moda za ispravljanje pogrešaka daje određene dodatne informacije koje bi trebale biti sakrivene.
Prevencija
Kako bi se spriječilo curenje informacija, ovdje sam naveo najčešća četiri načina kako spriječiti ovaj problem.
Uključivanje definirane stranice s greškom
Za početak, potrebno bi bilo uključiti opciju kojom bi se onemogućilo prikazivanje detalja zbog kojih je došlo do pogreške. Naravno, ovo se radi prije nego se aplikacija izdaje, a ne za vrijeme izgradnje aplikacije, jer bi to otežalo njezinu izradu.
Prema tome, treba napraviti sljedeće; otvoriti datoteku web.config
te unutar dijela system.web
unijeti sljedeće:
<customErrors mode="On"/>
Ovo je najosnovniji način na koji je omogućeno prikazivanje definirane stranice s greškom, tj. automatski generirane stranice koja se kreira pri izradi novog projekta. Ukoliko je potrebno, ta stranica se može modificirati, a isto tako, postoji mogućnost prikaza više definiranih stranica s greškom ukoliko za to ima potrebe. Za takav način, potrebno je promijeniti istu stvar, ali s drugačijim parametrima:
<customErrors mode="On" defaultRedirect="~/Error"> <error redirect="~/Error/404Error" statusCode="404" /> </customErrors>
U kodu vidimo da je da se kao osnovni prikaz prikazuje stranica "Error", dok je moguće i pozvati stranicu "404Error", gdje je svaka drugačije kreirana od strane programera. Te stranice prikazuju se ukoliko dođe do pogreške u samom radu sustava, ali ih je isto moguće pozvati kao rezultat određene akcije, a to se radi u samom kontroleru na sljedeći način:
return View("Error"); // ili return View("404Error");
Promjena zaglavlja
Kod ASP.NET aplikacije, moguće je također prikriti odnosno prikazati čim manje nepotrebnih informacija u zaglavlju.Ovdje je prikazano kako maknuti njih nekoliko koji otkrivaju informacije o verziji ASP.NET-a te o verziji sustava.
- Da biste uklonili
X-AspNet-Version
zaglavlje potrebno je unutarweb.config
datoteke podsystem.web
dodati sljedeće:
<httpRuntime enableVersionHeader="false"/>
- Da biste uklonili
X-AspNetMvc-Version
zaglavlje potrebno je u datotekuGlobal.asax.cs
(nemojte to zamijeniti saGlobal.asax
) dodati sljedeće:
MvcHandler.DisableMvcResponseHeader = true;
- Da biste uklonili zaglavlje
X-Powered-By
, potrebno je u datoteciweb.config
unijeti sljedeće:
<httpProtocol> <customHeaders> <remove name="X-Powered-By" /> </customHeaders> </httpProtocol>
Sve ove načine moguće je učiniti sve skupa zajedno i programski tako da u Global.asax.cs
dodamo sljedeći programski kod:
Response.Headers.Set("Ime servera","Opis servera"); Response.Headers.Remove("X-AspNet-Version"); Response.Headers.Remove("X-AspNetMvc-Version");
Ovdje vidimo da se kao zaglavlje dodaje korisnički definirano zaglavlje, što je svakako korisno, dok se ostale informacije o sustavku uklanjaju.
Isključivanje trace-a
Ukoliko ste tijekom razvoja u bilo kojem dijelu uključivali tracing, isti je potrebno isključiti, odnosno ukoliko je isti bio automatski uključen, potrebno ga je isključiti, a to se radi na vrlo jednostavan način. Potrebno je samo unutar datoteke <web.config> unijeti sljeće:
<trace enabled="false"/>
Promjena kompilacijskog načina rada
Kao posljeđnji korak prije objave web aplikacije, potrebno je promijeniti način rada aplikacije. Dok je ista bila u fazi razvoja, onda se kompilacija izvršavala u debug modu, što za produkcijsku aplikaciju nije dobro rješenje jer je moguće doći do određenih informacija koje ne bi smjele izaći "van". Upravo iz tog razloga, potrebno je iz "debug" načina rada promijeniti kompilaciju u "release" način rada.
To se može izvršiti pri samoj kompilaciji web aplikacije, ali se isto može promijeniti u "Settings" opcijama prilikom same objave aplikacije.
Za sam kraj, vrijedi napomenuti da se sve ove dosad promijene mogu načiniti uz samo jednu promjenu, a to je da se omogući opcija "retail", odnosno da se u web.config
unutar konfiguracije samog .NET razvojnog okruženja u datoteci machine
doda sljedeće:
<deployment retail="true" />
Omogućavanjem ove opcije, automatski se onemogućuje "debug" način rada, isključuje se tracing te se kompilacija izvršava u "release" načinu rada.==
Cross-Site Request Forgery (CSRF)
Cross-Site Request Forgery je vrsta napada koja prisiljava krajnjeg korisnika da izvršava određene akcije, kojih on je ili najčešće nije svjestan, u aplikaciji u koju je trenutno prijavljen. Ovakvi napadi ne služe za prikupljanje podataka, već za promjenu statusa korisnika, brisanje njegovih podataka ili općenito promjenu i/ili uništavanje podataka web aplikacije. Najčešće se ovakvi napadi manifestiraju preko e-maila ili posjeta određenih stranica.
Primjeri napada
Ovdje ću prikazati dva CSRF napada, odnosno dvije mogućnosti koje se mogu desiti, ukoliko napadač uspješno učini određene korake, a web aplikacija nije kvalitetno sigurnosno rješila ovaj problem, odnosno nema prevencije od CSRF napada.
E-mail i GET metoda (video)
Prvi primjer je vrlo jednostavan, ali i kreativan. Naime, radi se o tome da napadač pošalje e-mail nekom korisniku, gdje se nalazi neka poveznica ili slika (u primjeru se koristi poveznica). Bit nije u samoj slici ili poveznici, već u adresi te poveznice odnosno slike. Naime, kada dobijemo poruku od nekog poznatog GMail nas ne upozorava da sadržaj može biti zloćudan, dok kod sumnjivih e-mailova to ipak piše. To naravno nije toliko veliki problem, jer većina korisnika klikne odnosno potvrdi da se učita cijela e-mail poruka ili klikne jednostavno na link koji vidi. Kada se to desi, učitava se stranica, koja zapravo može biti adresa kojom se izvršava određena neželjena akcija u nekoj web aplikaciji. Upravo je to prvi primjer koji ću ovdje prikazati.
Prva stvar odnosno problem koji se ovdje dešava jest, ukoliko pogledamo programski kod, da se akcija brisanja korisnika događa kada se GET metodom poziva određena metoda. Prema OWASP-u, preporuča se da se GET metoda koristi samo za dohvat podataka.
[Authorize] [HttpGet] public ActionResult Delete(int id) { Employee employee = db.Employee.Find(id); if(employee != null) { db.Employee.Remove(employee); db.SaveChanges(); } return RedirectToAction("Index"); }
Isto tako, vidimo da je metoda osigurana sa anotacijom Authorize
da je mogu pozvati samo prijavljeni korisnici. Međutim, ukoliko je korisnik prijavljen, a pozove tu metodu slučajnim klikom na neku poveznicu ili otvaranjem e-maila, dolazi do brisanja podataka, bez ikakve provjere koji je to korisnik učinio, pošto nikakve daljnje provjere nema.
Cijeli primjer se može vidjeti i na videu, a do njega se može doći na sljedećoj poveznici: "Cross-Site Request Forgery with GET and E-mail"
Rješenje POST metodom? (video)
Da li rješenje leži u POST metodi? Definitivno otežavamo posao napadaču iz razloga jer se on ne može osloniti na sam pristup stranici pošto će se u tom slučaju koristiti GET metoda koja služi za prihvat podataka. Na taj način se riješio taj problem. Prikaz koda je u nastavku:
Ovdje vidimo GET metodu koja samo vraća podatke, odnosno pogled s detaljima korisnika, gdje se zatim pita korisnika da li je siguran da želi obrisati navedenog korisnika:
[Authorize] public ActionResult Delete(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Employee employee = db.Employee.Find(id); if (employee == null) { return HttpNotFound(); } return View(employee); }
Isto tako, u nastavku je POST metoda, koja briše korisnika ukoliko je korisnik potvrdio brisanje:
[Authorize] [HttpPost] public ActionResult Delete(int id) { Employee employee = db.Employee.Find(id); db.Employee.Remove(employee); db.SaveChanges(); return RedirectToAction("Index"); }
Međutim, ovdje možemo potencijalno opet vidjeti problem. Iako je korisnik morao potvrditi brisanje korisnika, opet nemamo nikakvu dodatnu provjeru ukoliko je to taj korisnik odnosno ukoliko je zahtjev došao sa te stranice. Naime, na određenim blogovima i sličnim stranicama, moguće je unijeti određene skripte, a prema tome, vrlo je lako unijeti skriptu koja koristi, recimo, jQuery sa POST metodom koja zove ovu akciju. Na taj način zaobilazimo GET metodu i direktno dolazimo do POST metode koja briše korisnika.
Video primjer se nalazi na sljedećoj poveznici "Cross-Site Request Forgery with POST and jQuery", dok je kod skripte u nastavku:
<script src="http://code.jquery.com/jquery-1.12.0.min.js"></script> <script> $.ajax({ method: "POST", url: "http://localhost:52148/Employees/Delete/10", contentType: "application/x-www-form-urlencoded" }); </script>
Prevencija napada u ASP.NET MVC-u
Da bi se riješio problem CSRF-a, u ASP.NET MVC-u postoji jedno vrlo jednostavno rješenje, a to je upotreba Anti-Forgery Tokena. Naime, Anti-Forgery Token funkcionira na sljedeći način:
- Klijent zahtjeva HTML s određenom formom.
- Server u zahtjevu dodaje dva slučajno generirana tokena u formu, jednog kao kolačić (engl. cookie), a drugog u samu formu kao skriveno polje.
- Kada klijent pošalje formu natrag serveru, mora poslati oba tokena, jedan kao kolačić, a jedan se šalje kao podatak u formi.
- Ukoliko server ne dobije oba tokena natrag, ne prihvaća se zahtjev.
Ovakav način prevencije funkcionira iz razloga jer zlonamjerna stranica ili e-mail u kojemu je određena poveznica ne može pročitati korisnikove tokene, odnosno, može poslati zahtjev, ali napadač ne može dobiti natrag odgovor.
Da bi se omogućio rad sa Anti-Forgery Tokenom, potrebno je dodati ga kao anotaciju za definiranu metodu, te isto tako, unutar forme, potrebno je pozvati HtmlHelper za dodavanje tokena u formu. U nastavku je prikaz metode sa anotacijom:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Delete(int id) { Employee employee = db.Employee.Find(id); db.Employee.Remove(employee); db.SaveChanges(); return RedirectToAction("Index"); }
Potrebno je učinit samo još jedan korak, a to je uključiti HtmlHelper u samu formu:
@using (Html.BeginForm()) { @Html.AntiForgeryToken() // Kod... }
Na ovaj način sprijećen je napad CSRF-a, dok je za neke dodatne potrebe moguće dodati i provjere da li se radi o korisniku koji može učiniti navedene promjene, o čemu će biti riječi kasnije.
Cross-Site Scripting
Cross-Site Scripting napadi su napadi koji služe za injektiranje programskog koda napadača u web aplikaciju koja je inače sigurna. Na taj način, napadač u web aplikaciju unese maliciozni kod, koji korisnik nenamjerno izvrši, što dovodi do napada na web aplikaciju odnosno podatke iste. Sličan primjer bio je prikazan kod CSRF-a, gdje je to prikazano u drugom kontekstu, ali mogli bismo reći da je to XSS napad.
Ono što je kod ovog napada opasno jest da korisnik misli da klikom na određenu akciju radi nešto što je dobro pošto vjeruje određenoj stranici koju posjećuje, a to je zapravo pokretač malicioznog koda. Naravno, kada korisnik to učini, moguće je pristupiti kolačićima, sesijskim tokenima i/ili drugim osjetljivim informacijama korisnika.
Postoji više vrsta XSS napada, a oni se mogu svrstati u sljedeće:
- Persistent XSS - ovakav napad se manifestira kada se maliciozni kod pohrani u bazu podataka, što je posljedica korisničkog unosa. Na taj način se svaki puta kada se podaci dohvaćaju iz baze, zapravo i dohvaća i maliciozni kod.
- Reflected XSS - ovakav napad manifestira se iz korisničkog preglednika, te izvršava određene operacije na serveru i nakon toga vrati određen rezultat korisniku.
- DOM based XSS - ovdje se koristi korisnički preglednik te parsiranje DOM-a koji zapravo omogućuje da se određeni podaci unesu u DOM i na taj način rade štetu.
Primjer i video prikaz
Ovdje ću prikazati na koji način je moguće napraviti vrlo jednostavan napad, odnosno provjeriti da li je web aplikacija ranjiva na XSS napade. U primjeru se nalazi textbox koji služi za unos podataka pretrage. Kada se podaci unesu, izvršava se određena akcija. Međutim, ukoliko se unese nešto poput
<script>alert("Bok")</script>
ASP.NET MVC to shvaća kao potencijalno opasan tekst odnosno kod pošto je automatski uključena opcija validacije korisničkog unosa. Validacija korisničkog unosa naziva se ValidateRequest
koji je omogućen odmah nakon kreiranja projekta, te ga je moguće isključiti ako je to potrebno.
Za onemogućavanje ove zaštite, potrebno je u kontroleru kao anotaciju unijeti:
[ValidateInput(false)]
A isto tako, ukoliko se radi o nekom property-u, što uglavnom i jest slučaj kako bi se sadržaj mogao dalje koristiti ili pohraniti, potrebno je u modelu istog unijeti sljedeće kako bi se to moglo pohraniti ili koristiti kao HTML:
[AllowHtml]
Naravno, potrebno je reći da isključivanje validacije korisničkog unosa može biti štetno za aplikaciju, međutim, ukoliko je to zbilja potrebno, dobro je da se radi određeno validiranje uz pomoć Regex
-a i sl.
Video primjer može se pogledati u nastavku: "Cross-Site Scripting Prevention"
Načini prevencije
Pošto je ovo jedan on najčešćih napada te ih ima više vrsta, postoji određen broj i mnogo načina prevencije ovog napada.
- Prvi način prevencije jest već uključen u ASP.NET MVC, a to je
ValidateRequest
koji služi za validaciju korisničkog unosa, te ukoliko primjeti određene tagove, automatski baca grešku kako se ne bi izvršila određena operacija zbog mogućeg potencijalno opasnog programskog koda. Iako u nekim situacijama korištenje istog nije najbolje rješenje, ranije sam naveo kako se ova opcija može isključiti.
- Drugi način prevencije je korištenje Microsoftove biblioteke
AntiXss
koji sadrži ugrađene metode za dohvaćanje podataka iz HTML-a, JSON-a i slično, odnosno, uzimajući cijeli kod, iz njega izvlači samo podatke, dok s druge strane postoji iHtmlEncode
koji radi na suprotni način, koji zapravo dohvaća sve "opasne" elemente.
- Sljedeća stvar kod prevencije XSS-a je postavljanje odgovarajućeg zaglavlja web aplikacije, primjerice
UTF-8
, pošto sam tijekom izrade ovog rada naišao na materijale koji su ukazivali da su mogući napadi XSS-a upotrebomUTF-7
. Osim enkodiranja, vrlo važno i korisno je koristiti takozvaneSecurity-Policy
mogućnosti zaglavlja, za svaki preglednik posebno. Na taj način se također onemogućuje XSS jer se omogućuje izvršavanje skripti samo iz aplikacije. Primjer kako se stavljaju stavljaju iste u zaglavlje je na sljedeći način:
// Chrome & Safari Response.AddHeader("X-WebKit-CSP", "default-src 'self'"); // Firefox Response.AddHeader("X-Content-Security-Policy", "default-src 'self'");
- Isto tako, vrlo je važno kod upotrebe JSON-a koristiti
Json.Encode()
, odnosnoEncoder.JavascriptEncode()
. - I za sam kraj, bilo bi dobro da se za svaki korisnički unos radi određena provjera, tj. vrlo je jednostavno onemogućiti unos određenih znakova pomoću
Regex
-a ukoliko znamo koje znakove možemo odnosno trebamo prihvaćati.
Zaštita podataka
Kao što znamo, svaki korisnik želi da su njegovi podaci sigurni. Upravo zato, ovo je veoma važan segment izrade web aplikacija, a najviše se veže za samu kriptografiju. Zahvaljujući kriptografiji, moguće je zaštiti komunikaciju s klijentom, njegove osobne podatke, lozinke i sve ostale važne podatke. Prema tome, veoma je korisno koristiti ovaj način zaštite, koji onemogućuje napadaču da dođe do podataka u izvornom obliku.
Korištenje SSL-a
Kod većine stranica, često možemo vidjeti da kod adrese umjesto http
piše https
. To znači da web stranica ili aplikacija ima određeni SSL certifikat što donosi određene sigurnosne benefite.
Stranice koje koriste SSL svakako su sigurnije, odnosno komunikacija je sigurnija pošto je ista enkriptirana, stoga je nerazumljiva drugim korisnicima. U današnje vrijeme SSL certifikati nisu niti toliko skupi, pa se svakako isplati uložiti u sigurnost web aplikacije.
Tip | HTTP | HTTPS |
---|---|---|
PORT | 80 | 443 |
SIGURNOST | Nesiguran | Siguran |
SLOJ | Aplikacijski sloj | Transportni sloj |
ENKRIPCIJA | Ne | Da |
CERTIFIKAT | Nije potreban | Potreban |
Da bi se mogao koristiti SSL, potrebno je isti uključiti. Ipak, prije toga, ukoliko se radi o produkcijskoj aplikaciji, potrebno je imati cerfifikat ili ga napraviti lokalno. Ukoliko se isti koristi za svrhu lokalnog korištenja, Visual Studio će generirati certifikat. Koraci kako omogućiti SSL su sljedeći, ali je važno da zbog automatskog generiranja certifikata pokrenete Visual Studio kao administrator:
- Potrebno je kliknuti na naziv projekta, te odabrati karticu
Properties
- U kartici
Properties
, potrebno je podEnable SSL
odabratiTrue
- Nakon toga, automatski se kao SSL porta stavka
44300
, a ukoliko se isti želi promijeniti, potrebno je prvo kliknuti na naziv projekta desnim klikom i odabratiUnload Project
- Kada smo napravili navedene korake, možemo desnim klikom klinuti na naziv projekta i odabrati
Edit <NazivProjekta>.csproj
- Potrebno je izmjeniti samo jednu liniju koda, te umjesto trenutnog 44300, promijeniti na sljedeće:
<IISExpressSSLPort>443</IISExpressSSLPort>
- Sada je potrebno ponovno učitati projekt s desnim klikom na projekt i odabrati
Reload Project
te pokrenuti aplikaciju. - Ukoliko se prilikom pokretanja pojavi zaslon kao na sljedećoj slici, odaberite
Yes
.
Isto tako, potrebno je napomenuti da se za stranice na kojima želimo imati SSL, trebamo staviti anotaciju:
[RequireHttps]
Moguće je to staviti i na razini klase kontrolera ili Area-a, pa će to vrijediti za navedene dijelove, ali napomena postoji u vezi prijave. Naime, ukoliko je anotacija stavljena na POST metodu kod prijave, isto tako mora i anotacija biti kod GET metode, kako bi se dogodio redirekt kod GET metode, jer se isti neće dogoditi ukoliko GET metoda ne zahtjeva SSL, a POST metoda zahtjeva, pa će doći do greške.
Zaštita lozinke
Zaštita korisničkih lozinki jedno je od vrlo važnih elemenata svake web aplikacije. U slučaju ASP.NET MVC-a, osnovni način pohranjivanja lozinke jest takav da se ona hash-ira i zatim sprema u bazu podataka. Na taj način lozinka nije poznata nikome osim samom korisniku (ovakav način je aktivan ukoliko se koristi Identity koji je defaultno složen pri kreiranju projekta).
O Identity-u ću reći ukratko na kraju, međutim, vrijedi reći da je do korisnički podataka, odnosno lozinke moguće doći pomoću klase ApplicationUserManager
.
ApplicationUserManager manager = new ApplicationUserManager();
Kada se kreira ovaj objekt, on ima metode kao što su:
-
AddPasswordAsync
-
ChangePasswordAsync
-
CheckPasswordAsync
-
GeneratePasswordAsync
-
HasPasswordAsync
-
RemovePasswordAsync
-
ResetPasswordAsync
Pomoću tih metoda, moguće je uspoređivati lozinke koju korisnik unese ili ih resetirati na korisnički zahtjev itd.
Na taj način, također, moguće je i korištenje PasswordHasher
-u i PasswordValidator
-u.
Zaštita baze podataka
Osim zaštite na razini programskog koda, moguće je zaštiti podatke i na razini baze podataka. Taj dio neću detaljno objašnjavati, ali postoje tri metode u SQL Serveru koje omogućavaju enkripciju pojedinih atributa ili cijele tablice, a to su:
-
EncryptByPassphrase
-
EncryptByKey
-
EncryptByCertificate
Također, vrijedi napomenuti da se prije pretraga ili korištenja podataka, također isti moraju dekriptirati.
Detalje i način kako to napraviti na razini baze podataka možete naći na sljedećoj poveznici: "Introduction to SQL Server Encryption and Symmetric Key Encryption Tutorial with Script"
Denial of Service
DoS napad je napad kojemu je cilj učiniti određeni resurs ili uslugu nedostupnom kako ju korisnik ne bi mogao u određenom trenutku koristiti. Ovaj napad manifestira se u velikom broju zahtjeva, i to veličine takve da server ne može obrađivati zahtjeve, pa dolazi do toga da je web aplikacija koja se pokreće na tom serveru nedostupna.
Prevencija napada
Što se prevencije ovakvih napada tiče, naišao sam na način kako to zaustaviti na razini servera. Naime, alat pod nazivom Dynamic IP Restrictions omogućuje blokiranje DoS napada. Naime, kada program vidi da sa određene IP adrese dolazi mnogo zahtjeva, moguće je konfigurirati koliko zahtjeva je maksimalno moguće u određenom intervalu, te ukoliko se taj broj premaši, dolazi do određenog vremena nedostupnosti servera za tu IP adresu. Kada se broj zahtjeva smanji, odnosno nakon što se ne pošalje zahtjev određeno i definirano vrijeme, ta IP adresa više nije blokirana.
ASP.NET Identity
ASP.NET Identity predstavlja novi korak i to je zapravo novi membership sustav za ASP.NET aplikacije. Identity omogućuje vrlo lake promjene i modifikacije osnovnih korisničkih operacija kao što je prijava ili odjava. Isto tako, Identity sadržava i automatski sadrži gotove primjere kodova za slanje potvrdnih e-mailova, hashiranje lozinki, resetiranje lozinki, dvofaktorsku autentifikaciju i slično.
Mogućnosti
Ranije sam naveo neke od mogućnosti Identity-a, a sada ću detaljno objasniti jednu po jednu mogućnost. Prema tome, mogućnosti odnosno ciljevi Identity-a su:
- Jedan jedini membership sustav
- Ovaj sustav koristi se za sve ASP.NET aplikacije kao što je MVC, WebForms, WebAPI i sl.
- Jednostavno praćenje korisničkih informacija
- Omogućeno je vrlo jednostavno dodavanje ili izbacivanje određenih informacija o korisniku, kao i način pohrane (ovisno u kojoj tablici)
- Podaci se automatski preko Entity Frameworka pohranjuju u bazi podataka (tablice s prefiksom AspNet), ali je to moguće promijeniti
- Role provider
- Posebno definirano kreiranje i korištenje rola po korisniku, a za pristup rolama i mijenjanje podataka u vezi s korisnicima postoje unaprijed definirane klase
- Korištenje claimova
- Unos opisnih podataka o korisniku umjesto podataka kao što su
True
iliFalse
- Primjer claima:
This user is Admin
- Unos opisnih podataka o korisniku umjesto podataka kao što su
- Social login provider
- Vrlo jednostavno postavljanje korisničkih računa s drugih platformi, odnosno, prijavljivanje korisnika s računom drugog servisa, s time da se osjetljivi podaci ne pohranjuju na serveru
- Integracija OWIN-a
- Autentifikacija je bazirana na OWIN-u (kolačiće kreira OWIN)
Primjer ERA modela
Na slici je prikazan ERA model koji sam koristio za projekt Rent a car. Naime, ovdje je korišten Identity, a osnovne tablice koje Identity koristi i generira su sljedeće:
- AspNetUsers
- AspNetRoles
- AspNetClaims
- AspNetUserRoles
- AspNetUserLogins
Naravno, moguće je mijenjati tablice i koristiti samo one koje su nam potrebne, a ono što je ovdje učinjeno jest da su tablice preimenovane:
modelBuilder.Entity<MyUser>().ToTable("Users"); modelBuilder.Entity<MyRole>().ToTable("Roles"); modelBuilder.Entity<MyUserRole>().ToTable("UserRoles"); modelBuilder.Entity<MyUserLogin>().ToTable("UserLogins"); modelBuilder.Entity<MyUserClaim>().ToTable("UserClaims");
Važno je reći da sam ovdje radio malo veće promjene, ne samo od promjene imena tablica i klasa, već i promjene primarnih ključeva, pošto je defaultno primarni ključ u Identity-u tipa String.
Iako je moguće raditi mnoge promjene, vrlo je važno držati se konvencija pri definiranju modela, te ne kreirati razne klase za izradu ili pohranjivanje korisničkih podataka u bazu i slično, kad većina tih klasa i metoda već postoji. Isto tako, u mapi App_Start
već postoje klase s definiranim načinima kako da se koristi OAuth
i primjerice dvofaktorska autentifikacija, pa je samo potrebno iste odkomentirati, pošto je preko Identity-a i Entity Frameworka sve već konfigurirano da ovakve stvari odmah budu funkcionalne.
Osnove i načini korištenja Identity-a mogu se vidjeti na sljedećoj poveznici: "ASP.NET Identity - Basics"
Literatura
- "Understanding Web Security Vulnerabilities and Preventing it in ASP.Net Applications"
- "OWASP - SQL Injection"
- "OWASP - SQL Injection Prevention Cheat Sheet"
- "OWASP - Guide to SQL Injection"
- "Query Parameterization Cheat Sheet"
- "How To: Protect From SQL Injection"
- "ASP.NET - Preventing SQL Injection Attacks"
- "Preventing SQL Injection in ASP.NET"
- "Parameter Manipulation"
- "Securing your ASP.NET MVC Apps"
- "MVC Security Extensions"
- "Preventing Parameter Tampering in ASP.NET MVC"
- "OWASP - Information Leakage"
- "OWASP - ASP.NET Misconfigurations"
- "Hack proof your ASP.NET applications from Sensitive Data Exposure and Information Leakage"
- "Information Leakage"
- "Remove IIS Server version HTTP response header "
- "OWASP - Cross-Site Request Forgery (CSRF)"
- "OWASP - Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet"
- "XSRF/CSRF Prevention in ASP.NET MVC and Web Pages"
- "Preventing Cross-Site Request Forgery (CSRF) Attacks in ASP.NET Web API"
- "OWASP - Cross-Site Scripting (XSS)"
- "How To: Prevent Cross-Site Scripting in ASP.NET"
- "Preventing XSS in ASP.NET"
- "OWASP Top 10 for .NET developers part 2: Cross-Site Scripting (XSS)"
- "Preventing XSS in ASP.NET Made Easy"
- "Hack Proof Your ASP.NET Application From Cross Site Scripting (XSS)"
- "What is SSL and How to Implement in ASP.Net Web Application"
- "Configuring an ASP.NET project for development with SSL"
- "SQL Server Encryption"
- "Encrypt a Column of Data"
- "SQL SERVER – Introduction to SQL Server Encryption and Symmetric Key Encryption Tutorial with Script"
- "OWASP - Denial of Service"
- "Dynamic IP Restrictions"
- "Introduction to ASP.NET Identity"
- "ASP.NET MVC and Identity 2.0: Understanding the Basics"