So, I'm working on some PHP and using prepared statements to access my database with mysqli, the following statement has a different outcome when prepared statements are used than if I
run the query using HeidiSQL, manually inserting the values
run the query without using prepared statements and manually entering the values
Prepared statement query:
$qStr = "SELECT id, head, descrip, img.dir
FROM CS_P, img,Q_Q_Items
WHERE CS_P.id = Q_Q_Items.I_ID
AND CS_P.id = img.Product_ID
AND img.priority=1
AND CS_P.id NOT IN(
SELECT ID2
FROM I_Cols, Q_Q_Items
WHERE ID2 = Q_Q_Items.I_ID
AND Q_Q_Items.Q_ID = ?
AND ID1 IN (".$madeMarks.")
)
AND Q_Q_Items.Q_ID = ?;";
Manually entered query:
SELECT id, head, descrip, img.dir
FROM CS_P, img,Q_Q_Items
WHERE CS_P.id = Q_Q_Items.I_ID
AND CS_P.id = img.Product_ID
AND img.priority=1
AND CS_P.id NOT IN(
SELECT ID2
FROM I_Cols, Q_Q_Items
WHERE ID2 = Q_Q_Items.I_ID
AND Q_Q_Items.Q_ID = 2
AND ID1 IN (35)
)
AND Q_Q_Items.Q_ID = 2;
The difference:
This statement is intended to omit rows where the value of ID_Cols.ID1 matches one of the values to be bound in place of $madeMarks. $mademarks is just a string of question marks that are inserted into the string so that I can insert numeric values that are posted to the PHP using AJAX and correspond to choices the user has previously made.
When I run this query on the SQL server directly it correctly omits rows where ID1 is in the values entered in place of $madeMarks in the prepared statement, with prepared statements these rows are always included and they shouldn't be as this means a previous choice made by the user clashes with this choice meaning it should not be displayed.
Is the way prepared statements are processed making this query impossible to be executed as intended?
Further information:
I have echoed out the values and they are correct integers, no strings or characters and all of the data is correct before it is bound to the prepared statement, I have used intval to make sure of this.
JSON encoded array of values bound to the query:
[2,35,2]
Bind type string:
'iii'
$madeMarks:
'?'
I have bound the values to the statement with 'i' binds as the database is expecting.
ID1 and ID2 are integers.
//////////////////////////////////////////////////////////////////////
It is now functional.
This isn't really an answer but is is a workaround, I just used preg_replace to replace any non-numeric chars with empty characters and then built the in statement with a for loop, inserting values from the choices array into the statement. There is no vulnerability to SQL injection as it is only numeric characters, this would be vulnerable to injection if it was a string that I had to compare.
Do not do this, it is hackish and bad, just use PDO.
Related
I am having with my query because Insert into value and select is not working, Is this the proper way of using it? thankyou!
This is my query line
$sql = "INSERT INTO `stud_class` (`stud_fullname`, `stud_uid`,`stud_code`, `stud_subject`, `stud_cname`,`stat`) VALUES ('$stud_full','$stud_uid',(SELECT subject_code,subsubject,class_Name FROM subject WHERE subject_code = '$subcode'),1)";
A subquery that's used as an expression is only allowed to return one value, not multiple columns.
You need to use the SELECT query as the source of all the values, not as an expression inside the VALUES list.
$sql = "INSERT INTO `stud_class` (`stud_fullname`, `stud_uid`,`stud_code`, `stud_subject`, `stud_cname`,`stat`)
SELECT '$stud_full','$stud_uid', subject_code,subsubject,class_Name, 1
FROM subject WHERE subject_code = '$subcode')";
You should also use a prepared statement rather than substituting variables into the SQL string. See How can I prevent SQL injection in PHP?
Can someone help me get the SELECT query (2). below to work?
This string used for both SELECT statements:
$seller_items = ('6','9','12','13','14','15','16','17','18','19','20','22','23','24','25','26','28','27','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50','51','53','54','55','57','58','59','60','62','63','64','65','61','67','56','69','70','74','73','75','78','80','76','72','95','94','101','102','71','103','2','104','4','81','21','10','11','3','79','5','8','7','97','93','96','98');
(1). This SELECT query is working fine:
if ($stmt = $mysqli->prepare("SELECT info FROM items WHERE item_id IN $seller_items AND active = ?")){
$stmt->bind_param("s",$active);
(2). This SELECT query is not working:
if ($stmt = $mysqli->prepare("SELECT info FROM items WHERE item_id IN ? AND active = ?")){
$stmt->bind_param("ss",$seller_items,$active);
I think placing the variable in the SELECT query itself may defeat the purpose of a prepared statement.
I can get the IN predicate to work just fine with a non-prepared statement. It's the prepared statement with which I am having the problem.
Thank you in advance.
As #Dai mentioned the IN cannot be parameterized with just one variable. Sure it can be done with a series of parameters but the number of them is fixed. The idea with prepare statements is that the insertion of values are expected the same position, the same number of parameters and the same kind.
If the amount of parameter inside of the IN is fixed, something like this works:
$a=[1,2,3];
$s=$mysqli->prepare("SELECT id FROM users WHERE role_id IN (?,?,?)");
$s->bind_param('iii',$a[0],$a[1],$a[2]);
$s->execute();
$s->bind_result($id);
$c=[];
while($s->fetch()){
$c[]=$id;
}
var_dump($c);
Maybe this is not the answer that you are looking for, but if the amount of variables is not know better insert the imploded array string inside the original SQL command.
$a=[1,2,3];
$b="('".implode("','",$a)."')";
$s=$mysqli->prepare("SELECT id FROM users WHERE role_id IN {$b}");
$s->execute();
$s->bind_result($id);
$c=[];
while($s->fetch()){
$c[]=$id;
}
var_dump($c);
THE ISSUE
I understand that using prepared statement prevents injection as the prepared statement execution consists of two stages: prepare and execute.
OK, but I do not really get what is going on if a bound parameter value is then used as a User-Defined Variables in MySQL.
Is the initially safe bound parameters process can be used for execution (and therefore injection) in STEP 2 ?
// The user input that may be the target for injection
$userInput = "input";
// STEP 1 -------------------
$q = "SET #param1 = :param1;";
// Execute query to set mysql user-defined variables
$param = [
'param1' => $userInput
];
$stmt = $pdo->prepare($q);
$stmt->execute($param);
// STEP 2 -------------------
// Query DB with User-Defined Variables
$q = "
SELECT ...
WHERE
table.field1 = #param1 OR
table.field2 = #param1 OR
table.field3 = #param1
";
// Query
$stmt = $pdo->query($q);
// STEP 3 -------------------
// Fetch Data
$row = $stmt->fetch();
WHY DO I WANT TO USE THIS APPROACH ?
I use this to avoid multiple similar named parameters like in the following example hereafter as
you cannot use a named parameter marker of the same name more than
once in a prepared statement, unless emulation mode is on
from manual. It's a mess to maintain for complex queries:
$q = "
SELECT ...
WHERE
table.field1 = :param1_1 OR
table.field2 = :param1_2 OR
table.field3 = :param1_2
";
$param = [
'param1_1' => $userInput
'param1_2' => $userInput
'param1_3' => $userInput
];
$stmt = $pdo->prepare($q);
$stmt->execute($param);
Yes, you can assume that a user variable takes the place of a single scalar value in a query, just like a bound parameter placeholder. It's an effective protection against SQL injection.
Proof: Try to perform an SQL injection using a user variable.
SET #s = 'Robert''; DROP TABLE Students;--';
SELECT * FROM Students WHERE name = #s;
This does NOT drop the table. It probably returns nothing, because there is no student with that strange, long name (unless you go to school with Little Bobby Tables).
However, I wonder if a query like this:
SELECT ...
WHERE
table.field1 = #param1 OR
table.field2 = #param1 OR
table.field3 = #param1
Indicates that field1, field2, and field3 should really be a single field in a child table. If you're searching for the same value in multiple columns, it could be a repeating group. For example, if it's phone1, phone2, phone3, that's a multi-valued attribute that should be stored in one column over multiple rows in a child table. Then you can search with a single parameter.
Q: Is the initially safe bound parameters process can be used for execution (and therefore injection) in STEP 2 ?
A: The pattern shown in the code in the question does not open up a SQL Injection vulnerability.
A user defined-variable used as a value in a SQL statement (as shown in the pattern in the question) is seen by MySQL as a value. That is, MySQL will not interpret the contents of the user-defined variable as part of the SQL text.
To get that to happen, to introduce a SQL Injection vulnerability, we would would need to dynamically construct SQL text and get that prepared/executed with the MySQL PREPARE/EXECUTE SQL statements.
https://dev.mysql.com/doc/refman/5.7/en/prepare.html
So, yes. Using a user-defined variable (as shown in the code in the question) does not in itself introduce SQL Injection vulnerability.
(But, just so there is no misunderstanding... it is possible to write vulnerable code, both with and without user-defined variables.)
In my table i have query:
$sql="SELECT * FROM `jom_x1_organizatori`
WHERE Organizator='".$sta_dalje."' Order by `Struka`,`Zanimanje`";
$sta_dalje=$_POST["state_id"] from another table and value is:
ЈУ Гимназија са техничким школама Дервента
"ПРИМУС" Градишка
In case 1 working.
How to make query?
Firts of all: Never build the query by concatenating the query string with user input! If you do, then escape the input with the library's dedicated function (mysqli_real_escape_string for example). Without escaping you will open a potential security hole (called SQL injection).
"ПРИМУС" Градишка is not working because after concatenating, the query will be invalid. Now imagine, what happens, if I post the following input: '; DROP TABLE jom_x1_organizatori; --
Your query will be:
SELECT * FROM `jom_x1_organizatori`
WHERE Organizator=''; DROP TABLE jom_x1_organizatori; --' Order by `Struka`,`Zanimanje`
Whenever you can use prepared statements to bind parameters (and let the library to do the hard work), but always escape and validate your input (using prepared statements, escaping is done by the library)!
$sta_dalje = (sting)$_POST["state_id"]; // Do filtering, validating, force type convertation, etc!!
// Prepare the statement (note the question mark at the end: it represents the parameter)
$stmt = $mysqlLink->mysqli_prepare(
"SELECT * FROM `jom_x1_organizatori` WHERE Organizator = ?"
);
// Bind a string parameter to the first position
$stmt->bind_param("s", $sta_dalje);
For more info about prepared statements:
mysqli prepared statements
PDO prepared statements
Please note that the old mysql extension is deprecated, do not use it if not necessary!
Just a side note
Do not use SELECT * FROM, always list the columns. It prevents to query unnecessary data and your code will be more resistant to database changes, plus debugging will be a bit simplier task.
Use escape string
$sta_dalje = mysqli_real_escape_string($con, $_POST["state_id"]);
And your where condition can be simply
Organizator='$sta_dalje'
I had the following piece of code with PDO prepared statements:
$stmt = $conn->prepare('SELECT `myColumn1` FROM my_table '.
'WHERE `myColumn2`=:val LIMIT 1');
$stmt->bindValue(":val", $value);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
This works fine. It sends the following query:
113 Query SELECT `myColumn1` FROM my_table WHERE `myColumn2`=":val" LIMIT 1
and it returns the correct value.
But it doesn't work if I change the first line to
$stmt = $conn->prepare('SELECT `myColumn1` FROM my_table '.
'WHERE `myColumn2`=":val" LIMIT 1');
or
$stmt = $conn->prepare('SELECT `myColumn1` FROM my_table '.
'WHERE `myColumn2`=':val' LIMIT 1');
The same query is sent, but PDO returns false.
Can anybody explain why?
From the page you quote:
The parameters to prepared statements don't need to be quoted; the driver automatically handles this.
The purpose of the quotation marks is to delimit string data from the rest of the query, since it cannot easily be separated (unlike numbers, which have an obvious format). Since using prepared statements means that query and data are passed separately, the quotes are unnecessary.
One of the advantages of prepared statements are that types are handled for you (sort of...). In other words, prepared statements allow MySQL (or whatever RDBMS) to decide how to handle data. When putting quotes, that would force it to be a string which doesn't make sense. If it's supposed to be a string, then the server will handle that.