I want to make a quick and easy demonstration about how SQL injection work. And I\'ve solved some of my problems. I have a table with random usernames, passwords and emails in,
mysqli_query() does not support multi-query by default. It has a separate function for that: mysqli_multi_query().
SQL injection is not only about running multiple statements, the famous XKCD cartoon notwithstanding.
Your code has a bad SQL injection vulnerability. Do you think that using prepare()
somehow makes a query safe, even though you interpolate content from your $_POST request data directly into the SQL string?
Your code is this:
$searchterm = $_POST['searchterm'];
$sql = "SELECT username, email FROM Members where username = '$searchterm'";
if ($stmt = $conn->prepare($sql)) {
/* execute statement */
$stmt->execute();
...
It's easy for unsafe input to make SQL injection mischief this way. It might even be innocent, but still result in problems. Suppose for example the search is: O'Reilly
. Copying that value directly into your SQL would result in a query like this:
SELECT username, email FROM Members where username = 'O'Reilly'
See the mismatched '
quotes? This won't do anything malicious, but it'll just cause the query to fail, because unbalanced quotes create a syntax error.
Using prepare()
doesn't fix accidental syntax errors, nor does it protect against copying malicious content that modifies the query syntax.
To protect against both accidental and malicious SQL injection, you should use bound parameters like this:
$searchterm = $_POST['searchterm'];
$sql = "SELECT username, email FROM Members where username = ?";
if ($stmt = $conn->prepare($sql)) {
$stmt->bind_param('s', $searchterm);
/* execute statement */
$stmt->execute();
...
Bound parameters are not copied into the SQL query. They are sent to the database server separately, and never combined with the query until after it has been parsed, and therefore it can't cause problems with the syntax.
As for your question about mysqli::query()
, you may use that if your SQL query needs no bound parameters.
Re your comment:
... vulnerable to injection, so I can show the students how much harm a malicious attack may [do].
Here's an example:
A few years ago I was an SQL trainer, and during one of my trainings at a company I was talking about SQL injection. One of the attendees said, "ok, show me an SQL injection attack." He handed me his laptop. The browser was open to a login screen for his site (it was just his testing site, not the real production site). The login form was simple with just fields for username and password.
I had never seen his code that handles the login form, but I assumed the form was handled by some code like most insecure websites are:
$user = $_POST['user'];
$password = $_POST['password'];
$sql = "SELECT * FROM accounts WHERE user = '$user' AND password = '$password'";
// execute this query.
// if it returns more than zero rows, then the user and password
// entered into the form match an account's credentials, and the
// client should be logged in.
(This was my educated guess at his code, I had still not seen the code.)
It took me 5 seconds to think about the logic, and I typed a boolean expression into the login form for the username, and for the password, I typed random garbage characters.
I was then logged into his account — without knowing or even attempting to guess his password.
I won't give the exact boolean expression I used, but if you understand basic boolean operator precedence covered in any Discrete Math class, you should be able to figure it out.