ChatGPT解决这个技术问题 Extra ChatGPT

How can prepared statements protect from SQL injection attacks?

How do prepared statements help us prevent SQL injection attacks?

Wikipedia says:

Prepared statements are resilient against SQL injection, because parameter values, which are transmitted later using a different protocol, need not be correctly escaped. If the original statement template is not derived from external input, SQL injection cannot occur.

I cannot see the reason very well. What would be a simple explanation in an easy English and some examples?


Y
Your Common Sense

The idea is very simple - the query and the data are sent to the database server separately. That's all.

The root of the SQL injection problem is in the mixing of the code and the data.

In fact, our SQL query is a legitimate program. And we are creating such a program dynamically, adding some data on the fly. Thus, the data may interfere with the program code and even alter it, as every SQL injection example shows it (all examples in PHP/Mysql):

$expected_data = 1;
$query = "SELECT * FROM users where id=$expected_data";

will produce a regular query

SELECT * FROM users where id=1

while this code

$spoiled_data = "1; DROP TABLE users;"
$query        = "SELECT * FROM users where id=$spoiled_data";

will produce a malicious sequence

SELECT * FROM users where id=1; DROP TABLE users;

It works because we are adding the data directly to the program body and it becomes a part of the program, so the data may alter the program, and depending on the data passed, we will either have a regular output or a table users deleted.

While in case of prepared statements we don't alter our program, it remains intact That's the point.

We are sending a program to the server first

$db->prepare("SELECT * FROM users where id=?");

where the data is substituted by some variable called a parameter or a placeholder.

Note that exactly the same query is sent to the server, without any data in it! And then we're sending the data with the second request, essentially separated from the query itself:

$db->execute($data);

so it can't alter our program and do any harm. Quite simple - isn't it?

The only thing I have to add that always omitted in the every manual:

Prepared statements can protect only data literals, but cannot be used with any other query part.
So, once we have to add, say, a dynamical identifier - a field name, for example - prepared statements can't help us. I've explained the matter recently, so I won't repeat myself.


"for example, by default PDO do not use prepared statements" - it is not exactly true, because PDO emulate prepared statements only for drivers that doesn't support such feature.
@zaq178miami: "PDO emulates prepared statements only for drivers that don't support the feature" - is not exactly true. MySQL has supported prepared statements for quite a while now. The PDO driver has as well. But yet, MySQL queries were still prepared by PDO by default, last time i checked.
What is different between $spoiled_data = "1; DROP TABLE users;" -> $query = "SELECT * FROM users where id=$spoiled_data";, compared to: $db->prepare("SELECT * FROM users where id=?");-> $data = "1; DROP TABLE users;" -> $db->execute($data);. Won't they do the same thing?
@Juha Untinen The data can be anything. It will not parse the data. That is DATA not the command. So even if the $data contains sql commands, it will not be executed. Also, if the id is a number, then the string content will generate a report or value zero.
@JuhaUntinen I think when executing the query with prepared statement the query will be executing like this SELECT * FROM users where id='1;DROP TABLE users', so this value can never be found the database and it will not return anything. Oh ghosh!! so its saving us from the SQL injection too!!!!!! Cheers
R
Rany Albeg Wein

Here is an SQL statement for setting up an example:

CREATE TABLE employee(name varchar, paymentType varchar, amount bigint);

INSERT INTO employee VALUES('Aaron', 'salary', 100);
INSERT INTO employee VALUES('Aaron', 'bonus', 50);
INSERT INTO employee VALUES('Bob', 'salary', 50);
INSERT INTO employee VALUES('Bob', 'bonus', 0);

The Inject class is vulnerable to SQL injection. The query is dynamically pasted together with user input. The intent of the query was to show information about Bob. Either salary or bonus, based on user input. But the malicious user manipulates the input corrupting the query by tacking on the equivalent of an 'or true' to the where clause so that everything is returned, including the information about Aaron which was supposed to be hidden.

import java.sql.*;

public class Inject {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=user&password=pwd";
        Connection conn = DriverManager.getConnection(url);

        Statement stmt = conn.createStatement();
        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='" + args[0] + "'";
        System.out.println(sql);
        ResultSet rs = stmt.executeQuery(sql);

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

Running this, the first case is with normal usage, and the second with the malicious injection:

c:\temp>java Inject salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary'
salary 50

c:\temp>java Inject "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' OR 'a'!='b'
salary 100
bonus 50
salary 50
bonus 0

You should not build your SQL statements with string concatenation of user input. Not only is it vulnerable to injection, but it has caching implications on the server as well (the statement changes, so less likely to get a SQL statement cache hit whereas the bind example is always running the same statement).

Here is an example of Binding to avoid this kind of injection:

import java.sql.*;

public class Bind {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=postgres&password=postgres";
        Connection conn = DriverManager.getConnection(url);

        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?";
        System.out.println(sql);

        PreparedStatement stmt = conn.prepareStatement(sql);
        stmt.setString(1, args[0]);

        ResultSet rs = stmt.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

Running this with the same input as the previous example shows the malicious code does not work because there is no paymentType matching that string:

c:\temp>java Bind salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
salary 50

c:\temp>java Bind "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?

Does using a prepared statement from the program connecting to the database have the same effect as using a prepared statement that's part of the db? For example Postgres has it's own prepared statement and would using it prevent SQL injection? postgresql.org/docs/9.2/static/sql-prepare.html
@Celeritas I don't have a definitive answer for Postgresql. Looking at the docs, it appears the effect is the same. PREPARE creates a fixed named statement that is already parsed (i.e. the statement is not going to change any more regardless of the input) while EXECUTE will run the named statement binding the parameters. Since PREPARE only has session duration, it really looks like it is meant for performance reasons, not for preventing injection via psql scripts. For psql access, could give permissions to stored procedures and bind the parameters within the procs.
@Celeritas I tried above code using PostgreSQL 11.1 on x86_64 and SQLi example above worked.
R
Robert Harvey

Basically, with prepared statements the data coming in from a potential hacker is treated as data - and there's no way it can be intermixed with your application SQL and/or be interpreted as SQL (which can happen when data passed in is placed directly into your application SQL).

This is because prepared statements "prepare" the SQL query first to find an efficient query plan, and send the actual values that presumably come in from a form later - at that time the query is actually executed.

More great info here:

Prepared statements and SQL Injection


N
N.Vegeta

I read through the answers and still felt the need to stress the key point which illuminates the essence of Prepared Statements. Consider two ways to query one's database where user input is involved:

Naive Approach

One concatenates user input with some partial SQL string to generate a SQL statement. In this case the user can embed malicious SQL commands, which will then be sent to the database for execution.

String SQLString = "SELECT * FROM CUSTOMERS WHERE NAME='"+userInput+"'"

For example, malicious user input can lead to SQLString being equal to "SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

Due to the malicious user, SQLString contains 2 statements, where the 2nd one ("DROP TABLE CUSTOMERS") will cause harm.

Prepared Statements

In this case, due to the separation of the query & data, the user input is never treated as a SQL statement, and thus is never executed. It is for this reason, that any malicious SQL code injected would cause no harm. So the "DROP TABLE CUSTOMERS" would never be executed in the case above.

In a nutshell, with prepared statements malicious code introduced via user input will not be executed!


Really? The accepted answer doesn't tell exactly that?
@Your Common Sense The accepted answer is filled with a lot of valuable information but it made me wonder what the implementation details of the separation of the data & query entails. Whereas focusing on the point that the maliciously injected data (if there was one) would never be executed hits the nail on the head.
And which "implementation details" are provided in your answer that do not present there?
if you try to see where I'm coming from, you'll realise that my point is as follows: The brief desire to see the implementation details stemmed from a need to understand the explicit reason why the malicious user input won't cause any harm. Not so much a need to see the implementation details. Which is why realising that the implementation details were such that, at no point will maliciously entered SQL be executed, sent home the message. Your response answers the question, how (as requested)?, but I'd imagine other folks (like me) would be satisfied with a succinct response to why?
Saying the data is sent separately from the query and that the program remains intact, still doesn't show how exactly harm is prevented. Is it because the query part is screened before execution? is it because the query is never executed? This is exactly the thought process that triggered a desire to see what exactly is going on under the hood. I believe my response answered that and got an upvote, followed by a downvote (I'm guessing came from you), hope you can see why this will be useful to others.
w
wulfgarpro

When you create and send a prepared statement to the DBMS, it's stored as the SQL query for execution.

You later bind your data to the query such that the DBMS uses that data as the query parameters for execution (parameterization). The DBMS doesn't use the data you bind as a supplemental to the already compiled SQL query; it's simply the data.

This means it's fundamentally impossible to perform SQL injection using prepared statements. The very nature of prepared statements and their relationship with the DBMS prevents this.


P
Peter Mortensen

In SQL Server, using a prepared statement is definitely injection-proof because the input parameters don't form the query. It means that the executed query is not a dynamic query. Example of an SQL injection vulnerable statement.

string sqlquery = "select * from table where username='" + inputusername +"' and password='" + pass + "'";

Now if the value in the inoutusername variable is something like a' or 1=1 --, this query now becomes:

select * from table where username='a' or 1=1 -- and password=asda

And the rest is commented after --, so it never gets executed and bypassed as using the prepared statement example as below.

Sqlcommand command = new sqlcommand("select * from table where username = @userinput and password=@pass");
command.Parameters.Add(new SqlParameter("@userinput", 100));
command.Parameters.Add(new SqlParameter("@pass", 100));
command.prepare();

So in effect you cannot send another parameter in, thus avoiding SQL injection...


R
Rany Albeg Wein

The key phrase is need not be correctly escaped. That means that you don't need to worry about people trying to throw in dashes, apostrophes, quotes, etc...

It is all handled for you.


P
Peter Mortensen
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");

Let’s assume you have that in a Servlet you right. If a malevolent person passed a bad value for 'filter' you might hack your database.


s
sin906

The simple example:

  "select * from myTable where name = " + condition;

And if user input is:

  '123'; delete from myTable; commit;

The query will be executed like this:

  select * from myTable where name = '123'; delete from myTable; commit;

D
DanAllen

Root Cause #1 - The Delimiter Problem

Sql injection is possible because we use quotation marks to delimit strings and also to be parts of strings, making it impossible to interpret them sometimes. If we had delimiters that could not be used in string data, sql injection never would have happened. Solving the delimiter problem eliminates the sql injection problem. Structure queries do that.

Root Cause #2 - Human Nature, People are Crafty and Some Crafty People Are Malicious And All People Make Mistakes

The other root cause of sql injection is human nature. People, including programmers, make mistakes. When you make a mistake on a structured query, it does not make your system vulnerable to sql injection. If you are not using structured queries, mistakes can generate sql injection vulnerability.

How Structured Queries Resolve the Root Causes of SQL Injection

Structured Queries Solve The Delimiter Problem, by by putting sql commands in one statement and putting the data in a separate programming statement. Programming statements create the separation needed.

Structured queries help prevent human error from creating critical security holes. With regard to humans making mistakes, sql injection cannot happen when structure queries are used. There are ways of preventing sql injection that don't involve structured queries, but normal human error in that approaches usually leads to at least some exposure to sql injection. Structured Queries are fail safe from sql injection. You can make all the mistakes in the world, almost, with structured queries, same as any other programming, but none that you can make can be turned into a ssstem taken over by sql injection. That is why people like to say this is the right way to prevent sql injection.

So, there you have it, the causes of sql injection and the nature structured queries that makes them impossible when they are used.