O/Rマッパーは不要なのでしょうか?::SQLインジェクション対策「入力は汚染されている前提で設計せよ」

オブジェクト関係マッピング(O/Rマッパー)の不要という議論はたびたびありますが、
セキュリティ面と複雑性について思ったことを書いてみます。



自分はセキュリティ上の配慮から、ORMは総論としてあったほうがいいと思う派です。



O/Rマッピングとは

物事を複雑にしない
ともかく、システムとはシンプルであって、人々の作業をサポートするものでなくてはいけない。

そのためには、システムやデータベースも、普通の現実世界を単純に現実化できるレベルまで簡素化されてこないといけない。

O/Rマッピングは、その過渡期でのゆがんだ形であると思っている。

将来、オブジェクトを中心とした設計でシステムを作れるようにするためには、ここらへんの問題は解決されなくてはいけない。

かつてはORMは仕様上の統一性から、生み出された経緯がありました。


統一したSQL記法を皆が覚えるのは大変だから、変数だけ入れればなんとかなる統一関数でいいじゃん、という考え方です。



8年前からこのような議論がありました。



複雑さを増すために不要という効率性と、仕様の可視性との戦いでした。



ORMの必要か不要の議論はなお展開です。



この議論に昨今では一つの重要な判断軸が入ってくると思います。


セキュリティ面です。





セキュリティ面

SQLインジェクション対策


ORMでは、複雑さを取り除くために、別の複雑さが発生するという部分がありました。


生産性をとるためのトレードオフをするかしないかという、葛藤がありました。




一方でときは流れ、SQLインジェクションなる(当時)未知なる脅威が生まれてきます。


入力項目に見込んでいたSQLとは異なる変数を強制的に代入して、意図されていない情報を引き出す手段です。


SQLインジェクション

SQLインジェクション(英: SQL Injection)とは、アプリケーションのセキュリティ上の不備を意図的に利用し、アプリケーションが想定しないSQL文を実行させることにより、データベースシステムを不正に操作する攻撃方法のこと。また、その攻撃を可能とする脆弱性のこと。
SQLに別のSQL文を「注入 (inject)」されることから、「ダイレクトSQLコマンドインジェクション」とも呼ばれる。

入力は汚染されている前提で設計せよ

直接SQLを叩いたほうが生産性が高い場合は、よくありますが、ユーザー(Botかも知れません)の様々な入力に耐えうるプレースホルダを自作するのは、あとから効率的でなくなります。





師匠「入力は汚染されている前提で設計せよ」





わが師の言葉でした(なぜか遠い目)



SQLを使うプログラマーにとっては、同時に入力情報の安全性を加味しなければならなくなりました。

それをどのレイヤーで担保するかは、プログラマーに委ねられる形になっています。


エスケープ文字はないか、SQL文を代入されていないか、予期している型通りの入力か、最大値を超えていないか等について開発者が注意する必要が出てきました。



入力フォームに工夫するか、Javascriptで入力を監視するか、RDBの設定を変更するか、
CGIのレベルで入力チェックするか、手段はたくさんあります。



オブジェクトとして汎用的に捉えるのであれば、SQLを発行するタイミングに工夫することが考えられます。

開発言語でのチェック

PHPには、予期せぬ入力を自動エスケープするmagic_quotes_gpcという仕組みや、
MySQL用に文字列をエスケープする関数mysql_real_escape_string()があります。



Pythonには、自動的に処理するプログラムメソッドがあります。



PythonDjangoの場合

入力のプレースホルダを行ってくれる役割を持つORMもあります。

例えばDjangoに実装されているものは



カスタム SQL の実行

connection や cursor は、 PEP 249 で定義さ れている標準的な Python DB-API のほぼ全てに加えて、 Django の トラン ザクション処理 を実装しています。 Python DB-API にあまり詳しくないのなら、パラメタつきの SQL を実行するときに、SQL 文にパラ メタを直接指定して実行するのではなく、 cursor.execute() の SQL 文内でプレースホルダ "%s" を使うことに注意してください。このテクニックを使えば、データベース ライブラリの中で自動的にクオート処理が行われます。 (また、 Django の使って いるプレースホルダ "%s" は、 SQLitePython バインディングで使われて いる "?" と違うということにも注意しましょう。この設計は、一貫性と安全性 に配慮して決められています。)

とあります。


外部入力を前提としたWebサービスであれば、マッパーを使うことは有効な手段だと思っています。




おまけ1:ORM以外のカスタムSQLアプローチが向いている処理

さいごにORMの例外について書きます。


明らかに安全なデータを内部で回す場合のバッチ処理などで、自作のSQLを直接たたくのは正解だと思います。


時間が浮きますしね。


おまけ2:

最近のPythonのSQLAlchemyのモジュールなどは、自分でSQLを書くより、柔軟でものすごい使い方ができる場面があります。

自分が遭遇したのは、こんな場面です。


『カラム構成が一部異なるテーブルがあったとして、2つのテーブル名を指定して、その後、数行書くだけで、テーブル間に共通しているカラムを軸にレコードを同期させる』


な… 何を言ってるのか わからねーと思うが

おれも何をされたのかわからなかった…

頭がどうにかなりそうだった…


催眠術だとか超スピードだとか

そんなチャチなもんじゃあ 断じてねえ

もっと恐ろしいものの片鱗を味わったぜ…

という感じです。おふざけはさておき、

この場合、SQLを自作する場合は、「SELECT INTO」は、カラムが完全一致していないため使えないので、それぞれのカラム情報を引っ張ってきて、カラム名と型を比較して、共通するものだけSELECTして、Primary Keyを確認しながら、INSERTするという処理を行わなければなりません。



ところがSQLAlchemyなどの最新のORMを使うと、あっという間にこれができてしまいます。

おれは今やつのスタンドをほんのちょっぴりだが体験した

い…いや…体験したというよりはまったく理解を超えていたのだが……


ORMの記法を覚えるまでがくせがありすぎて大変というものがありますが。。。


現実に効率面でもマッパーは進化してきています。