⛳
セキュリティの学習のために、SQLインジェクションを起こせる簡単なWebアプリをPythonで作る
準備
必要なライブラリをインストールする。
$ 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