クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

入力が命令に変わる攻撃(SQLインジェクション)

このレッスンのあとで: 打ち込まれた文字で変わらないように、検索の質問文を直せる。

55分のレッスンコマンド3件SQL インジェクションのラボ:弱い検索を攻撃し、クエリを直して確かめる

アポストロフィで出るエラー

洋書を売る小さなネットショップがあります。お客さんが検索窓に「Alice's Adventures」と入力しました。

画面には、見慣れない英語のエラーが出ました。店主は小さな不具合だと思っています。でも本当は、検索窓に打った文字が、店の質問文の一部になっていたのです。

たった1つのルール

注意

攻撃してよいのは、自分専用のラボのコピーだけです。このラボは、わざと弱く作った店で、インターネットにはつながっていません。許可のない他人のシステムを攻撃すると、不正アクセス禁止法に触れる犯罪になります。

このレッスンのゴール

メモ

打ち込まれた文字で変わらないように、検索の質問文を直せる。

今日のキーワード

ことばいみれい
データベースデータをまとめて持ち、質問に答える仕組みです。
クエリプログラムがデータベースに送る質問文です。SELECT name FROM products
SQLインジェクション打ち込んだ文字で、クエリを書き換える攻撃です。
パラメータクエリの外に分けて送る値です。?
文字列プログラムの中の文字のまとまりです。"pen"
入力利用者が画面に打ち込む、または送る内容です。

学生が入力するセルは、このプレビューでは空白です。

つなげたクエリは書き換えられる

クエリを、決まった前半と利用者の入力をつなげて作るプログラムがあります。データベースには、長い1本の文字列が届きます。

どこまでがプログラマーの文で、どこからが利用者の文字か、データベースには見分けられません。入力に引用符が入っていると、文字列がそこで終わってしまい、後ろがクエリの続きとして読まれます。

プログラムが作るクエリ

コード: python
# term を "Notebook" や "Tom's" に変えて、実行します。
term = "ペン"
start = "SELECT name, price FROM products WHERE name LIKE '%"
query = start + term + "%'"
print(query)

例: 3つの検索

入力データベースに届くクエリ起こること
ペン... LIKE '%ペン%'思ったとおりに動く
Tom's... LIKE '%Tom's%'引用符で文字列が終わり、クエリが壊れる
x' AND 1=2 --... LIKE '%x' AND 1=2 --%'入力した人が、条件を自分で足せてしまう

学生が入力するセルは、このプレビューでは空白です。

ラボの店を見てみる

ラボには、この弱点を持つ本物の店があります。ラボを起動して、次の3つを実行しましょう。どれも見るだけです。

1つ目はふつうの検索です。2つ目はアポストロフィ入りの名前で、店はこれをうまく扱えません。3つ目は、クエリを作っているコードの1行を表示します。

ふつうの検索

コマンド
curl -s -G --data-urlencode "q=pen" http://app:8000/search

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

アポストロフィ入りの名前

コマンド
curl -s -G --data-urlencode "q=Baker's" http://app:8000/search

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

クエリを作っている行

コマンド
docker exec app grep -n "rows = db.execute" /tmp/app/app.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

3つの答えを読む

1つ目は Pen 1.2 と、名前と値段を表示します。2つ目は database error で始まる行を表示します。店は、あなたのアポストロフィをそのままクエリに入れたのです。

3つ目は、行番号と長いコードを1行表示します。その中の + を探してください。+ のたびに、入力がクエリへつなげられています。

パラメータで文字を分ける

直し方は パラメータ です。クエリには値が入る場所に ? だけを書き、打たれた文字は別にして送ります。現場では、プレースホルダやプリペアドステートメント(prepared statement)とも呼びます。

データベースは先にクエリを読み、形を決めてしまいます。あとから届く文字は、値としてしか扱われません。引用符が入っていても、クエリは終わりません。

パラメータを使った検索

コード: python
import sqlite3
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE products (name TEXT, price REAL)")
db.execute("INSERT INTO products VALUES (?, ?)", ("Tom's ノート", 480))
db.execute("INSERT INTO products VALUES (?, ?)", ("ペン", 120))
# term を変えて実行します。何を打っても、クエリは途中で終わりません。
term = "Tom's"
rows = db.execute("SELECT name, price FROM products WHERE name LIKE ?", ("%" + term + "%",)).fetchall()
print(rows)

よくある間違い

注意

「アポストロフィを消せば安全だ」と考える人がいます。それは違います。取りこぼしがあれば、そこが穴になります。パラメータは、入力をクエリに混ぜないから安全です。どんな入力でも同じです。値はすべてパラメータで渡しましょう。

パラメータにできないもの

パラメータは値の代わりになります。でも、列の名前や並べ替えの向きの代わりにはなりません。値段順や名前順を選ばせるページは、その語をクエリに入れるしかありません。

安全な方法は、許す語の短い一覧を作ることです。選ばれた語が一覧になければ、決めておいた初期値を使います。

一覧にある語だけを使う

コード: python
ALLOWED = ["name", "price"]
# choice を "price" にして、次に "stock; drop" にして、実行します。
choice = "name"
if choice in ALLOWED:
    column = choice
else:
    column = "name"
print("ORDER BY " + column)

このラボとソースについて: SQL インジェクションのラボ:弱い検索を攻撃し、クエリを直して確かめる

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。