Pastaruoju metu galvojau, kaip padaryti kai kuriuos svarbiausius galutinius taškus saugesnius naudojant vietoje saugomą JWT.
Galbūt tai nėra geriausia saugumo praktika (dėl XSS atakų galimybės), bet tai buvo reikalavimas, kurio aš nenustačiau. Turėjau prisitaikyti.
Taigi, kad tai būtų saugesnė, radau sprendimą, kuris, tikiuosi, padės ir jums.
Darau prielaidą, kad visi žinome, kas yra JWT – backend paslaugos išduotas prieigos raktas, kurio frontend negali keisti, nes tai pakeistų parašą ir automatiškai panaikintų prieigos raktą.
Skamba puikiai, kol nesusidursime su situacija, kai tokenas gali būti pavogtas – kaip mano atveju su XSS ataka. Daugelis svetainių saugo žetonus vietinėje saugykloje, o ne tik HTTP slapuką, todėl jie taip pat yra pažeidžiami.
Taigi, kai neįmanoma pakeisti būdo, kuriuo priekinėje sistemoje saugomas šis prieigos raktas, turime pakeisti prieigos rakto išdavimo ir patvirtinimo būdą, ir tai yra momentas, kai noriu supažindinti jus su paprastu sprendimu – JWT su maišos piršto atspaudu, bet pažiūrėkime kodą
Tarkime, kad turime galinį tašką žetonams išduoti, pavadinkime jį prisijungimo tašku
@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, }; } }
Taigi, ką mes darome čia, iš esmės renkame vartotojo agentą ir nuotolinį IP iš vartotojo, kuris prisijungia, sumaišo jį ir įdedame jį į JWT prieigos raktą, todėl net jei vartotojas ar užpuolikas norėtų pamatyti, kas yra JWT prieigos rakte, jie tik pamatyti kokią nors atsitiktinę maišos reikšmę
Bet kaip tai padidins mano saugumą, klausiate? Pažvelkime į antrąjį galinį tašką, parodykime piršto atspaudą
@Controller('v1/fingerprint') @UseGuards(AuthGuard(JWT_STRATEGY)) export class ShowFingerPrintAction { @Get() async handle(@Req() request) { return { fingerprint: request.user.fingerprint, }; } }
Iš pradžių tai nieko ypatingo. Galutinis taškas, grąžinantis piršto atspaudą iš užklausos, taigi, kas daro jį saugesnį? 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; } }
Kaip matote, strategijoje išgauname tas pačias reikšmes, kaip ir tada, kai jas ištraukėme iš prisijungimo galinio taško, ir lyginame – jei pasikeitė IP arba vartotojo agentas, uždrausime prieigą prie savo pirštų atspaudų galinio taško.
Tai yra atvejis, kai net jei užpuolikas pavogė mūsų žetoną, jis negali nieko su juo padaryti, nes
Taip galime labai paprastai apsaugoti savo esminį galinį tašką, tačiau, žinoma, yra tam tikrų kompromisų
Dėl pirmosios problemos, žinoma, galite patikrinti tik IP ir net nenaudoti maišos išduodami prieigos raktą, bet tada potencialus užpuolikas žino atakos vektorių (IP klaidinimas ar kiti metodai), todėl saugiau paaukoti šiek tiek pasirodymas dėl saugumo.
Tikiuosi, kad šis metodas padės jums įdiegti papildomą saugumo lygį svarbiems galutiniams taškams.
Nuoroda į veikiantį šaltinio kodą: Šaltinio kodas
Patarimai: