Beim Erstellen einer Full-Stack-Webanwendung ist die Kommunikation zwischen Ihrem Client und Server durch verschiedene Schwachstellen gefährdet, wie z. B. XSS (Cross-Site Scripting), CSRF (Cross-Site Request). Forgery) und Token Sidejacking. Als Webentwickler ist es äußerst wichtig, solche Schwachstellen zu kennen und zu wissen, wie man sie verhindert.
Da ich auch versuche, diese Schwachstellen in meinen APIs zu erkennen und zu verhindern, dienen diese Anleitungen auch als Referenz für die Erstellung dieses Artikels und sind allesamt lesenswert:
Lassen Sie uns zunächst die drei zuvor erwähnten Schwachstellen definieren.
Laut OWASP.org
Cross-Site Scripting (XSS)-Angriffe sind eine Art Injektion, bei der bösartige Skripte in ansonsten harmlose und vertrauenswürdige Websites eingeschleust werden. XSS-Angriffe treten auf, wenn ein Angreifer eine Webanwendung verwendet, um Schadcode, im Allgemeinen in Form eines Browser-Side-Scripts, an einen anderen Endbenutzer zu senden. Fehler, die den Erfolg dieser Angriffe ermöglichen, sind recht weit verbreitet und treten überall dort auf, wo eine Webanwendung in der von ihr generierten Ausgabe Eingaben eines Benutzers verwendet, ohne diese zu validieren oder zu kodieren.
Laut OWASP.org
Cross-Site Request Forgery (CSRF) ist ein Angriff, der einen Endbenutzer dazu zwingt, unerwünschte Aktionen in einer Webanwendung auszuführen, in der er derzeit authentifiziert ist. Mit ein wenig Hilfe von Social Engineering (z. B. dem Versenden eines Links per E-Mail oder Chat) kann ein Angreifer die Benutzer einer Webanwendung dazu verleiten, vom Angreifer gewünschte Aktionen auszuführen. Handelt es sich bei dem Opfer um einen normalen Benutzer, kann ein erfolgreicher CSRF-Angriff den Benutzer dazu zwingen, Anfragen zur Statusänderung auszuführen, etwa Geld zu überweisen, seine E-Mail-Adresse zu ändern usw. Wenn es sich bei dem Opfer um ein Administratorkonto handelt, kann CSRF die gesamte Webanwendung kompromittieren.
Laut JWT Cheatsheet
Dieser Angriff erfolgt, wenn ein Token von einem Angreifer abgefangen/gestohlen wurde und er sich damit unter Verwendung einer gezielten Benutzeridentität Zugriff auf das System verschafft.
Als ich anfing, eine Full-Stack-Anwendung mit Angular und Laravel zu erstellen. Ich habe JSON Web Tokens (JWT) zur Authentifizierung verwendet. Es ist einfach zu verwenden, aber auch leicht auszunutzen, wenn es nicht richtig implementiert wird. Häufige Fehler, die ich gemacht habe, waren:
Lokaler Speicher ist eine häufige Wahl, da er leicht über JavaScript abgerufen und darauf zugegriffen werden kann. Außerdem ist er persistent, was bedeutet, dass er nicht gelöscht wird, wenn der Tab oder der Browser geschlossen wird, was ihn sehr anfällig für Cross-Site macht Scripting (XSS)Angriffe.
Wenn ein XSS-Angriff Folgendes in Ihre Website einschleust:
1 |
|
JWTs haben eine TTL, und wenn sie in Laravel nicht richtig konfiguriert sind, ist der Token standardmäßig auf 3600 Sekunden (1 Stunde) eingestellt, was Hackern eine offene und umfassende Gelegenheit gibt, den Token zu stehlen und ihn als Opfer zu verwenden bis der Token abläuft.
Mit einem Aktualisierungstoken kann der Benutzer ein neues Zugriffstoken erhalten, ohne sich erneut authentifizieren zu müssen. TTL spielt bei Tokens eine entscheidende Rolle. Eine längere TTL stellt, wie bereits erwähnt, ein Sicherheitsrisiko dar, eine kürzere TTL führt jedoch zu einer schlechten Benutzererfahrung und zwingt sie dazu, sich erneut anzumelden.
Wir werden eine grundlegende React Express-Anwendung erstellen, um diese Schwachstellen zu beheben und zu mindern. Um die Ausgabe der Anwendung, die wir durchführen werden, besser zu verstehen, sehen Sie sich das Diagramm unten an.
Bei der Authentifizierung sendet der Benutzer Benutzernamen und Passwort und sendet sie zur Überprüfung an die /login-API. Beim Anmelden wird der Server Folgendes tun:
Anmeldeinformationen in der Datenbank überprüfen
Die Benutzeranmeldeinformationen im JSON-Format werden in der Datenbank zur Authentifizierung überprüft.
Benutzer-Fingerabdruck generieren
Generieren Sie einen zufälligen Byte-Fingerabdruck für den verifizierten Benutzer und speichern Sie ihn in einer Variablen.
Hashen Sie den Fingerabdruck
Der generierte Fingerabdruck wird gehasht und in einer anderen Variablen gespeichert.
Erstellen eines Cookies für den generierten Fingerabdruck (Original-Fingerabdruck)
Der ungehashte Fingerabdruck wird in einem gehärteten Cookie mit dem Namen __Secure_Fgp mit den Flags httpOnly, secure, sameSite=Strict und maxAge von 15 Minuten gesetzt.
Creating a token for the User credentials with the Hashed Fingerprint
Generating a JWT token for the verified user with its hashed fingerprint.
Creating a cookie for the token
After generating JWT token, the token will be sent as a cookie.
After the process, there will be 2 cookies will be sent, the original fingerprint of the user, and the generated token containing the data with the hashed fingerprint of the user.
When an authenticated user accessed the protected route. A middleware will verify the cookies of the user.
Fetching cookies
The middleware of the server will fetch the 2 cookies from the client upon request.
Verify the JWT
Using JWT token, it will verify the token from the fetched cookie. Extract the data inside the JWT (e.g. User details, fingerprint etc.)
Hash the __Secure_Fgp cookie and compare it to the fingerprint from the payload JWT token.
Now for the implementation
Here are all the libraries that we need:
jsonwebtoken
For generating, signing and verifying JWT Tokens
crypto
To generate random bytes and hashing fingerprints
cookie-parser
For parsing Cookie header and creating cookies
cors
Configuring CORS policy
csurf
Generating CSRF Tokens
1 2 3 |
|
Create a server.js file and edit the package.json, write "start": "nodemon server.js" under the scripts object.
1 2 3 4 |
|
Since we are using JWT, we’re gonna need a HMAC key
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
After setting up the Express server, we can start by creating our /login API.
I did not used database for this project, but feel free to modify the code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Assuming that the user is registered in the database, First, we’re gonna need two functions, one for generating a random fingerprint and hashing the fingerprint.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
As discussed earlier, we are going to generate a fingerprint for the user, hash that fingerprint and set it in a cookie with the name __Secure_Fgp..
Then generate a token with the user’s details (e.g. id, username and password) together with the original fingerprint, not the hashed one since we are going to use that for verification of the token later.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
After log in, it will pass two cookies, token and __Secure_Fgp which is the original fingerprint, into the front end.
To validate that, we are going to create a middleware for our protected route. This middleware will fetch the two cookies first and validate, if there are no cookies sent, then it will be unauthorized.
If the token that was fetched from the cookie is not verified, malformed or expired, it will be forbidden for the user to access the route.
and lastly, it will hash the fingerprint from the fetched cookie and verify it with the hashed one.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
To use this middleware we are going to create a protected route. This route will return the user that we fetched from the verified token in our middleware.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
With all of that set, we can now try it on our front end…
For this, I used some dependencies for styling. It does not matter what you used, the important thing is that we need to create a form that will allow the user to login.
I will not create a step by step in building a form, instead, I will just give the gist of the implementation for the client side.
In my React app, I used shadcn.ui for styling.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
This is a simple login form with a button that will navigate the user to the other page that will fetch the protected route.
When the user click submit, it will POST request to the /login API in our server. If the response is success, it will navigate to the page.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
In the other page, it will fetch the /protected API to simulate an authenticated session of the user.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Make sure to put credentials: ‘include’ in the headers to include cookies upon request.
To test, run the app and look into the Application tab of the browser.
1 2 3 4 5 |
|
Under Application tab, go to cookies and you can see the two cookies that the server generated.
Token is good for 15 mins, and after that the user will need to reauthenticate.
With this, you have the potential prevention of XSS (Cross-Site Scripting) and Token Sidejacking into your application. This might not guarantee a full protection but it reduces the risks by setting the cookie based on the OWASP Cheat sheet.
1 2 3 4 5 6 |
|
For the CSRF, we are going to tweak a few things on our server side using this:
1 2 |
|
then we’ll add it to the middleware
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
For this we’ll need an API that will generate a CSRF Token and passed it as a cookie to the front end.
1 2 3 4 5 6 7 8 |
|
Take note that this csrfProtection will only apply to the POST, PUT, DELETE requests, anything that will allow user to manipulate sensitive data. So for this, we’ll just secure our login endpoint with CSRF.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
We need to make a GET request to the /csrf-token API and save the token in our local storage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
I know, I know… we just talked about the security risk of putting tokens in a local storage. Since there are many ways to mitigate such attacks, common solution would be to refresh this token or just store it in the state variable. For now, we are going to store it in the local storage.
This will run when the component loads. everytime the user visits the App.tsx, it will generate a new CSRF Token.
Now since our /login API is protected with CSRF, we must include the CSRF-Token in the headers upon logging in.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Now, when the App.tsx load, we can now see the Cookies in our browser.
The XSRF-TOKEN is our generated token from the server, while the _csrf is the token generated by the csrfProtection = csrf({ cookie: true });
Here is the full code of the application.
https://github.com/Kurt-Chan/session-auth-practice
This might not give a full protection to your app but it reduce the risks of XSS and CSRF attacks in your website. To be honest, I am new to this integrations and still learning more and more about this.
If you have questions, feel free to ask!
Das obige ist der detaillierte Inhalt vonVerhindern von CSRF- und XSS-Angriffen mit JWT- und Fingerabdruck-Cookies in Express. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!