0

We develop a website in JAMStack, all URLs are static HTML page, and each interaction with the server are made by a fetch call on our REST API (micro-services).

When a user sign in, we want to proposes an option to permit them to stay connected as long as they want (years if neeeded). This increase the risks of interception of this session id, so we want to mitigate some attacks (hijacking, session replay, session fixation, tampering, CSRF ... )

My idea is to keep the session id in a cookie (httpOnly, sameSite, secure, path=/api/), and after sign-in to share a random secret to save in a non-extractable CryptoKey that will sign API requests like this:

crypto.sign('HMAC', CryptoKey, timestamp + API_REQUEST_URL + API_REQUEST_JSON)

I put a scenario to explain this in details. Each micro-services share a SERVER_SECRET, a random value of 128 bits

Sign-in:

 Client
   | Send JSON { email, password }
   V
 Server
   | Verify user credentials
   | Generate a random SESSION_ID of 128 bits
   | Generate a CLIENT_SECRET = sha1(SERVER_SECRET + SESSION_ID)
   | Store in DB sessions[SESSION_ID] the identity { user, authorizations, ... }
   | Add in the response header the cookie containing SESSSION_ID
   | Return JSON { CLIENT_SECRET, timestamp }
   V
 Client
   - Save CLIENT_SECRET in a non-extractable CryptoKey
   - Save in localStorage DELTA_SYNC, the delta in seconds from the server timestamp and the client

Next API requests:

 Client
   | Calcul the current TIME_STEP (current timestamp divided per 5 seconds + DELTA_SYNC)
   | Uses the CryptoKey to sign (TIME_STEP + URL + JSON)
   | Send the JSON + header x-signature
   V 
 Server
   | Calcul the current TIME_STEP
   | Extract SESSION_ID from the cookie
   | Generate the SECRET_CLIENT = sha1(SERVER_SECRET + SESSION_ID)
   | Check authenticity crypto.verify('HMAC', SECRET_CLIENT, x-signature, TIME_STEP + URL + JSON)
   |     --> If not match retry with (TIME_STEP - 5 seconds)
   | If matches, we check if the session is still alive in DB
   | Then we got the user & his authorizations to execute the request
   | (...)
   | For all responses: 
   |    - We also sign the TIME_STEP+URL+JSON and add it to the header x-signature
   |    - To prevent time desynchrosiation, we also add the header x-timestamp of our timestamp
   |      (the client can readjust the DELTA_SYNC if needed)
   V
 Client
   | Calcul the TIME_STEP and check authenticity of the answer
   

Advantages:

  • If a CRSF attack steal the SESSION_ID, it's useless without the CryptoKey to sign the request
  • If a XSS attack access the CryptoKey to sign, it's useless without the SESSION_ID
  • Prevent replay attacks (time step of 5 seconds)
  • Prevent session fixation (the SESSION_ID alone isn't enough)
  • Each client/server have proof that the request/response isn't tampered
  • User can stay logged as long as he want

Security questions:

  1. Do you believe there is still some attacks possible to hijack the session of the user? (excepted of a physical access to the device of the victim)

Crypto questions:

  1. Is it safe to generate the CLIENT_SECRET from sha1(SERVER_SECRET + SESSION_ID) or do you have any suggestions to improve this point?

2 Answers 2

2

If a CRSF attack steal the SESSION_ID, it's useless without the CryptoKey to sign the request

A CSRF attack cannot steal a cookie in the first place. It can only make use of the existing cookie for requests in the scope for the cookie. Needing the client side secret in addition to the cookie helps against this.

If a XSS attack access the CryptoKey to sign, it's useless without the SESSION_ID

XSS is script execution in the context of the current page. There are no inherent restrictions to this execution, so it might do signed API requests in the name of the user in the same way as the intended code could do it. Or it might just call internal functions which encapsulates all this functionality already.

Prevent session fixation (the SESSION_ID alone isn't enough)

Session fixation is caused by not assigning a new SESSION_ID when logging in or changing the user. If your application has this bug, then it keeps having it after adding your CLIENT_SECRET since based on your design the same SESSION_ID also results in the same CLIENT_SECRET, i.e. it has no user specific component.

Each client/server have proof that the request/response isn't tampered

Tampered by whom? If it is about MITM-attacks this is correct, but only as long as the MITM did not get the CLIENT_SECRET from the response during login. If it is about client-side tampering like with developer console, browser plugins or XSS - then there is no such proof.

11
  • Thank you for your response. 1- For CSRF attack, you've confirmed it's help. 2- For XSS, my mistake, you're right this not changes anything. I think we should mitigate XSS attacks because we use a strict CSP where inlined scripts aren't authorized and where all external resources have an integrity attribute hash.
    – lakano
    Mar 3, 2023 at 15:32
  • 3- For session fixation, we generate at each sign in a new random session id of 128 bits. Also, it's not possible to set a session from GET/POST, it's only through the cookie (inaccessible from JavaScript) 4- Indeed, for a MITM is already there at the sign in request, this also mean he just need to re-use the email/password ;-) For a client-side tampering, it's like having access to the computer of the victim => game over!
    – lakano
    Mar 3, 2023 at 15:32
  • @lakano: "we generate at each sign in a new random session id of 128 bits" - in this case your application is not vulnerable for session fixation even without the client secret. I just said: if it would be vulnerable then adding the client secret would not fix this. Mar 3, 2023 at 15:33
  • Understood, thank you so much for your feedback! :)
    – lakano
    Mar 3, 2023 at 15:34
  • Just to match the initial question and complete/validate your answer: « Do you believe there is still some attacks possible to hijack the session of the user? (excepted of a physical access to the device of the victim) » ( For the crypto question, I suppose I need to ask this on the Crypto StackExchange )
    – lakano
    Mar 3, 2023 at 15:43
0

A couple of things to note:

  • since you store the SERVER_SECRET and SESSION_ID on the server side, why would you want to calculate the CLIENT_SECRET with every request and not just store it along side the SESSION_ID (e.g. at a lookup table)? That's too CPU expensive and there's not a real security benefit, because an attacker with access to the CLIENT_SECRET at the server side should be able to also access the others too
  • you calculate too many things with every request; you calculate the TIME_STEP and CLIENT_SECRET, verify a signature, make a db call, recalculate the TIME_STEP in case of a glitch and (if "For all responses" you also mean error responses) you calculate a signature for each response. That's a lot to process with each request/response cycle, which is probably susceptible to a DoS attack
  • DoS or not, your design will cost you a lot on CPU and network resources utilization, which is not ideal for a cloud solution
6
  • Thank you for your suggestions. (1) This prevent to call the DB if the proof of authenticity is false. Also, we have some micro-services where we just want to restrict a little bit the access but where we don't need DB access at all, and don't need to known who is logged or if he session is still alive. Nevertheless, you're right and we can permit the 2 options, if we store it also in DB, and when we need the DB to directly take it here rather that requiring CPU time for this.
    – lakano
    Mar 5, 2023 at 11:37
  • About (2) and (3), security require sacrifices. If you have a better design for prevent a session to be stolen, I will be really interested.
    – lakano
    Mar 5, 2023 at 11:40
  • @lakano you can use caching to avoid hitting the db with every request, which also has advantages and disadvantages on its own. On the replay attack, you can use numbers that increment with each request (given that they refer to the same session id) which may facilitate things. The (3) point is crucial, because it can lead to OpEx attacks which, in turn, can render a solution unusable. So it's not just about sacrifices - if your solution costs more than the financial benefits of the product then it probably won't survive the test of time
    – user284677
    Mar 5, 2023 at 13:55
  • Your solution doesn't prevent a session to be stolen and used, a MITM can still easily increment himself the number, it's surely enough to stop scripts kiddies, but we want a decent security. About DDoS, we're using CF workers, they have auto-scaling and they already blocking DDoS before our code is reached. I heard your advices, and I would love to find a better way to protect the user session, but I don't find a better idea that the one I've proposed for the same security.
    – lakano
    Mar 5, 2023 at 16:08
  • 1
    @lakano ok. Another solution would be to implement mTLS, however I'm not sure whether that would be suitable in your case, given your current approach
    – user284677
    Mar 5, 2023 at 16:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .