paint-brush
Ako ne možete kontrolirati pohranu, kontrolirajte pristupby@axotion
199 čitanja

Ako ne možete kontrolirati pohranu, kontrolirajte pristup

by Kamil Fronczak4m2025/01/18
Read on Terminal Reader

Predugo; Citati

Nedavno sam razmišljao o tome kako neke od mojih ključnih krajnjih tačaka učiniti sigurnijim kada koristim lokalno pohranjeni JWT.
featured image - Ako ne možete kontrolirati pohranu, kontrolirajte pristup
Kamil Fronczak HackerNoon profile picture

Nedavno sam razmišljao o tome kako neke od mojih ključnih krajnjih tačaka učiniti sigurnijim kada koristim lokalno pohranjeni JWT.


Možda to nije najbolja praksa za sigurnost (zbog mogućnosti XSS napada), ali to je bio zahtjev koji nisam postavio. Morao sam se prilagoditi.


Dakle, kako bih ovo učinio sigurnijim, pronašao sam rješenje koje će, nadam se, pomoći i vama.

Problem

Pretpostavljam da svi znamo šta je JWT - token koji izdaje backend servis, a koji frontend ne može modificirati, jer bi to promijenilo potpis i automatski poništilo token.


Zvuči odlično, dok se ne nađemo u situaciji da token može biti ukraden - kao u mom slučaju sa XSS napadom. Mnoge web stranice pohranjuju tokene u lokalnu pohranu umjesto kolačića samo za HTTP, tako da su i oni ranjivi.

Rešenje

Dakle, kada nije moguće promijeniti način na koji frontend pohranjuje taj token, moramo promijeniti način na koji izdajemo i validiramo token, a ovo je trenutak kada želim da vas upoznam sa jednostavnim rješenjem - JWT sa heširanim otiskom prsta, ali da vidimo kod


Pretpostavimo, da imamo krajnju tačku za izdavanje tokena, nazovimo je krajnja tačka za prijavu

 @Controller('v1/sign-in') export class SignInAction { constructor(private jwtService: JwtService) {} @Post() async handle( @Req() request: Request, @Body() body: SignInHttpRequest, ): Promise<SignInHttpResponse> { const ip = request.headers['x-forwarded-for'] || request.socket.remoteAddress; const userAgent = request.headers['user-agent']; const token = this.jwtService.sign({ email: body.email, fingerprint: getSHA512Hash(`${ip}${userAgent}`), // It's worth to mention, that to make it even more secure, you should add salt }); return { token, }; } }


Dakle, ono što mi radimo ovdje je u osnovi prikupljanje korisničkog agenta i udaljene IP adrese od korisnika koji se povezuje, heširanje i zatim stavljanje u JWT token, pa čak i ako korisnik ili napadač želi vidjeti šta je unutar JWT tokena, oni će samo pogledajte neku nasumično heširanu vrijednost


Ali kako će to povećati moju sigurnost pitate se? Hajde da pogledamo našu drugu krajnju tačku, pokažimo otisak prsta


 @Controller('v1/fingerprint') @UseGuards(AuthGuard(JWT_STRATEGY)) export class ShowFingerPrintAction { @Get() async handle(@Req() request) { return { fingerprint: request.user.fingerprint, }; } }


U početku, nije ništa posebno. Krajnja tačka koja vraća otisak prsta iz zahtjeva, pa šta je čini sigurnijom? JWT_STRATEGY!


 export const JWT_STRATEGY = 'JWT'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, JWT_STRATEGY) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: 'hard!to-guess_secret', passReqToCallback: true, }); } async validate(request: Request, payload: any): Promise<any> { const fingerprint = payload.fingerprint; const ip = request.headers['x-forwarded-for'] || request.socket.remoteAddress; const userAgent = request.headers['user-agent']; const calculatedFingerprint = getSHA512Hash(`${ip}${userAgent}`); // It's worth to mention, that to make it even more secure, you should add salt if (fingerprint !== calculatedFingerprint) { throw new BadRequestException('Invalid fingerprint'); } return payload; } }

Kao što možete vidjeti, u strategiji izdvajamo iste vrijednosti kao kada smo ih izdvojili iz krajnje tačke za prijavu i upoređujemo ih - ako se IP ili korisnički agent promijenio, tada ćemo zabraniti pristup našoj krajnjoj tački otiska prsta.


Ovo je slučaj kada čak i ako nam je napadač ukrao token, ne može ništa s njim, jer

  • On ne zna šta je heš unutar otiska prsta
  • Nećemo mu dozvoliti da se poveže na naše krajnje tačke, zbog različitog IP-a i korisničkog agenta (i možda više faktora)


Tako možemo zaštititi našu ključnu krajnju tačku na vrlo jednostavan način, ali naravno, postoje neki kompromisi

  • Za svaki poziv, moramo izračunati sha512 hash
  • Ako korisnik ima dinamički IP, morat će se često prijavljivati


Za prvi problem, naravno, možete provjeriti samo IP i čak ga ne heširati prilikom izdavanja tokena, ali tada potencijalni napadač zna vektor napada (IP lažiranje ili druge metode), pa je sigurnije žrtvovati neke performanse za sigurnost.


Nadam se da će vam ova metoda pomoći da uvedete dodatni sloj sigurnosti na vaše ključne krajnje tačke.


Link do radnog izvornog koda: Izvorni kod


Savjeti:

  • Zaglavlje X-FORWARDER-FOR može se lako lažirati osim ako ne koristite usluge kao što je Cloudflare, imajte to na umu


L O A D I N G
. . . comments & more!

About Author

Kamil Fronczak HackerNoon profile picture
Kamil Fronczak@axotion
I’m a 2X-year-old tech dude from Poland, and this is my blog about tech stuff: NestJS, Node

HANG TAGS

OVAJ ČLANAK JE PREDSTAVLJEN U...