jimmycouch
jimmycouch

Reputation: 108

How can I secure this form post from manipulation

I have a very unique situation. I am building an application where a user uploads a QR code to my site, and when decoded, it has a matching ID already stored in the DB (as in I already have a record of the qr code before the user uploads it)

When they upload it, I decode it, which is a base64 string like so 6BbW0pxO0YENxn38HMUbcQ==

Like I said, That code corresponds to some information, and after they upload the QR image, I redirect them to one more page, which shows them the qr code, the corresponding information stored in the DB, and they are also prompted with a submit button. When pressed, I take note that they have confirmed, and I do some other things.

To elaborate

As a user I go to www.url.com/code/upload and upload an image. I am then redirected to /code/new with the page displaying the data stored in the DB from the correspond decoding of the image.

How can I make the submit button on /code/new reliable? Here are the solutions I can think of, and their vulnerabilities

Essentially, I have a user upload a qr code, and after submission, I need to verify to verify that they were the one that uploaded the image. I lose all state, and information after they upload the image.

A proposed solution:

After the user uploads the image, create a unique string and store it in the session and also a record in the database (A user is logged in, so its the current_user.session_token), then when the user clicks submit on /uploads/new, I grab the session token, and verify that the current_user.session_token == session_token.

At this point, I can only verify that the current user was the one that uploaded the image.. I then need to somehow grab the base64 string that was from the uploaded QR code.

Another proposed solution: I temporarily put the base64 string as an attribute of the user when they upload the file. So I will have current_user.session_token and current_user.base64_string. Then when the user clicks on the 2nd submit button, I do something like

#check if the user's token is equal to the sessions, and the string is not nil
if current_user.session_token == session_token && !current_user.base64_string.nil?

       data = current_user.base64_string 
       #hooray! I have the QR code
 end

Then delete the session token, and then delete the current_user's base64_string and session_token

if a malicious user attempts to forge their session token, it won't match their's store in the database, then when I handle the post request, I will not proceed. But If a legitimate user makes the second post request, the session Id matches, and I am able to grab the base64 string.

TL;DR, I need 2 concurrent post requests that carry the information without losing it, and can verify that both the first and second post requests were done by the same user.

If anyone had the patience to read through that, I appreciate it! if there are any suggestions I would greatly appreciate it, or if you think my solution is sufficient or not, please let me know. Thanks!

Upvotes: 1

Views: 428

Answers (1)

Neil Slater
Neil Slater

Reputation: 27207

Sign the parameters generated from the first request, and verify the signature when you process the second request.

The technology to use for this is HMAC. Here's how to use one version available in Ruby:

require 'openssl'
secret = 'ABCDEFGHIJKLMNOP'
data = 'user:code'
signature = OpenSSL::HMAC.hexdigest( 'sha256', secret, data )
p signature

Output:

"bd7194c0604902d6594694d25e7f27bdc2d10926638e0ce8bdda3f6debb37f6a"

This is how you can use it to link two HTTP routes together so that the second one can trust that parameters sent to it via the first one have not been tampered with:

  • When the first route is called, generate or fetch a secret. It is important that this secret value is not ever sent or exposed to the end user. It can simply be application configuration (which then applies to all linked requests), but if you can store it associated with perhaps the QR code, then the strongest protection is to generate a long random string just before creating the signature, and to store it ready to use to confirm the second step. Something like SecureRandom.hex is great for a short-term secret if you have somewhere server-side to store it.

  • Combine all the parameters on the form that you want to be tamper-free into one long message. Easiest thing to do is .join them in an array, and you should use a delimiter that is not allowed in any value, and that you are also not accepting due to validation. This string is the value to use for data in the example.

  • Generate the form that calls the second route. In addition to the params you want to accept at next stage, add the signature value generated as above. Do not send the value of secret to the client by putting in the form or cookie etc.

  • When you receive the request from the second route to finalise the multi-stage request, generate the signature from the user-sent params (after validating them), and compare with the one sent to you from the form. If it is the same, then the request is valid. If it is different then the data may have been tampered (provided you have no bugs - do check things such as consistent character encoding if any param can contain non-ASCII characters)

  • Provided you have kept the secret truly secret from the end user, they have next to no chance of generating a correct signature. Only your code in routes one and two knows how to do it (because it has access to correct secret, not because of any special fact on how it is written). Therefore you can trust that the values have not been modified.

Upvotes: 1

Related Questions