I've recently been working on remediating security vulnerabilities for a PHP legacy application. We've refactored all MYSQLI queries to use prepared statements as such. This has mostly been straightforward, but I've run into a few isolated situations in which I need to use placeholders (?) for a WHERE clause that is dynamically generated, and can have different elements depending on user input. I'm having trouble finding documentation that shows how to achieve this, so I may need to change my approach.
I'm in a position where I will not know which items need to be added to the where clause until the user selects input. What is considered the best approach to solve this problem?
The original, functioning query works like so:
$query ="SELECT [fields] FROM table1, table2 where (table1.id = table2.cc_id) $where order by num, name, due_date";
$where is a series of dynamic AND statements. I'm only aware of the number of AND clauses/values when the page runs. Example:
AND table1.business_line_id IN (2) AND table1.client_id IN (1,2,3,5) AND table1.application_id in (7,47) AND table1.region_id in (1,4,5,6,8,14,15,21)
The rest of the code related to this item:
$params = array($where);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
if(!$stmt = $connection->prepare($query)) {
showerror($connection);
die();
}
// $stmt -> bind_param(get_types($params),...$params);
$stmt -> bind_param('s', $params);
$stmt -> execute();
The array of parameters returns an array of length 1 with the correct $where value of type string. I've tried to implement a placeholder, but this does not traditionally follow the structure of placeholders in which we use field = placeholder (user_id = ?) or VALUES(?,?,?). Is it possible to generate a query in which we use placeholders dynamically generated sections of the WHERE/AND clauses with this syntax?
Here's an example of something that does not work.
// $query ="SELECT [fields] from table1, table2 WHERE (table1.id = table2.cc_id) ? order by num, name, due_date";
Understandably, MYSQLI's $stmt = $connection->prepare($query) takes issue with this at the placeholder value (?). If I try to include the $where variable directly, the bind_param() call takes issue because it has no parameters to bind (number of variables doesn't match).
Here's an example that does work, but I'm concerned it won't properly protect against SQL injection. Just passing $where and removing any parameter binding will give me the proper output.
$query ="SELECT [fields] FROM table1, table2 where (table1.id = table2.cc_id) $where order by num, name, due_date";
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
if(!$stmt = $connection->prepare($query)) {
// added this during debug to show the malformed query
showerror($connection);
die();
}
$stmt -> execute();
if (!$result = $stmt->get_result()) showerror($connection);
Is there a way to pass in this clause using placeholders?
EDIT: adding $where build logic for clarity.
$where = "and cc.business_line_id in ($business_line_ids)";
// some code
foreach($ar_select as $value) {
if (isset($_POST["${value}_list"])) {
$thisfield = ($value == "assignee_status_id") ? "table2.status_id" : "cc.$value";
$this_id_list = implode(",", $_POST["${value}_list"]);
$where .= " AND $thisfield in ($this_id_list) ";
}
}
// some code
$ar_dates = array("status_date","target_date","due_date");
foreach($ar_dates as $thisdate) {
if (strlen($_POST["$thisdate"])) {
$date = $_POST["$thisdate"];
$op_date = $_POST["op_$thisdate"];
$where .= " AND $thisdate " . date_operator($op_date) ." '$date' ";
}
}
if (strlen($_POST["keyword_name"])) {
$keyword_name = $_POST["keyword_name"];
$where .= " AND asn.name LIKE '%$keyword_name%' ";
}