phpmysqlpdopreparebindparam

Understanding PDO Prepared Statements and Binding Parameters


From experience and also having been told constantly the benefits of using prepared statements and binding my parameters, I have constantly used those two techniques in my code, however I would like to understand exactly the purpose of each of those two techiques:

From my understanding of prepared statements:

$sql = "SELECT * FROM myTable WHERE id = ".$id;
$stmt = $conn->prepare($sql);
$stmt->execute();

The previous code should create a sort of a buffer in the database with the query I proposed. Now FROM MY UNDERSTANDING (and I could be very wrong), the previous code is insecure, because the string $sql could be anything depending on what $id actually is, and if $id = 1; DROP TABLE myTable;--, I would be inserting a malicious query even though I have a prepared statement.

FROM MY UNDERSTANDING this is where binding my parameters com in. If I do the following instead:

$sql = "SELECT * FROM myTable WHERE id = :id";
$stmt = $conn->prepare($sql);
$stmt->bindParam(':id', $id);
$stmt->execute();

The database should know exactly all the parts of the sql statement before hand: SELECT these columns: * FROM myTable and WHERE id = "a variable that was input by the user", and if "a variable that was input by the user" != a variable, the query fails.

I have been told by some my understanding is correct, and by others that it is false, could someone please let me know if I am wrong, correct, or missing something? And elaborate as much as you want, all feedback is greatly appreciated!


Solution

  • You're correct that the first case is insecure. It's important to understand though, that preparing a statement only has value if you are using variable data, and/or executing the same query repeatedly. If you are executing plain statements with no variables, you could simply do this:

    $sql = "SELECT * from myTable WHERE this_column IS NOT NULL";
    $result = $conn->query($sql);
    

    And end up with a PDOStatement object to work with, just like when you use PDO::exec().

    For your second case, again, you're largely correct. What's happening is the variable passed to the database is escaped and quoted (unless you specify otherwise with the third argument to PDOStatement::bindParam(), it's sent as a string which is fine for most cases.) So, the query won't "fail" if bad data is sent. It behaves exactly as if you had passed a valid number that didn't exist as an ID in the database. There are, of course, some edge cases where you are still vulnerable even with a correctly prepared statement.

    Also, to make life easier, you can use prepared statements like this, to do implicit binding:

    $sql = "SELECT * FROM myTable WHERE id = :id";
    $stmt = $conn->prepare($sql);
    $stmt->execute([":id"=>$id]);
    

    Or even like this, with un-named parameters:

    $sql = "SELECT * FROM myTable WHERE id = ?";
    $stmt = $conn->prepare($sql);
    $stmt->execute([$id]);
    

    Naturally, most of this has been explained in the comments while I was typing up the answer!