Web制作、Web開発の歩き方

初心者のためのDjango入門

■第10話:SQLインジェクション対策をして素のSQLを実行しよう

(最終更新日:2023.07.29)

Djangoフレームワークのイメージ
この記事は5分で読めます!
(絵が小さい場合はスマホを横に)

「検索速度を上げるために素のSQLを実行したい!」

DjangoのORMは様々な書き方に対応できるため、基本的なデータベース操作はORMで充分である。 しかしながら、検索速度を上げたいときなど、どうしても素のSQLを使わざる得ないこともある。 そんなときに重要なのが、SQLインジェクション対策だ。 今回は素のSQLを使う上で、セキュリティ的に重要なSQLインジェクション対策の手法を紹介する。


1.SQLインジェクションとは

SQLインジェクションとは、ユーザーが入力した情報に基づきSQLを実行する際に、 その情報がSQLの命令として認識され、悪意のあるSQL文を実行させる方法である。 下記はその例で、黄色のマーカー部分が悪意のあるユーザーがパラメータとして入力した例である。 本来ある検索の命令(SELECT * FROM 商品データ WHERE id = 1;)を;を入れることで一旦終われせ、 その後のSQL文(SELECT * FROM 顧客データ;)で本来公開してはいけない顧客データを表示させている。

SELECT * FROM 商品データ WHERE id = 1;SELECT * FROM 顧客データ;

実際、SQLインジェクションにより、数千万円の損害賠償が発生したという事例はある。このような悲劇を招かないためにも、 ユーザーが入力した情報(黄色の部分)はSQL文を実行せず、ただの値としてのみ入力させる必要がある。これがSQLインジェクションの根本的な対策になる。

2.PythonのSQLインジェクション対策

PythonのSQLインジェクション対策と書いたが、PHPでもPerlでも若干書き方が変わるだけでやることは変わらない。 SQL部分とパラメータ部分(ユーザー入力部分)に分けて、パラメータ部分を値(SQLとして読ませないように)して、悪意のあるコードを実行させないということである。 まずは、SQL対策をしていない記述を見てみよう。下記はパラメータとしてユーザーid番号を入力して、その個人データを取得している。 この場合、本人のidが1番で本人のデータだけが取得できる。

UniqueConstraintの使用例

SQLインジェクション対策をしていないコード

次に、このparameter部分に悪意のあるコード(1 OR 1 = 1)を入力する。すると、本人以外の全ての人のデータが表示される。 もちろん、今回のようにidを直接指定してデータを取得するということは無いのだが、 1=1というような、いつでも成立する検索条件を書かれるとデータを盗られてしまう。

UniqueConstraintの使用例

対策していないコードに対して、SQLインジェクションが成功した例

ここで、上記のような操作をさせないためにも、SQLインジェクション対策を行う。 同じ入力値に対して、SQLインジェクション対策したコードに変える。 するとエラーを吐き出し、SQLは実行されない。書き方自体は難しくないので、素のSQLを扱う際は覚えておこう。 下記の書き方はPythonで対策を行った一例だが、他にもDjangoには、execute関数を用いた対処法、raw関数を用いた対処法がある。 %sは連続した文字列を意味する。 つまり、入力されたデータ(1 OR 1 = 1)が一つの文字列(タプルや配列の先頭の文字列)として認識され、プレースホルダとして機能している。

UniqueConstraintの使用例

SQLインジェクション対策した例

SQLインジェクション対策に関しての 詳しい情報は、安全SQLの呼び出し方を見てほしい。 各言語(Java, PHP, Perl)の書き方、具体的な保護のメカニズムを説明している。 アプリケーション側の言語問わず、最も安全な方法は、prepared文で事前にSQLの骨組み部分を記述し、 SQLを実行する直前にbindするパラメータを値にして、SQLを実行する静的プレースホルダが良いとされている。 SQLの実行効率もこの方法が良い。 また、今回用いたMySQLdbではなく、mysql-connector-pythonを用いる場合は、prepared=Trueにして、その機能を有効にできる。 調べて試してみよう。

3.Djangoにおける生SQLの実行

最後にDjangoにおける生SQLの実行とSQLインジェクション対策について説明する。 SQLインジェクション対策については、フレームワークを用いないPythonで行うときと殆ど変わらない。 下記はDjangoで生SQLを実行する際に、SQLインジェクション対策を行った例である。 Django公式ドキュメントに書かれたコードそのままであるが、ここでやるべきは、 プレースホルダを用いて、実際、悪意のあるコードが弾かれることを確認するということだ。 そこだけ頭の片隅に覚えておこう。

ExclusionConstraintの使用例


4.まとめ

今回、主にPythonのSQLインジェクション対策について説明した。 DjangoのORM機能は充実しており、SQLを書かずともデータベース操作することができる。 生SQLを書くことはほとんどないだろう。 しかしながら「検索スピードを上げたい」「複雑な命令が必要」という場合は使う必要が出てくる。 そんなとき、頭の片隅に今回の対策を思い出して頂ければ幸いである。


▼参考図書、サイト

安全なウェブサイトの作り方 - 1.1 SQLインジェクション  IPA 情報処理推進機構
Python MySQL Connector エスケープについて  fukuの犬小屋
DjangoにおけるSQLインジェクション  Qiita
Performing raw SQL queries  Django公式ドキュメント


Introduction to Django for Beginners ■ Episode 10: Protect Against SQL Injection and Use Raw SQL (Last updated: 2023.07.29) Image of the Django framework This article takes about 5 minutes to read! (Rotate your phone for a better view if the image appears small.) "I want to improve search speed using raw SQL!" Django's ORM is flexible and covers most database operations. However, in cases where performance is crucial, you may need to use raw SQL. When doing so, SQL injection prevention becomes critical. This article explains how to securely execute raw SQL with proper countermeasures. [Table of Contents] What is SQL Injection? Preventing SQL Injection in Python Using Raw SQL in Django Summary 1. What is SQL Injection? SQL injection is an attack method where user input is interpreted as SQL commands and malicious SQL is executed. For example, in the case below, the highlighted portion is the malicious input. A normal query like SELECT * FROM products WHERE id = 1; is prematurely ended with a semicolon, and the next query SELECT * FROM customers; is executed, revealing confidential customer data. SELECT * FROM products WHERE id = 1;SELECT * FROM customers; There are real cases where SQL injection has caused losses in the tens of millions of yen. To prevent such incidents, user input must not be executed as SQL, but treated strictly as data. This is the core concept of SQL injection prevention. 2. Preventing SQL Injection in Python Although this example uses Python, the concept applies across languages like PHP and Perl. The idea is to separate the SQL and parameter (user input) parts, ensuring that input is treated only as data and not executable code. First, here is an example without any protection — fetching user data by inputting an ID. Example of code without SQL injection protection Code without SQL injection protection Now, if a malicious input like 1 OR 1 = 1 is given, it returns all user data. While this is an oversimplified example, an always-true condition like 1 = 1 can expose unintended data. SQL injection attack succeeds Example of a successful SQL injection To prevent this, modify the code to use prepared statements or parameterized queries. The following example shows a secure implementation. It uses a placeholder like %s to represent user input, which is treated as a single string value, preventing the execution of malicious commands. Example of protected SQL Code with SQL injection protection For more details on SQL injection protection, refer to the Secure SQL Practices Guide from Japan's Information-technology Promotion Agency (IPA). Using prepared statements and binding parameters just before execution is considered the safest approach across all languages. If using mysql-connector-python instead of MySQLdb, enable prepared=True to activate this feature. 3. Using Raw SQL in Django In Django, executing raw SQL with protection is quite similar to plain Python. The following example is from Django’s official documentation. The key point is to use placeholders so that even if malicious input is provided, it is safely handled. Always remember this when working with raw SQL. Example using ExclusionConstraint in Django 4. Summary This article focused on SQL injection prevention, especially in Python. Django’s ORM is powerful enough to avoid writing SQL in most cases. However, in scenarios that demand performance or complex logic, raw SQL might be necessary. When that time comes, keep today’s practices in mind to protect your data. ▼References   How to Create Secure Websites - 1.1 SQL Injection (IPA, Japan)   About Escaping in Python MySQL Connector (Fuku’s Dog House)   SQL Injection in Django (Qiita)   Performing Raw SQL Queries (Official Django Documentation)