Registration, login, session and posting security

2019-08-05 04:15发布

问题:

This will be the first time I ask a question to anyone in regard to web development. The reason why I decided to ask this question, spite many similar questions asked before by others, is because the answers that others received in the past came in great quantity and subjectivity, so I became confused and doubtful as to how to approach it.

I realize that there are different ways to approach this, which is why my question may cause some discussion, but I would like everyone to remember the specifics of my question, should any discussion arise: That I'm not asking for what is the most secure approach, but whether my approach is secure at all, or not; I'm aiming for the security level of popular forums, not large company websites such as Amazon or Paypal where money transactions take place.

What I use:

  • PHP 5.5 for password_hash
  • PDO w/prepared statements

REGISTRATION

All inputs are cleaned with the following:

  • trim
  • stripslashes
  • htmlspecialchars

and they must pass through preg_match.

The user must choose an account name and a nickname. The account name is used to login with, whereas the nickname is used to communicate with others on the forum. These two names cannot be identical with each other, nor can they already exist in the database.

The user is asked a random question to check if it's human or not.

LOGIN

All inputs are cleaned with the following:

  • trim
  • stripslashes
  • htmlspecialchars

and they must pass through preg_match.

An IP address can only attempt to log in once per minute, to a total of 3 times per hour, before the user will have to wait for 24 hours to attempt to log in again.

When account name is not found, or password is wrong, they receive the same message that either could be wrong.

Upon successful login a function is called, which creates a random 32 character long string, using characters ranging between "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$-_.,". This function will then loop this string through all the user rows in the database until it can no longer find an identical string, then pass the string and place it in the account's name (under SID1), before the same function is called again and another 32 character long string is placed (under SID2).

Whenever the user logs in, a new set of strings are made.

SESSION

$_SESSION (session_start) is not used. $_COOKIE is (for "remember me"). So I apologize if I'm using the wrong terminology by referring to it as a session or as a SID.

The two 32 character long strings that were placed in the database are merged and placed in a cookie under the same name. This 64 character long string is from then on the only measure of identifying the user. Whenever the user needs permission to do something, such as viewing a particular forum, or editing his account settings, these two 32 character long unique IDs (explode) are used to call the database and return the user's rank. In other words, the user id and account name is never used to call the database with, only the unique IDs in the user's cookie is. Before these strings are used to identity the user, however, it has to pass through a preg_match, just in case there's any unforseen way of tampering with the input from the cookie (I don't know whether there is or not, which is why I decided to take that precaution).

The cookie is set to destroy in 4 weeks, unless the user did not set "remember me" upon login.

Whenever the user is logged out, the cookie is destroyed by setting it to -1 year.

POSTING

Users are allowed to post any UTF-8 character they want, and this one input only has to pass through htmlentities before it is stored in the database. Passing scripts does not seem to work this way. They can write <script>alert('hello');<script> and it will appear that way in the post but the script will not run. But if I attempt to clean the output, instead of input, the script will run. Though, I may not know all the ways to pass scripts. So, enlighten me if you can think of any, or maybe I'll just have to find out the hard way.

I've made it so that if the user writes an URL in their post the URL is automatically put into an "a href". Likewise, images are put into "img", and YouTube videos are put into "iframe". Next step is to only allow one image and one youtube video per post, but I think I'm going to have to find out how caching works because my site's response time went from 0.25 seconds to over 3 seconds as soon as posts contained external images/videos. I will probably have to check external images whether they're really images or not to avoid hostile code hiding in the image but I'm not sure yet how that works; maybe the best way to find out whether the URL is an image or not is to check whether it's an image so I'll probably have to rewrite all of it.

Except for SSL, I'm not quite sure what else I can do to secure my site, or whether my login/registration/session/posting approach is in any way good enough.

Please let me know if I've missed something.

回答1:

I'm not taking half of a tenth of the security measures you take, but I take one you don't mention. You seem not to use SSL, so your id/password pass through the tubes in a readable state at login time. This means they can be listened to (no idea how, I couldn't do it myself, but they actually can be).

A little and easy trick I use against that:
- my login form is constructed by a php script, which sets a javascript variable (var salt) to a random value (lets say sha1() of current timestamp)
- this value is memorized serverside through a session variable (or maybe a cookie in your configuration) - when user submits the login form, a javascript function (lets say crypt_pw() ) is called
- this crypt_pw() function does a sha1 of the sha1 of the password (1) concatenated to the previously described salt before actually submiting
- the server receives this sha1(sha1(password)+salt) and compares it with a sha1((db stored password).salt).
(1)password stored in DB is already hashed so that it's not directly readable.
Something like:

$query="select count(*) from user_table where login='$login'
and sha1(concat(password,'salt'))='$jsCryptedBeforeSendPassword'"

The whole thing is much easier to do than to explain, and it insures that nobody can do anything with any "sniffed" password: sent from any other computer, the session variable won't exist serverside to allow comparing password. Even from the very same client on the very same computer but at a different time, the login form will be php constructed with another salt value so the sniffed crypted password can't be reused.

Btw, I can't check the code right now, maybe I salt-&-hash the login value too, not only the password one. This is all unuseful if you're https connected, of course.