How to monitor SQL (which uses prepared stataments)? - php

When first developing an PHP app (MySQl, but using ODBC interfaces to allow for future expansion), I was simply assigning my SQL to a variable and calling odbc_exec().
That made debugging simple, as I just had to examine my variable $sql.
Of course, I soon realized that I have to use prepared statements to sanitize user input.
My question is how to discover the exact SQL which is being executed in the databse, in order to debug my prepared statements.
I reazlise that I can't do it from PHP, but are their any external monitor tools which can interecpt the SQL? Or even a MySql command to echo, if I leave a console window open?

Use the MySQL query log.

You can start mysql server by --log[=file_name] to have a log file.

Here's some code I cam up with to aid debugging within PHP code.
However, to be absolutely certin of what is being executed by MySql, the other posters are correct. Look at the query log.
function OdbcPrepareAndExecute($sql, $parameter_array)
{
if (substr_count($sql, '?') != count($parameter_array))
{
ob_start();
var_dump($parameter_array);
$parameter_array_dump .= ob_get_clean();
ReportErrorAndDie('Invalid parameters',
'Request to prepare ODBC statement "' . $sql .'" with ' .
substr_count($sql, '?') . ' place-holders, but ' .
count($parameter_array) . ' parameters in array : ' .
$parameter_array_dump
);
}
LogDatabaseActivity('Prepare SQL "' . $sql . '"');
$prepared_statement = #odbc_prepare($_SESSION['connection'], $sql);
if($prepared_statement === False)
{
ReportErrorAndDie('Database problem',
'Could not prepare ODBC statement "' . $sql .'"');
}
LogDatabaseActivity('Execute prepared SQL statement with the following parameters:');
ob_start();
var_dump($parameter_array);
$parameter_array_dump = ob_get_clean();
LogDatabaseActivity($parameter_array_dump);
$expanded_sql = $sql;
for ($i = 0; $i < count($parameter_array); $i++)
$expanded_sql = substr_replace($expanded_sql,
$parameter_array[$i],
strpos($expanded_sql, '?'),
1);
LogDatabaseActivity('(preapred statement expands to "' . $expanded_sql . '")');
$result = #odbc_execute($prepared_statement, $parameter_array);

Related

Right format for query

I am making a test program in which I need to use this code:
"href=\"testmng.php?manageqn=" . htmlspecialchars_decode($r['testname'], ENT_QUOTES) . "?subjectname=". htmlspecialchars_decode($r['subname'], ENT_QUOTES)
My question is what is the right format when manageqn and subjectname have the right values:
else if ((isset($_REQUEST['manageqn'])) && (isset($_REQUEST['subjectname']))) {
$testname = $_REQUEST['manageqn'];
$subname = $_REQUEST['subjectname'];
$result = executeQuery("select testid from test where testname='" . htmlspecialchars($testname, ENT_QUOTES) . "';");
if ($r = mysql_fetch_array($result)) {
$_SESSION['testname'] = $testname;
$_SESSION['subjectname'] = $subname;
$_SESSION['testqn'] = $r['testid'];
header('Location: prepqn.php');
}
}
Assuming you're using mysqli to connect to the database, you need to escape the string using the myqli_real_escape_string() PHP function, otherwise you risk adding sql injection to your application:
executeQuery("select testid from test where testname='" . myqli_real_escape_string($testname) . "';");
I'd recommend however to switch to a parametrized query approach, by using the prepared statements feature that mysqli provides. You can then have executeQuery() like this:
executeQuery("select testid from test where testname=?", $testname)
no need to escape strings in order to have a safe query.
If you're using the deprecated mysql driver, then you should use mysql_real_escape_string().

php pdo query fails to return result despite prepared statement being correct

I have the following php/pdo which calls a a mysql procedure which returns a filename however despite the prepared statement being correct and returning the following from a cut and pasted db query:
db query/result
> call sp_CDRbyCustomer('Wind', 'R2X', DATE(DATE_SUB(NOW(), INTERVAL 10 WEEK)), DATE(NOW()));
> +------------------------------------------------------+
> | Exported filename |
> +------------------------------------------------------+
> | '/tmp/CDR_for_Wind_20140704-20140912_1410516460.csv' |
> +------------------------------------------------------+
> 1 row in set (0.02 sec)
the php/pdo
which should return our filename is:
include('db.ini');
define('DEBUG', true);
if (DEBUG ) {
openlog("$iam", LOG_PID | LOG_ODELAY,LOG_LOCAL4);
syslog(LOG_INFO, "START OF DEBUG LOG.");
}
/***** connect to database using db.ini for credentials *****/
try {
$dbConn = new PDO('mysql:host='.$host.';dbname='.$db, $dbUser, $dbPass, array (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION));
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
$structure_name = "'Wind'";
$cug_uid = "'R2X'";
$num_weeks = 10;
/**** Query - export file name ****/
$sqlQuery4filename = ("
call sp_CDRbyCustomer($structure_name, $cug_uid, DATE(DATE_SUB(NOW(), INTERVAL $num_weeks WEEK)), DATE(NOW()))
");
$sqlQuery4filename = preg_replace( "/\r|\n/", "", $sqlQuery4filename );
debug("01: sqlQueryfile string is '" . $sqlQuery4filename . "'");
$stmnt = $dbConn->prepare($sqlQuery4filename);
debug($stmnt);
$stmnt->execute();
$stmnt->closeCursor();
$filename = $stmnt->fetch(PDO::FETCH_ASSOC);
$filename = $filename['Exported filename'];
debug("01: filename string is '" . $filename . "'");
function debug($debug) {
if(!DEBUG) return;
print '
<div class="debug">
';
print_r($debug);
print '
</div><!-- end of debug -->
';
}
However I only ever get an empty string returned.
I have had this working, but after subversioning it I ended up with the wrong version and can no longer get it to work, anybody give me any clues to what I'm doing wrong.
You need to properly debug this:
See what the exact SQL command is you execute (I can't tell from your question).
Test the command against the database, for instance with PHPmyAdmin.
Show what the fetch() result is, before you access it as an array.
If you follow the PHP execution trial from the beginning to the end, you should be able to work out where it goes wrong.
You need to bind the output parameter. See Example #4 in the docs http://php.net/manual/en/pdo.prepared-statements.php
<?php
$stmt = $dbh->prepare("CALL sp_returns_string(?)");
$stmt->bindParam(1, $return_value, PDO::PARAM_STR, 4000);
// call the stored procedure
$stmt->execute();
print "procedure returned $return_value\n";
?>
So after much debugging and many different coding attempts I eventually retrieved teh required result.
I tried numerous attempts to use binParam but the returned result would always be a null string.
I had changed to mysql procedure to output the required filename so the mysql command SELECT #filename would provide the desired result.
I then retrieved this value with the following:
/**** Query - export file name ****/
$sql = 'call sp_CDRbyCustomer( \'' . $structure_name . '\', \'' . $cug_uid . '\', DATE(DATE_SUB(NOW(), INTERVAL ' . $num_weeks . ' WEEK)), DATE(NOW()), #filename )';
$stmnt = $dbConn->prepare($sql);
debug($stmnt);
$stmnt->execute();
$stmnt->closeCursor();
$filename_result = $dbConn->query("select #filename")->fetch();
$filename = $filename_result['#filename'];
I'm not sure if this is the best way to do this but it does produce the result I desire :-)

PDO/prep statement/whitelisting/set charset, is that safe enough to prevent injection

I am converting from extension mysql to PDO and after reading all I could from you gurus in SO and elsewhere, I have some residual doubts. I came up with the following to address sql injection for a typical query. I am just wondering if that's enough or may be I am going a bit overboard with the whitelisting, before I replicate this to all my application.
It's not clear to me if I did the whitelisting properly, ie, if I should also escape somehow.
Also, I am not sure if I should setAttribute emulate to false for every query or just once for the script.
$link = new PDO("mysql:host=$hostname;dbname=$database;charset=utf8", $username, $password);
$link->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$arr_i=$arr_k='';
$m_act=$v_act='Y';
$table = array('prices', 'versions', 'models');
$allowedTables = array('prices', 'versions', 'models');
$field = array('model_id', 'version_id', 'price', 'models.active', 'versions.active');
$allowedFields = array('model_id', 'version_id', 'price', 'models.active', 'versions.active');
if(count( array_diff($field, $allowedFields))==0 AND count( array_diff($table, $allowedTables))==0){
$sql = "SELECT COUNT(DISTINCT `" . $field[0] . "`) as ctmod FROM `" . $table[0] . "`
INNER JOIN `" . $table[1] . "` USING (`" . $field[1] . "`)
INNER JOIN `" . $table[2] . "` USING (`" . $field[0] . "`)
WHERE `" . $field[2] . "` BETWEEN :arr_i AND :arr_k
AND " . $field[3] . " = :m_act
AND " . $field[4] . " = :v_act";
$stmt = $link->prepare($sql);
$stmt->bindParam(':arr_i', $arr_i, PDO::PARAM_INT);
$stmt->bindParam(':arr_k', $arr_k, PDO::PARAM_INT);
$stmt->bindParam(':m_act', $m_act, PDO::PARAM_STR);
$stmt->bindParam(':v_act', $v_act, PDO::PARAM_STR);
for ($i=0; $i < $ctpri; $i++){
$k=$i+1;
$arr_i=$arr_pri[$i]+1;
$arr_k=$arr_pri[$k];
$stmt->execute();
while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
$ctmod[] = $r['ctmod'];
}
}
}
else{die();}
I suspect that you indeed going a bit overboard with the whitelisting. And not only with whitelisting but even with prepared statements too. And to satisfy your wrong views, you over-engineered your query to the point of totally uncomprehensible mess.
What you need to understand is that any constant value is safe by design. So, there is absolutely no point in using nor whitelisting nor prepared statements for it.
So, instead of
AND " . $field[3] . " = :m_act
you should write just
AND versions.active = 'Y'
without any binding or whitelisting.
All you need to protect is dynamical values only. So, you have to use prepared statements for $arr_i and $arr_k only. All other query parts have to be written into query directly, just like you did it before.
Yes, your code is thoroughly safe from SQL injection. Good job.
Though as #YourCommonSense points out, there's no reason in the example you show to make table and columns names into variables at all. It would be simpler to just write them into the query literally.
Therefore, I assume you're asking this question because you do sometimes choose table and column names through application logic or variables, even though you haven't shown it in this particular example.
The only tips I would offer are:
All the string concatenation, with ending double-quotes, using . and re-starting double-quotes makes the code look untidy and it can be confusing to keep track of which double-quotes you've started and stopped. An alternative style of PHP string interpolation for variables is to enclose in curly braces, like the following:
$sql = "SELECT COUNT(DISTINCT `{$field[0]}`) as ctmod FROM `{$table[0]}`
INNER JOIN `{$table[1]}` USING (`{$field[1]}`)
INNER JOIN `{$table[2]}` USING (`{$field[0]}`)
WHERE `{$field[2]}` BETWEEN :arr_i AND :arr_k
AND `{$field[3]}` = :m_act
AND `{$field[4]}` = :v_act";
And for yet another alternative, you can use here documents, so you don't have to worry about delimiting the string at all. Nice if you have literal double-quotes inside your string, because you don't have to escape them:
$sql = <<<GO
SELECT COUNT(DISTINCT `{$field[0]}`) as ctmod FROM `{$table[0]}`
INNER JOIN `{$table[1]}` USING (`{$field[1]}`)
INNER JOIN `{$table[2]}` USING (`{$field[0]}`)
WHERE `{$field[2]}` BETWEEN :arr_i AND :arr_k
AND `{$field[3]}` = :m_act
AND `{$field[4]}` = :v_act
GO;
Finally, it has nothing to do with SQL injection, but a good practice is to check the return value from prepare() and execute(), because they return false if an error occurs in parsing or execution.
if (($stmt = $link->prepare($sql)) === false) {
trigger_error(PDO::errorInfo()[2], E_USER_ERROR);
}
(That example uses PHP 5.4 syntax to dereference an array returned from a function.)
Or else you can configure PDO to throw exceptions, so you don't have to check.
$link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

How do I make sure that values from MySQL keep their type in PHP?

I am having a problem in my PHP script where values called from MySQL are being returned as strings, despite being marked in the database as int and tinyint.
This is a problem because when converting an array based on MySQL date into JSON data, values that should be integers are placed in double quotes, which is causing trouble in both Javascript and iPhone apps that use that JSON data. I am getting JSON values that look like "key" : "1", when what I want is "key" : 1.
After doing some research, it seems that it should be possible to get the values as their native type so long as one has PHP 5.3, and the mysqlnd module installed. I have 5.3.3 and phpinfo() seems to indicate I have the mysqlnd module installed and running:
mysqlnd enabled
Version mysqlnd 5.0.10 - 20111026
However, my values are still being returned as strings.
I have looked at the PHP manual entry for mysqlnd, and it's always possible I'm missing the obvious, but I don't see anything that indicates I need to do anything specific in my code to get the native values.
What exactly do I do to get my MySQL functions in PHP to give me the MySQL results in their native type?
In order to fascillitate an answer below, this is the command I use to connect to the database:
private function databaseConnect()
{
$this->mysqli = new mysqli(Database::$DB_SERVER, Database::$DB_USERNAME, Database::$DB_PASSWORD);
$this->mysqli->set_charset("utf8");
return true;
}
private function dbConnect()
{
Database::$USE_MYSQLI = extension_loaded('mysqli');
if (!$this->databaseConnect())
{
echo "Cannot Connect To The Database Server";
throw new Exception();
}
if (!$this->databaseSelectDB())
{
echo "The database server connected, but the system could not find the right database";
throw new Exception();
}
}
private function databaseQuery($query)
{
return $this->mysqli->query($query);
}
public function doQuery($query)
{
$result = $this->databaseQuery($query);
if ($result == FALSE)
{
//ErrorHandler::backtrace();
die("This query did not work: $query");
}
return $result;
}
private function getRows($table, $matches, $orderBy = array(), $limit = array())
{
$calcFoundRows = '';
if (count($limit) > 0)
{
$calcFoundRows = ' SQL_CALC_FOUND_ROWS';
}
$query = 'SELECT ' . $calcFoundRows . ' * FROM ' . $table;
if (count($matches) > 0)
{
$query .= ' WHERE ';
$keys = array_keys($matches);
$first = true;
foreach ($keys as $key)
{
if (!$first)
{
$query .= ' AND ';
}
$first = false;
// now he is safe to add to the query
// the only time this is an array is when this is called by getSelectedUsers or getSelectedArticles
// in that case it is an array of array's as the key (which is the column name) may have more than
// one condition
if (is_array($matches[$key]))
{
$firstForColumn = true;
foreach ($matches[$key] as $conditions)
{
if (!$firstForColumn)
{
$query .= ' AND ';
}
$firstForColumn = false;
// if the value is an array we generate an OR selection
if (is_array($conditions[1]))
{
$firstOr = true;
$query .= '(';
foreach ($conditions[1] as $value)
{
if (!$firstOr)
{
$query .= ' OR ';
}
$firstOr = false;
// clean this guy before putting him into the query
$this->cleanMySQLData($value);
if ($conditions[0] == Selection::$CONTAINS)
{
//$query .= 'MATCH (' . $key . ') AGAINST (' . $value . ') ';
$value = trim($value, "'");
$value = "'%" . $value . "%'";
$query .= $key . ' LIKE ' . $value;
}
else
{
$query .= $key . ' ' . $conditions[0] . ' ' . $value;
}
}
$query .= ')';
}
else
{
// clean this guy before putting him into the query
$var = $conditions[1];
$this->cleanMySQLData($var);
if ($conditions[0] == Selection::$CONTAINS)
{
//$query .= 'MATCH (' . $key . ') AGAINST (' . $var . ') ';
$var = trim($var, "'");
$var = "'%" . $var . "%'";
$query .= $key . ' LIKE ' . $var;
}
else
{
$query .= $key . ' ' . $conditions[0] . ' ' . $var;
}
}
}
}
else
{
// clean this guy before putting him into the query
$this->cleanMySQLData($matches[$key]);
$query .= $key . " = " . $matches[$key];
}
}
}
if (count($orderBy) > 0)
{
$query .= " ORDER BY ";
$first = true;
foreach ($orderBy as $orderCol)
{
if (!$first)
{
$query .= ',';
}
$query .= $orderCol;
$first = false;
}
}
if (count($limit) > 0)
{
$query .= ' LIMIT ' . $limit[0];
if (count($limit) > 1)
{
$query .= ',' . $limit[1];
}
}
$result = $this->doQuery($query);
$data = array();
while ($row = $this->databaseFetchAssoc($result))
{
$data[] = $row;
}
if (strlen($calcFoundRows) > 0)
{
$numRows = $this->databaseCountFoundRows();
$key = '^^' . $table . '_selectionCount';
Session::getSession()->putUserSubstitution($key, $numRows);
}
return $data;
}
What exactly do I do to get my MySQL functions in PHP to give me the MySQL results in their native type?
You connect to the database, then you prepare your query, execute it, bind the result and then you fetch it.
Let's do these steps line-by-line:
$conn = new Mysqli('localhost', 'testuser', 'test', 'test');
$stmt = $conn->prepare("SELECT id FROM config LIMIT 1");
$stmt->execute();
$stmt->bind_result($id);
$stmt->fetch();
var_dump($id); # it's an int!
This works for me. As you wrote your code is more complex, you will need to locate the place where you query the database. Check that you're using Mysqli::prepare() and if not, introduce it.
You will also need to use Mysqli_Stmt::execute() and then Mysqli_Stmt::bind_result() otherwise the (here integer) type is not preserved for that result column.
However, my values are still being returned as strings.
You are in PHP, where it does not matter that your data are int, bool, strings... if they have such a type they are called scalar data and dynamic castings will allow you to make them behave as you want.
For example, the string "12345"+"54321" will give you 66666.
If you absolutely want your data to be of a particular type, as in every language, it is not the driver's job. In Java you've got something like .getString, .getInt methods in JDBC's interfaces, in PHP you do not have as it is not very useful. You will have to cast yourself your data with intval boolval strval... functions or (int), (bool)... casting operators.
As your post said you can have it by using server-side prepared statement:
Advantages of using mysqlnd for PDO
mysqlnd returns native data types when using Server-side Prepared Statements, for example an INT column is returned as an integer variable not as a string. That means fewer data conversions internally.
With PDO
You have to put this line after your connection
$PDO->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
then, when you want to query :
$s = $PDO->prepare('yourquery');
//here, binding params
$s->bindValue('paramName','paramValue');
$s->execute();
With Mysqli
As you use mysqli, the syntax will be a little different :
Note: there is no way of client-side prepared statement so you won't need the configuration line that I put with PDO.
So your query will look like that:
$statement = $MySQli->prepare('your query');
$statement->bind_param('si', $stringParam, $intParam);
$statement->bind_result($var1, $var2 /*,...*/);
$statement->execute();
while($statement->fetch()){
//process here, result will be in var1, var2...
}
You can see that, here, there is no built-in fetchAll method.
To bind your data you need to use variables as it is not passed as value like in PDOStatement::bindValue() but by reference. Moreover the types are defined in the first arg (s for string, i for integer...)
There are no named parameters only indexed ones.
The fetch method works in a different way and needs you to call bind_result BEFORE the execute statement;
This is possible with mysqlnd, but only after you enable it. To enable it you need to call the method options and set the option to true:
$this->mysqli->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
It is enabled by default for prepared statements. If you use prepared statements all the time then you already have the data returned in the correct type.
$stmt = $mysqli->prepare('SELECT 1');
$stmt->execute();
$ret = $stmt->get_result();
echo gettype($ret->fetch_row()[0]);
The above code will return:
integer
However, if you used normal unprepared queries then all values are returned as strings even with mysqlnd. But you can enable the same setting to apply to unprepared queries, too.
$ret = $mysqli->query('SELECT 1');
echo gettype($ret->fetch_row()[0]);
// outputs:
// string
// Enable the same setting for unprepared queries
mysqli_options($mysqli, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
$ret = $mysqli->query('SELECT 1');
echo gettype($ret->fetch_row()[0]);
// outputs:
// integer
The reason for this is the technical implementation of mysqlnd. The same applies to PDO_MYSQL as well, but in PDO this setting is enabled by default when you have emulated prepared statements switched off. There is a very good explanation by Ulf Wendel in one of the posts you linked.
This is the new default with PDO_MYSQL when compiled against mysqlnd, turning off the PDO prepared statement emulation and disabling PDO::ATTR_STRINGIFY_FETCHES. MySQL Prepared Statements use different binary communication protocol but non-prepared statements. The binary protocol avoids unecessary type casts to strings.
Julien Pauli explains the difference between textual and binary MySQL protocols in this presentation: https://www.slideshare.net/jpauli/mysqlnd
This probably is not the best answer but you can use something such as intval() to reinforce the data type. I've personally had this experience when using PDO, Mysqli, or even the old (now deprecated) mysql functions. You can run test cases with is_int() and is_string() to be 100% certain that the fetched data from the database really is a string versus and int. Sorry I couldn't be more help!

PDO : Insert query containing ' is not executing in SQLite3 with PHP 5.3.4 with

I am creating a blogging application using PHP5/SQLite3 . To insert a post in database I am executing query written below.
$db=connectToDatabase();
$tempcontent=$db->escapeString($tempcontent);
$query = "INSERT INTO posts VALUES (null,$temptitle, $tempcontent, $tempcategory, $tempauthor)";
$db->query($query);
$db=disconnectToDatabase();
I am having problem when the text input contain ' or " . when there is ', the query is not getting executed at all . If ' is not there then " is displayed with escape (\") in browser .
Sorry, I forgot to mention :
connectTodatabase() function is providing very general way to connect to database. as :
try {
$db1 = new PDO("sqlite:blog.db");
}catch( PDOException $exception ){
die($exception->getMessage());
}
return $db1;
Just post the whole class because we are not mind readers or psychics here.
The probably problem is you are not escaping data you include in your query.
Use either mysql_real_escape_string():
http://php.net/manual/en/function.mysql-real-escape-string.php
Or PDO prepared statements:
http://www.php.net/manual/en/pdo.prepare.php

Categories