I'm writing a function which will drop a table if it already exists. It will ask the user what they'd like to call the table, take that response and put it into a php variable. I want to make a customized drop statement then for sql so that there are no errors with sql. Here's what I have.
$table = $_POST["tablename"]; //gets table name from html page
drop_table($db, $table); //call function
function drop_table($db,$table){
$drop = "DROP TABLE IF EXISTS .$table. "; //this is the part I can't figure out. How do I add in the table name to the statement,
$q = mysqli_query($db, $drop); //since the sql statement has to be in quotes?
}
Thanks!
P.Ss This is an internal system for analyses only. No worries with dropping tables if just my colleagues and I are using it
Your problem here is a syntax error by attempting to concatenate in $table with dots. Remove those.
$drop = "DROP TABLE IF EXISTS $table ";
But the much much larger problem is that you are permitting end users to drop any table in your database, since you have not filtered the input in any way.
You need to be sure that your users are only dropping tables in the currently selected database, which means at the very least, not permitting . inside $table to prevent things like $table = 'information_schema.user'
if (strpos($table, '.') !== FALSE) {
// don't allow the action!
}
Another step to take would be to verify that the value of $table exists in information_schema.TABLES and belongs to the correct current database before executing the DROP statement.
// If this returns 1, the table exists in the correct database and can be dropped.
// note that $table is escaped here. I didn't fill in the mysqli_query() but obviously
// this is to be executed. It would be even better with a MySQLi prepared statement
"SELECT 1
FROM information_schema.TABLES
WHERE
TABLE_SCHEMA='the_allowed_database'
AND TABLE_NAME='" . mysqli_real_escape_string($db, $table) . "'"`
After passing this check, you would do well to specify a prefix to tables which are flexible in the environment and are therefore permissible to delete, so that a user could not delete every table in the active database. For example, only permit deletion of tables with the prefix usertable_.
if (strpos($table, 'usertable_') !== 0) {
// don't permit deletion
}
This is a very difficult design to secure, and I would recommend you step back and rethink the strategy here. You need to be extremely careful when allowing users to drop tables based on form input.
Do you mean:
$drop = "DROP TABLE IF EXISTS " . $table;
I really, really hope you've thought through the consequences of someone being able to drop tables from your database by entering the right name in the URL.
Related
I am trying to use PHP to run consecutive MYSQL statements as shown in the code snippet below (which just copies one row to another and renames the id via a tmp table).
I am getting a repeated syntax error message. I've tried numerous iterations. And the code looks like code I've researched in the PHP Manual and other myql questions on SO (which do not include the php dimension).
Can anyone shine a light on why my php syntax is incorrect?
include("databaseconnect.php");// This obviously works. Used a zillion time
$sql ="CREATE TEMPORARY TABLE tmp SELECT * FROM event_categoriesBU WHERE id
= 1;";
$sql.="UPDATE tmp SET id=100 WHERE id = 1;";
$sql.="INSERT INTO event_categoriesBU SELECT * FROM tmp WHERE id = 100;";
if ($conn->query($sql) === TRUE)
{
echo "Table row copied successfully. Do something with it";
}
else
{
echo "Error creating table: " . $conn->error;
//close connection etc
}
PHP Message back:
Error creating table: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'UPDATE tmp SET id=100 WHERE id = 1INSERT INTO event_categoriesBU SELECT * FROM t' at line 1
Don't run a bunch of queries at once. Usually the success of one depends on all the other operations having been performed correctly, so you can't just bulldozer along as if nothing's gone wrong when there's a problem.
You can do it like this:
$queries = [
"CREATE TEMPORARY TABLE tmp SELECT * FROM event_categoriesBU WHERE id = 1",
"UPDATE tmp SET id=100 WHERE id = 1",
"INSERT INTO event_categoriesBU SELECT * FROM tmp WHERE id = 100"
];
foreach ($query as $query) {
$stmt = $conn->prepare($query);
$stmt->execute();
}
Don't forget to enable exceptions so that any query failures will stop your process instead of the thing running out of control.
The reason you don't use multi_query is because that function does not support placeholder values. Should you need to introduce user data of some kind in this query you need to use bind_param in order to do it safely. Without placeholder values you're exposed to SQL injection bugs, and a single one of those is enough to make your entire application vulnerable.
It's worth noting that PDO is a lot more flexible and adaptable than mysqli so if you're not too heavily invested in mysqli, it's worth considering a switch.
You can't execute more than one SQL statement with query($sql), you must use multi_query($sql). Your script will then become something like this:
if ($conn->multi_query($sql) === TRUE)
{
echo "Table row copied successfully. Do something with it";
}
See the documentation for a complete example.
However, note that, as other users have well explained in the comments, executing multiple queries at the same time with this method can be potentially dangerous and should be avoided when possible, especially when handling user inputs.
You have better control of the execution flow if you execute one query at a time and check its result, rather than grouping all of them in a single call.
I am trying to use PHP to run consecutive MYSQL statements as shown in the code snippet below (which just copies one row to another and renames the id via a tmp table).
I am getting a repeated syntax error message. I've tried numerous iterations. And the code looks like code I've researched in the PHP Manual and other myql questions on SO (which do not include the php dimension).
Can anyone shine a light on why my php syntax is incorrect?
include("databaseconnect.php");// This obviously works. Used a zillion time
$sql ="CREATE TEMPORARY TABLE tmp SELECT * FROM event_categoriesBU WHERE id
= 1;";
$sql.="UPDATE tmp SET id=100 WHERE id = 1;";
$sql.="INSERT INTO event_categoriesBU SELECT * FROM tmp WHERE id = 100;";
if ($conn->query($sql) === TRUE)
{
echo "Table row copied successfully. Do something with it";
}
else
{
echo "Error creating table: " . $conn->error;
//close connection etc
}
PHP Message back:
Error creating table: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'UPDATE tmp SET id=100 WHERE id = 1INSERT INTO event_categoriesBU SELECT * FROM t' at line 1
Don't run a bunch of queries at once. Usually the success of one depends on all the other operations having been performed correctly, so you can't just bulldozer along as if nothing's gone wrong when there's a problem.
You can do it like this:
$queries = [
"CREATE TEMPORARY TABLE tmp SELECT * FROM event_categoriesBU WHERE id = 1",
"UPDATE tmp SET id=100 WHERE id = 1",
"INSERT INTO event_categoriesBU SELECT * FROM tmp WHERE id = 100"
];
foreach ($query as $query) {
$stmt = $conn->prepare($query);
$stmt->execute();
}
Don't forget to enable exceptions so that any query failures will stop your process instead of the thing running out of control.
The reason you don't use multi_query is because that function does not support placeholder values. Should you need to introduce user data of some kind in this query you need to use bind_param in order to do it safely. Without placeholder values you're exposed to SQL injection bugs, and a single one of those is enough to make your entire application vulnerable.
It's worth noting that PDO is a lot more flexible and adaptable than mysqli so if you're not too heavily invested in mysqli, it's worth considering a switch.
You can't execute more than one SQL statement with query($sql), you must use multi_query($sql). Your script will then become something like this:
if ($conn->multi_query($sql) === TRUE)
{
echo "Table row copied successfully. Do something with it";
}
See the documentation for a complete example.
However, note that, as other users have well explained in the comments, executing multiple queries at the same time with this method can be potentially dangerous and should be avoided when possible, especially when handling user inputs.
You have better control of the execution flow if you execute one query at a time and check its result, rather than grouping all of them in a single call.
I am using a PDO prepare statement to select the result.
My index.php file is:
include('operations.php');
$userprofileobj = new operations();
if(isset($_SESSION['user_email']))
{
$results = $userprofileobj->verify_user('account', $_SESSION['user_email'])->fetch(PDO::FETCH_ASSOC);
echo $results['username'];
}
My operations.php file is:
<?php
include('userclass.php');
class operations extends userclass
{
public function verify_user($table_name, $user_email)
{
$stmt = $this->con->prepare("select * from " . $table_name . " where username = :user_email");
$stmt->execute([
':user_email' => $user_email,
]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
I am trying to match email and the result should be fetched in index.php. But I got an error:
Fatal error: Call to a member function fetchColumn() on boolean in operations.php on line 84
Your result variable (besides being overwritten) is not what you think it is. It's a PDO statement.
Try this instead:
$stmt = $this->con->prepare("select * from ".$table_name." where username = :user_email");
$stmt->execute([
':user_email' => $user_email,
]);
if (false !== ($row = $stmt->fetchColumn()))
{
return $row;
}
However, this will only return the first column of the first row. Instead you probably want:
return $stmt->fetchAll(PDO::FETCH_ASSOC);
I changed $result to $stmt, because it's not a result. It's a Statement Object.
Original issues
In your original code (see below) you are overwriting it with the return from execute which is a Boolean.
// Old code (don't use this)
$result = $result->execute([
':user_email' => $user_email,
]);
//$result = TRUE|FALSE
if ($result->fetchColumn() !== false)
{
return $result;
}
And then you try to call a method of the Boolean, which, well, won't work. But the problems are deeper than just that. Let’s assume you don’t overwrite it.
// Old code (don't use this)
$result->execute([
':user_email' => $user_email,
]);
//$result = PDOStatment object.
if ($result->fetchColumn() !== false)
{
return $result;
}
Now the result is still your PDOStatement, which is good, but as I said, you're not saving the fetched data. This time you return the PDOStatement object. Which is not what you want.
Then further, as I stated earlier, if you do save it and return it, it's still probably not what you are after. Because fetchColumn() accesses only one row at a time and only one column.
But I have no way to know what you want. Maybe that is what you want? In that case your query is less than ideal. Maybe you just want to see if a user exists with a given email? In that case I would use this query.
$result = $this->con->prepare("SELECT id FROM ".$table_name." WHERE username = :user_email");
$result->execute([
':user_email' => $user_email,
]);
// There isn't any need to check it (see below)
return $result->fetchColumn();
PDOStatement::fetchColumn() returns a single column from the next row of a result set or FALSE if there are no more rows.
I can also tell by your stuff, that your database setup is probably wrong. That is, if you really need a dynamic table $table. The reason I can say this is you should not be duplicating any user data (or any data really, this is called normalization), and having the table dynamic implies that the email may exist separately in two (or more) tables.
If that is not the case then don't make it dynamic. Why is this an issue? Well, think what happens if a user changes their "email" now, because it exists in two tables (potentially). You'll have to update it in both places. But it's worse than that as it overcomplicates anything you do with the emails.
Without seeing the schema for your tables, I can only speculate on that, and how to fix it. But generally you would use a foreign key and associate the user record to that. Then using a JOIN you can access the email without duplication.
That said, there are a few cases where this may be acceptable, but I have no way to know if that is true in your case. Just a quick example would be a separate table for users and administrators (basically two-user systems).
Security
The last thing is be very very careful with this:
"select * from ".$table_name." where username = :user_email"
The issue here is it's open to SQL injection. Anytime you concatenate a variable into SQL, you open the door for injection attacks. Well, you may say I'm passing in a canned string account. Which is OK, but there is no validation at the point of failure. So maybe in five months you reuse this code and forget that you never validated the table name. Maybe not, but the fact remains that if user data could get into that argument, you have no protection whatsoever against injection on the table name. The possibility for it is there.
Something as simple as this:
public function verify_user($table_name,$user_email){
$allowed = ['account','users'];
if(!in_array($table_name, $allowed )) throw new Exception('Invalid table name');
}
See now it's virtually impossible to inject something into the table name. Further because it's in the same method (at the point of failure), you will never lose that protection. It's very easy to be in a rush latter and copy a piece of code change a few things and .... well you know.
Just my two cents.
UPDATE
So even if the chance is small that user input could get into $table you can not guarantee it 100%, because from within verify_user you have no way to know where the data came from, but you are trusting on faith that it's not user input. When it comes to SQL injection, you can't say well this is OK, because I will only call this method a certain way. It has to be 100% injection proof or as close as is humanly possible.
Why is this important, you ask? Imagine this.
$userprofileobj->verify_user('account --',$_SESSION['user_email']);
Those two little --s are like // in PHP, but for SQL, they comment out the rest of the line in SQL so your query becomes this.
"select * from account -- where username = :user_email"
Or (essentially)
"select * from account"
So we just modified what your query does. Now thankfully it's not really possible to run two queries at once in PDO. You can do it (with some work) in MySqli. But because of security reasons, they have mostly done away with this ability. The reason is this (or worse like creating database users).
$userprofileobj->verify_user('account; DROP TABLE account --',$_SESSION['user_email']);
Which, if you could do two queries, would do this:
SELECT * FROM account
DROP TABLE account
In any case this is dangerous stuff and is to be avoided at all costs. Being too lazy (and I am a lazy programmer, so don't take that wrong) to put a table name in is not an answer you want to give after your database has been compromised and you have exposed user data to a third party. It's just not an option.
All this does:
if(!in_array($table_name, ['table1', 'table2', ...])) throw new Exception('Invalid table name');
Is throw an error if "needle" $table_name is not in "haystack" - a canned list of table names. So if I do this (using our example above):
if(!in_array('account --', ['table1', 'table2', ...])) throw new Exception('Invalid table name');
It won't find account -- in our list of table1 and table2 and will blow up, thus preventing the injection attack.
$result = $this->con->prepare("select * from ".$table_name." where username = :user_email");
$result = $result->execute(..)
You're overwriting $result. The $this->con->prepare(..) sets $result to be a PDO Statement (See http://php.net/manual/en/pdo.prepare.php). A PDO Statement object has the method ->execute(...), which returns a boolean (true/false) as well as the ->fetchColumn() method. When you're doing your execute() you're overwriting your PDO Statement Object with the result of the execute() which is only a boolean and has no methods in it at all. As such that's why $result has no ->fetchColumn() method.
I am trying to use PHP to run consecutive MYSQL statements as shown in the code snippet below (which just copies one row to another and renames the id via a tmp table).
I am getting a repeated syntax error message. I've tried numerous iterations. And the code looks like code I've researched in the PHP Manual and other myql questions on SO (which do not include the php dimension).
Can anyone shine a light on why my php syntax is incorrect?
include("databaseconnect.php");// This obviously works. Used a zillion time
$sql ="CREATE TEMPORARY TABLE tmp SELECT * FROM event_categoriesBU WHERE id
= 1;";
$sql.="UPDATE tmp SET id=100 WHERE id = 1;";
$sql.="INSERT INTO event_categoriesBU SELECT * FROM tmp WHERE id = 100;";
if ($conn->query($sql) === TRUE)
{
echo "Table row copied successfully. Do something with it";
}
else
{
echo "Error creating table: " . $conn->error;
//close connection etc
}
PHP Message back:
Error creating table: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'UPDATE tmp SET id=100 WHERE id = 1INSERT INTO event_categoriesBU SELECT * FROM t' at line 1
Don't run a bunch of queries at once. Usually the success of one depends on all the other operations having been performed correctly, so you can't just bulldozer along as if nothing's gone wrong when there's a problem.
You can do it like this:
$queries = [
"CREATE TEMPORARY TABLE tmp SELECT * FROM event_categoriesBU WHERE id = 1",
"UPDATE tmp SET id=100 WHERE id = 1",
"INSERT INTO event_categoriesBU SELECT * FROM tmp WHERE id = 100"
];
foreach ($query as $query) {
$stmt = $conn->prepare($query);
$stmt->execute();
}
Don't forget to enable exceptions so that any query failures will stop your process instead of the thing running out of control.
The reason you don't use multi_query is because that function does not support placeholder values. Should you need to introduce user data of some kind in this query you need to use bind_param in order to do it safely. Without placeholder values you're exposed to SQL injection bugs, and a single one of those is enough to make your entire application vulnerable.
It's worth noting that PDO is a lot more flexible and adaptable than mysqli so if you're not too heavily invested in mysqli, it's worth considering a switch.
You can't execute more than one SQL statement with query($sql), you must use multi_query($sql). Your script will then become something like this:
if ($conn->multi_query($sql) === TRUE)
{
echo "Table row copied successfully. Do something with it";
}
See the documentation for a complete example.
However, note that, as other users have well explained in the comments, executing multiple queries at the same time with this method can be potentially dangerous and should be avoided when possible, especially when handling user inputs.
You have better control of the execution flow if you execute one query at a time and check its result, rather than grouping all of them in a single call.
Fairly new to php, please forgive me if my code is not eloquent.
I have a table/form with multiple rows/cells. Each cell is a drop down box. I've built this to UPDATE existing mysql-rows.
I'm having trouble figuring out how to press the submit button on the form and UPDATE all the mysql-rows. I've successfully updated mysql fields individually, but this is my first time trying to update multiple sql-rows at the same time.
In my front-end (let me know if you need me to post it), I run the below code to create a new increment variable for each -input type="submit" name="blah"...-
$namefieldname = 'name'.$i;
$positionfieldname = 'position'.$i;
$hoursdropdown = 'hours'.$i;
$overtimedropdown = 'overtime'.$i;
So name="blah" I write as name="'.$namefieldname.'" for example. I have a different form that uses this same $i concept to INSERT information and it works fine.
Below is the code that I'm not getting to work. This is what the -form action- goes to. The page processes without errors - but doesn't update mysql. I've double checked all names and everything is spelled correctly.
For now I've cut down the columns to try to get just this one column to update mysql.
<?php
include_once('toolbox.php');
//mysql connect info
$projectid = $_POST['projectid'];
$transcriberid = $_POST['transcriberid'];
for($i=0; $i<$staffcount; $i++) {
$positionfieldname = 'position'.$i;
//is a drop down of 5 choices
$thisposition = $_POST[$positionfieldname];
mysql_query("UPDATE transcribers SET transcriber_position='$thisposition' WHERE transcriber_id= '$transcriberid'");
}
header('Location: ./);
?>
First, you shouldn't use the mysql extension in new programs any more, because it is deprecated since version 5.5, and it was removed in PHP 7.0.0. Instead, use MySQLi or PDO_MySQL extension.
Second, it is important to handle user given data (e.g. from $_POST) appropriately before using them to concatenate sql statements. If the user gives a quote sign, he/she could alter your statement such as terminating it and update some other data in some other tables. If you use PDO_MySQL, function PDO::quote() can help you to prevent this:
$thisposition = $mysql_connection->quote($_POST[$positionfieldname]);
Even better is to use a prepared statement. It contains only placeholders where user provided data is used and the actual data is bound to this placeholders.
$stmt = $mysql_connection->prepare("UPDATE transcribers SET transcriber_position=:value");
$stmt->bindValue(':value', $transcriber_position, PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
For updating multiple columns in a table, you can just list them, separated with comma, here an example:
mysql_query("UPDATE transcribers SET transcriber_position='$thisposition', transcriber_value=5 WHERE transcriber_id= '$transcriberid'");