セキュリティの学習のために、SQLインジェクションを起こせる簡単なWebアプリをPythonで作る

2024/06/30に公開

準備

必要なライブラリをインストールする。

$ pip install flask

ソースコード

main.py
from flask import Flask, request, render_template_string
import sqlite3

app = Flask(__name__)


# データベースの初期化
def init_db():
    conn = sqlite3.connect('example.db')
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT UNIQUE)''')

    users = ['Alice', 'Bob', 'Charlie']
    for user in users:
        cursor.execute("INSERT OR IGNORE INTO users (name) VALUES (?)", (user,))

    conn.commit()
    conn.close()


@app.route('/', methods=['GET', 'POST'])
def home():
    results = None
    if request.method == 'POST':
        name = request.form['name']
        conn = sqlite3.connect('example.db')
        cursor = conn.cursor()

        # SQLインジェクションが可能なクエリ
        query = f"SELECT * FROM users WHERE name = '{name}'"
        try:
            cursor.execute(query)
            results = cursor.fetchall()
        except sqlite3.OperationalError as e:
            results = [str(e)]

        conn.close()

    return render_template_string('''
        <h1>Search Users</h1>
        <form action="/" method="post">
            Name: <input type="text" name="name"><br>
            <input type="submit" value="Search">
        </form>
        {% if results is not none %}
            <h2>Results</h2>
            {% for row in results %}
                <p>{{ row }}</p>
            {% endfor %}
        {% endif %}
    ''', results=results)


if __name__ == '__main__':
    init_db()
    app.run(debug=True)

起動

$ python main.py

検証

  • http://127.0.0.1:5000 にアクセスする
  • Aliceで検索する
    • Aliceのユーザー情報が表示される
  • ' OR name <> ' で検索する
    • 全ユーザーが表示される(SQLインジェクション)

解説

以下のクエリが実行されるため、全ユーザーがヒットする。

SELECT * FROM users WHERE name = '' OR name <> ''

修正方法

パラメータ化されたクエリを使用する。

cursor.execute("SELECT * FROM users WHERE name = ?", (name,))

補足

SQliteは、1度に一つのSQLしか実行できないため、'; select * from users; --のようなクエリはエラーになる。

実行されるSQL

SELECT * FROM users WHERE name = ''; select * from users; --'

エラー内容

sqlite3.ProgrammingError: You can only execute one statement at a time.

※ 最後の--はシングルクオートを無視するために必要。これより後ろは、コメントとして解釈される。

メモ:SQLiteのコマンド

# 接続
sqlite3 ./example.db

# テーブル一覧
.tables

# テーブル定義
.schema

Discussion