SQL query in php with input from html dropdown-ThrowExceptions

Exception or error:

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.

How to solve:

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).

Leave a Reply

Your email address will not be published. Required fields are marked *