Slow Encryption in Coldfusion - mysql

I have been trying to encrypt all the user's password in the MySql database using the following logic:
<cfquery datasource="mydatabase" name="userlist">
select userid, password
from mytable
LIMIT 0, 2000
</cfquery>
<cfoutput query="userlist">
<cfset pwd = encrypt(#userlist.password#, mykey, "AES/CBC/PKCS5Padding", "hex")>
<cfquery datasource="mydatabase">
update members
set password = '#pwd#'
where userid = '#userid#'
</cfquery>
</cfoutput>
This seems simply enough just to encrypt 2000 records. But the CF takes over 2 hours to do this! It averages only encrypting 13 records in one minutes. Is it something wrong with the code or is there a problem in CF Admin setting that needs to be tweak to speed up this thing?
p.s. the CF template does run but it will show 504 time-out on the browser in few minutes. However, the 2000 records will still be completed in the background in 2 hours time.
EDITED:
I ran the first set of query on the database directly. It only took 0.05 seconds to retrieve all 2000 rows. The second query to updated the database with already encrypted password took also less than a second. That leaves only the CFOUTPUT query and the CFSET ENCRYPT lines that are causing the the 2.5 hours run. I don't know how else to optimize this.

Working within comments was getting difficult..
In this post, I don't salt hashes, but it's just example stuff, you should definitely salt (add random, but consistent, string data).
Are you familiar with hashing? There is no need to encrypt passwords in a decryptable format. Instead, you can hash the user's entered password each time they enter it (registration, login, matching for password-change).
Among the several reasons hashing is preferred: Users typically insist on using ONE or as few as possible passwords everywhere. By providing their password in password-recovery (email "Your password is p#55word"), or over the phone, you may expose much of their online existence.
I do not use mysql often but you can md5 hash on the database side, as shown here, MySQL MD5 SELECT. Further, if you salt the hash (add your own string (like "sodium") to the hash that is not exposed anywhere, so long as it's the same salt for the user each time), you greatly increase the security as databases of straight hashes of entire dictionaries exist. Cookies md5's to 597b56e53847cd6a4712ac183f61fa68 but Cookiess (one more s) md5's to the very different 16fb9ddb550f238b848f8490854ef792.
You could use the link I referenced above to hash into a new column and experiment for data integrity for your own ease. Something like
update table
set hPwd = md5(password + 'Sodium' + username)
where hPwd = ''
limit(0, 2000)
so that you can still do it in pages if you like. There exist several hash functions, see dev.mysql.com/doc/refman/5.5/en/encryption-functions.html like SHA1.
Jack replied
Thanks. I would definitely use hash if not for the times that we may need to go into an account as a customer sees it, which means we need to know the password
Let me explain
When a user logs in, you would match their criteria like this
select data_you_want
from users_table
where username = <cfqueryparam cfsqltype="cf_sql_varchar" value="#form.username#">
and password = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hash(form.password)#">
If that matches, you'd store the hashed password if you want to store the password in session data. Any pages where you want to re-affirm that the logged in user has the correct credentials, you'd use a query like this (notice the lack of hashing this time, vs the login query). Additionally you'd store asAdmin = 0, as a flag indicating that this is a normal user logging into their own account.
select data_you_want
from users_table
where username = <cfqueryparam cfsqltype="cf_sql_varchar" value="#form.username#">
and password = <cfqueryparam cfsqltype="cf_sql_varchar" value="#form.password#">
So, RE: wanting admins to be able to login. You create an admin gateway page that requires a different set of credentials (I'll get back to that) and if successful, you allow the user to login.
Because you want the user to still have some security, you might create a device like this... If a user is asking for admin assistance, you can give them a page to create an admin key like say #randrange(1,1000000)#
<cfset akey = randrange(1,1000000)>
...
update users_table
set akey = <cfqueryparam cfsqltype="cf_sql_varchar" value="#akey#">
where userID = <cfqueryparam cfsqltype="cf_sql_integer" value="#session.userID#">
...
You've requested some help from customer service and want them to login to your account.
Please give them this key #decimalformat(akey)#.
<!--- Decimal formatting makes the key easier to read, it's not stored that way --->
And then, when an admin logs in from a login page within the admin system, they enter the username and THIS KEY rather than a password.
That looks like this
select data_you_want
from users_table
where username = <cfqueryparam cfsqltype="cf_sql_varchar" value="#form.username#">
and akey = <cfqueryparam cfsqltype="cf_sql_varchar" value="#form.akey#">
If successful, it stores information similar to the login page that users normally access, no longer worries about the admin key.
Like
<cfscript>
AdminID = session.userID;
structclear(session); // get rid of all admin credentials, we're mimicing the user.
session.asAdmin = AdminID; // Create a flag that we can append to database activity if we want to track whether a user did this or an admin (and which admin!) did this on their behalf. If it's 0, it's the normal user, if it's greater than 0, it signifies which admin user did this.
session.username = GetUserStuffs.username;
session.pwd = GetUserStuffs.password; // because we're pulling a hashed password from the db, we do not rehash it.
session. ...other credentials you want in the session scope, the same things you'd store on normal user login...
</cfscript>
<!--- Now, use a client side redirect to send the admin, now logged in as the user, to the site root, or any normal-member-accessible-page. --->
<script>
window.location = "/";
</script>
You can also reset the akey for the user on this page, so that the key is only good once.
Alternatively you can do this all without an akey but that allows your admins free roam of user accounts and that's grossly insecure.

I think the issue may be because of having lots of individual cfquery calls when you execute the code so it's not very efficient. In the past I've tackled this by just using ColdFusion to create and render the SQL in your browser. You can then take that output and run it directly against the database which should be quicker to execute.
So your code would be something like this:
<cfquery datasource="mydatabase" name="userlist">
select userid, password
from mytable
LIMIT 0, 2000
</cfquery>
<cfoutput>
<pre>
<cfloop query="userlist">
<cfset pwd = encrypt(userlist.password, mykey, "AES/CBC/PKCS5Padding", "hex")>
SET #pwd = '#pwd#';
SET #userid = #userlist.userid#;
UPDATE members
SET password = #pwd
WHERE userid = #userid;
</cfloop>
</pre>
</cfoutput>
This will produce an output something like this:
SET #pwd = '0E8C9C1026C0EB950CA8A398C8E5ED6B';
SET #userid = 1;
UPDATE members
SET password = #pwd
WHERE userid = #userid;
SET #pwd = 'F6B9CE35A39A15D791F7C3711399493A';
SET #userid = 2;
UPDATE members
SET password = #pwd
WHERE userid = #userid;
SET #pwd = '1DB3AA5B3DF622CE905BD4E992B24E6D';
SET #userid = 3;
UPDATE members
SET password = #pwd
WHERE userid = #userid;
You can then run those SQL statements directly against the database server.
Ideally you should be using one-way encryption, but that's another question which is detailed here: https://security.stackexchange.com/questions/211/how-to-securely-hash-passwords

Related

Look up my current userID with mySql

This scripts selects an account form my MySQL database with the userID 1:
$_SESSION['user_id'] = 1;
If my MySQL account has id 1 then it will change that account.
I wonder how you can change the = 1; so it automatically looks up which account you are on at the moment? Right now it only works with the account with userID 1.
MySQL database name for my userid is "userID".
Usually you would say something like
SELECT * FROM user_profiles WHERE user_id=1 in MySQL.
Also, I would recommend that you use PDO and properly escape the user input using
htmlspecialchars($_SESSION['user_id'])
in your PHP file since the user_id input most likely can be manipulated by the user.
You are using Session so it can not change the userID unless you destroy that session by using this PHP function:
session_destroy();
And if you want to know your current userID:
echo $_SESSION['user_id'];

Test whether or not log-in system is protected against sql injection

So for a school project I have to make a site with a log-in system. It has a username and password field, and a submit button. It compares the username and password with those in a MySQL database. If the combination is in the database, the user may proceed, else they are redirected to the log-in page. I use prepared PDO statements for my database connection.
Now my teacher wants me to test the safety by performing sql attacks on the log-in system. Unfortunately I have no idea what to put in these boxes, and what would be the outcome. For example, I have tried putting values in both username and password fields that will return true, like this:
1==1, 1===1, 0 is null
But I do not know whether or not I have succeeded and if attackers may access or truncate my database by these sort of statements.
Html code:
<form method="post" action="includes/login.php">
<input type="text" name="gebruikersnaam" >
<input type="password" name="wachtwoord" >
<input type="submit" value="login">
</form>
Php authentication:
$myusername=$_POST['gebruikersnaam'];
$mypassword=$_POST['wachtwoord'];
$sql="SELECT * FROM leerling WHERE leerlingnummer='$myusername' and wachtwoord='$mypassword'";
$sql2="SELECT * FROM lop WHERE gebruikersnaam='$myusername' and wachtwoord='$mypassword'";
$statement2=$conn->prepare($sql2);
$statement2->execute();
$count2=$statement2->rowcount();
if($count2==1){proceed}
$statement = $conn->prepare($sql);
$statement->execute();
$count= $statement->rowcount();
if($count==1){proceed}
else {deny access}
Imagine this query:
SELECT id FROM users WHERE email=? AND password=? LIMIT 1
Now imagine the values would be foo#bar.hello and an empty string for password:
SELECT id FROM users WHERE email='foo#bar.hello' AND password='' LIMIT 1
This would not be harmful if these credentials are not in your database. Now lets give different input:
For email we fill in an empty string, and for password we insert ' OR 1=1 (Note the first apostrophe)
Your teacher wants you to find out whether this means your SQL server will execute the following query:
SELECT id FROM users WHERE email='' AND password='' OR 1=1 LIMIT 1
SQL is a declarative language with which you declare the expectations you have for your result. If your server would interpret our input as stated above, the first users id would be considered correct, simply because one is equal to one.
As it is, it is susceptible to SQL injection
The thing to look at when trying to inject is can I close the statement I'm in right now and add more to the end.
so if you enter username = 123456' -- the SQL statement becomes SELECT * FROM leerling WHERE leerlingnummer='123456' --' and wachtwoord='unimortant'
the -- starts a comment so all it does is select whatever student number is entered ignoring the password.
PDO has good alternatives to prevent this from happening called Prepared Statements. You declare your SQL queries and only enter where user infromation is going to be entered by using a ? or :lable and then bind user input to those points. The page does a way better job at explaining it. This way all user data is clearly seperated from the rest of the command and will be treated as a litteral string rather than a command. Stopping SQL injection.
$sql="SELECT * FROM users WHERE username = '{$_REQUEST['username']}' AND password = '{$_REQUEST['password']}";
Write query in such format will avoid sql injection.
$sql = 'SELECT * FROM users WHERE username = ? AND password = ?';
$query = $db->prepare($sql);
$query->bindParam(1, $_REQUEST['username']);
$query->bindParam(2, $_REQUEST['password']);
Or pass the parameter to mysql_real_escape_string function and then pass to queries.
$username=mysql_real_escape_string($_REQUEST['username']);
$password=mysql_real_escape_string($_REQUEST['password']);

SELECTing in a WHERE clause, inside a SELECT statement

I'm not exactly sure on the correct technical wording, so excuse my title, but here's the problem. I have a MySQL database, and in the user table I have *user_name*, a *password_salt*, and an md5 password containing the password then salt. In a program, users connect and I get one query to send to validate a user.
When a user connects I need a way of selecting their user_name, and comparing the given password to the stored password, which requires retrieving the salt somewhere in the WHERE statement (I guess).
This is my hypothetical "example":
SELECT user_name
FROM users
WHERE user_name='$nick' AND
password = md5(CONCAT('$md5pass', md5((select password_salt FROM users where user_name='$nick'))))
LIMIT 1
Resolution Update: Got it working, thanks for the suggestions, a normal select sufficed, the problem was that the sql-auth api wasn't receiving the password unless the port was specified.
Actually you can freely use any column from table declared in "FROM" clause not only in "SELECT" clause, but also in "WHERE" clause, so I don't see a need to subquery here. Let it be simply:
SELECT user_name
FROM users
WHERE user_name='$nick' AND
password = md5(CONCAT('$md5pass', md5(password_salt)))
LIMIT 1
This way a row is selected only if it matches both:
- user name is correct
- the password in row matches given password
I am not sure though if I used md5() functions correctly. I copied your example.
SELECT user_name
FROM users
WHERE user_name='$nick' AND
password = md5(CONCAT('$md5pass', password_salt))
LIMIT 1
Try this instead:
SELECT user_name
FROM users
WHERE user_name='$nick' AND
password = md5(CONCAT('$md5pass', md5(password_salt)))
LIMIT 1

Login Server Side Logic

This question has more to do with how I am setting up my server side code for a simple login script. I'm interested in the best way to achieve my goal, which is of course to verify a users username and password against a database and present them with either a successful login, a registration page, or a username or password found, but the alternative is wrong.
Right now, I have it set up where my sql query scans the database for both the user and pass:
SELECT * FROM test WHERE userName='" + userName + "' AND pass='" + password + "'"
Problem with this approach is it either returns a true or false...I cannot tell if one of the inputs was correct and the other wasn't. It either finds the record, or it doesn't.
So I could query based on the username alone, and if found check the record for the correct password before passing the user onto a successful login. That way I know if the password is wrong, but I have no idea if the password is right and the user simply types the wrong username.
Alternatively, I could extend on that, and if the user isn't found, requery the database based on the password and determine if I can find a record but the username doesn't match. It seems like a lot of back and forth with the database, which is fine. But i'd like to hear from some experts on whether or not this is a proper approach.
I have not much idea wether stored procedure is supported in my sql or not. If it is supported then you can make SP like this way to check all cases. Below is code for MSSQL, you can check it with my sql :
IF EXISTS(SELECT [id] FROM [dbo].[users] WHERE [user_name] = #user_name AND [password] = #password)
BEGIN
SELECT 1 AS RETURNVAL --Valid User
END
ELSE IF NOT EXISTS(SELECT [id] FROM [dbo].[users] WHERE [user_name] = #user_name)
BEGIN
SELECT 0 AS RETURNVAL -- User doesn't exist
END
ELSE
BEGIN
SELECT -1 AS RETURNVAL -- Password Not Correct
END
You don't want to disclose too many information to people with bad intents trying to probe your system for available usernames (or even – god forbid – passwords that are in use).
When a login attempt failed, simply display a message stating:
Username and/or password mismatch.
As an aside, use prepared statements, rather than string concatenation when working with your database; it protects you from SQL injection attacks.
Plus – although it's not entirely clear from your code snippet – don't store plain passwords or plain password hashes. Rely on one of the many available and well tested encryption/hashing libraries e.g. PHP's crypt function (make sure you select a proper hashing function such as SHA512).
Your code in the most simplest form would then look like this:
// coming from your login page
$dbh = new PDO(…);
$sth = $dbh->prepare('SELECT `digest` FROM `users` WHERE `name` = :name LIMIT 1');
$sth->prepare(array( ':name' => $_POST['username'] ));
$result = $sth->fetch();
if($result !== FALSE && crypt($_POST['password'], $result['digest']) === $result['digest']) {
printf('You logged in successfully as %s', htmlspecialchars($_POST['username']));
} else {
echo 'Sorry, username and/or password did not match! Please try again.';
sleep(1);
exit;
}

How to hash passwords in MySQL?

How I will make every password in my user table encrypted(md5()) except one particular row using a single query?
UPDATE table SET Password = MD5(Password)
I will say though that MD5 isn't a very good level of encryption and you should consider something stronger such as ENCRYPT with a custom salt. Read about it here
EDIT: Looks like the original question changed. Here's the altered query to accomodate
UPDATE table SET Password = MD5(Password) WHERE ID!=[specified index]
EDIT: Worth noting
MD5 Encryption Hacked
Hash Functions in MySQL
There are a lot more hash functions than MD5 to use for storing passwords in you MySQL database.
You can find a list of them on MySQL :: 11.10.2. Encryption and Compression Functions.
Save Password (hash):
UPDATE users SET password = SHA('secret_password') WHERE ....;
Check Password:
SELECT COUNT(*) FROM users WHERE name = 'username' && password = SHA('typed_password');
If the result is > 0, the user provided the correct password.
When hashing passwords, do not forget to salt them, so that same passwords do not yield same hashes:
SET #salt := CONV(FLOOR(RAND() * 0x100000000), 10, 16)
UPDATE passwords
SET password = CONCAT(#salt, SHA(CONCAT(#salt, #typed_password)))
SELECT 1
FROM passwords
WHERE SHA(CONCAT(SUBSTRING(password, 1, 8), #typed_password)) = SUBSTRING(password, 9, 40)
Concerning you edit: do you have an ID or username that identifies this row?
UPDATE mytable
SET password = MD5(password)
WHERE id <> 123
Edited in response to edit in OP.
UPDATE userTable
SET password = MD5(password)
WHERE NOT (<criteria to identify row to exclude>)
I think it is a little bit more update
SET PASSWORD FOR 'existinguser'#'localhost' = PASSWORD('newpass');
or
UPDATE user SET password = PASSWORD('newpass');
Hope this help