paint-brush
Якщо ви не можете контролювати сховище, контролюйте доступза@axotion
199 показання

Якщо ви не можете контролювати сховище, контролюйте доступ

за Kamil Fronczak4m2025/01/18
Read on Terminal Reader

Надто довго; Читати

Нещодавно я думав про те, як зробити деякі з моїх важливих кінцевих точок більш безпечними при використанні локально збереженого JWT.
featured image - Якщо ви не можете контролювати сховище, контролюйте доступ
Kamil Fronczak HackerNoon profile picture

Нещодавно я думав про те, як зробити деякі з моїх важливих кінцевих точок більш безпечними при використанні локально збереженого JWT.


Можливо, це не найкраща практика з точки зору безпеки (через можливість атак XSS), але це була вимога, яку я не встановлював. Довелося адаптуватися.


Тому, щоб зробити це більш безпечним, я знайшов рішення, яке, сподіваюся, допоможе і вам.

Проблема

Я припускаю, що ми всі знаємо, що таке JWT — маркер, виданий серверною службою, який не може бути змінений зовнішнім інтерфейсом, оскільки це призведе до зміни підпису та автоматичного визнання маркера недійсним.


Звучить чудово, поки ми не зустрінемося з ситуацією, коли маркер може бути вкрадено - як у моєму випадку з XSS-атакою. Багато веб-сайтів зберігають маркери в локальному сховищі замість файлів cookie лише HTTP, тому вони також уразливі.

Рішення

Отже, коли неможливо змінити спосіб, у який інтерфейс зберігає цей маркер, ми повинні змінити спосіб випуску та перевірки маркера, і це момент, коли я хочу представити вам просте рішення – JWT із хешованим відбитком, але давайте подивимось код


Припустімо, що у нас є кінцева точка для видачі токенів, назвемо її кінцевою точкою входу

 @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, }; } }


Отже, те, що ми тут робимо, — це збирання агента користувача та віддаленої IP-адреси від користувача, який підключається, хешування, а потім поміщення в маркер JWT, тож навіть якщо користувач або зловмисник захоче побачити, що всередині маркера JWT, вони лише побачити випадкове хешоване значення


Але як це підвищить мою безпеку, запитаєте ви? Давайте подивимося на нашу другу кінцеву точку, покажемо відбиток пальця


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


Спочатку нічого особливого. Кінцева точка, яка повертає відбиток із запиту, тож що робить її безпечнішою? JWT_СТРАТЕГІЯ!


 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; } }

Як бачите, у стратегії ми витягуємо ті самі значення, що й під час вилучення їх із кінцевої точки входу, і порівнюємо їх – якщо IP-адреса чи агент користувача змінилися, ми заборонимо доступ до нашої кінцевої точки відбитків пальців.


Це той випадок, коли навіть якщо зловмисник вкрав наш токен, він нічого не зможе з ним зробити, тому що

  • Він не знає, що таке хеш у відбитку пальця
  • Ми не дозволимо йому підключатися до наших кінцевих точок через іншу IP-адресу та агент користувача (і, можливо, інші фактори)


Ось як ми можемо захистити нашу важливу кінцеву точку дуже простим способом, але, звичайно, є певні компроміси

  • Для кожного виклику ми повинні обчислити хеш sha512
  • Якщо користувач має динамічний IP, йому доведеться часто входити в систему


Для першої проблеми ви, звичайно, можете перевірити лише IP-адресу і навіть не хешувати її під час видачі маркера, але тоді потенційний зловмисник знає вектор атаки (підробка IP-адреси чи інші методи), тому безпечніше пожертвувати деякими продуктивність для безпеки.


Я сподіваюся, що цей метод допоможе вам запровадити додатковий рівень безпеки для ваших важливих кінцевих точок.


Посилання на робочий вихідний код: вихідний код


Поради:

  • Заголовок X-FORWARDER-FOR можна легко підробити, якщо ви не використовуєте такі служби, як Cloudflare, майте це на увазі


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

ПОВІСИТИ БИРКИ

ЦЯ СТАТТЯ БУЛА ПРЕДСТАВЛЕНА В...