I'm trying to learn more about MySQL and how to protect against SQL injections so my research has brought me to Prepared Statements which seems to be the way to go.
I'm also working on learning how to write Stored Procedures and am now trying to combine the two. There isn't much info on this though.
At the moment in my PHP test app I have a function that calls an SP with a normal MySQL command like this:
mysql_query("CALL usp_inserturl('$longurl', '$short_url', '$source')");
How can I do the same with MySQLi and a Prepared Statement to make it as safe as possible to injections?
Thanks!
Try the following:
$mysqli= new mysqli(... info ...);
$query= "call YourSPWithParams(?,?,?)";
$stmt = $mysqli->prepare($query);
$x = 1; $y = 10; $z = 14;
$stmt->bind_param("iii", $x, $y, $z);
$stmt->execute();
You can use both of them in the same time: just make preparation with stored procedure:
//prepare and bind SP's parameters with your variables only once
$stmt=$db->prepare("CALL MyStoredProc(?,?)");
$stmt->bind_param('is',$i,$name);
//then change binded variables and execute statement
for($i=1;$i<9;$i++)
{
$name="Name".$i;
$stmt->execute();
}
Bear in mind that you should do the preparation only once (not again for each execution), then execute it more times (just change parameter value before).
This one was a bit tricky but I eventually figured out how to both use a stored procedure (using IN parameters) that uses a prepared statement and retrieve the data through PHP. This example uses PHP 7.4.6 and MySQL 8.0.21 Community edition.
Here is the Stored Procedure:
CREATE DEFINER=`root`#`loalhost` PROCEDURE `SP_ADMIN_SEARCH_PLEDGORS`(
IN P_email VARCHAR(60),
IN P_password_hash VARCHAR(255),
IN P_filter_field VARCHAR(80),
IN P_filter_value VARCHAR(255)
)
BEGIN
#Takes admin credentials (first tow paramaters and searches the pledgors_table where field name (P_filter_field) is LIKE value (%P_filter_value%))
DECLARE V_admin_id INT(11);
BEGIN
GET DIAGNOSTICS CONDITION 1 #ERRNO = MYSQL_ERRNO, #MESSAGE_TEXT = MESSAGE_TEXT;
SELECT 'ERROR' AS STATUS, CONCAT('MySQL ERROR: ', #ERRNO, ': ', #MESSAGE_TEXT) AS MESSAGE;
END;
SELECT admin_id INTO V_admin_id FROM admin_table WHERE password_hash = P_password_hash AND email = P_email;
IF ISNULL(V_admin_id) = 0 THEN
SET #statement = CONCAT('SELECT pledgor_id, email, address, post_code, phone, alt_phone, contact_name
FROM pledgors_table
WHERE ',P_filter_field, ' LIKE \'%', P_filter_value, '%\';');
PREPARE stmnt FROM #statement;
EXECUTE stmnt;
ELSE
SELECT 'ERROR' AS STATUS, 'Bad admin credentials' AS MESSAGE;
END IF;
END
And here is the PHP script
query = 'CALL SP_ADMIN_SEARCH_PLEDGORS(\''.
strtolower($email).'\', \''.
$password_hash.'\', \''.
$filter_field.'\', \''.
$filter_value.'\');';
$errNo = 0;
//$myLink is a mysqli connection
if(mysqli_query($myLink, $query)) {
do {
if($result = mysqli_store_result($myLink)) {
while($row = mysqli_fetch_assoc($result)) {
$data[] = $row;
}
mysqli_free_result($result);
}
} while(mysqli_next_result($myLink));
}
else {
$errNo = mysqli_errno($myLink);
}
mysqli_close($myLink);
You might find the following answer of use:
MySql: Will using Prepared statements to call a stored procedure be any faster with .NET/Connector?
In addition:
GRANT execute permissions only so your application level user(s) can only CALL stored procedures. This way, your application user(s) can only interact with the database through your stored procedure API, they can not directly:
select, insert, delete, update, truncate, drop, describe, show etc.
Doesn't get much safer than that. The only exception to this is if you've used dynamic sql in your stored procedures which I would avoid at all costs - or at least be aware of the dangers if you do so.
When building a database e.g. foo_db, I usually create two users. The first foo_dbo (database owner) is the user that owns the database and is granted full permissions (ALL) so they can create schema objects and manipulate data as they want. The second user foo_usr (application user) is only granted execute permisisons and is used from within my application code to access the database through the stored procedure API I have created.
grant all on foo_db.* to foo_dbo#localhost identified by 'pass';
grant execute on foo_db.* to foo_usr#localhost identified by 'pass';
Lastly you can improve your code example above by using mysql_real_escape_string:
http://php.net/manual/en/function.mysql-real-escape-string.php
$sqlCmd = sprintf("call usp_inserturl('%s','%s','%s')",
mysql_real_escape_string($longurl),
mysql_real_escape_string($shorturl),
mysql_real_escape_string($source));
$result = mysql_query($sqlCmd);
Related
Im new to database and i have written a LOT of PHP code that accesses a database using MySQL.
I didnt take into account SQL injection attacks so i have to re-write all that PHP code to use mysql prepared statements.
After looking at videos on how to used prepared SQL statements, to perform just ONE SQL command requires a whole lot of "prepared" statements. My existing code has lots of different SQL statements all over the place, it would be a nightmare to change all that code to pack and unpack all the required preparation for each "prepared" statement command.
Is there some kind of wrapper i can use to prevent turning one line of regular SQL into 6 or 7 lines of prepared statements?
For example use to do this line line of SQL
SELECT * from users where userid=10
needs many more lines of prepared SQL statements, especially if there are lots of other SQL statements too it now becomes very complex.
Is there was some sort of one line wrapper that i can call that accepts the template SQL string, plus the parameters, which also executes the command and returns the result in just one line of wrapper for different types of MYSQL statements it would be great and the code would be much less confusing looking and error prone.
For example
$users=WrapAndExecute($db,"SELECT * from users where userid=?","s",$userid);
$data=WrapAndExecute($db,"UPDATE table SET username=?,city=?","ss",$name,$city);
$result=WrapAndExecute($db,"DELETE from table where id=?","s",$userid);
$result=WrapAndExecute($db,"INSERT into ? (name,address) VALUES(?,?)","ss","users",$name,$address);
Each of those lines above would create a prepared statement template, do the bind, execute it and return the result that a regular MYSQL statement would. This would create minimal impact on existing code.
Anybody knows how to do this or if some easy php library or class already exists to do this, that i can just import and start using it?
Thanks
You don't need to change a query to a prepared statement if it has no PHP variables in it. If it has just constant expressions, it's safe from SQL injection.
$sql = "SELECT * from users where userid=10"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
You don't need to change a query that contains PHP variables, as long as the value of that variable is a constant specified in your code. If it doesn't take its value from any external source, it's safe.
$uid = 10;
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
You don't need to change a query that contains PHP variables, as long as you can filter the value to guarantee that it won't risk an SQL injection. A quick and easy way to do this is to cast it to an integer (if it's supposed to be an integer).
$uid = (int) $_GET['uid'];
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
That leaves cases where you are using "untrusted" values, which may have originated from user input, or reading a file, or even reading from the database. In those cases, parameters are the most reliable way to protect yourself. It's pretty easy:
$sql = "SELECT * from users where userid=?"; // Safe!
// two lines instead of the one line query()
$stmt = $pdo->prepare($sql);
$stmt->execute([$_GET['uid']]);
$data = $stmt->fetchAll();
In a subset of cases, you need one additional line of code than you would normally use.
So quit your whining! ;-)
Re your comment about doing prepared statements in mysqli.
The way they bind variables is harder to use than PDO. I don't like the examples given in http://php.net/manual/en/mysqli.prepare.php
Here's an easier way with mysqli:
$sql = "SELECT * from users where userid=?"; // Safe!
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('i', $_GET['uid']);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_all();
I don't like the stuff they do in their examples with bind_result(), that's confusing and unnecessary. Just use get_result(). So with mysqli, you need two more lines of code than you would with PDO.
I've written query wrappers for mysqli that emulate the convenience of PDO's execute() function. It's a PITA to get an array mapped to the variable-arguments style of bind_param().
See the solution in my answers to https://stackoverflow.com/a/15933696/20860 or https://stackoverflow.com/a/7383439/20860
I were in the same boat, and I wrote such a wrapper that works exactly the way you want, save for it's being a class, not a function.
$user = $sdb->getRow("SELECT * from users where userid=?s", $userid);
$sdb->query("UPDATE table SET username=?s, city=?s", $name, $city);
$sdb->query("DELETE from table where id=?s", $userid);
$sdb->query("INSERT into ?n (name,address) VALUES(?s,?s)","users", $name, $address);
The above is a working code, as long as you have somewhere in your bootstrap file
$db = mysqli_connect(...);
...
require 'safemysql.class.php';
$sdb = new SafeMySQL('mysqli' => $db);
Note that none of the other suggestions could do anything like that.
Also note that if I were writing it today, I would have used PDO, as this class is duplicating a lot of functionality already exists in PDO.
Take a look at the PDO extension in PHP - http://php.net/manual/en/intro.pdo.php: it it secure against injections thanks to prepared statements; also, it allows you to connect to many different databases (e.g. MySQL, MSSQL, etc.).
You can then build your own wrapper as you wish to keep it clean; for example your own wrapper could be as follows:
(following example will return user rows as objects)
// connect to DB
$GLOBALS['default_db'] = new DB('localhost','db_name','username','password') ;
// Get users and output results
$query = new DBQuery('SELECT * FROM users WHERE userid = ?',array(10)) ;
var_dump($query -> results()) ;
var_dump($query -> num_rows()) ;
// DB connection
class DB {
public $connection;
public function __construct($host , $dbname , $username , $password) {
$this->connection = new \PDO('mysql:host=' . $host . ';dbname=' . $dbname , $username , $password);
}
}
// Wrapper
class DBQuery {
private $num_rows = 0;
private $results = array();
public function __construct($query , $params = null , $class_name = null , DB $db = null) {
if ( is_null($db) ) {
$db = $GLOBALS['default_db'];
}
$statement = $db->connection->prepare($query);
$statement->execute($params);
$errors = $statement->errorInfo();
if ( $errors[2] ) {
throw new \Exception($errors[2]);
}
$fetch_style = ($class_name ? \PDO::FETCH_CLASS : \PDO::FETCH_OBJ);
$this->results = $class_name ? $statement->fetchAll($fetch_style , $class_name) : $statement->fetchAll($fetch_style);
$this->num_rows += $statement->rowCount();
while ( $statement->nextrowset() ) {
$this->results = array_merge($this->results,$class_name ? $statement->fetchAll($fetch_style , $class_name) : $statement->fetchAll($fetch_style));
$this->num_rows += $statement->rowCount();
}
}
public function num_rows() {
return $this->num_rows;
}
public function results() {
return $this->results;
}
}
Since a key requirement seems to be that you can implement this with minimal impact on your current codebase, it would have been helpful if you had told us what interface you currently use for running your queries.
While you could use PDO:
that means an awful lot of work if you are not already using PDO
PDO exceptions are horrible
Assuming you are using procedural mysqli (and have a good reason not to use mysqli_prepare()) its not that hard to write something (not tested!):
function wrapAndExecute()
{
$args=func_get_args();
$db=array_shift($args);
$stmt=array_shift($args);
$stmt_parts=explode('?', $stmt);
if (count($args)+1!=count($stmt_parts)) {
trigger_error("Argument count does not match placeholder count");
return false;
}
$real_statement=array_shift($stmt_parts);
foreach ($args as $k=>$val) {
if (isnull($val)) {
$val='NULL';
} else if (!is_numeric($val)) {
$val="'" . mysqli_real_escape_string($db, $val) . "'";
}
$real_statement.=$val . array_shift($stmt_parts);
}
return mysqli_query($db, $real_statement);
}
Note that this does not handle IS [NOT] NULL nicely nor a literal '?' in the statement nor booleans (but these are trivial to fix).
I have to deal with large mysql DB. Sql queries with lot of calculations (in select clause) and several kind of conditions in where clauses. So, I decided to use row/direct sql queries to deal with DB by using $db = ConnectionManager::getDataSource('default');
If I use this, how I prevent sql injection in mysql query? "mysql_real_escape_string" no longer exists. Is there any way to use PDO within CakePHP?
You can use this in your controller (or component)
// Initiate PDO connection
$this->_pdocon = $this->WhateverYourModel->getDataSource()->getConnection();
try {
// Select Query
$company = "What";
$stmt = $this->_pdocon->prepare('SELECT * FROM `agents` WHERE `company` LIKE :company LIMIT 2');
$stmt->bindValue(':company', $company, PDO::PARAM_STR);
// Start transaction
$this->_pdocon->begin();
// Loop through the events
if( $stm->execute() ) {
while ($row = $stmt->fetchAll(PDO::FETCH_ASSOC)) {
$stmt2 = $this->_pdocon->prepare("INSERT INTO `company`
(`id`, `name`, `identityno`, `modified`, `created`)
VALUES
(NULL, :name, :identityno, NOW(), NOW())");
$stmt2->bindValue(':name', $row['name'], PDO::PARAM_STR);
$stmt2->bindValue(':identityno', $row['id'], PDO::PARAM_INT);
$stmt2->execute();
}
}
// Commit transaction
$this->_pdocon->commit();
// Get last insert Id
$row_id = $this->_pdocon->lastInsertId();
var_dump($row_id);
} catch (PDOException $e) {
// Rollback transaction
$this->_pdocon->rollback();
echo "! PDO Error : " . $e->getMessage() . "<br/>";
}
This is what I ended-up. Using PDO has been solved thousands of issues. Now the system is fast and no memory exhaust error. And I can not putting all issues, errors what I got, in my question. It's good to giving direct answer rather trying to changing questions in here!
A large part of the point of cakePhp is not to do this. Therefore I would recommend not doing this.
Cakephp has a its own implementation for accessing a DB and you should use it if at all possible. Is there a particular reason you want to go around it?
if you realy want to, you can still use mysqli but I cant recommend it.
I have a simple MySQL stored procedure that takes two parameters and inserts a row into a table. I can execute it just fine from Zend Framework 2 like this:
$result = $this->dbAdapter->query('CALL sp_register_user(?, ?)', array('username', 'password'));
I can also access any result sets returned from my stored procedure.
What I want now is to have an output value from my stored procedure as a third parameter, so something like this:
DELIMITER //
CREATE PROCEDURE sp_register_user(IN username VARCHAR(50), IN password VARCHAR(128), OUT code INTEGER)
NOT DETERMINISTIC
COMMENT 'Registers a user'
BEGIN
INSERT INTO user VALUES (username, password);
SET code = 123;
END //
The question is how I can access this output variable from PHP (ZF2). I have only been able to find examples of how to do it directly through PDO, which I am using. Example 4 on this page shows how to do it through PDO directly. My concern is that if I use the PDO object directly, I am losing some abstractions and I am thereby assuming that I will always be using PDO.
Still, I tried to make it work with PDO directly, like this:
$username = 'my_username';
$password = 'my_password';
$code = 0;
$stmt = $this->dbAdapter->createStatement();
$stmt->prepare('CALL sp_register_user(?, ?, ?)');
$stmt->getResource()->bindParam(1, $username);
$stmt->getResource()->bindParam(2, $password);
$stmt->getResource()->bindParam(3, $code, \PDO::PARAM_INT, 3);
$stmt->execute();
However, I get an error saying that the statement could not be executed.
The ideal solution would be one where I could make use of ZF2's abstraction layer, but any ideas on how to access the output parameter are welcome and appreciated.
this must work, because i m using it :
$str = "DECLARE #Msgvar varchar(100);DECLARE #last_id int;
exec CallEntry_Ins $CallLoginId,".$this->usrId .",#Msg = #Msgvar OUTPUT,#LAST_ID = #last_id OUTPUT;
SELECT #Msgvar AS N'#Msg',#last_id AS '#LAST_ID'; ";
$stmt = $db->prepare($str);
$stmt->execute();
$rtStatus = $stmt->fetchAll();
$rtStatus[0]["#LAST_ID"] //accessing Op para
I'm attempting to convert my script that I use for registering a user on my website from SQL to SQLi. I have some code and wondered if it was correct. Thanks.
$members = new mysqli("localhost", "root", "pass", "members");
$check = $members->prepare("select email from users where email = ?");
$check->bind_param('s', $_POST['r_email']);
$check->execute();
$check->store_result();
if ($check->num_rows > 0) {
echo "user already registered";
} else {
$user_id = mt_rand(100000000, 999999999);
$add_user = $members->prepare("insert into users(email, password, user_id) values(?, ?, ?)");
$add_user->bind_param('ssi', $r_email, $r_password, $user_id);
$r_email = $_POST['r_email'];
$r_password = md5($_POST['r_password']);
$add_user->execute();
$add_user->close();
}
$check->close();
$members->close();
Dealing with the error message you noted in your comment, 'All data must be fetched before a new statement prepare takes place'' ...
The error means exactly what it says: You're trying to prepare a new statement before you've fetched all the data from the previous statement. From the manual entry on mysqli::use_resultdocs ...
Used to initiate the retrieval of a result set from the last query
executed using the mysqli_real_query() function on the database
connection.
Either this or the mysqli_store_result() function must be called
before the results of a query can be retrieved, and one or the other
must be called to prevent the next query on that database connection
from failing.
Further, from the manual entry on mysqli_stmt::num_rowsdocs ...
Returns the number of rows in the result set. The use of
mysqli_stmt_num_rows() depends on whether or not you used
mysqli_stmt_store_result() to buffer the entire result set in the
statement handle.
You need to call mysqli_stmt::store_result before you check mysqli_stmt::num_rows (as described at mysqli_stmt::num-rows). After that, you need to close the statement using mysqli_stmt::close (mysqli_stmt::close).
Edit: Also, using md5 for password hashing (especially without a salt) is very insecure. Take a look at https://stackoverflow.com/a/1581919/140827 for suggestions on more secure solutions (bcrypt, salt, etc.)
I need to optimize a script for high performance so the question is how can I add MySQL multi-query to this code?
foreach($multiContent as $htmlContent) {
$email = urldecode($email['1']);
$user = $user['1'];
//store in db
$db->query("UPDATE eba_users
SET mail = '$email'
WHERE username = '$user'");
//echo "email is $email and user is $user\n";
}
//close if ($array_counter % 200
unset($array_counter);
unset($data);
}
If you're using mysqli or PDO already, you should be using prepared statements for your queries since they are supported. This will also have a slight increase in performance since the entire query doesn't need to be sent again to the DB server. However the biggest advantage is the increased security that prepared statements provide.
Other than this, try adding an index on username to speed this query up if you haven't already.
Edit:
If you want to do it all in one query, as you seem to suggest, you could also use ON DUPLICATE KEY UPDATE as mentioned as an answer to this question:
INSERT INTO eba_users (`username`, `mail`)
VALUES
('username1','$email1'),
('username2','$email2'),
('username3','$email3'),
('username4','$email4'),
....
('usernameN','$emailN'),
ON DUPLICATE KEY UPDATE `mail`=VALUES(mail);
However this may not be as fast as using prepared statements with a regular UPDATE.
Edit2: As requested, here is probably a close approximation of what you should be doing to bind the parameters in mysqli:
if ($stmt = $mysqli->prepare("UPDATE eba_users SET mail= ? WHERE username= ?")) {
/* loop through array of users */
foreach ($array as $username => $newemail) {
/* bind parameters for markers */
$stmt->bind_param("ss", $newemail, $username);
/* execute query */
$stmt->execute();
}
}
Of course this doesn't provide any sort of error messages in case this fails. For that, you can look into mysqli::error