I found this in some code examples while googling :
$sql = 'INSERT INTO users (username,passwordHash) VALUES (?,?)';
it's new to me, but I would guess that it a substitution method and equivalent to
$sql = "INSERT INTO users (username,passwordHash) VALUES ($username,$passwordHash)";`
or
$sql = 'INSERT INTO users (username,passwordHash) VALUES (' . $username . ',' . $passwordHash . ')';`
would that be correct? Is it an actual PHP syntax, or was he just trying to simplify his example?
Thanks for the feedback, folks
This is pretty common in prepared statements. The ? merely serves as a placeholder, as seen below from the PHP documentation:
$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (?, ?)");
$stmt->bindParam(1, $name);
$stmt->bindParam(2, $value);
// insert one row
$name = 'one';
$value = 1;
$stmt->execute();
// insert another row with different values
$name = 'two';
$value = 2;
$stmt->execute();
The question marks are placeholders for values in prepared SQL statements - and are an important protection against SQL Injection Attacks. Your first alternative would not work properly unless every user encloses their name in quotes* and you enclose the password hash in quotes. Your second alternative is vulnerable to SQL Injection Attacks.
With placeholders, you pass the values for the placeholders when you execute the SQL.
* And Tim O'Reilly knows he really has to type "'Tim O''Reilly'".
it's not the same. question marks are used for prepared statement queries. these basically allow you to run the same query multiple times while only having the system parse the query once.
Related
I'm building a search engine, and I've been experimenting with structuring parameterized SQL statements in PHP. I was wondering what the rules are for where parameters can be used.
e.g. This works:
$var = $unsafevar;
$stmt = mysqli_prepare($connection, "SELECT * FROM users WHERE username = ?");
mysqli_stmt_bind_param($stmt, 's', $var);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
$row = mysqli_fetch_assoc($result);
This doesn't:
$var = 'SELECT';
$var2 = 11;
$stmt = mysqli_prepare($connection, "? * FROM users WHERE username = ?");
mysqli_stmt_bind_param($stmt, 'ss', $var, $var2);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
$row = mysqli_fetch_assoc($result);
Where can parameters be used and where can they not be?
Or, more simply, what does paramaterization literally do to the variables? Does it put single quotes around them? If that is the case, is there a way to paramaterize wildcards, column names, or the SQL clauses themselves?
Database, schema, table and column names cannot be parameterized - you would need to resort to dynamic SQL if you wish to "parameterize" them.
The linked article is an extensive discussion on the use of dynamic SQL, mostly dealing with SQL Server though applicable to most SQL databases.
My mnemonic for parameters is this:
Anywhere you could use a single scalar value, you can use a parameter.
That is, a quoted string literal, quoted date literal, or numeric literal.
Anything else (identifiers, SQL keywords, expressions, subqueries, a list of values for an IN() predicate, etc.) cannot be parameterized.
I learned that using prepared statement (parameterized queries) can be used for preventing SQL injection attack with few exceptions mentioned in this post: Are PDO prepared statements sufficient to prevent SQL injection?.
My question is how a prepared statement prevents SQL injection? What I've learned so far:
When using parameterized query, the client sends the query to server for preparation (optimization) without any data, and later, parameters (user input or any) will be sent to the server so that the user data resides outside the original query.
When using prepared query, if user data is not escaped, then it will not prevent any SQL injection.
For this reason, I failed to test the example because using execute(), bind_param(), binVlaue(), binParam() are all escaping the data when used so that we can't send un-escaped data with them for testing the safety of prepared statement with un-escaped parameters.
The basic problem is this:
$value = "Foo' OR 1 = 1 --";
$query = "SELECT id FROM users WHERE name = '$value'";
Concatenated as above, this is the actual query:
SELECT id FROM users WHERE name = 'Foo' OR 1 = 1 --'
The problem here is the character ', which in a string literal context has the special meaning of terminating the string literal.
One technique to deal with this is to escape characters which have a special meaning in string literals:
SELECT id FROM users WHERE name = 'Foo\' OR 1 = 1 --'
The other way is to separate the string literal out entirely, so there cannot be any confusion about where it starts and ends:
query: SELECT id FROM users WHERE name = $1
$1: Foo' OR 1 = 1 --
The former technique is escaping, the latter is parameterized queries. You only need to use one of them. If you escape values that you also parameterize, you're just messing up your values with unnecessary backslashes.
SQL injection means, that the user input is used as part of the SQL statement.
If you use prepared statements then the user input will be treated as a content and not as a part of the SQL command.
However if you build your SQL command by joining user input strings together, then again you are introducing SQL injection vulnerability.
So in short it is safe to use prepared queries, but the user input should be used as parameters only, not part of the query text.
Yes, you can prevent sql injection by pdo and mysqli
<< View Demo >>
Using PDO:
Step - 1: Connect Database
Here i create database "inphplab". You can change as per your requirnment.
$dbConnection = new PDO('mysql:dbname=inphplab;host=127.0.0.1;charset=utf8', 'root', '');
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Step - 2: Insert Data into table "employee"
Here i am taking two variable and declare name and address as below.
$name = "bapuRocks";
$address = "India";
$stmt = $dbConnection->prepare("INSERT INTO employee ( name, address) VALUES ( :name, :address )");
$stmt->bindValue(':name', $name);
$stmt->bindValue(':address', $address);
printf("%s Row inserted.\n", $stmt->execute());
Using MySQLi:
Step - 1: Connect Database
Here i create database "inphplab". You can change as per your requirnment.
$mysqli = new mysqli('localhost', 'root', '', 'inphplab');
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
Step - 2: Insert/Delete Data into table "employee"
Here i am taking two variable and declare name and address as below.
$stmt = $mysqli->prepare("INSERT INTO employee SET `name`=?,`address`=?");
$stmt->bind_param('ss', $name, $address);
$name = 'Yuvraj Sinh';
$address = 'India';
/* execute prepared statement */
$stmt->execute();
printf("%s Row inserted.\n", $stmt->affected_rows);
/* close statement and connection */
$stmt->close();
/* Clean up table CountryLanguage */
$mysqli->query("DELETE FROM employee WHERE name='Bharat Sinh'");
printf("%s Row deleted.\n", $mysqli->affected_rows);
/* close connection */
$mysqli->close();
Easiest way to prevent SQL injection is to convert all user input into HEX using a simple hex2str() function and then converting it back using str2hex() -- this ensures that all data will be safe.
function strToHex($string){
$hex = '';
for ($i=0; $i<strlen($string); $i++){
$ord = ord($string[$i]);
$hexCode = dechex($ord);
$hex .= substr('0'.$hexCode, -2);
}
return strToUpper($hex);
}
function hexToStr($hex){
$string='';
for ($i=0; $i < strlen($hex)-1; $i+=2){
$string .= chr(hexdec($hex[$i].$hex[$i+1]));
}
return $string;
}
EDIT
$ID = strToHex($_GET["ID"]);
$COL1 = strToHex($_GET["COL1"]);
$COL2 = strToHex($_GET["COL2"]);
$query = "insert into my_db.my_table(ID, COL1, COL2) VALUES('$ID','$COL1','$COL2')";
Simple as that
I have been using prepared insert statements for some years and assumed it was binding parameters properly or would give an error but it seems not as the following php binds and inserts a record without any errors but changes a string which should be an int to a zero. So it may be ok for preventing SQL injection attacks but you would end up with a spurious record in the table. e.g.:
$q = "INSERT INTO `table` (id1, id2) VALUES (?, ?)";
$stmt = mysqli_stmt_init($dbc);
$stmt->prepare($q);
$id1 = 'aaaaaaa';
$id2= 'aaaaaaa';
$result = $stmt->bind_param('ii', $id1, $id2);
echo '<p>' . $result . '</p>'; // Trying to bind a string to an int returns true!
echo $dbc->error; // nothing
$stmt->execute(); // inserts record changing $id2 to zero but auto-increments primary key $id1
echo $dbc->error; // nothing
This is running in an Xampp environment with Apache/2.2.14, PHP/5.3.1 and MySQL 5.1.41. Can anyone tell me what is going on?
$stmt->bind_param() doesn't check the given variables for a certain type, it only converts them into the specified type. And your string 'aaaaaaa' is converted into an int-value: 0. That's the way php does it.
The database insert statement is the wrong place to check, if your variables contain useful/correct values. Do that before and only try to insert them, if your validations work.
To do the validation for an int, you could use the php-function is_numeric() or is_int().
I'm not an expert for sure, but at first look.
You have:
$id1 = 'aaaaaaa';
$id2= 'aaaaaaa';
$result = $stmt->bind_param('ii', $id1, $id2);
Thing is your 'ii' parameter says that you will be binding integers! And in fact your $id1 and $id2 are strings. For strings you should go with:
$result = $stmt->bind_param('ss', $id1, $id2);
This question already has answers here:
PDO Prepared Inserts multiple rows in single query
(21 answers)
Closed 9 years ago.
i have an array from a post:
//this are arrays
$name = $_POST["name"];
$age = $_POST["age"];
$date = $_POST["date"];
i have an insert query in PDO:
$stmt = $db->prepare("INSERT INTO staff (name, age, address) VALUES (:name, :age, :address)");
my question how can i generate/or achieve a query using php that looks like:
INSERT INTO staff (name, age, address) VALUES
($name[0], $age[0], $address[0]), ($name[1], $age[1], $address[1])... etc
I try to keep server load low.
It is not possible with prepared statements.
If you want to insert a variable amount of records in one query then the SQL query must be generated dynamically, which makes it impossible to prepare it beforehand.
If you use the database driver's escape_string function (that would be quote() for MySQL) then you can build the query and still get the security that prepared statements give you.
$query = "INSERT INTO table (a, b, c) VALUES";
$tuple = " ('%s', '%s', '%s'),";
foreach ($items as $item) {
$a = $db->quote($item['a']);
$b = $db->quote($item['b']);
$c = $db->quote($item['c']);
$query .= sprintf($tuple, $a, $b, $c);
}
$query = rtrim($query, ","); // Remove trailing comma
// Use the query...
Addendum:
If you are using prepared statements then you can insert records separately and not have to worry about inserting them in one go. That is actually the whole point of prepared statement.
Unlike good old SQL queries, which are a one-step process and are just sent and then forgotten...
$db->query("INSERT INTO table (a, b, c) VALUES ('a', 'b', 'c')");
...prepared statements are a two-step process.
First, you create the prepared statement. This statement is then sent to the database, telling it that "this is what you should be expecting". The database will often also optimize the query to make it faster. This step then gives you back a statement handle (often called $stmt in PHP documentation).
$stmt = $db->prepare('INSERT INTO table (a, b, c) VALUES (:a, :b, :c)');
Second, with this handle you can then proceed to insert stuff:
foreach ($records as $record) {
$stmt->execute(array(
':a' => $record['a'],
':b' => $record['b'],
':c' => $record['c'],
));
}
Because the database already knows what to expect it can optimize the speed of the INSERTs, meaning that you do not have to go through the stuff that I mentioned above this addendum.
Wikipedia actually has a quite nice writeup of prepared statements.
You could prepare the appropriate statement, i.e. something like:
$sql = "INSERT INTO staff (name, age, address) VALUES ";
$values = array();
for ($i = 1; $i <= $count; $i++) {
$values[] = "(:name$i, :age$i, :address$i)"
}
$stmt = $db->prepare($sql . implode(', ', $values));
But, imho, you'll probably be just as well off lopping through each set of values. It's not much of a burden for the server, and your code will be simpler.
If you are using PDO, you should prepare your statement, and execute it with different values:
$stmt = $db->prepare("INSERT INTO staff (name,age,address) VALUES (:name,:age,:address)");
for($i=0;$i<count($name);$i++)
{
$stmt->execute(array(
":name" => $name[$i],
":age" => $age[$i],
":address" => $address[$i]));
}
However, if you would like to put it into a single query, you should use $db->exec():
$query = "INSERT INTO staff (name,age,address) VALUES ";
$values = array();
for($i=0;$i<count($names);$i++)
{
array_push($values, "('" . $name[$i] . "','" . $age[$i] . "','" . $address . "')");
}
$db->exec($query . implode("," . $values));
Be warned this method, contrary to the prepare and execute method, is vulnerable since it does not validate the input
Add the delayed keyword to the insert (insert delayed ...) to reduce serverload, and just loop over the prepared statement with all values. MySQL will queue it internally anyway as separate insert statements if you do a big bunch of inserts, the extended syntax is just syntactic sugar.
I have read a lot about sql injection and I understand how it could cause problems (ie: DROP TABLE __ etc). But I am unsure how the tutorials I have followed actually prevent this from happening. I am just learning PDO and I think I understand it.
Is this code safe from SQL injection? and why is it? (It takes quite a bit more work using these prepared statements so I want to be sure I am not just wasting my time - also if the code can be improved please let me know!)
$conn = new PDO("mysql:host=$DB_HOST;dbname=$DB_DATABASE",$DB_USER,$DB_PASSWORD);
// Get the data
$firstname = $_POST["v_firstname"];
$lastname = $_POST["v_lastname"];
$origincountry = $_POST["v_origincountry"];
$citizenship = $_POST["v_citizenship"];
$gender = $_POST["v_gender"];
$dob = $_POST["v_dob"];
$language = $_POST["v_language"];
$landing = $_POST["v_landing"];
$email = $_POST["v_email"];
$phone = $_POST["v_phone"];
$cellphone = $_POST["v_cellphone"];
$caddress = $_POST["v_caddress"];
$paddress = $_POST["v_paddress"];
$school = $_POST["v_school"];
$grade = $_POST["v_grade"];
$smoker = $_POST["v_smoker"];
$referred = $_POST["v_referred"];
$notes = $_POST["v_notes"];
//Insert Data
$sql = "INSERT INTO clients (firstname, lastname, origincountry, citizenship, gender, dob, language, landing, email, phone, cellphone, caddress, paddress, school, grade, smoker, referred, notes)
VALUES (:firstname, :lastname, :origincountry, :citizenship, :gender, :dob, :language, :landing, :email, :phone, :cellphone, :caddress, :paddress, :school, :grade, :smoker, :referred, :notes)";
$q = $conn->prepare($sql);
$q->execute(array(':firstname'=>$firstname,
':lastname'=>$lastname,
':origincountry'=>$origincountry,
':citizenship'=>$citizenship,
':gender'=>$gender,
':dob'=>$dob,
':language'=>$language,
':landing'=>$landing,
':email'=>$email,
':phone'=>$phone,
':cellphone'=>$cellphone,
':caddress'=>$caddress,
':paddress'=>$paddress,
':school'=>$school,
':grade'=>$grade,
':smoker'=>$smoker,
':referred'=>$referred,
':notes'=>$notes));
Yes, the code is safe, as PDO will properly escape and quote the array of parameters for you.
Your code is safe from SQL injection because you're using paramaterized query, which basically means that once the query is being built and sent to the sql server, it's being escaped, same could be achieved by using php's built in function mysql_real_escape_string().
The following video is great informational video about sql injection from OWASP:
SQL Injection
The rule is: Do not construct sql by hand, in which you do something like:
sqlStatement = 'select field1, field2, field3 from mytable where index = '' + myVariable + ''
The above is dangerous, because if your app allows a user to pass data into myVariable, they could potentially send full SQL commands to your db server.
Using parameterized queries, as you do above, is the solution.