セキュリティ関連でよく言われますよね。
でも、インジェクションの脆弱性はSQLだけではないのです。
まず、インジェクションという言葉の意味ですが、「注入」と言う意味です。
何を注入するかと言うと、「悪意」です(笑)
はい。
簡単に言うと、プログラム上に不備があり、注入された悪意をはじけないとセキュリティホールになります。
以下、具体的に見ていきましょう。
【SQLインジェクション】
例えば、ログインのIDとパスワードを認証することを考えます。
ユーザが入力したIDとパスワードを受け取ったところから考えてみましょう。
DBにSQL文でそのIDとパスワードが存在するかチェックに行きますよね?
<入力>
ID: nana
PW: july
<SQL>
ostringstream sql;
sql << "select id, pw from user where id='" << ID << "' and pw='" << PW << "'";
このプログラム、問題あるでしょうか?
おおありです!
ユーザがPWに「' or 0=0」と入力したらどうなるでしょう?
上のプログラムで作成されるSQL文は以下のようになります。
select id, pw from user where id='nana' and pw='' or 0=0
赤字がキーポイントです。
意味は、idフィールドが"nana"でpwフィールドが空文字の場合、
もしくは0が0と等しい場合、のレコードを返しなさい、となります。
当然ながら0と0はいつでも等しいので、全レコードを返すことになります。
これが先ほど言った「悪意」です。
解決策は、プレースホルダを使用するか、自分でSQLエスケープをすることです。
Javaの場合、JDBCのようなものがDB間の違いを吸収してくれます。
でもC++の場合、そのようなものは無いのでDBの提供するライブラリによって、プレースホルダがあったりなかったりします。
基本的には自分でSQLエスケープすることになるかと思います。
あとで、漏補で補足事項を書こうと思います。
【OSコマンドインジェクション】
これは、system()関数にユーザが入力した値を使用する場合などにおきます。
良くある例では、sendmail を引き合いに出します。
http://www.thinkit.co.jp/cert/tech/7/5/4.htm
ここでは、ファイルをダウンロードさせる例を考えてみましょう。
WEBでユーザがファイル名のリンクを押すと、filename=sample.txt などの引数が送信され、
そのファイルをダウンロードすることを考えます。
string file="sample.txt"; ←ユーザ入力した値
ifstream(file.c_str());
このプログラム、問題あるでしょうか?
おおありです!
引数の値はユーザ側で改ざんして送信することができます。
改ざんした値が、../../../passwd.txtなどだったらどうでしょうか?
もしパスワードファイルがあればダウンロードできてしまします。
もちろんファイルのパスが分かっていることが前提となりますが、WEBサーバのOSと使用しているWEBサーバが分かるとたいていどこに何があるか分かってしまいます。
なので、とても危険なのです。
ピリオドやスラッシュ(/)、\を入力したらエラーではじくようにしましょう!
他にもセキュリティに関する注意点はいくつもあります。
十分調べてプログラム設計しておきましょう!
【漏補 】
漏:
SQLのプレースホルダについてです。
プレースホルダを使用するとシングルクォートなどをエスケープしてくれて、インジェクションの攻撃から守ってくれます。
ただ、like を使用する場合、難があります。
具体的にSQLを書いてみましょう。memoというフィールドが入力した単語を含むレコードを検索します。
select * from t_contents where memo like ?
では、%が無いのでだめです。 (?はプレースホルダです。)
例えば、ナナという文字を入力した場合上記のSQL文は以下のようになります。
select * from t_contents where memo like 'nana'
フィールドがnanaと完全一致するレコードしかひっかかりませんよね?
補:
これにはテクニックがあるのですが、結局自分でエスケープしないといけません。
以下のようにします。
select * from t_contents where memo like '%' || ? || '%'
意味、分かるでしょうか?
リプレース後のSQL文は以下のようになります。
select * from t_contents where memo like '%' || 'nana' || '%'
||は文字列連結なので、SQLの中で like '%nana%' としているのと同じです。
これなら分かりますよね?
でもこれでは不十分です。
例えば「100%ジュース」という単語を含むレコードを探したい場合どうなるでしょうか?
上記と同じくリプレース後のSQLのlikeの部分だけ抜き出してみると、以下のようになります。
like '%100%ジュース%'
これだと、100 とジュースが含まれるレコードが抽出されてしまいます。
なので、%と_をエスケープする必要があります。
(likeではこの2つが特殊文字です)
プレースホルダに代入する前にlikeエスケープをして、それから上記の like '%' || ? || || '%' に代入しましょう。