Any vulnerability in populating email in registration form? - ruby-on-rails

My app allows users to invite people they know by email. If the invitee is not already registered, we send an email asking the invitee to register.
I'd like to be able to send unique signup links that contain an email GET param and use this GET param on the app side to populate the email field of the registration form.
Will I introduce any additional security vulnerability by doing so?
An example...
Email URL: https://www.domain.com/signup?email=me#email.com
The app will then use params[:email] to retrieve the email from the URL and set that as the registration form's email field.

You'll be leaking a bit of data potentially. If you did sequential numbers, and I got number 999, I'd know to try numbers 999 and below and I'd be able to get email addresses.
Here are some ideas on how to generate random, unique params: PHP: How to generate a random, unique, alphanumeric string?
You'll need to feed in the email address instead of an ID though.

No; as long as you treat that string as untrusted data and handle it appropriately, this doesn't introduce any vulnerabilities.

Related

Sending mail 'to' OpenStruct through mailer

I have an app where users can sign up for workshops and admin has a possibility to write an e-mail to all the participants through the app. The fragment of code to send mail message to the group looks like this
workshop.students_all.each do |user|
WorkshopNotifyGroupMailer.notify_user(user, workshop, subject, body).deliver_later
end
so it's nothing extraordinary (User and Workshops are instances of models).
Now, I wanted to add one additional e-mail address to be sent each time a group is notified (just to have a copy how does the sent mail look like). I thought of doing it something like that (to keep the code short):
admin = OpenStruct.new(email: 'admin#email.com', first_name: 'Nameless') #These are fields taken from User instance by mailer
WorkshopNotifyGroupMailer.notify_user(admin, workshop, subject, body).deliver_later
Unfortunately, I receive "Unsupported argument type: OpenStruct" error. Is there a way to send an e-mail which uses an instance of a model using some kind of artificial structure? (In this case just assume admin is not on the user list and won't be)

Don't allow a user to change email address using devise with Rails

I'm using devise to allow users to log in to a site. The authentication key is set to a username, which is to be unique. Also to be unique is the email address provided. It seems that somehow devise has already figured out that the email address should be unique. So that's good.
Now I want to let people change their passwords. I link over to my edit_user_registration_path, but notice that the user is allowed to change their email address. One option is to set reconfirmable to false... but I don't think I want to allow users to change their email addresses at all.
I think I could just remove the field from the devise view, but theoretically a carefully crafted PUT method could still let them change their email address. Is there a way to stop this field from being mutable? Or is it better to just let the email address be reconfirmable?
See http://trak3r.blogspot.com/2007/03/immutable-activerecord-attributes.html
So
class User
def email=(address)
if new_record?
write_attribute(:email, address)
else
raise 'email is immutable!'
end
end
end
In my case, the "answer" was to simply allow people to edit their email addresses. There are enough use cases in which somebody could legitimately want to change their email address that I figured there was no harm in trying to stop it.

How do you create a unique identifier for users for tracking purposes?

I would like to be able to add a user referal params on all invite links sent out from my site... example:
http://site.com/invited_by?=ajraux
How can I generate a code that is short like "ajraux" for all users on my site? Does it need to be a field in the database? Can it be created on the spot? How are web companies doing this?
Thanks
You could create random numbers and encode them in base-36, something simple like this:
rand(1e12).to_s(36)
Generate one for each user on first use and store it with the user. Add a unique constraint on your random token (in both your model and the database) and generate a new one if you get a uniqueness violation. You might want to log a warning somewhere that you'll see it if you need to try more than, say, five times to get a unique value; if you start getting a lot of warnings then bump that 1e12 up to 1e15 (or higher).
That would give you a random looking token attached to each user, the tokens would be URL-safe, they're quick and easy to generate, you shouldn't get that many collisions, and it will be easy to backtrack from a token to the user.
One way of doing this is to use the unique identifier of the user, say "id", that's in the database, but this is also dangerous because you are revealing too much about your database.
So you can add a twist to the previous situation and encrypt and decrypt the id so that when it's in the url it is encrypted and then when you receive it you can decrypt it and use it.
As cjapes said, using ID is not a good solution and mu is too short's answer is good for most cases. However if you have a site which offers vanity URLs, like about.me does then each user will have a permalink kind of column storing their vanity URL. You can just use the value from that column when building the URL.
On the receiving end you can do:
#referring_friend = User.find_by_permalink(params[:permalink])

Generate temporary URL to reset password

I am looking to implement a Forgot Password feature on my website. I like the option where an email containing a temporary one-time use URL that expires after some time is sent to the user.
I have looked at the following pages to get these ideas but I am not sure how to implement this using ASP.NET and C#. As one of the users indicated, if I can implement this without storing this information inside the database, that will be ideal. Please advise.
Password reset by emailing temporary passwords
Thanks.
Probably the easiest way is going to be to modify your users table to add 2 extra columns, OR if you don't want to modify the existing table you could add a new dependent table called "UserPasswordReset" or something like that. The columns are like this:
PasswordResetToken UNIQUEIDENTIFIER,
PasswordResetExpiration DATETIME
If you go with the additional table route, you could do also add the UserID column, make it a primary key and a foriegn key reference back to your users table. A UNIQUE constraint would also be recommended. Then you simply use a Guid in your asp.net application as the token.
The flow could be something like this:
User requests password reset for their account
You insert a new record in the table (or update their user record) by setting the PasswordResetExpiration to a date in the future (DateTime.Now.AddDays(1)), and set the token to Guid.NewGuid()
Email the user a link to your ResetPassword.aspx page with the guid in the query string (http://www.yoursite.com/ResetPassword.aspx?token=Guid-here)
Use the ResetPassword.aspx page to validate the token and expiration fields. (I.E. Make sure DateTime.Now < PasswordResetExpiration)
Provide a simple form that allows the user to reset this password.
I know you wanted to avoid modifying the database, but it really is probably the simplest method.
#Alex
You can also use System.Security.Cryptography classes in .NET for the hash algorithms. For example:
using System.Security.Cryptography;
...
var hash = SHA256CryptoServiceProvider.Create().ComputeHash(myTokenToHash);
...
Here, the System.Guid class in your friend, as it will generate a unique (well, unique enough) 128-bit number:
Generate a new Guid ( System.Guid.NewGuid() )
Store that Guid somewhere (Application object maybe?)
Send a custom URL in an email with that Guid
When the user hits the site, make them enter the password you sent in the email
If the passwords match, go ahead and force them to enter a new password
I used a Hashing Class to create unique automatic logins made up of the current date/time and the users email address:
string strNow = DateTime.Now.ToString();
string strHash = strNow + strEmail;
strHash = Hash.GetHash(strHash, Hash.HashType.SHA1);
get the Hash Class from: http://www.developerfusion.com/code/4601/create-hashes-md5-sha1-sha256-sha384-sha512/
Then just take it from the URL using:
if (Request.QueryString["hash"] != null)
{
//extract Hash from the URL
string strHash = Request.QueryString["hash"];
}
I would definitely include the database in this process. Once a reset is requested, it's a good idea to indicate that the account is locked out.
For example, if you are changing your pw because you think your account may have been compromised, you definitely don't want it to remain accessible while you go about the change process.
Also, inclusion of "real" information in the reset token could be decoded if someone really wants it and has the horsepower. It would be safer to generate a random string, save it in the db in the row for that user, and then key back to it when the link is clicked.
This gives you two things:
1) There's nothing to decrypt, and therefore nothing of value can be gained from it.
2) The presence of the token in the user record indicates that reset is in progress and the account should be treated as locked out.
The goal of sending some data|string to user email is validation of account owner. Please care about some points:
Avoid sending important information in reset or activate link.
It's best way to store unique string data conjunction with user
account and send it as that link. but be aware if you send just one
section as link to user email and just check it in page, your
application may be in dangerous by brute-force or dictionary
attacker. It's enough to check a list of string to find some links
and change password. I know that has a little chance, but not zero.
Result:
I think it's better if you
combine user email with string link then encrypt them
(not hash because hashed value can't be reverse) and send to user
email.
User click and your page get the encrypted value.
decrypt value.
extract user email.
find email in database.
compare string from received link with other one attached to user
email in database.
Good luck.
I'd use a hash code to validate details in the password reset url. This can all be done without writing anything to the DB or sending any privileged info to an attaker.
To briefly explain normal password salt and hashing; say the salt is 1111 and the pasword is password, you'd concatenate the two and hash the string 1111password, say this gives you a hash of 9999, you'd then store the original salt 1111 and hash 9999 in your user record.
When you are validating a password you use the stored salt, concatenate the password attempt, hash it and compare with the stored hash. For example asecret becomes 1111asecret but hashes to 8888. This doesn't match the original hash so the password match fails.
Of course the salt and hash would normally be properly generated and calculated with established crypto libraries (don't invent your own!).
For the password reset URL I'd put in the unique identifier for the user, i.e. email address, the date the request is made, and a new hash. This hash would be generated from those details concatenated together plus the salt and hash already stored for the user.
For example:
Email: user#example.com
Request Date: 2014-07-17
Salt: 1111
Hash: 9999
Generate a new hash of those concatenated, i.e. 'user#example.com2014-07-1711119999', say this gives a hash of 7777.
The URL I then generate would then have the email, request date and the new hash:
https:\\www.example.com\ResetPassword?email=user#example.com&requestdate=2014-07-17&hash=7777
The server will combine the email and supplied date with it's salt and hash and confirm the hash it generated is the same as the supplied one. If this is Ok then it will show the reset form with the same three parameters hidden behind it, otherwise an error. These get resubmitted and rechecked when the new password is entered to prevent that form being spoofed.
The email address needs to be supplied to make the request and it is only sent out in an email to the same address. the date is hardly priveleged info and the hash is not reversible so gives nothing anyway. Nothing has been written to the database and any tampering with the parameters causes the hash to fail and the URL to report an error.
There is an issues with this approach. A safe hash makes the token really long. Either you integrate the salt into the hash itself (makes it about 20 charactes longer), or you store this unique salt in the database. If you store the salt in the database, you could as well store a random token which is not derrived from any existing
Depending on your needs, you could encrypt information, in a format similar to the following format
(UserId)-(ExpireDate)
Encrypt the data, make that the link, then decrypt the data and take action from there...
Crude, but most likely usable, and not requiring DB usage

Signup or Invitation Email Verification w/o Database

I'd like to keep my database clean of stale almost-accounts, and I was thinking about making new signups and invitations put their data into the welcome email as an encrypted or hashed url. Once the link in the url is visited, the information is then added into the database as an account.
Is there something that currently does this? Any references, thoughts, or warnings about doing user registration this way?
Thanks!
Edit:
I've made a working example, and the url is 127 characters.
http://localhost/confirm?_=hBRCGVqie5PetQhjiagq9F6kmi7luVxpcpEYMWaxrtSHIPA3rF0Hufy6EgiH%0A%2BL3t9dcgV9es9Zywkl4F1lcMyA%3D%3D%0A
Obviously, more data = larger url
def create
# Write k keys in params[:user] as v keys in to_encrypt, doing this saves LOTS of unnecessary chars
#to_encrypt = Hash.new
{:firstname => :fn,:lastname => :ln,:email => :el,:username => :un,:password => :pd}.each do |k,v|
#to_encrypt[v] = params[:user][k]
end
encrypted_params = CGI::escape(Base64.encode64(encrypt(compress(Marshal.dump(#to_encrypt)), "secret")))
end
private
def aes(m,t,k)
(aes = OpenSSL::Cipher::Cipher.new('aes-256-cbc').send(m)).key = Digest::SHA256.digest(k)
aes.update(t) << aes.final
end
def encrypt(text, key)
aes(:encrypt, text, key)
end
def decrypt(text, key)
aes(:decrypt, text, key)
end
# All attempts to compress returned a longer url (Bypassed by return)
def compress(string)
return string
z = Zlib::Deflate.new(Zlib::BEST_COMPRESSION)
o = z.deflate(string,Zlib::FINISH)
z.close
o
end
def decompress(string)
return string
z = Zlib::Inflate.new
o = z.inflate(string)
z.finish
z.close
o
end
Thoughts:
Use true asymmetric cypher for the "cookie" to prevent bots creating accounts. Encrypt the "cookie" using public key, verify it by decoding with private key.
Rationale: If only a base64 or other algorithm was used for encoding the cookie, it would be easy to reverse-engineer the scheme and create accounts automatically. This is undesirable because of spambots. Also, if the account is password protected, the password would have to appear in the cookie. Anyone with access to the registration link would be able not only to activate the account, but also to figure out the password.
Require re-entry of the password after activation through the link.
Rationale: Depending on the purpose of the site you may want to improve the protection against information spoofing. Re-entering the password after activation protects against stolen/spoofed activation links.
When verifying the activation link, make sure the account created by it is not created already.
How do you protect against two users simultaneously creating an account with the same name?
Possible answer: Use email as the login identifier and don't require unique account name.
Verify the email first, than continue account creation.
Rationale: This will minimize the information you need to send in the cookie.
There are some e-mail clients which break URLs after 80 letters. I doubt that you can fit all the information in there.
Some browsers have limitations for the URL, Internet Explorer 8 has a limit of 2083 characters, for example.
Why don't you clean your database regularly (cron script) and remove all accounts that haven't been activated for 24 houres?
I have done pretty much the same before. I only have 2 suggestions for you,
Add a key version so you can rotate the key without breaking outstanding confirmation.
You need a timestamp or expiration so you can set a time limit on confirmation if you want to. We allow one week.
As to the shortest URL, you can do better by making following changes,
Use a stream cipher mode like CFB so you don't have to pad to the block size.
Compress the cleartext will help when the data is big. I have a flag and only use compression when it shrinks data.
Use Base64.urlsafe_encode64() so you don't have to URL encode it.
There's a few problems with your solution.
Firstly, you're not setting the IV of the cipher. In my view this has exposed a serious bug in the Ruby OpenSSL wrapper - it shouldn't let you perform an encryption or decryption until both key and iv have been set, but instead it's going ahead and using an IV of all-zeroes. Using the same IV every time basically removes much of the benefit of using a feedback mode in the first place.
Secondly, and more seriously, you have no authenticity checking. One of the properties of CBC mode is that an attacker who has access to one message can modify it to create a second message where a block in the second message has entirely attacker-controlled contents, at the cost of the prior block being completely garbled. (Oh, and note that CFB mode is just as much a problem in this regard).
In this case, that means that I could request an account with Last Name of AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA and my own email address to recieve a valid URL. I can then, without knowing the key, modify the email address to victim#victim.com (and garble the Last Name in the process, which doesn't matter), and have a valid URL which I can submit to your server and create accounts for email addresses that I don't control.
To solve this, you need to compute a HMAC over the data, keyed with a secret that only the server knows, and send that as part of the URL. Note that the only reason you need encryption at all here is to secure the user's password - other than that it could just be plaintext plus a HMAC. I suggest you simply send as the url something like:
?ln=Last%20Name&fn=First%20Name&email=foo#bar.com&hmac=7fpsQba2GMepELxilVUEfwl3%2BN1MdCsg%2FZ59dDd63QE%3D
...and have the verification page prompt for a password (there doesn't seem to be a reason to bounce the password back and forth).
I will take a crack at describing a design that may work.
Prerequisities:
Cryptography library with support for RSA and some secure hash function H (eg. SHA-1)
One pair of private and public keys
Design:
Unique user identifier is e-mail address
An account has associated password and possible other data
The activation cookie is kept as small as possible
Process:
User is asked for e-mail address and password. Upon submission of the form a cookie is computed as
cookie = ENCRYPT(CONCAT(email, '.', H(password)), public key)
E-mail is sent containing a link to the activation page with the cookie, eg.
http://example.org/activation?cookie=[cookie]
The activation page at http://example.org/activation decrypts the cookie passed as parameter: data = SPLIT(DECRYPT(cookie, private key), '.')
In the same activation page the user is asked for password (which must be hashed to the the same value as in cookie) and any other information necessary for the account creation
Upon submission of the activation page a new account is created
Please point out anything that I have missed or any improvements. I'd be glad to update the answer accordingly.

Resources