SQL query in php with input from html dropdown - php

Ok so what I'm trying to do is make a query evaluation tool for a project. I get the user input as a php variable and use it in my query, so it's like the user is submitting the query. I am using mysqli
I'm currently doing this with my my query
$sql = "SELECT * FROM `idk` where `alias` = '".$s."'";
I get the values from the form below
$s = $_GET['val'];
<select name="selector" id = "selector">
<option value="">Select a criteria:</option>
<option value="0">id</option>
<option value="1">name</option>
<option value="2">email</option>
<option value="3">alias</option>
<option value="4">position</option>
I'm wondering if I can add user input for the where clause condition(=,!=) and get it like i get the value for the column instead of hard coding it.
Keep in mind that this is for a uni project and no one is going to use it or delete my database, but any security suggestions would be appreciated.

The shortest possible answer: yes. You can control everything. All you need to achieve this is add another choice element into your form that will let the user control the way values are compared. I'll use a simplified example.
If you have an input for the user's value:
<input type="number" name="age" value="" />
add a choice field before it:
<select name="age_comparison">
<option value="equal">equal to</option>
<option value="not_equal">not equal to</option>
<option value="less_than">less than</option>
<option value="greater_than">greater than</option>
</select>
<input type="number" name="age" value="" />
Note: the position isn't relevant for handling - at all. I just think it's a better user experience if users read your page just like they would a piece of text in real life. Meaning that when input comes later it will read as "age greater than X", which reads better than "age X, greater than", if you put the input first.
For the same reason (better user experience), I tend to label the options with something that reads more like a human, hence "equal to" instead of just "equal". But I omitted it from the option values since it doesn't add any valuable information to the code ("equal" tells the whole story as a parameter value).
Then, after your form is submitted, you can detect your user's choice and build the query string accordingly:
if (isset($_GET['age'])) {
$age = $_GET['age'];
}
if (isset($_GET['age_comparison'])) {
switch ($_GET['age_comparison']) {
case 'equal':
$ageComparison = '=';
break;
case 'not_equal':
$ageComparison = '!=';
break;
case 'less_than':
$ageComparison = '<';
break;
case 'greater_than':
$ageComparison = '>';
break;
}
}
// if we have all the parameters, we can query the database
if (isset($age) && isset($ageComparison)) {
// the following line is very unsafe - we'll be on that in a minute
$queryString = 'SELECT * FROM students WHERE age '.$ageComparison.$age;
...
Note: I used $_GET because you used it in your question. If you're going to have multiple parameters in your form, I suggest you rather use the post method and avoid having a whole bunch of parameters added to the url of your page. Your form values will be under the same keys, only in the $_POST variable. Plus, when you use post, it's enough to detect if the name of the submit is set - if it is, the rest of the inputs from the same form is guaranteed to be present as well. With get you have to check every parameter individually.
So there you have it. A query with variable comparison operators.
BUT
We're not done yet. There are some bad practices to shed away and some good practices to pick up on the way.
First and foremost, never build queries by directly inserting parameters:
$sql = "SELECT * FROM `idk` where `alias` = '".$s."'";
That makes you wide open to SQL injection. You should use prepared statements instead. Prepared statements have built-in protection mechanisms and also take care of all your quoting needs (meaning you don't have to manually put uote marks around parameters that are strings). So you do it like this:
// the question mark means we'll be adding a parameter in that position
$queryString = 'SELECT * FROM students WHERE age '.$ageComparison.' ?';
// here I assume you have mysqli statement ready
// you can see an example about creating one in the link about prepared statements
$statement->prepare($queryString);
$statement->bindParam('i', $age);
You might say "But wait a minute! You just added the comparison operator directly! Isn't that dangerous?" - no, because it didn't come directly from the user. We decided its value in the switch statement, meaning we took user's input (which might be compromised) and turned it into a value that we control. The user can't decide what comes out of the switch statement, we can. Hence, it's safe to directly concatenate it into the query string because we know we've defined some safe values.
Speaking of the switch statement, there's an improvement to be made there, too. What if we add another option in the select, but we forget to add it in the switch statement? Or a malicious user compromises the value of the option being sent? We will end up with an error, since then no cases in the switch will be matched (a case for a value outside of the 4 we put there is not defined) and thus the variable $ageComparison will never be created and we'll never execute the query, because our if condition will fail. So how do we fix it? We add a default case (default executes when none of the cases are matched):
// remainder of statement cut for length
...
case 'greater_than':
$queryComparison = '>';
break;
default:
throw new Exception('Unsupported value for age comparison: '.$ageComparison);
}
Exceptions halt execution when not handled (the proper term is caught), but I'll leave it to you if you wish to explore that topic on your own (seems a bit much for a beginner, plus there's quite a bit of text here already).

Related

Is there a more dynamic way to sanitize ORDER BY

i have a from, 2 of the fields are the following
<select name="sortfield">
<option value="name" selected="selected">Name</option>
<option value="price">Price</option>
<option value="id">ID Code</option>
</select>
<select name="sortdir">
<option value="asc" selected="selected">Ascending</option>
<option value="desc">Descending</option>
</select>
this are obtained via $_REQUEST[] on the next page which is then added to the query to determin how the results of a query are sorted, the quick, dirty, un-sanitized way is this
$query .= "ORDER BY ".$_REQUEST['sortfield']." ".$_REQUEST['sortdir'];
obviously this can be a problem with sql injection, one way i can fix this is to change the values and have a switch case when making the query to swap out the values, something like this
switch($_REQUEST['sortfield'])
{
case '5524879':
$query .= "ORDER BY name";
break;
case '4587532':
$query .= "ORDER BY price";
break;
default:
$query .= "ORDER BY id";
}
while this can help prevent sql injection it's not very dynamic since page will have to be changed every time if the query/table structure changes, i am wondering if there is a more dynamic way to sanitize the strings rather than having to use a switch ... case
A more dynamic way would be to create an array of table fields with show fields, and use that as a white list when checking the value of of your sortfield
if ( !in_array( $_REQUEST['sortfield'], $table_fields ) ) {
// error
}
I also recommend not using $_REQUEST at all. You should know where your variables are coming from.
The variables in $_REQUEST are provided to the script via the GET,
POST, and COOKIE input mechanisms and therefore could be modified by
the remote user and cannot be trusted. The presence and order of
variables listed in this array is defined according to the PHP
variables_order configuration directive.
Yes, for the order by clause you can only format dynamical identifier, without checking it against a whitelist.
if (!empty($_GET['sortfield']))
{
$query .= "ORDER BY `".str_replace("`","``",$_GET['sortfield'])."`";
} else {
$query .= "ORDER BY id";
}
Note that it will protect you from injection but won't save from SQL syntax error - so, it's better to verify the field name anyway.
Also, there could be no id field in the table, and thus this code is not very dynamic too. So, it's better to stick to a whitelist anyway. After all, it is not that hard. Sometimes manual amendments are better then automated ones.

JSON from SQL Table get double result

first of all i have to tell you that it is my first step on php and JSON.
I decided to use JSON to get value from a customer SQL Table.
I get my results using this script :
mysql_connect($config['mysql_host'],$config['mysql_user'],$config['mysql_pass']);
//select database
#mysql_select_db($config['db_name']) or die( "Unable to select database");
mysql_query('SET CHARACTER SET utf8');
$fet=mysql_query('select * from vehicule');
$json = array();
while($r=mysql_fetch_array($fet)){
$json[] = $r;
}
header('Content-Type: application/json');
echo $json_data=json_encode($json);
Everything is ok, exept that my JSON results looks like :
0 = 462;
1 = "Hyundai ix20 crdi 115 panoramic sunsation";
10 = 1346450400;
11 = "462-Hyundai-ix20-crdi-115-panoramic-sunsation";
12 = 462;
...
id = 462;
kilometrage = 14400;
marque = 4;
modele = 137;
motorisation = 2;
ordre = 462;
prix = 17500;
puissance = 6;
titre = "Hyundai ix20 crdi 115 panoramic sunsation";
url = "462-Hyundai-ix20-crdi-115-panoramic-sunsation";
...
I have result of the table in 2 versions : one with 0:value, 1:value, 2... and the other one using the table key, how can i print only the second one ?
By the way can someone give me link so i can know by what i have to replace mysql which is think out of date ? (i'm a beginner few hours using PHP)
Thank you very much !
You have two different issues happening here. One is outright causing the issue you are seeing, and the other is a bad practice mistake that will leave you wide open for trouble in the long run.
The first issue is the one you're asking about. The mysql_fetch_array function (see the Docs here) expects a minimum of one input (the result input) that you are providing. It also has a second, optional input. That optional input defaults to MYSQL_BOTH, which returns an associative array with the results available both through keys (column names) and their indexes. Which is to say, that if you select the column 'id', you get it's value in both $array[0] and $array['id']. It's duplicated, and thus the JSON process carries over the duplication. You need to provide a second value to the function, either MYSQL_ASSOC to get $array['id'] or MYSQL_NUM to get $array[0].
Your second issue is the choice of functions. You're using the 'raw' mysql functions. These have been depreciated, which is a technical term that means 'these functions are no longer supported, but we've left them in to give you time to fix legacy code'. For legacy, read 'old'. Those functions will be going away soon, and you need to upgrade to a better option -- either the mysqli functions, or the PDO class. I strongly recommend the PDO class, as once you learn it it's easy to learn and has the advantage of being more portable. Whichever set you go with, you need to learn to use prepared statements as both a performance and security issue. Right at the moment, you're working with 'raw' statements which have a history of being very easy to interfere with via what's called an 'injection attack'. You can see a fictionalized example of such an attack here, and there are plenty of articles online about it. These attacks can be incredibly complex and difficult to fight, so using prepared statements (which handle it for you), is strongly recommended. In the specific example you're using here, you don't need to worry about it because you aren't including any user inputs, but it's an important habit to get into.

Mysql - PHP different values searches

I know how to perform mysql searches using for example the WHERE word. But my problem is that i need to search on different values, but these can vary in number. For example:
I can search for 3 variables Name, LastName, Age
BUT
I in other search, i can look for 2 variables Name, Age.
Is there a way to perform a MYSQL search with the same script, no matter the quantity of values i search.??
Ot it is a better practice to "force" the search of a fixed amount of variables.??
Thanks.!
Roberto
IMHO, it is far better to limit the search to a fixed number of variables. That way you are answering a specific question for a specific reason, not trying to fit a general answer to your specific question. Limiting the search criteria makes the statement(s) easier to debug and benchmark for performance.
Hope this helps.
Just use a variable for your search parameters and inject that into your query. Just ensure that in the function/method you put the variable into the proper format (which will depend on how you select the different values.)
SELECT *
FROM db
$variable;
There will be no WHERE clause seen, unless it is passed your values (meaning you can use this same query for a general search of the db) without fear of having an empty/required $variable.
Your $variable when constructed would need to have to have the WHERE clause in it, then each value you add, insert it (in a loop perhaps) in the proper format.
Hope this makes sense, if not let me know and I will try to clarify. This is the same method most people use when paginating (except they put the variable in the LIMIT instead of the WHERE)
EDIT:
Also make sure to properly sanitize your variable before injection.
Simple example of dynamically building a query:
$conditions = array();
if (...) {
$conditions['age'] = $age;
}
if (...) {
$conditions['name'] = $name;
}
...
if (!$conditions) {
die('No conditions supplied');
}
// if you're still using the mysql_ functions and haven't done so before:
$conditions = array_map('mysql_real_escape_string', $conditions);
foreach ($conditions as $field => &$value) {
$value = "`$field` = '$value'";
}
$query = 'SELECT ... WHERE ' . join(' AND ', $conditions);
It's really not hard to dynamically cobble together the exact query you want to create. Just be careful you don't mess up the SQL syntax or open yourself to more injection vulnerabilities. You may want to look at database abstraction layers, which pretty much allow you to pass a $conditions array into a function which will construct the actual query from it, more or less the way it's done above.

PHP Form Posting Values To Database

Basically, i have a working form where the user inputs details about their laptop to sell to my shop.
I give them a quote once they have submitted the Specs of the laptop.
At the moment i have got option boxes and checkboxes which each have a value-- for example these. ---
<label for="state">State</label><br>
<select name="state">
<option value="10">Excellent</option>
<option value="5">Good</option>
<option value="0">Poor</option>
</select><br>
The Values of the options they have selected get added up at the end and that gives them the quote - in the above example - "10" means £10 extra for a excellent condition laptop etc.
I use $_POST[state] to get the value of it to add onto the other options for the quote.
But my problem lies when i POST them to a database (so we can check when they come in).
When they get added to the database, obviously it just comes out as the values not the actually name of it like "excellent" or "good". just says "10" or "5".
Is there anyway to put the name of the option into the database instead of the value?
sure... just make sure that's what you want to do. It's usually not considered a good database practice to create denormalized tables like that, but you could do it. When you collect your post data, simply create another variable and assign a value to it based off the state value like so:
$stateText = '';
switch ($state){
case 10:
$stateText = 'Excellent';
break;
case 5:
$stateText = 'Good';
break;
case 0:
$stateText = 'Poor';
break;
default:
// bad value
$stateText = '';
}
...then store this to the database in a new column.
This is just one of many ways to do this.
You can only do it if you have a lookup, be it an array or in another table that stores the keys and values.
You should be carefuly not to store the post data directly into your database without sanitizing it, otherwise you might become subject to sql injection.
Is there anyway to put the name of the option into the database instead of the value?
There is, but it involves doing it explicitly (converting "10" into "Excellent" before inserting the value) rather than just basically tossing $_POST into the database as-is. You can make this very simple if you are building the <option>s with an array in the first place by reading the the array again and swapping the values with the keys.
$values = array(
10 => 'Excellent',
5 => 'Good',
0 => 'Poor',
);
$post_value = $_POST['state'];
$db_value = $values[$post_value];
// further validation: make sure the array key exists or use a default value
// further usage: build your HTML <options> with this array
However:
If you're going to do that, you're much better off storing the values as numbers and converting them to words when you display them (assuming the numbers do have some meaning). This also allows you to localize by providing translations.
Response to comments:
I would recommend a rating system, like 1 through 5, and calculate your price modifications internally - not directly from the user input or from a hardcoded value (in the database). This allows you to tweak the price changes from within your app, rather than from database values that were created at an earlier time, like if you decide an "Excellent" condition warrants an increase of 11 rather than 10 - unless you specifically want the prices "locked in" permanently at the time the product was posted.
Whatever you do, make sure to validate the input - I can't think of any good reason to use direct user input to calculate prices - it should be done internally based on product ids, and any other conditions. HTML source can be modified on-the-fly to post values you didn't expect from the dropdown.
You can't get it via the HTML form. But you can still do a server side that would map the values to the appropriate condition.
You can use a switch statement or an if statement to map them.
if(value == 10){
$condition = 'Excellent';
} else {//....}

"SELECT * FROM users WHERE id IN ( )" == FAIL

I have a function that I use called sqlf(), it emulates prepared statements. For instance I can do things like:
$sql = sqlf("SELECT * FROM Users WHERE name= :1 AND email= :2",'Big "John"','bj#example.com') ;
For various reasons, I cannot use prepared statements, but I would like to emulate them. The problem that I run into is with queries like
$sql = sqlf("SELECT * FROM Users WHERE id IN (:1)",array(1,2,3) );
My code works, but it fails with empty arrays, e.g. the following throws a mysql error:
SELECT * FROM Users WHERE id IN ();
Does anyone have any suggestions? How should I translate and empty array into sql that can be injected into an IN clause? Substituting NULL will not work.
Null is the only value that you can guarantee is not in the set. How come it is not an option? Anything else can be seen as part of the potential set, they are all values.
I would say that passing an empty array as argument for an IN() clause is an error. You have control over the syntax of the query when calling this function, so you should also be responsible for the inputs. I suggest checking for emptiness of the argument before calling the function.
Is there a possibility that you could detect empty arrays withing sqlf and change the SQL to not have the IN clause?
Alteratively, you could postprocess the SQL before passing it to the "real" SQL executor so that "IN ()" sections are removed although you'd have to do all sorts of trickery to see what other elements had to be removed so that:
SELECT * FROM Users WHERE id IN ();
SELECT * FROM Users WHERE a = 7 AND id IN ();
SELECT * FROM Users WHERE id IN () OR a = 9;
would become:
SELECT * FROM Users;
SELECT * FROM Users WHERE a = 7;
SELECT * FROM Users WHERE a = 9;
That could get tricky depending on the complexity of your SQL - you'd basically need a full SQL language interpreter.
If your prepare-like function simply replaces :1 with the equivalent argument, you might try having your query contain something like (':1'), so that if :1 is empty, it resolves to (''), which will not cause a parse error (however it may cause undesirable behavior, if that field can have blank values -- although if it's an int, this isn't a problem). It's not a very clean solution, however, and you're better off detecting whether the array is empty and simply using an alternate version of the query that lacks the "IN (:1)" component. (If that's the only logic in the WHERE clause, then presumably you don't want to select everything, so you would simply not execute the query.)
I would use zero, assuming your "id" column is a pseudokey that is assigned numbers automatically.
As far as I know, automatic key generators in most brands of database begin at 1. This is a convention, not a requirement (auto-numbered fields are not defined in standard SQL). But this convention is common enough that you can probably rely on it.
Since zero probably never appears in your "id" column, you can use this value in the IN() predicate when your input array is empty, and it'll never match.
The only way I can think to do it would be to make your sqlf() function scan to see if a particular substitution comes soon after an "IN (" and then if the passed variable is an empty array, put in something which you know for certain won't be in that column: "m,znmzcb~~1", for example. It's a hack, for sure but it would work.
If you wanted to take it even further, could you change your function so that there are different types of substitutions? It looks like your function scans for a colon followed by a number. Why not add another type, like an # followed by a number, which will be smart to empty arrays (this saves you from having to scan and guess if the variable is supposed to be an array).

Categories