Dokumentáció - Patrickito/TeamF GitHub Wiki

CAFF Parser

Általános

A parser C++ nyelven lett implementálva és egy .lib-et generál. Egy külső könyvtárat alkalmazva (SWIG) ebből a .lib-ből elkészíti az ehhez tartozó Wrapper dll-t. Az Api az így elkészített dll-t fogja használni. A C# alapú Api kódba berakja még a legenerált Caff_parser_Wrapper.cs-t, ami a dll-lel komunikál. Így az unmanaged objektumokból managed objektumok lesznek.

A fordítása Visual Studion keresztül működik (.proj).

Használat

A C# kódban létre kell hozni egy Parser példányt. Tőle lehet lekérni a parseCaff(<elérésiútvonal>/<caff-név>.caff) függvényt, ami visszaad egy Caff példányt. Ehhez tartozó függvények:

  • getAnimationNumber() - hány Ciff objektumból áll
  • getCaffAnimation(int animationIndex) - Ciff objektum, az index ismeretében
  • getYear() - létrehozás éve
  • getMonth() - létrehozás hónapja
  • getDay() - létrehozás napja
  • getHour() - létrehozás órája
  • getMinute() - létrehozás perce
  • getCreator() - aki létrehozta a fájlt

Ciff objektumhoz tartozó függvények:

  • getWidth() - kép szélessége
  • getHeight() - kép magassága
  • getCaption() - kép felirata
  • getTags() - kép tagjei
  • getDuration() - kép mutatási ideje
  • getPixelAt(int x, int y) - kép pixelének lekérése

Biztonsági megfontolások

Egy CAFF fájl feltöltésénél figyelni kell arra, hogy a támadó átírhatja a különböző bináris adatokat a fájlban. Ezeket a támadásokat próbáltuk a lehető legjobban kivédeni. Ha a fájl szerkezetében, vagy adataiban valami hiba van, abban a pillanatban exception-t dob a parser, így megakadályozva a fájl további beolvasását. Ezekre figyeltünk:

  • érvénytelen kiterjesztés
  • nem lehet megnyitni a fájlt
  • nem lehet bezárni a fájlt
  • több mint egy CAFF header blokk
  • több mint egy CAFF credit blokk
  • érvénytelen fájlméret
  • érvénytelen adat méret
  • érvénytelen CAFF magic
  • érvénytelen CIFF magic
  • érvénytelen blokk ID
  • érvénytelen blokk sorrend
  • érvénytelen header méret
  • érvénytelen felirat, nincs vége karakter
  • érvénytelen tags, érvénytelen vége karakter
  • érvénytelen év (1970-9999)
  • érvénytelen hónap
  • érvénytelen nap
  • érvénytelen óra
  • érvénytelen perc
  • érvénytelen dátum

Működés

A parser parseCaff metódusa a fájlt egy steramként olvassa be. Mindig csak annyit olvas be, ami éppen az aktuális adatszegmenhez szükség van. Ha bármi hibát észlel az előző szekcióban leírt hibákkal excepotiont dob. Elöször beolvassa az első blopkk header-jét, így megtudja az egész blokk ID-ját és méretét. Az ID alapján folytatódik a beolvasás. Lehet CAFF_HEADER, CAFF_CREDITS vagy CAFF_ANIMATION. Mind a háromnak megvan a saját feldolgozója. Ha mindent betudott szépen olvasni - vagyis nem talált hibát - akkor ellenőrzi még, hogy maradt-e a fájlban adat vagy sem. Ha nem, akkor visszatérési értéke egy kész Caff objektum lesz.

Tesztek

A natív parser teszetlését a .dll-en keresztül történt C# nyelven. A függvények Unit teszteit a "1.caff" fájllal vizsgáltuk meg. Tudjuk a várható eredményt és megvizsgáljuk egyezik-e. Az Exceptionök Unit tesztjeihez külön .Caff fájlokat írtunk mindhez. Így megvizsgálunk minden - általunk fellelhető - hibát.

A fuzzingoláshoz a WinAfl programot használtuk, és sikeresen átment a teszteken.

Tesztek:

  • Függvények Unit tesztjei:

    • animációk száma
    • létrehozás éve
    • létrehozás hónapja
    • létrehozás napja
    • létrehozás órája
    • létrehozás perce
    • létrehozó neve
    • képszélesség
    • képmagasság
    • kép felirat
    • kép tagek
    • kép mutatási ideje
    • kép pixele
  • Exceptionök Unit tesztjei:

    • érvénytelen kiterjesztés
    • nem lehet megnyitni a fájlt
    • több mint egy CAFF header blokk
    • több mint egy CAFF credit blokk
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_HEADER blokk ID adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_HEADER blokk lenght adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_HEADER blokk után lévő adatot a lenght alapján
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_CREDITS Headerjének a magic adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_CREDITS Headerjének a header_size adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_CREDITS blokk year adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_CREDITS blokk month adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_CREDITS blokk day adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_CREDITS blokk hour adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_CREDITS blokk minute adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_CREDITS blokk creator_len adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_ANIMATION blokk duration adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_ANIMATION blokk magic adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_ANIMATION blokk header_size adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_ANIMATION blokk content_size adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_ANIMATION blokk width adattagját
    • érvénytelen fájlméret - kis fájlméret miatt nem tudja beolvasni a CAFF_ANIMATION blokk height adattagját
    • érvénytelen adat méret - maradt feldolgozatlan adat a fájlban
    • érvénytelen CAFF magic
    • érvénytelen CIFF magic
    • érvénytelen blokk ID
    • érvénytelen blokk sorrend
    • érvénytelen header méret - nem egyezik a CAFF_HEADER blokkméret a header_size adattaggal
    • érvénytelen header méret - nem egyezik a CAFF_HEADER blokkméret az alapértelmezett mérettel
    • érvénytelen header méret - nem egyezik a CAFF_CREDITS blokkméret a struktúra méretével
    • érvénytelen header méret - nem egyezik a CAFF_ANIMATION blokkméret a struktúra méretével
    • érvénytelen felirat, nincs vége karakter
    • érvénytelen tags, érvénytelen vége karakter
    • érvénytelen év - kevesebb, mint 1970
    • érvénytelen év - több, mint 9999
    • érvénytelen hónap - kevesebb, mint 1
    • érvénytelen hónap - több, mint 12
    • érvénytelen nap - kevesebb, mint 1
    • érvénytelen nap - több, mint 31
    • érvénytelen óra
    • érvénytelen perc
    • érvénytelen dátum

Backend

Az alkalmazásunk mindenféle adatmanipulációs, adattároló műveletét a backend fogja elvégezni. Ez egy ASP.NET Core 3.1-ben fejlesztett alkalmazás. Biztonsági és tartóssági megfontolások miatt döntöttünk ezen keretrendszer mellett. Backendnek mindenképpen menedzselt nyelvet kívántunk választani, hiszen így kevesebb támadási pontot adunk a támadó számára.

A projektet amikor elkezdtük, akkor a .NET 5 volt elérhető, azonban az nem LTS verzió, így nem kívántuk használni, hiszen a támogatottsága hamarabb meg fog szűnni, mint a korábbi, .NET Core 3.1-nek.

Mivel semmilyen korábbi adatbázissal nem rendelkeztünk, ezért a gyors fejlesztés miatt Az EntityFramework Core-t (EFCore) használtuk, Code First adatbázis séma gyártással. Ez azt jelenti, hogy az létrehoztuk C# kódban az adott entitások osztályát, majd az EFCore segítségével ezeket az osztályokat adatbázis sémává alakítottuk. Az EFCore miatt könnyedén lehetséges adatbázis szervert váltani.

Feladatok

A Backend feladata, hogy egy WebAPI interfészt nyújtson a Frontendnek. Ennek megfelelően szükséges különböző endpointokat létrehozni, aminek a meghívásával lehetséges elvégezni adatmanipulációkat, adatokat létrehozni, lementeni. Ennek a rétegnek a feladatai között szerepel a felhasználókezelés, a különböző funkciók megfelelően történő levédése.

A Backend egy további fontos feladata, hogy logolja a különböző eseményeket, amik valamilyen szinten fontosak az működésben. Ezt a beépített logoló rendszerrel végeztük el.

Architektúra

A WebAPI elkészítése során klasszikus háromrétegű architektúrát használtunk. Ennek megfelelően található egy adatelérésri réteg (DAL), egy üzleti logikát tartalmazó réteg (services). A különböző kommunikáció miatt szükség volt különböző DTO-k létrehozására, ezek a DTO mappában található. Az adatbáziskapcsolatot EntityFramework Core segítségével valósítottuk meg. Emiatt könnyedén cserélhető az adatbázis a rendszer alatt.

Felhasználókezelés

Felhasználókat az ASP.NET Core beépített rendszerével, az ASP.NET Core Identityvel kezelünk. A Identitynek definiáljuk, hogy a jelszónak kötelező legalább 8 hosszúnak lennie, illetve számot, kis- és nagybetűt, valamint nem alfanumerikus karaktert is tartalmaznia kell. Ezzel a felhasználókat védjük, rájuk kényszerítve a kellően biztonságos jelszavak használatát, így bármilyen biztonsági hiba kihasználása után nagyobb biztonságban marad a felhasználói fiókjuk.

Regisztráció

A regisztrációhoz egy DTO került létrehozásra, amelyben szerepel a a felhasználónév és a jelszó. Ezt megkapva a /Authentication/register végpont létrehoz egy debug szintű logot, hogy érkezett egy kérés. Amennyiben már létezik ilyen felhasználónevű felhasználó, akkor egy Error szintű logot tesz a logok közé a logoló alrendszer.

Belépés

A belépéshez felhasználónév-jelszó párosra van szükség, amit ellenőriz a rendszer. Ha nem sikerül a bejelentkezés, akkor Error szintű log keletkezik. Ezzel a loggal lehet detektálni, ha túl nagy mértékben terhelik nem megfelelő bejelentkezéssel a rendszert, illetve ha valamilyen hiba miatt nem lesznek sikeresek a belépések.

Ahhoz, hogy a rendszer később is tudja, hogy a melyik felhasználóról van szó, egy úgynevezett JWT (Json Web Token) tokent küldünk sikeres authentikáció esetén válaszként a felhasználóhoz. Ezt a tokent a kérésekhez csatolva az ASP.NET Core Identity képes azonosítani a felhasználót és ellenőrzi, hogy van e jogosultsága az adott funkció használatához.

Egyéb

A felhasználókezelő endpoint rendelkezik még egy jelszóváltoztató, felhasználók listáját lekérő illetve egy jogosultsági szint megváltoztató funkcióval. A jelszó modosításon kívül csak admin jogkörrel érhetőek el a funkciók, amit a Authorize(Policy = SecurityConstants.AdminPolicy) attribútum biztosít a controlleren belül. Jelszóváltoztatás esetén is csak a saját jelszó változtatható meg, ezzel ügyelve arra, hogy egyik felhasználó ne férjen hozzá másik felhasználó adataihoz.

CAFF kezelés

CAFF feltöltés

A Backend nyújt egy endpointot CAFF fájlok feltöltésére. Ez az endpoint egy kitüntetett pontja az alkalmazásnak, hiszen itt lehetséges egy felhasználó által adott fájl bejuttatása a szerverre, ami biztonságkritikus folyamat, ennek megfelelően különös figyelmet igényel. Továbbá fokozza a biztonsági kockázatot az, hogy egy natív nyelven megírt programot használ az alkalmazás, a feltött fájlon végez műveleteket.

Ennek megfelelően az alkalmazásban mind az endpoint levédése, mint a natív alkalmazás futtatására figyelni kell. A natív alkalmazásról a korábbi fejezetben volt szó, így itt csak az endpointról lesz szó.

A végpontot Authorize attribútummal láttuk el, aminek a feladata, hogy csak beléptetett felhasználó férhessen hozzá a funkcióhoz. Ezenkívül amennyiben a natív alkalmazás valamilyen hibát talál a fájlban, exceptiont dob, azt semmiképp sem engedjük ki válaszként, mert akkor a támadó információt tudna kinyerni az adott kivételből. Amennyiben sikeres a feltöltés akkor az adatbázisban egy entitás formájában el lesz tárolva a CAFF fájl, amely megmutatja az adott fájl elérést. Továbbá eltárolásra kerül néhány preview, itt egy-egy entitás szintén a képek elérését mutatja meg.

Amint látható így letárolása kerül a felhasználó által feltöltött fájl. Ahhoz hogy ne legyen fájlnév ütközés, a rendszer biztosít egy egyedi azonosítót, ami alapján a fájl megtalálható. Az eltárolás helye egy sima mappa, amit a webszerver nem szabad hogy statikusan kiszolgáljon. Ezt a Kestrel jelenleg biztosítja, semmilyen statikus fájlt nem szolgál ki.

Egyéb CAFF műveletek

Lehetséges különböző preview képek lekérése egy-egy CAFF fájlhoz, CAFF fájl törlése ezen az endpointon. A megfelelő jogosultságokat elvárjuk itt is, mint korábban a felhasználókezelésnél, például admin jogkör nélkül nem lehetséges CAFF törlés. Amint látható, az adatbázisban az entitásokban csak egy elérési utat tárolunk, aminek következtében fontos, hogy együtt kezeljük a fájlokat az adott entitással. Ilyen eset, amikor törlés történik, akkor, hogy sem a CAFF, sem a preview képek ne foglalják a helyet, azok is törlésre kerülnek.

A feladatkiírásban a feladat neve CaffShop. Azonban úgy szerepel benne, hogy mindenki csak a saját CAFF fájlját láthatja, tehát mint bolt nem működőképes a rendszer. Ezért mi úgy döntöttünk, hogy egy feltöltött fájlt minden belépett felhasználó láthasson, és le is tölthessen, kvázi egy fizetéses rész kihagyásával ténylegesen egy online boltot valósítottunk meg.

Kommentek

A korábbiak szerint egy-egy fájlhoz lehetséges kommentet fűzni. Egy írása, törlése és egy adott caff-hoz tartozó kommentek lekérése lehetséges. Természetesen komment törléséhez admin jogkörrel kell rendelkezni.

Loglás

A logolásról néhány információ, ami meghatározza általános, melyik szintű logba mikor kerül írás. Amennyiben valamilyen rendellenes esemény (például kivétel dobása) történik az alkalmazásban azt a logoló rendszer Error szintű logként menti ki. Ebben az esetben semmilyen új információhoz nem juttatjuk az esetleges támadót, kifelé semmilyen esetben sem juttatunk ki információt.

Amennyiben valamilyen törlés történik az alkalmazásban, az Information szintű logba kerül. ez a későbbiekben, az esetleges hibák kiszűrése esetén tud segíteni, továbbá láthatóak lesznek az adatbázisban történő törlő műveletek.

Debug szintű logba írás akkor történik, ha valamilyen kritikus funkció hívása megörténik, ezzel szintén javítva a hibák kereshetőségét, illetve éles működés esetén a támadások kiinduló pontját könnyebb ezáltal megtalálni.

Tesztelés

Az elkészített alkalmazás helyes működése unit tesztekkel van ellenőrzive, ezek a tesztek a TeamF_Api_Test projektben találhatók. A tesztek elsősorban a Service és Controller osztályokat ellenőrzik. A tesztek során a szükséges dependency-k a Moq library segítségével mockolva vannak, ezért könnyen konfigurálható a működésük. A Service-ek esetén a tesztek kitérnak arra, hogy az egyes műveletek az elvárt módósításokat végezték el az adatbázison, ennek érdekében egy memóriában tárolt adatbázis jön létre a tesztekhez, amelyeken ellenőrizhető a tesztelt programrészek adataállományra gyakorolt hatása.

Azokon a pontokon, ahol a program működése során valamilyen kivétel várható, a tesztek kitérnek ezeknek az eseteknek az ellenőrzésére, ezért egy esetleges hiba folytán nem kerül az alkalmazás inkonzisztens állapotba. A Controller-ek esetén ellenőrizve a visszaadott válaszok státusz kódja is, mivel válasz kezelését kliensoldalon befolyásolhatja ez a tényező.

Az elkészített szoftver helyességéről statikus kódelemzéssel is meggyőződtünk. A fejlesztés során a kódot a SonarLint eszköz elemezte, amelynek feladata a potenciális hibák kiszűrése a program futtatása nélkül. Ennek az eszköznek köszönhetően megelőzhetőek azok a problémák, amelyek a program nem rendeltetésszerinti működését eredményezhetik, beleértve a teljesítmény szempontjából lényeges problémákat és a biztonsági szempontból kockázatos megoldásokat is.

Frontend

Általános:

A frontend az Angular webes keretrendszerrel és a bootstrap stílus könyvtárral került megvalósításra. Ez a kódbázis biztosítja a felhasználók hozzáférését a backend által nyújtott szolgáltatásokhoz. A jogosultság kezelések kliens oldalon is megtörténnek, például nem navigálhatunk olyan helyekre amikhez a bejelentkezésnél kapott tokenünk alapján nincs jogunk, ezeket az angularban már megtalálható guardok és canActivate megoldásokkal valósítottuk meg. Azonban nyílván fontos hogy annak ellenére hogy a frontend védve van ily módon, a backendnek saját magának is ellenőriznie kell hogy a kérés jogos-e. Ezért a lekérdezésekhez mellékeljük a saját tokenünket ami azonosítja a backend számára melyik felhasználó használja épp az alkalmazásukat. Ezeket a frontend minden lekérdezéshez mellékeli egy interceptor osztály segítségével.

Használat:

Amint kihostoljuk a szervert az "ng serve" utasításával onnantól kezdve a program tetszőleges böngészőből elérhetővé válik a megfelelű elérési út megadásával. (Development enviroment esetén ez a localhost:4200, production esetén a megvásárolt url-ünk lesz a célpont) ##Backend és a frontend kapcsolata: A backend az csak HTTP hívások könyvtára, vagyis tényleges webes oldalinformációkat (pl html vagy javascript fájlokat) nem tartalmaz. A megszokott webes kinézetért a frontend felelős, és a tényleges adatokat a backendtől kapjuk lekérdezés segítségével. Mivel a backend és a frontend nem ugyanazon a porton található, így a frontend által intézett kéréseket egy proxyn futtatjuk keresztül és ezen a proxyn keresztül kapjuk meg az információkat. Ahogy azt korábban a CAFF parsernél bőven részleteztük, különböző hibákat tudunk detektálni a caff fájlok parseolása során. Azonban mi a user felé csak azt az információt közöljük, hogy a caff fájlja hibás volt, azt nem közöljük milyen hiba történt. Ezzel az esetleges támadó nem jut extra információhoz, hogy melyik része volt a fájlnak. Azonban kizárást nem alkalmazunk, ha valaki túl sokszor próbálna ilyen fájlokat feltölteni. Az adminisztrációs joggal rendelkező felhasználók, képesek a felhasználók caff fájljait tulajdonosi jogtól függetlenül törölni, illetve képesek a felhasználók jogköreit módosítani. Egy felhasználó adminisztrátori és alap jogokkal rendelkezhet.

Tesztelés:

Mivel a frontend nem tartalmaz üzleti logikát, csak a backend által biztosított API hívások eredményeit jeleníti meg, illetve a felhasználó adatait ezeken a hívásokon keresztül átadja a backendnek. Így a tesztelés az megegyezik a backend API hívások tesztelésével amit pedig már tárgyaltunk egy korábbi fejezetben.

⚠️ **GitHub.com Fallback** ⚠️