I've been troubleshooting this for two days and am at a loss.
I have this code where a SELECT query is run in one table and then if the token matches the one in the db it runs a INSERT query on a different table.
The SELECT query works just fine however, no matter which way I execute the INSERT query it always throws back a db is locked error.
I have tried executing the INSERT query with
exec()
query()
querySingle()
prepare() / bindParam() / execute()
Understanding that it could also have not finished the SELECT query I have also tried
setting the busyTimeout() time out to 5 seconds
Adding a sleep() in between the SELECT and INSERT queries
Closing and reopening the file
Using object oriented and closing and reopening the file in each function
Code
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
$db = new SQLite3('../grasscar_test.sqlite');
$token = $_POST['token'];
$res = $db->query("SELECT key FROM admin WHERE key = '$token'");
if ($res === FALSE){
echo '{"correct":"no"}';
}
while ($row = $res->fetchArray()){
$key = "{$row['key']}";
if ($key === $token){
//generate token
$cookieog = openssl_random_pseudo_bytes(32);
$cookie = bin2hex($cookieog);
//respond with token
echo '{"correct":"yes","token":"'.$cookie.'"}';
//get expiary time
$expiary = date("Y-m-d H:i:s", strtotime("+30 minutes"));
//add token and expiary date
$insert = $db->exec("INSERT INTO admin_cookie (cookie, expiary) VALUES ('$cookie', '$expiary')"); //This is the line throwing the error
print_r($insert);
}else{
echo '{"correct":"no"}';
}
}
$db->close();
?>
Warning
Your query is insecure & vulnterable to SQL Injection. See https://www.php.net/manual/en/sqlite3.prepare.php for how to do prepared statements or How can I prevent SQL injection in PHP?. $token needs to be bound, rather than directly put into the query string. Or you can escape the $token, but binding is much better IMO & ends up with cleaner code IMO.
Option 1
You could get the whole SELECT into an array, then explicitly close the open connection or cursor for the SELECT:
while ($row = $res->fetchArray()){
$resultsArray[] = $row;
}
$res->finalize();
foreach ($resultsArray as $row){
// your existing code
}
https://www.php.net/manual/en/sqlite3result.finalize.php
Option 2: Not Cryptographically secure random
You could alternatively put it all into a single query, if you don't need to print everything to screen, and you don't need cryptographically secure randomness. I couldn't find anything to produce crypto-randomness in sqlite (in a couple minutes of searching).
INSERT INTO admin_cookie (cookie, expiary)
SELECT hex(randomblob(32)), datetime('now', '+30 minutes')
https://sqlite.org/lang_datefunc.html
https://sqlite.org/lang_corefunc.html#randomblob
P.S.
You could try pdo. Pretty sure it supports sqlite, & you might like the interface better.
Instead of INSERTing in a loop, build a VALUES list, then execute a single INSERT statement. However, that's a slight pain when using bound paramaters (though entirely doable by incrementing the name of the placeholder & building a bind array as you build the VALUES list)
You probably DO need cryptographically secure randomness, so Option 2 is not ideal. I'm not a security expert by any means.
Related
I would like to call mysql stored procedure with two in parameters say from and to and print the data in table. I have used the following code:
try
{
$age_from = $_POST['from'];
$age_to = $_POST['to'];
$stmt = $db->prepare("CALL proc_report_filtered(:age_from,:age_to)");
$stmt->bindParam(':age_from',$age_from);
$stmt->bindParam(':age_to',$age_to);
$records = $stmt->execute();
$records->setFetchMode(PDO::FETCH_ASSOC);
}
catch (PDOException $e)
{
die("Some problem getting data from database !!!" . $e->getMessage());
}
Later I used while loop as before to print the table data.
while ( $report = $records->fetch() )
Stored routine proc_report_filtered is a simple query to fetch data between the age range as follows:
select * from employee where age in between 30 and 40;
where 30 is $age_from and 40 is $age_to passed to stored routine.
Unfortunately the code doesnt work. Please correct me where I am going wrong. I am new to using php PDO.
In general, there is nothing essentially special in calling stored procedures with PDO. The only issue may affect only following queries, but not the current one.
So, your problem is not related to stored procedures, but to the basic PDO syntax only: you are using wrong code to get the records. Here goes the proper one
$stmt->execute();
$records = $stmt->fetchAll(PDO::FETCH_ASSOC);
In case it is not the issue but a copy/paste problem, and to help further visitors, here is a short check-list for the PDO debugging:
Step 1. Run your query in a DB console to make sure it works by itself.
Step 2. Make sure that you can see ALL PHP errors.
error_reporting(E_ALL);
ini_set('display_errors', 1); // for the dev server only
Step 3. Make sure that your PDO throws exceptions, by adding this code
$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
Step 4. Run your query without prepared statements, using PDO::query(), with values written by hand:
$stmt = $db->query("CALL proc_report_filtered(30,40)");
Step 5. run with a prepared statement but in a simplified form, to eliminate all possible spelling issues:
$stmt = $db->prepare("CALL proc_report_filtered(?,?)");
$stmt->execute(array($_POST['from'],$_POST['to']));
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.
I'm running a loop to collect the data from an XML file, after the 2000+ lines are done there is some validation rules that run afterwards. If it is safe I'd like to store the MySQL data, in the before mentioned loops I am wanting to store all the MySQL queries to an array (that being almost 2000 queries) and run them in a loop after validation rules have finished.
Is this a bad idea? If so, what would be the best idea behind storing the query and running it later?
To give you an idea of what I'm talking about if you didn't understand:
foreach($xml->object as $control) {
// Stores relative xml data here
}
if(1=1) // Validation rules run
for(...) { // For Loop for my queries (2000 odd loop)
Mysql_Query(..)
}
To insert a lot of date into a MySQL database I would start a transaction (http://php.net/manual/en/mysqli.begin-transaction.php), and then create a prepared statement and just loop through all the items and validate them straight away and execute the prepared statement. Afterwards just mark the transaction as successful and end it. This is the fastest approach.
The use of prepared statements also prevents SQL Injection.
What you could do is use a prepared statement for a library that supports it (PDO or mysqli). In PDO:
$stmt = $pdo->prepare("INSERT INTO t1 VALUES (:f1, :f2, :f3)");
$stmt->bindParam(":f1", $f1);
$stmt->bindParam(":f2", $f2);
$stmt->bindParam(":f3", $f3);
foreach ($xml->obj as $control) {
// create $array
}
if ($valid) {
foreach ($array as $control) {
$f1 = $control['f1'];
$f2 = $control['f2'];
$f3 = $control['f3'];
$stmt->execute();
}
}
There's nothing wrong with this approach, except for the fact that it uses more RAM than would be used by progressively parsing and posting the XML data records to the database. If the server running the php belongs to you that's no problem. If you're on one of those cheap USD5 per month shared hosting plans, it's possible you'll run into memory or execution time limits.
You mentioned "safety." Are you concerned that you will have to back out all the database updates if any one of them fails? If that's the case, do two things:
Don't use the MyISAM access method for your table.
Use a transaction, and when you've posted all your data base changes, commit it.
This transactional approach is good, because if your posting process fails for any reason, you can roll back the transaction and get back to your starting point.
Here's the code i am using withing a for loop which run 10 times:
$query = "UPDATE fblikes SET likes = '$var[$i]' WHERE link = '$web[$i]'";
if(mysql_query($query))
{
echo $query;
}
else
{
echo mysql_error();
}
The code runs, I do get ok! printed 10 times but nothing happens in the table. I also checked the 2 arrays i.e. $var and $web, they contain the correct values.
The query looks okay to me. Here's what i got (one of the 10 outputs) : UPDATE fblikes SET likes = '5' WHERE link = 'xxxxxxx.com/xxxx/iet.php';
I don't know what the problem exactly is, and to figure out you should print the value of $query, and show us what you get. More, please tell us the value of mysql_affected_rows() after the call to mysql_query().
However, your code implements some wrong patterns.
First of all, you are not escaping $var[$i] and $web[$i] with two potential effects:
You can produce wrong queries
You don't sanitize the input to the database, thus exposing your application to security issues
Moreover, you perform several similar queries that differ only on the inputs provided.
The solution, for both issues, is the use of prepared statements that will give you more control, security and performance. Consider abandoning mysql_* functions and switching to mysqli_* or PDO, and read about prepared statements.
I'm trying to loop data from a api and then post these values to a MySQL db.
something like this:
$values = json_decode(file_get_contents("my-json-file"));
$SQL = new mysqli(SQL_HOST, SQL_USER, SQL_PASS, DB_NAME);
$SQL->autocommit(FALSE);
foreach($values As $item)
{
$query = "INSERT INTO my_table VALUES ('".$item->value1."', '".$item->value2.";)";
$SQL->query($query);
if(!$SQL->commit())
{
echo "ERROR ON INSERT: [" . $query . "]<hr/>";
}
}
$SQL->close();
Since the loop is too fast, the SQL can't catch up. (Yea!)
I would then need something like this:
foreach($values As $item)
{
/**** STOP/PAUSE LOOP ****/
$query = "INSERT INTO my_table VALUES ('".$item->value1."', '".$item->value2.";");
$SQL->query($query);
if($SQL->commit())
{
/**** START THE LOOP AGAIN ****/
}
else
{
echo "ERROR ON INSERT: [" . $query . "]<hr/>";
}
}
Or how should I do this the right way?
EDIT: It inserts random posts every time.
EDIT 2: This is just example code. It does escape and all that, and yes the semi colon is wrong here but since so many commented on it i will not change it. This was not the problem in the real case.
I tried to run it on another server and there it worked. The problem was fixed by restarting MAMP.
Firstly, your idea that the loop runs too fast for MySQL to keep up is completely totally wrong. The $SQL->query() call will wait for the MySQL to return a response before proceeding, so the loop won't run any faster than MySQL is responding.
Now onto the actual problem.... your query:
$query = "INSERT INTO my_table VALUES ('".$item->value1."', '".$item->value2.";)";
There's a semi-colon in there at the end, after value2 which is invalid. I guess you intended to type a quote mark there? The semi-colon will be causing all your queries to fail and throw errors.
This may be the cause of your problem but you haven't got any error checking in there, so you won't know. Add some error checking to your code after calling the query; even if the query is right, it's still possible to get errors, and your code should check for them. See the examples on this manual page: http://www.php.net/manual/en/mysqli-stmt.error.php
Finally, since you're using the mysqli API, it's worth mentioning that your code would be a lot better and probably more secure if you used prepared statements. See the examples in PHP manual here: http://www.php.net/manual/en/mysqli-stmt.bind-param.php
[EDIT]
Another possible reason your query is failing is that you're not escaping the input values. If any of the input values contains a quote character (or any other character that is illegal in SQL) then the query will fail. In addition, this problem makes your code vulnerable to a SQL injection hacking attack.
You need to escape your input using $SQL->real_escape_string() OR by changing your query to use prepared statements (as recommended above).
Your query is inside the loop, which means that the loop will wait until your query finished executing before it continue, php code is processed in order...
Has #phpalix said, PHP goes in order, and waits for the previous action to finish.
I think you SQL is wrong. Try replacing your INSERT with this:
$query = "INSERT INTO my_table VALUES ('".$item->value1."', '".$item->value2."');";
And don't forget to run at least mysql_real_escape_string for each variable, for security measures.
As many of the answers and comments say, it does not continue until the SQL is done. The problem was in my local apache/mysql server. It was fixed by restarting it. Yes, stupid post.