paint-brush
Εάν δεν μπορείτε να ελέγξετε τον χώρο αποθήκευσης, ελέγξτε την πρόσβασημε@axotion
199 αναγνώσεις

Εάν δεν μπορείτε να ελέγξετε τον χώρο αποθήκευσης, ελέγξτε την πρόσβαση

με Kamil Fronczak4m2025/01/18
Read on Terminal Reader

Πολύ μακρύ; Να διαβασω

Πρόσφατα, σκέφτομαι πώς να κάνω ορισμένα από τα κρίσιμα τελικά σημεία μου πιο ασφαλή όταν χρησιμοποιώ τοπικά αποθηκευμένο JWT.
featured image - Εάν δεν μπορείτε να ελέγξετε τον χώρο αποθήκευσης, ελέγξτε την πρόσβαση
Kamil Fronczak HackerNoon profile picture

Πρόσφατα, σκέφτομαι πώς να κάνω ορισμένα από τα κρίσιμα τελικά σημεία μου πιο ασφαλή όταν χρησιμοποιώ τοπικά αποθηκευμένο JWT.


Μπορεί να μην είναι η καλύτερη πρακτική για την ασφάλεια (λόγω της πιθανότητας επιθέσεων XSS), αλλά αυτή ήταν μια απαίτηση που δεν τέθηκε από εμένα. Έπρεπε να προσαρμοστώ.


Για να το κάνω αυτό πιο ασφαλές, βρήκα μια λύση, η οποία, ελπίζω, θα σας βοηθήσει επίσης.

Το Πρόβλημα

Υποθέτω ότι όλοι γνωρίζουμε τι είναι το JWT - ένα διακριτικό που εκδίδεται από την υπηρεσία υποστήριξης, το οποίο δεν μπορεί να τροποποιηθεί από το frontend, επειδή αυτό θα άλλαζε την υπογραφή και θα ακύρωνε αυτόματα το διακριτικό.


Ακούγεται υπέροχο, μέχρι να συναντήσουμε την κατάσταση όπου το token μπορεί να κλαπεί - όπως στην περίπτωσή μου με την επίθεση XSS. Πολλοί ιστότοποι αποθηκεύουν διακριτικά σε τοπικό αποθηκευτικό χώρο αντί για cookie μόνο HTTP, επομένως είναι επίσης ευάλωτοι.

Η Λύση

Έτσι, όταν δεν είναι δυνατό να αλλάξουμε τον τρόπο με τον οποίο το frontend αποθηκεύει αυτό το διακριτικό, πρέπει να αλλάξουμε τον τρόπο έκδοσης και επικύρωσης του διακριτικού, και αυτή είναι η στιγμή που θέλω να σας παρουσιάσω μια απλή λύση - 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_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; } }

Όπως μπορείτε να δείτε, στη στρατηγική, εξάγουμε τις ίδιες τιμές όπως όταν τις εξάγαμε από το τελικό σημείο σύνδεσης και τις συγκρίνουμε - εάν η IP ή ο παράγοντας χρήστη έχει αλλάξει, τότε θα αρνηθούμε την πρόσβαση στο τελικό σημείο δακτυλικών αποτυπωμάτων μας.


Αυτή είναι η περίπτωση όπου ακόμα κι αν ο εισβολέας μας έκλεψε το διακριτικό, δεν μπορεί να κάνει τίποτα με αυτό, γιατί

  • Δεν ξέρει ποιος είναι ο κατακερματισμός μέσα στο δακτυλικό αποτύπωμα
  • Δεν θα του επιτρέψουμε να συνδεθεί στα τελικά σημεία μας, λόγω διαφορετικών IP και παράγοντα χρήστη (και ίσως και περισσότερων παραγόντων)


Έτσι μπορούμε να προστατεύσουμε το κρίσιμο τελικό μας σημείο με έναν πολύ απλό τρόπο, αλλά φυσικά, υπάρχουν κάποιες ανταλλαγές

  • Για κάθε κλήση, πρέπει να υπολογίσουμε το sha512 hash
  • Εάν ο χρήστης έχει δυναμική IP θα πρέπει να συνδέεται συχνά


Για το πρώτο πρόβλημα, θα μπορούσατε φυσικά να ελέγξετε μόνο για IP και να μην το κατακερματίσετε κατά την έκδοση ενός διακριτικού, αλλά στη συνέχεια ο πιθανός εισβολέας γνωρίζει τον φορέα της επίθεσης (πλαστογράφηση IP ή άλλες μέθοδοι), επομένως είναι ασφαλέστερο να θυσιάσει κάποια απόδοση για ασφάλεια.


Ελπίζω ότι αυτή η μέθοδος θα σας βοηθήσει να εισάγετε ένα επιπλέον επίπεδο ασφάλειας στα κρίσιμα τελικά σημεία σας.


Σύνδεσμος με τον πηγαίο κώδικα που λειτουργεί: Πηγαίος κώδικας


Συμβουλές:

  • Η κεφαλίδα X-FORWARDER-FOR μπορεί εύκολα να πλαστογραφηθεί εκτός και αν χρησιμοποιείτε υπηρεσίες όπως το Cloudflare, να το έχετε υπόψη σας