I've done quite a bit of research and preparation with my code to try and prevent SQL injections, but I wanted to discuss something that I'm not quite sure about.
I understand the the mysqli_real_escape_string does not escape _ (underscore) and % (percent) characters. If I'm not using any LIKE clauses in my SQL statements, does this open me up to any risk?
Below is an example of one the instances I'm interested in talking about. Here is the login script I'm using. I want to make sure that I'm not opening myself up to any injection vulnerabilities here. Your insight and feedback would be greatly appreciated.
// Initiate login process if the mode is set to login
if ($_REQUEST['mode'] == "login") {
// Open shared database connection
$cxn = connectDb();
// Escape characters to help prevent a SQL injection attack
$username = mysqli_real_escape_string($cxn, $_POST['user']);
// Convert submitted password to hashed value using
// custom password hashing function
$password = custompwhash($_POST['pass']);
// Execute SQL statement to determine if the credentials provided
// match a valid user
$sql = "SELECT count(*) as countOK FROM user_def WHERE ".
"username = '$username' AND password = '$password'";
$result = mysqli_query($cxn,$sql);
$row = mysqli_fetch_array($result);
extract($row);
// If the username value submitted is null, throw and error
if ($username == "") {
die2("Please enter your username and try again.<br />");
failedloginalert($username);
}
// If the password value submitted is null, throw and error
else if ($password == "") {
die2("Please enter your password and try again.<br />");
failedloginalert($username);
}
// If the credenetials provided match a valid user in the database,
// initate login
else if ($countOK == '1') {
$sql2 = "INSERT INTO `user_activity` (`username`, `time`, `ip`)".
" VALUES ('$username', NOW(), '{$_SERVER['REMOTE_ADDR']}')";
$result2 = mysqli_query($cxn,$sql2);
$_SESSION['auth'] = 1;
$_SESSION['username'] = $username;
// If the user does not need to be directed to a specific page, direct to home.php
if (empty($_GET['page'])) {
die2("<span style='color:#000;'>You have successfully logged in. <br /><br />
Please click here if you are not automatically redirected.</span>
<meta http-equiv='refresh' content='0;url=home.php'/>");
die();
}
// Otherwise, if the user does need to be directed to a specific page, direct to the requested page
else {
$loginredirectpage = $_GET['page'];
die2("<span style='color:#000;'>You have successfully logged in. <br /><br />
Please click here if you are not automatically redirected.</span>
<meta http-equiv='refresh' content='0;url=".$loginredirectpage."'/>");
die();
}
}
// Since the credenetials provided do not match a valid user in the database, throw an error
else {
die2("The username or password you entered is invalid. Please try again. <br/><br/>If the problem persists, reset your password.");
failedloginalert($username);
}
}
Start using PDO and Prepared Statements and your issues with SQL injection will go away.
If I'm not using any LIKE clauses in
my SQL statements, does this open me
up to any risk?
No it doesn't. = just gives you more exact answer than LIKE
SQL Injection is more to do with quote sign ' " since the technique tries to append on the query string. So I'd say % and _ won't open up any security risk if you filtered all the possible quotes. In this case, mysqli_real_escape_string() can help.
PDO is an abstraction layer that assists you with dealing with databases more efficiently. It can help you with SQL injection, but I don't recommend using it unless you're building something big that requires a lot of interaction with database.
So final my point is your code looks ok (:
Related
I have this particular code (this is a mwe) and I can't understand why there is not MySQL Error getting printed in browser or apache logs files :
$sql= "SELECT * from vulnDB where username = admin and password = '" . $_POST['password'] ."'";
if($result = mysqli_query($conn, $sql)) {
$row = mysqli_fetch_assoc($result);
if ($_POST['password'] == $row['password']) {
echo "Welcome!";
} else {
echo "This password is incorrect";
}
} else {
echo "Error";
}
When I enter legitimate values, the code is correctly executed (Welcome or This password is incorrect), when I enter " or ', I reach the Error clause but nothing is getting printed and this code seems to be immune to SQLi.
Is there some particular protection in PHP to avoid anyway SQLi injection to success?
Appropriate protection would consist on using PDO, but I have found this code which obviously seems to be vulnerable; and can't trigger sqli error syntax or other thinks out of it, only "error" gets printed.
To inject SQL in a useful way, you need to make the SQL statement valid with your parameter injected into the string. Simply injecting ' won't turn into valid SQL. (It would result in something like SELECT * from vulnDB where username = admin and password = '''.)
You'd need to inject something like ' OR password IS NOT NULL AND '' = ' (note: I haven't tested this) to create a working attack.
When you don't use prepared statements, and you print a string from user input directly into SQL that gets executed, you are vulnerable to SQL injection.
Before you say it: I know the passwords should be encrypted/hashed, but I want to get this down first:
I have this login function and a SQL database. However, the login function doesn't seem to work and I haven't the faintest idea why. I am probably missing something stupid, but have been struggling with this for a while now. Any help would be appreciated!
NOTE: the file db_connect.php is really just a basic connecting to the database, nothing wrong there
FUNCTION.PHP:
<?
function login($username, $password, $con)
{
$myQuery = "SELECT * FROM Members WHERE Username = '$username' and Password = '$password';";
$result = mysqli_query($con, $myQuery);
if (mysql_num_rows($result) == 0)
{
return false;
}
else
{
return true;
}
}
?>
PROCESS-LOGIN.PHP:
<?php
include 'db_connect.php';
include 'functions.php';
if (isset($_POST['username'], $_POST['pword'])) {
$username = $_POST['username'];
$password = $_POST['pword']; // The hashed password.
if (login($username, $password) == true) {
// Login success
header('Location: welcome.html');
}
else
{
// Login failed
header('Location: index.html');
}
}
else {
// The correct POST variables were not sent to this page.
echo 'Invalid Request';
}
?>
You are not providing the $con parameter to login function.
function login($username, $password, $con)
You are calling it as
login($username, $password)
Try providing the connection argument to see if it works.
Also note the answer kingkero made. You are using functions from different libraries.
Some things I noticed
Are you using method="POST" in your form?
Your SQL query is vulnerable to SQL injections
your mixing mysql_* with mysqli_* functions
missing $con parameter for login function
You are mixing MySQLi (mysqli_query) with MySQL (mysql_num_rows) - decide for either one (preferably the former).
If you are using MySQL, the parameters for mysql_query are in wrong order.
In addition to that you are failing to pass the connection to the login as a parameter (as WoLfulus mentioned).
Some additional info as you seem to be learning:
The return statement of login can be simplified to return mysql_num_rows($result) == 1;. This will return TRUE if one record was found and FALSE otherwise - no need for an if/else statement here, you already have the logic you need.
Right now anyone can access welcome.html without logging in by simply typing the address in the browser. This can be avoided by using sessions.
Since you don't properly escape the user input (which one should never trust!), you are vulnerable to SQL injections. mysql_real_escape_string is a start but no 100% solution. If you used prepared statements on the other hand, you wouldn't need to worry.
I'm answering since I don't have enough reputation to comment your question.. But you should keep your variables outside the quotes and add mysql_real_escape_string() to prevent mysql injection..
$myQuery = "SELECT * FROM Members WHERE Username = '$username' and Password = '$password';";
Should be:
$myQuery = "SELECT * FROM Members WHERE Username = '". mysql_real_escape_string($username) ."' and Password = '". mysql_real_escape_string($password) ."';";
This question already has answers here:
How can I prevent SQL injection in PHP?
(27 answers)
Closed 9 years ago.
I have problem with my login form sql injection is working on it so how to stop it.
I am using mysql_real_escape_string but nothing changed
if(isset($_POST['submit-login'])) {
$user = $_POST['username'];
$pass = $_POST['password'];
$username = mysql_real_escape_string($user);
$password = mysql_real_escape_string($pass);
$usertool = new Usertool();
if($usertool->login($username, $password)){
//successful login, redirect them to a page
header("Location: index.php");
}else{
$error = "Incorrect username or password. Please try again.";
}
}
Here is usertool
class usertool {
public function login($username, $password) {
$hashedPassword = md5($password);
$result = mysql_query("SELECT * FROM tbl_user WHERE uname = '$username' OR eemail = '$username' AND password = '$hashedPassword'");
if (mysql_num_rows($result) == 1) {
$_SESSION["user"] = serialize(new User(mysql_fetch_assoc($result)));
$_SESSION["login_time"] = time();
$_SESSION["logged_in"] = 1;
return true;
} else {
return false;
}
}
it is not a classic SQL injection in your case but rather wrong SQL logic.
You need to add braces to your query:
SELECT * FROM tbl_user
WHERE (uname = '$username' OR eemail = '$username')
AND password = '$hashedPassword'"
In your original query the whole statement evaluates to true if entered username or email matches, and password not even being checked
And regarding SQL injections in general, to make your queries safe, you have to format your query parts according to these rules
Formatting have to be complete. mysql_real_escape_string does incomplete formatting alone: you ought to add apostrophes around whatever data you escaped using this function.
Formatting have to be adequate, means you can't format a number or an identifier with string formatting. EVery SQL literal require it's own distinct formatting.
Formatting have to be done as close to the query execution as possible.
Following these rules you'll be pretty safe from injection. And using prepared statements is the easiest way to follow them.
One don't need neither mysqli not PDO to use native prepared statements though - you can create your own variant. But nevertheless, you have to move mysql_real_escape_string as closer to the query execution as possible and always add apostrophes around the result.
I'm getting this error and I don't quite understand why. I've been going over this for hours now, tried looking into it via research, no luck.
In my PHP login system, I check if the row is selected:
//Start session
session_start();
//Include database connection details
require_once('config.php');
//Array to store validation errors
$errmsg_arr = array();
//Validation error flag
$errflag = false;
//Connect to mysql server
$link = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
if(!$link) {
die('Failed to connect to server: ' . mysql_error());
}
//Select database
$db = mysql_select_db(DB_DATABASE);
if(!$db) {
die("Unable to select database");
}
//Prevent SQL injection.
function clean($str) {
$str = #trim($str);
if(get_magic_quotes_gpc()) {
$str = stripslashes($str);
}
return mysql_real_escape_string($str);
}
//Sanitize the POST values
$login = clean($_POST['login']);
$password = clean($_POST['password']);
//Input Validations
if($login == '') {
$errmsg_arr[] = 'Login ID missing';
$errflag = true;
}
if($password == '') {
$errmsg_arr[] = 'Password missing';
$errflag = true;
}
//If there are input validations, redirect back to the login form
if($errflag) {
$_SESSION['ERRMSG_ARR'] = $errmsg_arr;
session_write_close();
echo "input validation";
exit();
}
//Create query
$qry="SELECT * FROM details WHERE USERNAME='$login' AND PASSWORD='".md5($_POST['password'])."'";
$result=mysql_query($qry);
//Check whether the query was successful or not
if($result) {
if(mysql_num_rows($result) == 1) {
//Login Successful
session_regenerate_id();
$member = mysql_fetch_assoc($result);
$_SESSION['MEMBER_ID'] = $member['USERNAME'];
session_write_close();
header("location: client-index.php");
exit();
}else {
//Login failed
echo "login failed?";
exit();
}
}else {
die("Query failed");
}
?>
It echos "failed", for whatever reason.
If you told us what the error is, it might help us answer your question better.
But at any rate, this is emphatically the WRONG way to go about this. There are many serious problems with this system.
First of all, you're not sanitizing your inputs. Since you're mixing your data and commands together, all a user would have to do is enter a username of "x' or 1 = 1' --" to get into the system. Here's why: the only command the SQL server would get is "SELECT * FROM details WHERE USERNAME = 'x' or 1 = 1". In other words, if the username is x or 1 = 1 (which it is), then the SQL server would respond with a positive result. (The two dashes at the end of the "username" denote a comment in SQL, so everything after that in the query would be ignored).
A truly malicious attacker could even wreak havoc on your system by entering a username "x'--; DROP TABLES;', and your entire database would be gone. (See this comic for where I got this.)
In fact, you shouldn't even really be using mysql_query at all. According to the PHP documentation:
Use of this extension is discouraged. Instead, the MySQLi or PDO_MySQL extension should be used.
I would do a bit more reading on the subject if I were you. Even if this is just for practice, it's still best to get things right the first time. Look into PDO: it's not too hard to learn, yet quite useful. Its main advantage is that it does not mix data and commands, so you won't have the same problem of unsanitized inputs messing up your database.
Also, while it's good to see that you're hashing your passwords--and you'd be amazed at how many companies that should know better do not--MD5 is no longer considered cryptographically secure. It's relatively easy to get what's called a "hash collision," where two different plaintexts produce the same hash. Now, SHA-256 should be the minimum you use.
Also, on the subject of hashing, you should be adding something called salt. A salt is some kind of random text that you add to your plaintext in order to further obfuscate it. The reason for this is that there are what's called rainbow tables out there. A rainbow table is a list of pre-calculated hashes of all common passwords. If someone were to get a hold of your database, they could then compare all the passwords to rainbow tables to find their plaintexts.
Finally, in order to slow down brute force attacks--where an attacker tries all alphanumeric combinations until they get the password--you should also be using a loop where the hash algorithm gets re-calculated x number of times, usually between 1000 and 10000 times. PHP's crypt does this very nicely.
And BTW: don't feel bad. I've done all these things before, too. That's why I know that you shouldn't do them. Don't worry--you'll get there soon enough. Keep at it!
I am new to site so can not add comments maybe this wont help much but ill give it a go anyway
in your sql query it looks like you passing variable $login as text not variable value
$qry="SELECT * FROM details WHERE USERNAME='$login' AND PASSWORD='".md5($_POST['password'])."'";
and it should be
$qry="SELECT * FROM details WHERE USERNAME=".$login." AND PASSWORD='".md5($_POST['password'])."'";
is it a query failed or login failed?
anyway if query failed :
try to change your query in to this :
surrounds your fields with backtick (not single quote)
$qry="SELECT * FROM details WHERE `USERNAME`='$login' AND `PASSWORD`='".md5($_POST['password'])."'";
if login failed :
if(mysql_num_rows($result) == 1) {
//Login successful
}else {
//Login failed
}
are you sure that the query will only have 1 result? because with this condition, if results is greater than 1 it will also failed to login.
please help i have the following php code for my login session but i am trying to get the $_session['user_id'] instead of the $_session['email']. i tried print_f function to see what i can use but user_id array says 0 which cannot be right unless i read it wrong.
session_start();
$email = strip_tags($_POST['login']);
$pass = strip_tags($_POST['password']);
if ($email&&$password) {
$connect = mysql_connect("xammp","root"," ") or die (" ");
mysql_select_db("dbrun") or die ("db not found");
$query = mysql_query("SELECT email,pass FROM members WHERE login='$email'");
$numrows = mysql_num_rows($query);
if ($numrows!=0) {
// login code password check
while ($row = mysql_fetch_assoc($query)) {
$dbemail = $row['login'];
$dbpass = $row['password'];
}
// check to see if they match!
if ($login==$dbemail&&$password==$dbpass) {
echo "welcome <a href='member.php'>click to enter</a>";
$_SESSION['login']=$email;
} else {
echo (login_fail.php);
}
} else {
die ("user don't exist!");
}
//use if needed ==> echo $numrows;
} else {
die ("Please enter a valid login");
}
i am trying to get the $_session['user_id'] instead how can get this to use instead of $_session['email']. tried using $_session['user_id'] but instead i got undefined error msg.
Well, you don't define $_session['user_id'] anywhere in this script, so it's no surprise that it's not defined. You have to assign it a value before you can refer to it.
Also, note that there all kinds of security problems with this code.
You're running your MySQL connection as the root user. This is NOT a good idea.
You're trusting user input, which opens your script up to a SQL injection attack. Stripping HTML tags from the user input does not make it safe. Suppose that I came to your site, and filled in the "email" field with this:
bob#example.com'; GRANT ALL PRIVILEGES ON *.* TO 'evil_bob' IDENTIFIED BY '0wned_joo';
As currently written, your script would happily run its query as normal, and also create an account called "evil_bob" with full privileges to all the information in all of the databases on your server.
To avoid this, NEVER assume that user input is safe. Validate it. And to be extra sure, don't stick variables straight into SQL you've written. Use bound parameters instead. There are a few cases where it's hard to avoid -- for example, if you need to specify the name of a column rather than a piece of data, a bound parameter will not help and you'll have to do it some other way. However, for any piece of data you're using as part of a query, bind it.