beroの日記: SQLインジェクション対策:偽プリペアドステートメントに注意 4
「SQLインジェクション対策」でGoogle検索して上位15記事を検証した
まとめに
SQLインジェクションについて書くときに以下のメッセージを必ず含めて欲しいです。
-単にプリペアドステートメントを使え
-絶対に文字列結合でSQLを構築しようとしてはいけない
-IPAの「安全なSQLの呼び出し方」を読むこと
とあるが、
- 本物のプリペアドステートメントであることを確かめる
を付け加えたい。(まあ『IPAの「安全なSQLの呼び出し方」を読むこと』を守れば、そこに書いていることではあるが)
IPA本では本物のプリペアドステートメントを「静的プレースホルダ」偽物(エミュレーション)を「動的プレースホルダ」と呼んでいるが、
後者はプリペアドステートメントぽい記述をライブラリ内で文字列結合してSQLを構築している。
偽プリペアドステートメントも無意味ではない。
少なくとも自前で文字列連結するのに比べ、単純なバグやエスケープ忘れは無くなる(ことが期待できる)。
しかし文字コードの違いや不正文字によるエスケープ抜けは起こりうるので、脆弱性が残りうる。
IPA本では、Perl + MySQL (DBD::MySQL)、Java + MySQL(MySQL Connector/J)において、デフォルトでは動的プレースホルダなので、静的プレースホルダを使うようにパラメータを指定する必要がある旨書いている。
同様にPHP + MySQL (pdo_mysql) もデフォルトでは動的プレースホルダなので(バージョンによるかも)、パラメータを指定する必要がある
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES,FALSE)
PDO は元々この機能をサポートしていないドライバに対して プリペアドステートメントとバインドパラメータをエミュレートします。 このため、ある形式をサポートしているがその他の形式をサポートしていない ドライバの場合、名前もしくは疑問符形式のパラメータを他の適当な値に 書き換えることも可能です。
PDO::ATTR_EMULATE_PREPARES プリペアドステートメントのエミュレーションを有効または無効にする。 ドライバによってはネイティブのプリペアドステートメントをサポートしていなかったり 完全には対応していなかったりするものがある。この設定を使うと、常に プリペアドステートメントをエミュレートする (TRUE の場合) か、 ネイティブのプリペアドステートメントを使おうとする (FALSE の場合) かを設定できる。現在のクエリを正しく準備できなかった場合は、常にエミュレート方式を使う。
と書いてて気づいたが、恐ろしいことが書いてある。
現在のクエリを正しく準備できなかった場合は、常にエミュレート方式を使う。
いらんことすんなや
pdo_mysql:
if (mysql_stmt_prepare(S->stmt, sql, sql_len)) {
/* TODO: might need to pull statement specific info here? */
/* if the query isn't supported by the protocol, fallback to emulation */
if (mysql_errno(H->server) == 1295) {
if (nsql) {
efree(nsql);
}
goto fallback;
}
fallbackすんなや
pdo_sqlite:
i = sqlite3_prepare(H->db, sql, sql_len, &S->stmt, &tail);
if (i == SQLITE_OK) {
return 1;
}
pdo_sqlite_error(dbh);
return 0;
pdo_pgsql:
PDO_ATTR_CURSORがPDO_CURSOR_SCROLLだったらエミュレーション。
あとコンパイルオプションとかプロトコルが2以下(PostgreSQL 7.4未満)でも。
ネイティブprepareに失敗したらエラー。
sqliteとpsgqlはドライバレイヤではエラーを返しているが、上位で(マニュアル通り)fallbackしてるかどうかは知らね。
補足 (スコア:1)
とはいえ、今時は何らかのフレームワークを使ってるだろうから、フレームワーク->ORM->DBライブラリと追っかけて「本物のプリペアドステートメントであることを確かめる」のは結構大変な気がする。
---
ところで「絶対に文字列結合でSQLを構築しようとしてはいけない」件であるが、
入力の個数が不定な場合がある(特にIN句)。
select ... where .... in (?,?,?..?)
やむを得ずB部分を文字列結合で生成してprepare -> bindしてるが、「絶対に文字列結合を使わない」良い方法があるだろうか?
Re:補足 (スコア:1)
しまった表示されてない
select ... where .... in (?,?,?..?)
|-------A(固定)----------|-B(生成)-|
Re:補足 (スコア:1)
Re: (スコア:0)
PHPで、ですが…。
$args = array('a', 'b', 'c', ...);
$sql = 'SELECT ... WHERE ... IN (' . join(', ', array_fill(0, count($args), '?')) . ')';
なんてのはどうでしょう?