From 733426b031054c56d65a6b9a4f5935e556cfe7d8 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 1 Aug 2022 10:05:51 +0200 Subject: [PATCH 1/7] gh-95271: Extract placeholders howto from sqlite3 tutorial --- Doc/library/sqlite3.rst | 58 ++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index a0b5c73d7a4ad4..cb12f4a05b0285 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -85,7 +85,9 @@ At this point, our database only contains one row:: The result is a one-item :class:`tuple`: one row, with one column. Now, let us insert three more rows of data, -using :meth:`~Cursor.executemany`:: +using :meth:`~Cursor.executemany`. +We use :ref:`question mark placeholders ` +to bind ``data`` to the SQL statement. >>> data = [ ... ('2006-03-28', 'BUY', 'IBM', 1000, 45.0), @@ -104,32 +106,7 @@ Then, retrieve the data by iterating over the result of a ``SELECT`` statement:: ('2006-04-06', 'SELL', 'IBM', 500, 53.0) ('2006-04-05', 'BUY', 'MSFT', 1000, 72.0) - -.. _sqlite3-placeholders: - -SQL operations usually need to use values from Python variables. However, -beware of using Python's string operations to assemble queries, as they -are vulnerable to SQL injection attacks (see the `xkcd webcomic -`_ for a humorous example of what can go wrong):: - - # Never do this -- insecure! - symbol = 'RHAT' - cur.execute("SELECT * FROM stocks WHERE symbol = '%s'" % symbol) - -Instead, use the DB-API's parameter substitution. To insert a variable into a -query string, use a placeholder in the string, and substitute the actual values -into the query by providing them as a :class:`tuple` of values to the second -argument of the cursor's :meth:`~Cursor.execute` method. An SQL statement may -use one of two kinds of placeholders: question marks (qmark style) or named -placeholders (named style). For the qmark style, ``parameters`` must be a -:term:`sequence `. For the named style, it can be either a -:term:`sequence ` or :class:`dict` instance. The length of the -:term:`sequence ` must match the number of placeholders, or a -:exc:`ProgrammingError` is raised. If a :class:`dict` is given, it must contain -keys for all named parameters. Any extra items are ignored. Here's an example of -both styles: - -.. literalinclude:: ../includes/sqlite3/execute_1.py +You've now created an SQLite database using the :mod:`!sqlite3` module. .. seealso:: @@ -1444,6 +1421,33 @@ Python types via :ref:`converters `. How-to guides ------------- +.. _sqlite3-placeholders: + +SQL operations usually need to use values from Python variables. However, +beware of using Python's string operations to assemble queries, as they +are vulnerable to SQL injection attacks (see the `xkcd webcomic +`_ for a humorous example of what can go wrong):: + + # Never do this -- insecure! + symbol = 'RHAT' + cur.execute("SELECT * FROM stocks WHERE symbol = '%s'" % symbol) + +Instead, use the DB-API's parameter substitution. To insert a variable into a +query string, use a placeholder in the string, and substitute the actual values +into the query by providing them as a :class:`tuple` of values to the second +argument of the cursor's :meth:`~Cursor.execute` method. An SQL statement may +use one of two kinds of placeholders: question marks (qmark style) or named +placeholders (named style). For the qmark style, ``parameters`` must be a +:term:`sequence `. For the named style, it can be either a +:term:`sequence ` or :class:`dict` instance. The length of the +:term:`sequence ` must match the number of placeholders, or a +:exc:`ProgrammingError` is raised. If a :class:`dict` is given, it must contain +keys for all named parameters. Any extra items are ignored. Here's an example of +both styles: + +.. literalinclude:: ../includes/sqlite3/execute_1.py + + .. _sqlite3-adapters: Using adapters to store custom Python types in SQLite databases From c4ac173ec8022f59e51b374c8805ee23dbe390f7 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 1 Aug 2022 10:20:23 +0200 Subject: [PATCH 2/7] Syntax --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index cb12f4a05b0285..511f88debf885b 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -87,7 +87,7 @@ one row, with one column. Now, let us insert three more rows of data, using :meth:`~Cursor.executemany`. We use :ref:`question mark placeholders ` -to bind ``data`` to the SQL statement. +to bind ``data`` to the SQL statement:: >>> data = [ ... ('2006-03-28', 'BUY', 'IBM', 1000, 45.0), From 8e10ac4777343ba4d38d8594983b6c2ccdcc0dea Mon Sep 17 00:00:00 2001 From: Erlend Egeberg Aasland Date: Wed, 3 Aug 2022 09:17:01 +0200 Subject: [PATCH 3/7] Update Doc/library/sqlite3.rst --- Doc/library/sqlite3.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 511f88debf885b..05afbb7e5c641e 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1423,6 +1423,9 @@ How-to guides .. _sqlite3-placeholders: +Using placeholders to bind values in SQL queries +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + SQL operations usually need to use values from Python variables. However, beware of using Python's string operations to assemble queries, as they are vulnerable to SQL injection attacks (see the `xkcd webcomic From bf717fe4b21bba804431edb0b43c37282e093c8a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 3 Aug 2022 22:03:10 +0200 Subject: [PATCH 4/7] Address review: mention injection attacks --- Doc/library/sqlite3.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 9c89afab314444..d6f977e4d33f2d 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -85,9 +85,7 @@ At this point, our database only contains one row:: The result is a one-item :class:`tuple`: one row, with one column. Now, let us insert three more rows of data, -using :meth:`~Cursor.executemany`. -We use :ref:`question mark placeholders ` -to bind ``data`` to the SQL statement:: +using :meth:`~Cursor.executemany`:: >>> data = [ ... ('2006-03-28', 'BUY', 'IBM', 1000, 45.0), @@ -96,6 +94,12 @@ to bind ``data`` to the SQL statement:: ... ] >>> cur.executemany('INSERT INTO stocks VALUES(?, ?, ?, ?, ?)', data) +Notice that we used question mark placeholder to bind *data* to the query. +Always use placeholders instead of :ref:`string formatting` +to bind Python values to SQL statements, +in order to avoid SQL injection attacks. +See the :ref:`placeholders how-to ` for more details. + Then, retrieve the data by iterating over the result of a ``SELECT`` statement:: >>> for row in cur.execute('SELECT * FROM stocks ORDER BY price'): From a0cdf612e81f6b55f4ba2a35a51d436b8bad7480 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 3 Aug 2022 22:11:49 +0200 Subject: [PATCH 5/7] Improvements: - plural agreement, as CAM says - more brevity - add wikipedia link to SQL injection attacks --- Doc/library/sqlite3.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index d6f977e4d33f2d..5e8532ef4f5243 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -94,10 +94,10 @@ using :meth:`~Cursor.executemany`:: ... ] >>> cur.executemany('INSERT INTO stocks VALUES(?, ?, ?, ?, ?)', data) -Notice that we used question mark placeholder to bind *data* to the query. +Notice that we used question mark placeholders to bind *data* to the query. Always use placeholders instead of :ref:`string formatting` to bind Python values to SQL statements, -in order to avoid SQL injection attacks. +to avoid `SQL injection attacks`_. See the :ref:`placeholders how-to ` for more details. Then, retrieve the data by iterating over the result of a ``SELECT`` statement:: @@ -112,6 +112,7 @@ Then, retrieve the data by iterating over the result of a ``SELECT`` statement:: You've now created an SQLite database using the :mod:`!sqlite3` module. +.. _SQL injection attacks: https://en.wikipedia.org/wiki/SQL_injection .. seealso:: @@ -1457,7 +1458,7 @@ Using placeholders to bind values in SQL queries SQL operations usually need to use values from Python variables. However, beware of using Python's string operations to assemble queries, as they -are vulnerable to SQL injection attacks (see the `xkcd webcomic +are vulnerable to `SQL injection attacks`_ (see the `xkcd webcomic `_ for a humorous example of what can go wrong):: # Never do this -- insecure! From 1d840ca9533c406ea23baf8298609ac1b670f8f6 Mon Sep 17 00:00:00 2001 From: Erlend Egeberg Aasland Date: Wed, 3 Aug 2022 22:50:38 +0200 Subject: [PATCH 6/7] Use literal question mark Co-authored-by: CAM Gerlach --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 5e8532ef4f5243..9b1cc5b32d1fb9 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -94,7 +94,7 @@ using :meth:`~Cursor.executemany`:: ... ] >>> cur.executemany('INSERT INTO stocks VALUES(?, ?, ?, ?, ?)', data) -Notice that we used question mark placeholders to bind *data* to the query. +Notice that we used ``?`` placeholders to bind *data* to the query. Always use placeholders instead of :ref:`string formatting` to bind Python values to SQL statements, to avoid `SQL injection attacks`_. From 86d86c093f55afcba8a5aa92870b925a587e3aba Mon Sep 17 00:00:00 2001 From: Erlend Egeberg Aasland Date: Thu, 4 Aug 2022 11:43:45 +0200 Subject: [PATCH 7/7] Consistency FTW Co-authored-by: Ezio Melotti --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 9b1cc5b32d1fb9..1c4f0bb0312cdc 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -95,7 +95,7 @@ using :meth:`~Cursor.executemany`:: >>> cur.executemany('INSERT INTO stocks VALUES(?, ?, ?, ?, ?)', data) Notice that we used ``?`` placeholders to bind *data* to the query. -Always use placeholders instead of :ref:`string formatting` +Always use placeholders instead of :ref:`string formatting ` to bind Python values to SQL statements, to avoid `SQL injection attacks`_. See the :ref:`placeholders how-to ` for more details.