4

I am converting from extension mysql to PDO and after reading all I could from you gurus on Stack Overflow and elsewhere, I have some residual questions. I came up with the following to address SQL injection for a typical query. I am just wondering if that's enough or may be I am going a bit overboard with the whitelisting, before I replicate this to all my application.

It's not clear to me if I did the whitelisting properly, i.e., if I should also escape somehow.

Also, I am not sure if I should setAttribute emulate to false for every query or just once for the script.

$link = new PDO("mysql:host=$hostname;dbname=$database;charset=utf8", $username, $password);

$link->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

$arr_i = $arr_k = '';
$m_act = $v_act = 'Y';
$table = array('prices', 'versions', 'models');
$allowedTables = array('prices', 'versions', 'models');
$field = array('model_id', 'version_id', 'price', 'models.active', 'versions.active');
$allowedFields = array('model_id', 'version_id', 'price', 'models.active', 'versions.active');

if(count(array_diff($field, $allowedFields))==0 AND count(array_diff($table, $allowedTables))==0) {

    $sql = "SELECT COUNT(DISTINCT `" . $field[0] . "`) as ctmod FROM `" . $table[0] . "`
            INNER JOIN `" . $table[1] . "` USING (`" . $field[1] . "`)
            INNER JOIN `" . $table[2] . "` USING (`" . $field[0] . "`)
            WHERE `" . $field[2] . "` BETWEEN :arr_i AND :arr_k
            AND " . $field[3] . " = :m_act
            AND " . $field[4] . " = :v_act";

    $stmt = $link->prepare($sql);
    $stmt->bindParam(':arr_i', $arr_i, PDO::PARAM_INT);
    $stmt->bindParam(':arr_k', $arr_k, PDO::PARAM_INT);
    $stmt->bindParam(':m_act', $m_act, PDO::PARAM_STR);
    $stmt->bindParam(':v_act', $v_act, PDO::PARAM_STR);
    for ($i=0; $i < $ctpri; $i++) {
        $k = $i + 1;
        $arr_i = $arr_pri[$i] + 1;
        $arr_k = $arr_pri[$k];
        $stmt->execute();
        while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $ctmod[] = $r['ctmod'];
        }
    }
}
else{
    die();
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
BernardA
  • 1,391
  • 19
  • 48
  • 1
    http://codereview.stackexchange.com/ – Mihai Dec 21 '13 at 21:13
  • [An answer](https://stackoverflow.com/questions/20723398/pdo-prep-statement-whitelisting-set-charset-is-that-safe-enough-to-prevent-inje/20726310#20726310) is the subject of [a meta question](https://meta.stackexchange.com/questions/213570/what-is-wrong-with-the-tone-in-this-answer). – Peter Mortensen May 21 '23 at 18:02
  • That is, there was a new answer to the meta question in 2023... – Peter Mortensen Jul 23 '23 at 16:38

2 Answers2

5

I suspect that you indeed are going a bit overboard with the whitelisting. Not only with whitelisting, but with prepared statements too. By protecting constant values, you overengineered your query, making it difficult to read.

You need to understand that any constant value is safe by design. Therefore there is absolutely no point in whitelisting or binding it. Just write it as is.

So, instead of

AND " . $field[3] . " = :m_act

you should write just

AND versions.active = 'Y'

without any binding or whitelisting.

While only dynamical values should be protected. Which means, in your particular situation, only $arr_i and $arr_k must be bound. All other query parts have to be written into the query directly, just like you did it before.

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
  • 1
    @YCS, If I knew everything to be right I would not ask the question to start with. I appreciate your input, but certainly not the tone of it. If you intend to keep the same tone, I would appreciate if you refrain from answering any of my questions in the future. The fact that I am learning does not mean I will accept to be bullied. – BernardA Dec 23 '13 at 11:28
  • 5
    I have no idea what is that "tone" you are talking about. Nowhere I mentioned your skill or knowledge or whatever else personal feature. All I did is answered your question, providing the only reliable answer. So, please rein your imagination. – Your Common Sense Dec 23 '13 at 17:30
  • 3
    @YCS, That's exactly the problem. I have seen your interventions. You do it so often without been rebuffed that you think it is OK. Well, it isn't. If you are willing to discuss this further, I suggest you write me at my email on profile. I hate to bother other SO fellows with this. – BernardA Dec 24 '13 at 09:36
  • Re *"in using nor whitelisting"*: Do you mean *"in using whitelisting"* (without "nor")? Or something else (that sentence seems somewhat incomprehensible)? – Peter Mortensen Jul 23 '23 at 16:41
  • @PeterMortensen I meant something like neither ... nor ... . My English back in the day was horrible. But anyway, I rephrased it completely. – Your Common Sense Jul 23 '23 at 17:38
3

Yes, your code is thoroughly safe from SQL injection. Good job.

Though as @YourCommonSense points out, there's no reason in the example you show to make table and columns names into variables at all. It would be simpler to just write them into the query literally.

Therefore, I assume you're asking this question because you do sometimes choose table and column names through application logic or variables, even though you haven't shown it in this particular example.


The only tips I would offer are:

  • All the string concatenation, with ending double-quotes, using . and re-starting double-quotes makes the code look untidy and it can be confusing to keep track of which double-quotes you've started and stopped. An alternative style of PHP string interpolation for variables is to enclose in curly braces, like the following:

    $sql = "SELECT COUNT(DISTINCT `{$field[0]}`) as ctmod FROM `{$table[0]}`
        INNER JOIN `{$table[1]}` USING (`{$field[1]}`)
        INNER JOIN `{$table[2]}` USING (`{$field[0]}`)
        WHERE `{$field[2]}` BETWEEN :arr_i AND :arr_k
        AND `{$field[3]}` = :m_act
        AND `{$field[4]}` = :v_act";
    
  • And for yet another alternative, you can use here documents, so you don't have to worry about delimiting the string at all. Nice if you have literal double-quotes inside your string, because you don't have to escape them:

    $sql = <<<GO
        SELECT COUNT(DISTINCT `{$field[0]}`) as ctmod FROM `{$table[0]}`
        INNER JOIN `{$table[1]}` USING (`{$field[1]}`)
        INNER JOIN `{$table[2]}` USING (`{$field[0]}`)
        WHERE `{$field[2]}` BETWEEN :arr_i AND :arr_k
        AND `{$field[3]}` = :m_act
        AND `{$field[4]}` = :v_act
    GO;
    
  • Finally, it has nothing to do with SQL injection, but a good practice is to check the return value from prepare() and execute(), because they return false if an error occurs in parsing or execution.

    if (($stmt = $link->prepare($sql)) === false) {
        trigger_error(PDO::errorInfo()[2], E_USER_ERROR);
    }
    

    (That example uses PHP 5.4 syntax to dereference an array returned from a function.)

    Or else you can configure PDO to throw exceptions, so you don't have to check.

    $link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • Thanks, I feel better now. A couple of comments: you will need to take out the back quotes on the AND clauses. For some reason, probably because the string has a dot (models.active), I think, it will not work with them. I appreciate you showing me the way on the error handling, I have a lot to learn on that. One of them is where will I find the exceptions/errors thrown by PDO/PHP. – BernardA Dec 22 '13 at 00:29
  • @BernardA, you should delimit the table and column names separately: `\`models\`.\`active\``. Though delimiting these is not necessary anyway, because they are not [MySQL reserved words](http://dev.mysql.com/doc/refman/5.6/en/reserved-words.html) nor do they contain special characters or whitespace. – Bill Karwin Dec 22 '13 at 00:32
  • Thanks. That's fine. When I think about this, it is rather confusing to read the query like that. It is probably better to use keys in the arrays, with the name of the table/field. – BernardA Dec 22 '13 at 00:40
  • @YourCommonSense, I answered the OP's question in the first line of my answer. And given the number of times you have given tangential advice for good coding practices, I'm surprised at your comment. – Bill Karwin Dec 22 '13 at 06:36
  • @YourCommonSense, another thought: making it more convenient to format safe SQL is beneficial to security, because developers want to stay productive. If we show them how they can be safe *and* productive, they are more likely to accept best coding practices. – Bill Karwin Dec 22 '13 at 16:59