2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|

2010年03月02日(Tue)

SQLの数値リテラルをシングルクォートでくくる手法について 2010/03/02SQLの数値リテラルをシングルクォートでくくる手法について をブックマーク 2010/03/02SQLの数値リテラルをシングルクォートでくくる手法についてのブックマーク数

結論

セキュリティ対策の一環としてSQLの数値リテラル(が期待されるパラメータ)をシングルクォートでくくる(文字列扱いする)手法は使用すべきでない

理由

少なくともMySQLでは標榜されている通りの動作はしない上、意図しない副作用が発生する。

解説

数値リテラルをシングルクオートでくくる手法は、これを推奨する大垣氏によると次のような効果があるとされています。参考:なぜPHPアプリにセキュリティホールが多いのか? 第14回 減らないSQLインジェクション脆弱性

  • 攻撃目的の入力が行われてもSQLインジェクションが発生しない
  • 簡単に攻撃用の文字列が検出でき

これらはどちらも数値型が期待されるパラメータに不正なデータが渡されるとクエリエラーが発生することが前提となっています。例として、

SELECT * FROM table WHERE id = $VALUE; --ここでidは整数型

というSQLがあった場合に、$VALUEに"1 OR 1 = 1"という入力がされていた場合、WHEREが無効になってしまいますが、

SELECT * FROM table WHERE id = '$VALUE';

というSQLであれば、$VALUEが数値として解釈できなければクエリエラーになる(であろう)ということです。

しかし、MySQLの場合、データベース側で文字列を可能な限り数値に変換するという動作をするため、クエリエラーが発生しません。どのような変換がされるかはこれだけは覚えておきたい!!MySQL の6つの自動変換 - sakaikの日々雑感~©編に詳説されていますが、簡単に言えば

  • 頭から読んでいって数値と解釈できなくなったら後ろは無視する(例:123abc456→123)
  • 頭から数値と解釈できなければ0とみなす(例:abc123→0)

というものです。これは、数値(が期待される)パラメータを文字列扱いすればクエリエラーになる、という考えとは相容れません。

実際に試してみると、以下の通りwarningは出ていますがエラーにはなりません。

mysql> SELECT * FROM test;
+------+------+
| id   | text |
+------+------+
|    0 | 000  |
|    1 | 111  |
+------+------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM test WHERE id = '1a';
+------+------+
| id   | text |
+------+------+
|    1 | 111  |
+------+------+
1 row in set, 2 warnings (0.00 sec)

mysql> SELECT * FROM test WHERE id = 'hoge';
+------+------+
| id   | text |
+------+------+
|    0 | 000  |
+------+------+
1 row in set, 2 warnings (0.00 sec)

このような変換によってSQLインジェクションを成立させることは難しいとおもいますので、SQLインジェクションを防止するという点については機能しているといえるかもしれません。しかし、大垣氏はこのように主張されています。

整数型にキャストすればよいのでは? と考えられた方もいると思います。しかし,キャストする方法は2つの理由でベストプラクティスとは呼べません。キャストした場合,不正な攻撃目的の入力が行われてもクエリエラーが発生しない可能性があります。

この手法の前提が「不正入力があればクエリエラーが発生する」というものである以上、SQLインジェクションが成立しないのは単なる偶然と言うべきだと考えます。また、クエリエラーが発生しないので、簡単に攻撃用の文字列が検出できるという機能も実現されていません。

また、クエリエラーが発生しないだけでなく、開発者の意図しない検索条件の成立や異常データの挿入が行われるという問題があります。上記の例の通り、"1a"→1、"hoge"→0として検索条件が成立していますが、これはほぼすべての場合において開発者が意図したものではないはずですし、常識的な動作とも異なります。

なお、この手法を用いなかった場合には、MySQLにおいてもクエリは失敗します。

mysql> select * FROM test WHERE id = hoge;
ERROR 1054 (42S22): Unknown column 'hoge' in 'where clause'

mysql> SELECT * FROM test WHERE id = 1a;
ERROR 1054 (42S22): Unknown column '1a' in 'where clause'

これはおそらく開発者の意図通りでしょう。

もっと根本的なレベルでこの手法を用いるべきでない理由があります。このような問題がこの手法を推奨する側から全く示されていないことです。本記事の内容は、MySQLという超メジャーなデータベースについてすら全く検証が行われていないことを示しています。文字列→数値の変換については、数値リテラルをシングルクォートで囲むことの是非 - ockeghem(徳丸浩)の日記において一部のデータベースについて検証された結果が記載されていますが、データベースによってかなりばらつきがあります。パラメータやバージョンの違いによっても動作が変わるかもしれません。実際手元のMySQLはこの表とは違う動作をしました。

以上の通り、この手法は

  • 少なくともMySQLにおいては標榜されている機能は全く実現されない
  • 他のデータベースにおいても慎重に検証が必要だが、その分の労力はパラメータの検証やテストを確実に行う方向に費やすべき。
  • 仮に検証の結果ある時点では動いたとしても何らかのきっかけで意図した動作をしなくなる可能性がある

という理由で全く使うべきではありません。


2010年03月01日(Mon)

気象庁が津波の予想が過大だったと謝罪。 2010/03/01気象庁が津波の予想が過大だったと謝罪。 をブックマーク 2010/03/01気象庁が津波の予想が過大だったと謝罪。のブックマーク数

tag: news

南米チリ中部沿岸の大地震で発生した津波で、気象庁は1日午前10時15分、太平洋沿岸全域に出していた津波警報や注意報をすべて解除した。同庁の関田康雄・地震津波監視課長は同日午前の記者会見で、「津波の予測が過大だった。警報が長引き迷惑をかけたことをおわびしたい」と語った。

地球のほぼ真裏で起こった地震で予想3m、実績1.2mは大当たりの範疇だと思います。「過大」と言いますが、警報が出ているにもかかわらず海岸地帯でマラソン大会を強行した気が狂ってるとしか思えない自治体が存在することからも明らかなとおり、なんの根拠もなく勝手に過小に修正する人々が存在しますので、万が一過小な予想をした場合、甚大な被害が発生する可能性があります。

気象庁は批判に対し、「警報を無視し人命を軽視する人々が存在する以上、可能な限り大きな予想をしておく責任がある」と毅然として反論すべきでしょう。


2010年02月28日(Sun)

おやつ 2010/02/28おやつ をブックマーク 2010/02/28おやつのブックマーク数

tag: diary

枚方市駅構内で売っていた焼き芋スイートポテト

焼き芋スイートポテト

晩ご飯 2010/02/28晩ご飯 をブックマーク 2010/02/28晩ご飯のブックマーク数

tag: diary

ポテトとベーコンのグラタン(2日連続2回目)

ポテトとベーコンのグラタン


2010年02月23日(Tue)

FreeBSDでワンタイムパスワードを使ってsshログインする方法 2010/02/23FreeBSDでワンタイムパスワードを使ってsshログインする方法 をブックマーク 2010/02/23FreeBSDでワンタイムパスワードを使ってsshログインする方法のブックマーク数

FreeBSDには標準でワンタイムパスワード機構(OPIE)があり、ワンタイムパスワードでのsshログインが可能です。実際に試してみました。

前提

  • 現在は公開鍵認証だけ有効になっている(普通そうしますよね)
  • UNIXアカウント/パスワード認証は引き続き使用しない

まずこれを実現するために、PAMのsshdの設定を変更します。/etc/pam.d/sshdです。デフォルトでは

auth            sufficient      pam_opie.so             no_warn no_fake_prompts
auth            requisite       pam_opieaccess.so       no_warn allow_local
#auth           sufficient      pam_krb5.so             no_warn try_first_pass
#auth           sufficient      pam_ssh.so              no_warn try_first_pass
auth            required        pam_unix.so             no_warn try_first_pass

こうなってるところを
auth            requisite       pam_opie.so             no_warn no_fake_prompts

これだけに変更します。この変更は十分注意してください。油断するとパスワード無しでログイン出来る状態になったりします。ここにたどり着くまでにえらい苦労したんですがそれは別記

もしportsからsudoをインストールして使用している場合、こっちでもワンタイムパスワードが有効になってしまいます。それが鬱陶しい場合はsudoについてはワンタイムパスワード認証を無効にします。/usr/local/etc/pam.d/sudoの

# auth
auth            include         system

こうなっているところを

auth            required        pam_unix.so             no_warn try_first_pass

こう変更します。いっそのこと/etc/pam.d/systemの

auth            sufficient      pam_opie.so             no_warn no_fake_prompts
auth            requisite       pam_opieaccess.so       no_warn allow_local

この行をコメントアウトしてしまってもいいかもしれません。

sshdの設定を変更します。/etc/ssh/sshd_configです。

#ChallengeResponseAuthentication yes
 :
#UsePAM yes

デフォルトではこうですが、どっちかをnoにしていると思いますので元に戻します。変更したらsshdを再起動します。

公開鍵認証を使わずにログインを試して、リモートログイン出来ないことを確認します。繰り返しますが、設定ミスするとパスワード無しでログイン出来る状態になることがあります。

最後に、利用したいユーザーでワンタイムパスワードを有効にします。ローカルまたは安全な接続のコンソール上でopiepasswdを実行します

% opiepasswd -c foo
Adding foo:
Only use this method from the console; NEVER from remote. If you are using
telnet, xterm, or a dial-in, type ^C now or exit with no password.
Then run opiepasswd without the -c parameter.
Using MD5 to compute responses.
Enter new secret pass phrase: (パスフレーズ入力)
Again new secret pass phrase: (繰り返し)

ID foo OTP key is 499 ho1234
FOO BAR HOGE FUGA PIYO

これでユーザーfooのワンタイムパスワードが有効になりました。

最後から2行目にある499というのがシーケンスナンバーです。ワンタイムパスワードを1回使うと、この数字が1個減ります。その後のho1234はパスワードを生成するためのseed(種)です。ワンタイムパスワードを生成するにはシーケンスナンバーとseedとパスフレーズが必要です。

次に要求されるシーケンスナンバーは498になるので、作ってみます。

% opiekey 498 ho1234
Using the MD5 algorithm to compute response.
Reminder: Don't use opiekey from telnet or dial-in sessions.
Enter secret pass phrase:
NESS REND NICE WHEN EVER MANA

sshで実際に接続してみると、このようなプロンプトが出ますので

% ssh remotehost
otp-md5 498 ho1234 ext
Password:

さっき生成された"NESS REND NICE WHEN EVER MANA"を入力するとログインできるはずです。

ワンタイムパスワードはログイン先のホストで生成する必要はありません。シーケンス番号とseedとパスフレーズで一意なものが生成されるので、手元のホストでopiekeyコマンドを使って生成されたパスワードでもログインできます。他のOS用のパスワード生成機もあるらしいのですが実物は見つけられていません。

手元でパスワードが生成できない場合、opiekeyに-nオプションを付けると複数のパスワードを生成できます

% opiekey -n 5 999 ho1234
Using the MD5 algorithm to compute response.
Reminder: Don't use opiekey from telnet or dial-in sessions.
Enter secret pass phrase:
995: RACY FAT YAP TIE FORT NAP
996: GIN AWAY DIN HUGE FORT PAP
997: SKIM PAW BONE FALL DUMB BOCK
998: MITE CODY DUSK REEL GLUM ARK
999: HALE PET GEE WYNN DANK AWN

ので、これを控えておけばいいでしょう。未使用の物を間違っても落とさないように。

PAMの設定で悩みまくった記録 2010/02/23PAMの設定で悩みまくった記録 をブックマーク 2010/02/23PAMの設定で悩みまくった記録のブックマーク数

ワンタイムパスワードを使ってsshでログイン出来るようにするまでの/etc/pam.d/sshdをどう書いたらいいか悩みまくった試行錯誤の記録です。結論は、control-flagのsufficientは成功すればそこで打ち切りとマニュアルに書いてあるが実はそうではない

まずデフォルト。

auth            sufficient      pam_opie.so             no_warn no_fake_prompts
auth            requisite       pam_opieaccess.so       no_warn allow_local
auth            required        pam_unix.so             no_warn try_first_pass

これがどういう意味になるかは別記事に書きましたが、簡単に言うとワンタイムパスワードが有効なユーザーはワンタイムパスワード認証でログインでき、ワンタイムパスワードを設定していないユーザーはUNIX認証になります。

失敗例1。UNIX認証を無効にすればいいんだろ?とばかり最後の行をコメントアウトしたとしましょう。

auth            sufficient      pam_opie.so             no_warn no_fake_prompts
auth            requisite       pam_opieaccess.so       no_warn allow_local
#auth           required        pam_unix.so             no_warn try_first_pass

ワンタイムパスワード認証が有効なユーザーでは変化ありませんが、そうでないユーザーはなんとパスワード無しでログインできてしまうという大変危険な設定です。

失敗例2。そうかそうか、ワンタイムパスワード認証を使わないユーザーは上2行を突破するからそこで拒否すればいいんだろう、ということで、こうする。

auth            sufficient      pam_opie.so             no_warn no_fake_prompts
auth            requisite       pam_opieaccess.so       no_warn allow_local
auth            required        pam_deny.so

mod_deny.soはとにかく拒否、という認証モジュールです。これがどうなるかというと、ワンタイムパスワード認証を有効にしているユーザーもログインできなくなります。/var/log/auth.logを見ると

sshd[]: Accepted keyboard-interactive/pam for shin from xxx.xxx.xxx.xxx port 12345 ssh2
sshd[]: fatal: PAM: pam_setcred(): failed to set user credentials

なんて出てます。パスワードを間違えたときは

sshd[]: error: PAM: authentication error for foo from xxx.xxx.xxx.xxx

になるので、ワンタイムパスワード認証自体は正常に動いている様子。

マニュアルを見ても実際に試した結果でもcontrol-flagがsufficientのモジュールは成功すれば後ろの処理は走らないはずなので、後続に何が書いてあっても関係ないはず。なので、認証の問題ではなくてPAMのアカウント/セッション/パスワード管理がらみの問題かな、と判断し試行錯誤すること数日。

結局うまくいかないので、いよいよソースを追いかけてやっと原因がわかりました。

PAMの設定のauth(認証)サービスの部分は実は2回なめられます。実際の認証処理と、エラーメッセージにあるpam_setcred()の処理です。実際の認証処理ではcontrol-flagではマニュアル通りに処理されますが、pam_setcred()の処理でなめられるときは、sufficientだけ動作が異なり、成功しても処理が継続されるのです。PAMのソースに思いっきり書いてありました


/*
* For pam_setcred() and pam_chauthtok() with the
* PAM_PRELIM_CHECK flag, treat "sufficient" as
* "optional".
*/

なんでこんな実装になってるんでしょうね。いやなんか意味はあるんでしょう。しかしみんなよくこんなの悩まずに設定できるなぁ。だれも使ってないのか?

理由がわかったところでどう設定すべきか改めて考えて結局こうなりました。

auth            requisite       pam_opie.so             no_warn no_fake_prompts

とにかくシンプルに考えると、うちの要件ではワンタイムパスワード認証に成功するかどうかが全て、でいいのです。

なまじデフォルト設定をベースに考えると失敗する、という事例でした。


タグ

www.flickr.com
This is a Flickr badge showing public items from suzukis tagged with japan. Make your own badge here.

最近の話題 RSS feed

最近のコメント

あわせて読みたい
人気ブログランキング - とある技術屋の戯れ言 この日記のはてなブックマーク数
メール("no-spam."を削除してください)