可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm developing my own PHP framework. It seems all the security articles I have read use vastly different methods for user authentication than I do so I could use some help in finding security holes.
Some information that might be useful before I start. I use mod_rewrite for my MVC url's. Passwords are encrypted with 24 character salt unique to each user. mysql_real_escape_string and/or variable typecasting on everything going in, and htmlspecialchars on everything coming out.
Step-by step process:
Top of every page:
session_start();
session_regenerate_id();
If user logs in via login form, generate new random token to put in user's MySQL row. Hash is generated based on user's salt (from when they first registered) and the new token. Store the hash and plaintext username in session variables, and duplicate in cookies if 'Remember me' is checked.
On every page, check for cookies. If cookies set, copy their values into session variables. Then compare $_SESSION['name'] and $_SESSION['hash'] against MySQL database. Destroy all cookies and session variables if they don't match so they have to log in again.
If login is valid, some of the user's information from the MySQL database is stored in an array for easy access. So far, I've assumed that this array is clean so when limiting user access I refer to user.rank and deny access if it's below what's required for that page.
I've tried to test all the common attacks like XSS and CSRF, but maybe I'm just not good enough at hacking my own site! My system seems way too simple for it to actually be secure (the security code is only 100 lines long). What am I missing?
Edit: For calling functions from the controller, anything that uses anything other than SELECT queries will require $_POST data to confirm a delete, for example, in addition to the user rank requirements.
I've also spent alot of time searching for the vulnerabilities with mysql_real_escape string but I haven't found any information that is up-to-date (everything is from several years ago at least and has apparently been fixed). All I know is that the problem was something to do with encoding. If that problem still exists today, how can I avoid it?
The encrypt function I borrowed from somewhere and modified:
public function encrypt($str, $salt = NULL) {
if ($salt == NULL) $salt = substr(md5(uniqid(rand(), true)), 0, 24);
else $salt = substr($salt, 0, 24);
return $salt.sha1($salt.$str);
}
回答1:
Do yourself a favour and use a standard library for hashing your passwords.
Because security tends to be a lot more complicated and with more invisible screw up possibilities than most programmers could tackle alone, using a standard library is almost always easiest and most secure (if not the only) available option.
The standard library:
Take a look at: Portable PHP password hashing framework: phpass and make sure you use the CRYPT_BLOWFISH
algorithm if at all possible.
example of code using phpass (v0.2):
require('PasswordHash.php');
$pwdHasher = new PasswordHash(8, FALSE);
// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );
// $hash would be the $hashed stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
echo 'password correct';
} else {
echo 'wrong credentials';
}
PHPass has been implemented in some quite well known projects:
- phpBB3
- WordPress 2.5+ as well as bbPress
- the Drupal 7 release, (module available for Drupal 5 & 6)
- others
The good thing is that you do not need to worry about the details, those details have been programmed by people with experience and reviewed by many folks on the internet.
Whatever you do if you go for the 'I'll do it myself, thank you' approach, do not use MD5
anymore. It is a nice hashing algorithm, but utterly broken for security purposes.
Currently, using crypt, with CRYPT_BLOWFISH is the best practice.
CRYPT_BLOWFISH in PHP is an implementation of the Bcrypt hash. Bcrypt is based on the Blowfish block cipher, making use of it's expensive key setup to slow the algorithm down.
For more information on password storage schemes, you could also read Jeff`s blog post about it: You're Probably Storing Passwords Incorrectly
回答2:
I don't see any real prevention for CSRF attacks in there. CSRF is (from my understanding) the most commonly overlooked/screwed up vulnerability in websites. Many of the big guys have been hacked via CSRF vulnerabilities.
The current best solution to this problem is using synchronizer tokens.
You had also better be very religious about escaping/encoding user input properly. Things that are inserted into the database must be escaped, and things output back to the user have to be encoded based on their context: CSS, HTML, JavaScript, etc all have to be encoded differently.
Furthermore, you shouldn't combine SHA-1 with MD5. While SHA-1 is stronger than MD5, you are essentially weakening it by combining it with MD5. Additionally, neither of these algorithms are recommended for use in new applications. For the time being you should be using SHA-256 (it is available out-of-the-box in PHP).
回答3:
By trying to over-engineer the approach you may be undermining the security.
What session handler are you using? Have you checked that session_regenerate_id() renames an existing session rather than making a copy of it? If the latter then you are creating lots of session files that are much easier to find with a random search.
How do you deal with users splitting sessions? This line of code is going to break depending on the browser and how the user splits the session.
The only place you should be using session_regenerate_id() is when you authenticate the user and when the user logs out.
mysql_real_escape_string and/or variable typecasting on everything going in
NO, no, no. This keeps coming up here.
You don't change the representation of incoming data. By all means /validate/ input, and you should always /sanitize/ output - you don't /sanitize/ input.
If user logs in via login form, generate new random token to put in user's MySQL row. Hash is generated based on user's salt (from when they first registered) and the new token. Store the hash and plaintext username in session variables,
Nothing here which improves your security
Store the hash and plaintext username in session variables, and duplicate in cookies if 'Remember me' is checked
So you store the plaintext password on the users machine! This is so not secure. If you want a remember me function, then encrypt the password and an expiry date with a salt and store that.
On every page, check for cookies. If cookies set, copy their values into session variables.
So users can manipulate any value stored in the session by returning a cookie with the same name? OK so they probably can't predict the hash so it'd be tricky to fake the user - but there are lots of other ways this could be used to compromise your system.
You've tried very hard to make your system secure - but at best all you've actually done is obfuscated the parts of your system that work properly.
C.
回答4:
You may want to check the user.rank against a whitelist, to make sure that the value exists in the lookup table and is valid (active, not deleted etc), and also to force typecasting and range.
As for tokens, you will want to generate a new token for each page that expects to receive GET/POST data from a form. If a session is hijacked then a token for the session could be insufficient. Setting a shorter timeout for the session could mitigate.
Don't trust anything from a cookie right away, consider using http://php.net/manual/en/book.filter.php before stashing in the Session.
If you plan to have an admin panel for the system it's ideal to enforce password strength, manual admin login name selection (in preference to admin/password stock logins upon setup).
Blacklist the usual login names such as "a';DROP DATABASE..." and "Administrator", you know the drill.
Use least-privilege accounts when setting up your database, you don't want your visitors to be playing havoc with your tables.
回答5:
"Passwords are sha1 and md5 encrypted"
Why would there be a need to use two hash functions? I guess you do it due to a lack of understanding (no offense!). You should read this article on secure password schemes (hint: use bcrypt).
回答6:
with session_regenerate_id(); you'll make the "back" button in your browser unusable. But still combined with "remember me" feature it become useless.
I can't see the CSRF defence in your description
There is not a single problem with mysql_real_escape string