Creating new databases from potentially unsafe input via PDO and mysql - php

I'm working on automating the deployment of a piece of software I've written and need to be able to generate new mysql databases for new accounts. However, I'm having concerns when it comes to sanitizing the input.
I'm using PDO; however, apparently you can't use prepared statements with 'CREATE DATABASE'. So, I also tried using PDO::quote; however, then my newly created databases names are surrounded by single quotes (not the end of the world, but I'd still like to avoid that).
Is there any way to get this to work with prepared statements? If not, what can I do to protect myself as much as possible from sql injection attacks? My only other idea is to have a small whitelist of characters allowed.
Thanks!

you'll need to write a stored procedure to which you can then pass the sanitized input
(to use my example you'll need to change some variables like the database name and the correct user and pass and such, to make it run on your database of course)
example:
<?php
$dsn = 'mysql:dbname=scratch;host=127.0.0.1';
$user = 'root';
$password = '';
try {
$dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
$dbname = 'brand_new_db';
$statement = $dbh->prepare("CALL dbcreator(:db)");
$statement->bindParam(':db',$dbname);
if(!$statement->execute()){
print_r($statement->errorInfo());
}
else {
foreach( $dbh->query('SHOW DATABASES')->fetchAll() as $row){
print "$row[0]" . PHP_EOL;
}
}
stored procedure:
DELIMITER $$
DROP PROCEDURE IF EXISTS `scratch`.`dbcreator` $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `dbcreator`(IN dbname VARCHAR(64))
BEGIN
SET #db = dbname;
SET #statement = CONCAT('CREATE DATABASE ',#db);
PREPARE prepared_statement FROM #statement;
EXECUTE prepared_statement;
END $$
DELIMITER ;
You create an SQL statement to PREPARE, in our case CREATE DATABASE <our database>; since the CREATE DATABASE statement will not work with a variable, and then you just EXECUTE it. Finally you do a CALL dbcreator('<dbname>') to execute the stored procedure. It's that CALL dbcreator(:dbname), that you can use and bind params to.
As you can see, this way you can still bind your params safely through pdo while creating the database. Still it might not be a bad idea to prefix all database you create with some short fixed string for easy lookup and discrimination. In the stored procedure dbname is limited to 64 characters since that's mysql's current limit

Related

User inputs, clean and sanitize before sending to db

I've searched a lot of the questions here and I found that they either very old or suggesting using prepared statements PDO which I am not using. So I need your help please.
I have a small discussion/chat box where a user submit a message using a <textarea>
What I need is sanitize and filter the user input so it only accepts plain texts (e.g. no tags, no html tags, no scripts no links, etc). Also, it is important to allow line breaks.
Based on my reading I am doing the following in the following order:
trim()
htmlentities($comment, ENT_NOQUOTES)
mysqli_real_escape_string()
nl2br()
Is what I am doing is right? or I am missing something?
Also is there anything I have to do when echoing the data from the db?
really, appreciate your help and kindness
First, keep the text logical and clean:
trim() -- OK
htmlentities($comment, ENT_NOQUOTES) -- No; do later
mysqli_real_escape_string() -- Yes; required by API
nl2br() -- No; see below
The logic behind those recommendations: The data in the database should be just plain data. Not htmlentities, not br-tags. But, you must do the escape_string in order to pass data from PHP to MySQL; the escapes will not be stored.
But... That is only the middle step. Where did the data come from? Older versions of PHP try to "protect" you be adding escapes and other junk that works OK for HTML, but screws up MySQL. Turn off such magic escaping, and get the raw data.
Where does the data go to? Probably HTML? After SELECTing the data back out of the table, then first do htmlentities() and (optionally) nl2br();
Note, if you are expecting to preserve things like <I> (for italic), you are asking for trouble -- big trouble. All a hacker needs to do is <script> ... to inject all sorts of nastiness into your web page and possibly your entire system.
You also have another option. You can use prepared statements with mysqli
They aren't very difficult to learn and work a bit better than mysqli_real_escape_string() in that you don't need to worry about escaping every single variable that will be in your query. They are by nature "prepared" before they go into the database. There are other advantages to this as well, in that:
you do not need to addslashes() to be able to handle characters with
apostrophes etc.
for large databases, they will considerably speed
up your queries (much like PDO).
Here's how to do it:
You connect to the database by creating a new mysqli object like this:
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connection failed: " . $dbc->connect_error);
}
Next you want to convert your variables from your form.
Say you have a form field like this:
<input type="text" name="var1">
you can use htmlentities and trim together like so, and create your $var1 variable:
$var1 = htmlentities(trim($_POST['var1']));
Then you can create your transaction like this:
$stmt= $conn->prepare("insert into tablename (key1, key2) values (?,?)");
$stmt->bind_param("is",$var1, $var2);
$stmt->execute();
$stmt->close();
That's basically it. You do a query just like you normally would, but instead use the ? placeholders, assigning the datatype (above is i for integer, and s for string) and then bind them to your placeholders in the query.
That's basically it.
if you want to do it with a select with a variable, you use the normal select syntax and the same way with a ? with the variable, and then bind it. You can then bind your results into variables easily like so (assuming var3 is an integer):
$stmt= $conn->prepare("select var1, var2 from tablename where var3 = ?");
$stmt = bind_param("i", $var3);
$stmt->bind_result($var1, $var2);
$stmt->execute();
$stmt->close()
and then you can fetch your variables using this
$stmt->fetch();
or if your query brings back multiple rows
while ($stmt->fetch() {
echo $var1 . $var2;
}
nl2br() is used for output, you don't need to worry about input; it can be stored in the database as \n, and when you need it spits it out as breaks. If one of these variables needs the new lines turned into <br/> tags, you can, as you suggest use nl2br() on the variables (note this adds no security, but as you said you needed it), like so
echo nl2br($var1, false);
you can also use trim() and htmlentities() on this if it is being echoed into, say, a form input field and you don't want your form to break if there are html characters in the output.
Your question can lead me to build a full project with many features ;) lol
Before we start with out steps, we need a dummy (test) database for this scenario. We call out database chatbox with table called chat. You can simply create it by executing the following sql statement in your MySQL test environment:
CREATE TABLE `chat` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`msg` VARCHAR(200) NOT NULL DEFAULT '0',
`user_id` INT(11) NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
;
Now you can go a head and follow the steps here:
Step 1: Create project folder in your web server.
Build database connection based on PDO and call it dbConnect.inc.php:
<?php
// check if PDO driver not available
if (!defined('PDO::ATTR_DRIVER_NAME'))
echo 'PDO driver unavailable <br />';
// database configuration
$dbHost = "localhost";
$dbPort = "3306";
$dbName = "chatbox";
$dbUser = "root";
$dbPass = "";
$strDSN = "mysql:host=$dbHost:$dbPort;dbname=$dbName";
// database connection
try
{
$dbConn = new PDO($strDSN, $dbUser, $dbPass);
//Activate following line to view all error messages
$dbConn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e)
{
die("Could not connect to the database $dbName, error info: <br />"
. $e->getMessage());
exit();
}
I will test this works before go to next step. Btw the prepared method does not require mysqli_real_escape_string().
I have used PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION in stead of if statements, this method will give you useful error details while development the project. You will find out which method is more practical for getting error message while your development process of the project.
Step2: Create a file call filter.inc.php:
<?php
// filter for input
function filterInput($content)
{
$content = trim($content);
$content = stripslashes($content);
return $content;
}
//filter for viewing data
function filterOutput($content)
{
$content = htmlentities($content, ENT_NOQUOTES);
$content = nl2br($content, false);
return $content;
}
This file contain a function to filterInput to sanitize or filter your input content for comments or other inputs. And filterOutput that effect your data view.
All depending on your strategy and what you need, like if you need to allow people post url's or email address, should url and email become active link or only viewed as text etc. that way you can define which filter should be use for your content input and which filter should be used for you content output.
You can add or delete extra features to functions. There are many features for text input and output, you can test those individually and evaluate it, and even extend the filter function or create your own function.
Final step 3: Now we put the puzzles together in our index.php file:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Chat box</title>
</head>
<body>
<?php include './dbConnect.inc.php'; ?>
<?php include './filter.inc.php'; ?>
<h1>Chat box</h1>
<p>
<?php
// this is dummy user id, but use the id over user id when login or the way you want
// this is only example
$user_id = 1;
if (isset($_POST["msg"]))
{
$msg = filterInput($_POST["msg"]);
$sql = "INSERT INTO chat "
. "(msg, user_id) "
. "VALUES "
. "(:msg, :user_id)";
$stmt = $dbConn->prepare($sql);
$fieldsArr = [':msg' => $msg, ':user_id' => $user_id];
$stmt->execute($fieldsArr)
// refresh page after insert
header("Location: " . $_SERVER['REQUEST_URI']);
}
?>
<form action="index.php" method="post">
<textarea name="msg" id="msg" required></textarea>
<input name="submit" type="submit">
</form>
</p>
<p>Comments</p>
<p>
<?php
$sql = "SELECT * FROM chat WHERE user_id = (:user_id);";
$stmt = $dbConn->prepare($sql);
$fieldsArr = [':user_id' => $user_id];
$stmt->execute($fieldsArr)
while ($result = $stmt->fetch())
echo "<h3>" . filterOutput($result['msg']) . "</h3>";
$dbConn = null;
?>
</p>
</body>
</html>
This is to demonstrate how things works. You have insert, select statement as example and filter functions. You can make tests, extend it the way you like or further develop your own project.
Here is screen shot of the chatbox example I made:
filter_input could be another one you are looking for. It can save you hours from writing sanitizing and validation code. Of course, it does not cover every single case, but there is enough so that you can focus more on specific filtering/validating code.
Though it is strongly recommended to use prepared statements with
PDO/mysqli. But sometimes it is not so easy to convert the whole
project in the tail end of the project. You should learn PDO/mysqli for
your next project.
$comment = filter_input(INPUT_POST, 'comment', FILTER_SANITIZE_STRING);
There are different Types of filters for you. You can select depending on your needs. You can also use filter_has_var to check for variable set.
Your code looks fine, if you don't want to prepare statements then escaping is the next best thing. And when you echo it should be straightforward, it's only plain text.

What is the PDO equivalent of function mysql_real_escape_string?

I am modifying my code from using mysql_* to PDO. In my code I had mysql_real_escape_string(). What is the equivalent of this in PDO?
Well No, there is none!
Technically there is PDO::quote() but it is rarely ever used and is not the equivalent of mysql_real_escape_string()
That's right! If you are already using PDO the proper way as documented using prepared statements, then it will protect you from MySQL injection.
# Example:
Below is an example of a safe database query using prepared statements (pdo)
try {
// first connect to database with the PDO object.
$db = new \PDO("mysql:host=localhost;dbname=xxx;charset=utf8", "xxx", "xxx", [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
} catch(\PDOException $e){
// if connection fails, show PDO error.
echo "Error connecting to mysql: " . $e->getMessage();
}
And, now assuming the connection is established, you can execute your query like this.
if($_POST && isset($_POST['color'])){
// preparing a statement
$stmt = $db->prepare("SELECT id, name, color FROM Cars WHERE color = ?");
// execute/run the statement.
$stmt->execute(array($_POST['color']));
// fetch the result.
$cars = $stmt->fetchAll(\PDO::FETCH_ASSOC);
var_dump($cars);
}
Now, as you can probably tell, I haven't used anything to escape/sanitize the value of $_POST["color"]. And this code is secure from myql-injection thanks to PDO and the power of prepared statements.
It is worth noting that you should pass a charset=utf8 as attribute, in your DSN as seen above, for security reasons, and always enable
PDO to show errors in the form of exceptions.
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
so errors from you database queries won't reveal sensitive data like your directory structure, database username etc.
Last but not least, there are moments when you should not trust PDO 100%, and will be bound to take some extra measures to prevent sql injection, one of those cases is, if you are using an outdated versions of mysql [ mysql =< 5.3.6 ] as described in this answer
But, using prepared statements as shown above will always be safer, than using any of the functions that start with mysql_
Good reads
PDO Tutorial for MySQL Developers
There is none*! The object of PDO is that you don’t have to escape anything; you just send it as data. For example:
$query = $link->prepare('SELECT * FROM users WHERE username = :name LIMIT 1;');
$query->execute([':name' => $username]); # No need to escape it!
As opposed to:
$safe_username = mysql_real_escape_string($username);
mysql_query("SELECT * FROM users WHERE username = '$safe_username' LIMIT 1;");
* Well, there is one, as Michael Berkowski said! But there are better ways.
$v = '"'.mysql_real_escape_string($v).'"';
is the equivalent of $v = $this->db->quote($v);
be sure you have a PDO instance in $this->db so you can call the pdo method quote()
There is no need of mysql_real_escape_string in PDO.
PDO itself adjust special character in mysql query ,you only need to pass anonymous parameter and bind it run time.like this
Suppose you have user table with attribute name,email and password and you have to insert into this use prepare statement like this
you can pass name as => $name="Rajes'h ";
it should execute there is no need of equivalent of mysql_real_escape_string
$stmt="INSERT into user(name,email,password) VALUES(:name,:email,:password)";
try{
$pstmt=$dbh->prepare($stmt);//$dbh database handler for executing mysql query
$pstmt->bindParam(':name',$name,PDO::PARAM_STR);
$pstmt->bindParam(':email',$email,PDO::PARAM_STR);
$pstmt->bindParam(':password',$password,PDO::PARAM_STR);
$status=$pstmt->execute();
if($status){
//next line of code
}
}catch(PDOException $pdo){
echo $pdo->getMessage();
}
The simplest solution I've found for porting to PDO is the replacement for mysql_real_escape_string() given at https://www.php.net/manual/en/mysqli.real-escape-string.php#121402. This is by no means perfect, but it gets legacy code running with PDO quickly.
#samayo pointed out that PDO::quote() is similar but not equivalent to mysql_real_escape_string(), and I thought it might be preferred to a self-maintained escape function, but because quote() adds quotes around the string it is not a drop in replacement for mysql_real_escape_string(); using it would require more extensive changes.
In response to a lot of people's comments on here, but I can't comment directly yet (not reached 50 points), there ARE ACTUALLY needs to use the $dbh->quote($value) EVEN when using PDO and they are perfectly justifiable reasons...
If you are looping through many records building a "BULK INSERT" command, (I usually restart on 1000 records) due to exploiting InnoDb tables in MySQL/Maria Db. Creating individual insert commands using prepared statements is neat, but highly inefficient when doing bulk tasks!
PDO can't yet deal with dynamic IN(...) structures, so when you are building a list of IN strings from a list of user variables, YOU WILL NEED TO $dbh->quote($value) each value in the list!
So yes, there is a need for $dbh->quote($value) when using PDO and is probably WHY the command is available in the first place.
PS, you still don't need to put quotes around the command, the $dbh->quote($value) command also does that for you.
Out.
If to answer the original question, then this is the PDO equivalent for mysql_real_escape_string:
function my_real_escape_string($value, $connection) {
/*
// this fails on: value="hello'";
return trim ($connection->quote($value), "'");
*/
return substr($connection->quote($value), 1, -1);
}
btw, the mysqli equivalent is:
function my_real_escape_string($value, $connection) {
return mysqli_real_escape_string($connection, $value);
}

pdo insert without variables

I'm trying to insert a new record into a table that contains only an auto number primary key using the following code,
$pdo_conn = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
$sqla = "insert into tbl_gen (gen_sk) values (null)";
$qa = $pdo_conn->prepare($sqla);
$qa->execute();
is this the right way to go about it?
running the sql command in mysql workbench does the job, I'm just feeling like maybe I'm using pdo the wrong way.
Prepared statements are intended to be re-used. If you're just doing a simple one-shot query, then use $pdo->exec() instead. This avoids the overhead of preparing the statement, and just simply "does it".
But regardless, there's nothing "wrong" with how you're going about it.

mysqli_multi_query not inserting to mysql db

I have used tutorials, examples and looked at numerous other questions about my problem and I still can't get it to work, I am relatively new to PHP and do not have any understanding of PDO. I have changed my code to mysqli rather than mysql to get rid of the depreciated code my university gave me but they have been less than helpful during this situation.
If anyone could shed some light onto this issue for me I would be very grateful.
Below are my code samples:
<?php /*connect to the db */
$link=mysqli_connect("dbhost","user","pass");
mysqli_select_db("db",$link);
/*checking connection*/
if ($link->connect_errno)
throw new exception(sprintf("Could not connect: %s", $link->connect_error));
session_start();
$insert_query="
INSERT INTO testone_tbl (age,hours,flexibility,fastpaced,retailexp,
workedus,conviction,permit,education)
VALUES ('$age','$hours','$flexibility','$fastpaced','$retailexp','$workedus',
'$conviction','$permit','$education');
INSERT INTO testtwo_tbl
(contribute,insales,initiative,success,alternatives,targets,
newthings,custfeed,incdevelop,standards,confident,stretch,
opportunities,polite,ideas,deadline,supported,duties)
VALUES ('$contribute','$insales','$initiative',
'$success','$alternatives','$targets','$newthings',
'$custfeed','$incdevelop','$standards','$confident','$stretch',
'$opportunities','$polite','$ideas','$deadline','$supported','$duties')";
/*execute multi_query*/
mysqli_multi_query ($link, $insert_query);/*error1*/
/*close connection*/
if(!$link>connect_errno) $link->close(); /*error2*/
?>
The data is both from the form this is written in (the last form) and sessions from the previous forms. However I am also getting this error: Warning: mysqli_multi_query() expects parameter 1 to be mysqli and Warning: mysqli_close() expects parameter 1 to be mysqliand I have been stuck on this the past few days! Thank you in advance.
You should first check with your web host if they have enabled multi-SQL-queries.
Some web hosts only allow single-SQL queries to help prevent against injection attacks.
If, however, you want to multi-insert to the same table, you could do it like this:
INSERT INTO tbl_name (col1, col2)
VALUES ('?', '?'),
('?', '?'),
('?', '?'); # inserts 3 records to the same table in one query
Also, if you do have PDO available to you, use it!
With a PDO object, your queries will be safer by using prepared statements. Example:
$db = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
$data = array($col1, $col2, $col3);
$sql = "INSERT INTO tbl_name (col1, col2, col3) VALUES ('?', '?', '?');";
$query = $db->prepare($sql); # prepares the sql statement
$query->execute($data); #binds the array of data to the ?
#question mark parameters, and executes.
If you create a database abstraction layer, you could change the database connection mode without having to rewrite your code which executes your queries.
Also, do you have a reason not to loop and query? Example:
$sql_array = array("INSERT INTO tbl_one(col1) VALUES '?';",
"INSERT INTO tbl_two(col3) VALUES '?';");
function performAll($sql_array) {
# execute all of the queries
}
It has occured to me that you may be using some function to access your database connection. Now that is not a problem, unless you actually try to access the database connection from within a function (in case you have not told us). Example:
$db = new PDO("...", $user, $pass);
$query = $db->prepare($sql); # works fine
function executeQuery($sql) {
$query = $db->prepare($sql); # error: $db is not defined
# within the scope of this function
...
}
To get around this, use the global keyword in PHP. Example:
$db = new PDO("...", $user, $pass);
function executeQuery($sql) {
global $db; # use $db in the global scope
$query = $db->prepare($sql); # works fine
...
}
From the warnings it is clear that $link is not a mysqli object. Either you did not connect, or at some point you reassigned $link to something else.
You also need to check your connection immediately after your connect. An intermediate action on the link (in this case, mysqli_select_db) will clear any errors that were set.
You should also not mix-and-match object-oriented and procedural style interfaces for mysqli. The object-oriented style is much clearer, but if it's too difficult to change the existing code then stick to the procedural style.
Connect like this instead:
$link = mysqli_connect("dbhost","user","pass", "db"); // no need for an extra db select
if (mysqli_connect_errno()) {
throw new Exception("Could not connect: ".mysqli_connect_error());
}
Also, I hope this isn't your real code, because it is wide open to mysql injection attacks. Consider dropping the use of multi-queries entirely and using prepared statements with placeholders.

How do you connect/retrieve data from a MYSQL database using objects in PHP?

Generally I connect and retrieve data using the standard way (error checking removed for simplicity):
$db = mysql_select_db("dbname", mysql_connect("host","username","passord"));
$items = mysql_query("SELECT * FROM $db");
while($item = mysql_fetch_array($items)) {
my_function($item[rowname]);
}
Where my_function does some useful things witht that particular row.
What is the equivalent code using objects?
Since version 5.1, PHP is shipped with the PDO driver, which gives a class for prepared statements.
$dbh = new PDO("mysql:host=$hostname;dbname=$db", $username, $password); //connect to the database
//each :keyword represents a parameter or value to be bound later
$query= $dbh->prepare('SELECT * FROM users WHERE id = :id AND password = :pass');
# Variables are set here.
$query->bindParam(':id', $id); // this is a pass by reference
$query->bindValue(':pass', $pass); // this is a pass by value
$query->execute(); // query is run
// to get all the data at once
$res = $query->fetchall();
print_r($res);
see PDO driver at php.net
Note that this way (with prepared statements) will automatically escape all that needs to be and is one of the safest ways to execute mysql queries, as long as you use binbParam or bindValue.
There is also the mysqli extension to do a similar task, but I personally find PDO to be cleaner.
What going this whole way around and using all these steps gives you is possibly a better solution than anything else when it comes to PHP.
You can then use $query->fetchobject to retrieve your data as an object.
You can use the mysql_fetch_object()
http://is2.php.net/manual/en/function.mysql-fetch-object.php

Categories