So this is my first attempt at converting some previously used code to prepared statements in order to prevent SQL injections. I'm using mysqli procedural, as follows,
Old query:
mysqli_query($con,"UPDATE ulogs SET invalid_hits = invalid_hits + 1 WHERE user_id = $link AND date = '$date'");
if(mysqli_affected_rows($con) < 1) {
mysqli_query($con,"INSERT INTO ulogs(user_id,date,invalid_hits,unique_hits,non_unique_hits,earned,ref_earned,bonus) VALUES ('$link','$date',1,0,0,0,0,0)");
}
Prepared query:
$q1 = "UPDATE ulogs SET invalid_hits = invalid_hits + 1 WHERE user_id =? AND date =?";
$qa1 = "INSERT INTO ulogs (user_id,date,invalid_hits,unique_hits,non_unique_hits,earned,ref_earned,bonus) VALUES (?,?,1,0,0,0,0,0)";
if ($stmt = mysqli_prepare($con, $q1)) {
mysqli_stmt_bind_param($stmt,"is", $link, $date);
mysqli_stmt_execute($stmt);
$ucheck = mysqli_stmt_affected_rows($stmt);
mysqli_stmt_close($stmt);
}
if ($ucheck < 1) {
if ($stmt = mysqli_prepare($con, $qa1)) {
mysqli_stmt_bind_param($stmt,"is", $link, $date);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
}
}
mysqli_close($con);
exit();
And many more which get triggered under different circumstances but basically the same UPDATE first, then INSERT if nothing was updated.
Apparently it worked as I based my queries on examples from php.net. Apparently...
Because, after about 1 hour later, Apache was returning 500 server errors with php timing out while waiting for data from the db. Whereas with non-prepared queries such thing never happened.
So question is: have I done something wrong here? $stmt was being closed every time, along with $con so I don't know what may have caused the db to hang. Note that the server load did not went up either. It was just php-fpm reaching max clients due processes waiting for the db.
While I still haven't found WHY my original code for prepared statements ended up crashing the DB few hours later, I did eventually find a SIMPLE and WORKING out-of-the-box alternative by using a popular class made especially for this purpose: https://github.com/colshrapnel/safemysql
This class is pretty straightforward so even a newbie like myself was able to implement it using the examples found on their Github page.
Special thanks goes out to #YourCommonSense for pointing me in the right direction.
Related
The PHP code I have inserts the HTML form data from the previous page into the database and in the same SQL statement return the PostID back from the inserted data. The PostID column is AUTO_INCREMENTING. I have been researching this problem for a week or two now and have found no significant solutions.
<?php
include("dbconnect.php");
mysql_select_db("astral_database", $con);
session_start();
$username = $_SESSION['username'];
$forumtext = $_POST["forumtext"];
$forumsubject = $_POST["forumsubject"];
$postquery = 'INSERT INTO Forums (Creator, Subject, Content) VALUES ("$username", "$forumsubject", "$forumtext"); SELECT LAST_INSERT_ID()';
$result = mysql_query($postquery, $con);
if (!$con) {
echo "<b>If you are seeing this, please send the information below to astraldevgroup#gmail.com</b><br>Error (331: dbconnect experienced fatal errors while attempting to connect)";
die();
}
if ($username == null) {
echo "<b>If you are seeing this, please send the information below to astraldevgroup#gmail.com</b><br>Error (332: Username was not specified while attempting to send request)";
die();
}
if ($result != null) {
echo "last id: " . $result;
$fhandle = fopen("recentposts.txt", "r+");
$contents = file_get_contents("recentposts.txt");
fwrite($fhandle, json_encode(array("postid" => $result, "creator" => $username, "subject" => $forumsubject, "activity" => time())) . "\n" . $contents);
fclose($fhandle);
mysql_close($con);
header("location: http://astraldevgroup.com/forums");
die();
} else {
die("<b>If you are seeing this, please send the information below to astraldevgroup#gmail.com</b><br>Error (330: Unhandled exception occured while posting forum to website.)<br>");
echo mysql_error();
}
mysql_close($con);
?>
First off, the mysql_query doesn't return anything from the SELECT statement. I haven't found anything that will properly run both the SELECT statement and the INSERT statement in the same query. If I try running them in two different statements, it still doesn't return anything. I tried running the following statement in the SQL console and it ran perfectly fine without errors.
INSERT INTO Forums (Creator, Subject, Content) VALUES ("Admin", "Test forum 15", "This is a forum that should give me the post id."); SELECT LAST_INSERT_ID();
The mysql_query function does not run multiple statements
Reference: http://php.net/manual/en/function.mysql-query.php
mysql_query() sends a unique query (multiple queries are not supported) to the currently active database on the server ...
That's one reason your call to mysql_query isn't returning a resultset.
The most obvious workaround is to not try to run the SELECT in the same query. You could use a call to the mysql_insert_id instead.
Reference: PHP: mysql_insert_id http://php.net/manual/en/function.mysql-insert-id.php
Answers to some of questions you didn't ask:
Yes, your example code is vulnerable to SQL Injection.
Yes, the mysql_ interface has been deprecated for a long time.
Yes, you should being using either PDO or mysqli interfaces instead of the deprecated mysql_ functions.
FOLLOWUP
Re-visiting my answer, looking again at the question, and the example code.
I previously indicated that the code was vulnerable to SQL Injection, because potentially unsafe values are included in the SQL text. And that's what it looked like on a quick review.
But looking at it again, that isn't strictly true, because variable substitution isn't really happening, because the string literal is enclosed in single quotes. Consider what the output from:
$foo = "bar";
echo '$foo';
echo '"$foo"';
Then consider what is assigned to $postquery by this line of code:
$postquery = 'INSERT ... VALUES ("$username", "$forumsubject", "$forumtext")';
Fixing that so that $username is considered to be a reference to a variable, rather than literal characters (to get the value assigned to $username variable incorporated into the SQL text) that would introduce the SQL Injection vulnerability.
Prepared statements with bind placeholders are really not that hard.
$result will never be null. It's either a result handle, or a boolean false. Since you're testing for the wrong value, you'll never see the false that mysql_query() returned to tell you that the query failed.
As others have pointed out, you can NOT issue multiple queries in a single query() call - it's a cheap basic defense against one form of SQL injection attacks in the PHP mysql driver. However, the rest of your code IS vulnerable other forms of injection attacks, so... better start reading: http://bobby-tables.com
Plus, on the logic side, why are you testing for a null username AFTER you try to insert that very same username into the DB? You should be testing/validating those values BEFORE you run the query.
the issue
So I bumped into something curious this morning when I was updating my database. I executed a collation change in my database, changing it from latin1 to uft8. However, my queries failed suddenly on my table. After some debugging, (rebuilding the table even with its original setup, but to no such avail) and receiving 500 internal errors, i realized it had to do with the prepared statement, so i tore it out, and replaced it with a regular mysqli_query, and it surprisingly worked. So now I am wondering, was my prepared statement wrong the whole time, or did it fail because of a change in the database.
the setup
This is the current table set up. I changed it back to latin (and its innoDB) yet it didnt gave me the results back i wanted when i changed everything back to the original settings (which is how it is now)
the code
the original code was this and it worked fine until the change
require_once '../db/dbControl.php';
$id = mysqli_real_escape_string($con,$_GET["id"]);
$sql = "SELECT *
FROM project
WHERE project.ProjectId = ? ";
$stmt1 = mysqli_prepare($con, $sql);
mysqli_stmt_bind_param($stmt1,'i',$id);
mysqli_stmt_execute($stmt1);
mysqli_stmt_bind_result($stmt1,$ProjectId,$ProjectTitel,$ProjectOmschrijving, $ProjectOmschrijving,$ProjectDatum,$ProjectClient,$ProjectUrl);
while (mysqli_stmt_fetch($stmt1)){
the code itself of the page
}
So right now I am just using a regular mysqli_query in order to make it work
require_once '../db/dbControl.php';
id = mysqli_real_escape_string($con,$_GET["id"]);
$sql = "SELECT *
FROM project
WHERE project.ProjectId = '". $id ."'";
$result = mysqli_query($con,$sql);
while($rows=mysqli_fetch_array($result)){
$ProjectId = $rows['ProjectId'];
$ProjectTitel = $rows['ProjectTitel'];
$ProjectExpertise = $rows['ProjectExpertise'];
$ProjectOmschrijving = $rows['ProjectOmschrijving'];
$ProjectDatum = $rows['ProjectDatum'];
$ProjectClient = $rows['ProjectClient'];
$ProjectUrl = $rows['ProjectUrl'];
the code itself of the page
}
I am a little bit confused (maybe i overlooked something here because to focussed on a little bit of code) but it only happens on the project table. I checked it against code that involves readouts, and they work all fine with prepped statements.
Hope anyone can spot what I couldn't
I wont be able to tell you what happened but here are two thought's.
The prepared statement execution consists of two stages: prepare and
execute. At the prepare stage a statement template is sent to the
database server. The server performs a syntax check and initializes
server internal resources for later use.
A prepared statement can be executed repeatedly. Upon every execution
the current value of the bound variable is evaluated and sent to the
server. The statement is not parsed again. The statement template is
not transferred to the server again.
Maybe this is what happened, it could be that the prepared statements never reseted after you changed to utf8.
Every prepared statement occupies server resources. Statements should be closed explicitly immediately after use. If not done explicitly, the statement will be closed when the statement handle is freed by PHP.
Using a prepared statement is not always the most efficient way of
executing a statement. A prepared statement executed only once causes
more client-server round-trips than a non-prepared statement. This is
why the SELECT is not run as a prepared statement above.
Maybe the server memory (is/was) full?
Tekst from:
http://php.net/manual/en/mysqli.quickstart.prepared-statements.php
I'm using a forum type system for users to ask questions or say whats on their minds and I'm having a problem related to updating database information. I have no idea what's wrong here but I do know what's happening.
Some posts cannot be edited. Everything will happen as usual but will not update the database. Some posts have the wrong body but the correct title.
It doesn't make sense and I'll do some tests to check if the mysql is working.. but until then, any thoughts?
UPDATE:
This query is passing... but the database isn't updating for this particular row. This one only...
$new_body = $_POST['new_body'];
$old_body = $_POST['old_body'];
mysql_query("UPDATE questions SET body='".htmlspecialchars($new_body, ENT_QUOTES)."' WHERE body='".htmlspecialchars($old_body, ENT_QUOTES)."'") or die(mysql_error());
Also, if someone could enlighten me on SQL Injections and how to prevent them, I'd greatly appreciate it.
The columns are id, pin, locked, body, date, numberofcomments (i know I can just use php to read the amount of comments but I did this prior to learning that) and views.
UPDATE: Works now. Replaced the WHERE body to WHERE id. Stupid mistake. I could still use some sql injection enlightening though!
As I mentioned in comments first of all use a primary key in your WHERE clause to target specific record in your table instead of using body column. That being said your update statement should look something like this
UPDATE questions SET body = ? WHERE id = ?
Now to prevent sql injections use switch to mysqli_* or PDO extension and use prepared statements instead of interpolating query strings.
Your code using prepared statements with mysqli_* might look like
$id = $_POST['id'];
$new_body = $_POST['new_body'];
$old_body = $_POST['old_body'];
//Do validation, sanitation, and encoding if necessary here before you put into database
...
$db = new mysqli('localhost', 'user', 'password', 'dbname');
if ($db->connect_errno) {
die('Connection failed: %s\n' . $db->connect_error); //TODO better error handling
}
$sql = 'UPDATE questions SET body = ? WHERE id = ?';
$stmt = $db->prepare($sql);
if (!$stmt) {
die('Can\'t prepare: ' . $db->error); //TODO better error handling
}
$stmt->bind_param('si', $new_body, $id);
$stmt->execute();
$stmt->close();
$db-close();
Further reading:
How can I prevent SQL injection in PHP? It's the absolute must read
Please use Mysqli or PDO. Mysql_* is deprecated and insecure.
Have you tried checking if the post exists? As it seems a problem that the post doesn't exist or it's not finding it.
Do you get any mysql_error's or any output from mysql?
Also have you tried updating using phpmyadmin - Seeing if it outputs any errors there?
$new_body = $_POST['new_body'];
$old_body = $_POST['old_body'];
mysql_query("UPDATE questions SET body='".htmlspecialchars($new_body, ENT_QUOTES)."' WHERE body='".htmlspecialchars($old_body, ENT_QUOTES)."'") or die(mysql_error());
I haven't used mysql_ in a while, in favour of PDO, so this syntax may be incorrect. But you could try this:*
$new_body = htmlentities($_POST['new_body']);
$old_body = htmlentities($_POST['old_body']);
$sql1=mysql_query("SELECT * FROM questions WHERE body='$old_body'") or die(mysql_error());
if(mysql_num_rows($sql1)>"0")
{
$res=mysql_query("UPDATE questions SET body='$new_body'") or die(mysql_error());
echo 'Updated';
}
else
{
//Insert.
}
Well I've did do my research and I just can't seem to figure this out. So long story short, I'm using something like this:
btw, "(WebsiteInfo)" is just to sensor out my website/database information.
$SQL = new PDO('mysql:dbname=(WebsiteInfo);host=(WebsiteInfo);charset=utf8', '(WebsiteInfo)', '(WebsiteInfo)');
$SQL -> setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$SQL -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Now, that's just currently just to start the database connect up. Which btw is mostly just a copy/paste code I found on this website too which is to prevent MySQL injection. Actually from this link How can I prevent SQL injection in PHP?. If there's anything wrong with it, I wouldn't mind advice or tips. As long as if it makes sense because I just started using databases probably not even a week ago so everything is new to me. Now there's this:
$Exicu = $SQL -> prepare("SELECT `tags` FROM `users` WHERE `user_id` = :a ") -> execute(array( ':a' => 16));
$Exicu is just there, because I have been trying to get the results from the query (if I said that right). Also the 16 is the users ID, but this will change often so that's why 16 isn't just tossed in the prepare statement. I've tried a lot of things but none of them worked. It either didn't work or made the PHP crash.
But anyway, things I already tried for $Exicu is $Exicu->rowCount(), $Exicu->bindParam, a while loop with $row = $Exicu->fetch(), $SQL->query($Exicu)->fetchAll();, a foreach loop with ($Exicu->fetch(PDO::FETCH_ASSOC) as $row), $Exicu->get_result(), echo PDO::query($Exicu);, echo mysql_result($Exicu), and just echo $Exicu. Yes I know, that looks pretty sloppy.
But none of these seemed to work to just show me the tags from the database of the specific user. So that's pretty much what I need help with. There's no problem when I use something like this echo mysql_result( mysql_query("SELECT (etc,etc)") ) but that doesn't have protection from MySQL injections.
I do my PDO queries like this:
$user_id = 16;
$query = $SQL->prepare('SELECT tags FROM users WHERE user_id = :uid');
$query->bindValue(':uid', $user_id, PDO::PARAM_INT);
$query->execute();
while ($row = $query->fetch(PDO::FETCH_ASSOC))
{
echo $row['tags'];
}
This will select data from the database, bind values in it safely and then we echo out the results.
The loop is needed to iterate through every result returned from the query. You can skip the looping part and create a variable like in the while statement, and use $row as an array with your results.
There is a thing called user defined function.
I am wondering why noone on this site ever using them.
For some reason everyone is ready to make a mile long single line of chained methods, instead of clean and concise function call:
$user_id = 16;
$tags = getone('SELECT tags FROM users WHERE user_id = ?',array($user_id));
there are many ways to create such a function. A quick and dirty one
function getone($sql, $data) {
global $SQL;
$stmt = $SQL->prepare($sql);
$stmt->execute($data);
return reset($stmt->fetch());
}
but of course it would be better to make set of functions and put them in a class
I had a quick question with regard to prepared statements within PHP. I was previously using the mysql_query function to manipulate database data, but was told that for security issues I should consider using prepared statements. I have made the transition, but I have a few questions on how to detect whether a query has failed.
Below I have a piece of example code. The $con variable is a connection which is specific depending on the query I am attempting, in this case the connection would be to my database through an account with only select permissions.
$stmt = $con->stmt_init();
$stmt->prepare("SELECT COUNT(*) FROM users WHERE username=?");
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($user_count);
$stmt->fetch();
$stmt->close();
I was wondering how one can detect failure within any of these steps? The most simple solution I can imagine would be simply to wrap the code using a try/catch.. but I was wondering if there is a more sophisticated way of doing this.
Thanks for reading my question.
To expand on Jared's comment, you could do the following:
$stmt->execute();
if( !$stmt->errorCode() ){
// do something with results
}else{
// do something with the error
}
$stmt->close();