本日、Webアプリでの入力検証戦略について質問がありました。
トップの答えは、執筆時点で、中に示唆するPHP
だけで使用するhtmlspecialchars
とmysql_real_escape_string
。
私の質問は、これは常に十分ですか?知っておくべきことがもっとありますか?これらの機能はどこで壊れますか?
本日、Webアプリでの入力検証戦略について質問がありました。
トップの答えは、執筆時点で、中に示唆するPHP
だけで使用するhtmlspecialchars
とmysql_real_escape_string
。
私の質問は、これは常に十分ですか?知っておくべきことがもっとありますか?これらの機能はどこで壊れますか?
回答:
データベースクエリに関しては、常に準備済みのパラメーター化されたクエリを使用してください。ライブラリはこれをサポートしています。これは、などのエスケープ関数を使用するよりもはるかに安全です。mysqli
PDO
mysql_real_escape_string
はい、mysql_real_escape_string
事実上、文字列エスケープ関数です。それは魔法の弾丸ではありません。危険な文字を1つのクエリ文字列で安全に使用できるようにエスケープするだけです。ただし、事前に入力をサニタイズしないと、特定の攻撃ベクトルに対して脆弱になります。
次のSQLを想像してください。
$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);
これはエクスプロイトに対して脆弱であることを確認できるはずです。パラメータに一般的な攻撃ベクトルが含まれていると
想像してid
ください。
1 OR 1=1
エンコードする危険な文字はないため、エスケープフィルターを直接通過します。私たちを残して:
SELECT fields FROM table WHERE id= 1 OR 1=1
これは素敵なSQLインジェクションベクトルであり、攻撃者がすべての行を返すことを可能にします。または
1 or is_admin=1 order by id limit 1
生成する
SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1
これにより、攻撃者はこの完全に架空の例で最初の管理者の詳細を返すことができます。
これらの機能は便利ですが、注意して使用する必要があります。すべてのWeb入力がある程度検証されていることを確認する必要があります。この場合、数値として使用している変数が実際に数値であることを確認しなかったため、悪用される可能性があることがわかります。PHPでは、一連の関数を使用して、入力が整数、浮動小数点、英数字などであることを確認する必要があります。しかし、SQLに関しては、準備されたステートメントの値にほとんど注意してください。上記のコードは、データベース関数が1 OR 1=1
有効なリテラルではないことがわかっているため、準備されたステートメントであれば安全でした。
についてはhtmlspecialchars()
。それは独自の地雷原です。
PHPには、さまざまなHTML関連のエスケープ関数がすべて選択されており、どの関数が何を実行するかについての明確なガイダンスがないという点で、PHPには実際の問題があります。
まず、HTMLタグの内部にいると、本当に困ります。見る
echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';
私たちはすでにHTMLタグの中にいるので、危険なことをするのに<や>は必要ありません。私たちの攻撃ベクトルはjavascript:alert(document.cookie)
結果のHTMLは次のようになります
<img src= "javascript:alert(document.cookie)" />
攻撃はまっすぐに進みます。
悪くなる。どうして?htmlspecialchars
(この方法で呼び出された場合)は、単一引用符ではなく二重引用符のみをエンコードするためです。したがって、もし
echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";
悪意のある攻撃者はまったく新しいパラメーターを注入できるようになりました
pic.png' onclick='location.href=xxx' onmouseover='...
くれます
<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />
これらの場合、特効薬はありません。入力を自分で意味付けする必要があります。不正な文字を除外しようとすると、必ず失敗します。ホワイトリストのアプローチを取り、優れたcharsのみを通過させます。XSSのチートシートを見て、ベクターがどのように多様化できるかについての例を確認してください。
使っても htmlspecialchars($string)
HTMLタグの外でマルチバイト文字セットの攻撃ベクトルに対して脆弱です。
最も効果的な方法は、次のようにmb_convert_encodingとhtmlentitiesを組み合わせて使用することです。
$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');
これでも、UTFの処理方法が原因でIE6は脆弱です。ただし、IE6の使用が減少するまで、ISO-8859-1などのより制限されたエンコーディングにフォールバックすることができます。
マルチバイト問題の詳細な調査については、https://stackoverflow.com/a/12118602/1820を参照してください。
$result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";
、2のようにパラメーターを引用符で囲んでも問題ありません。2番目の例(URLを含む属性)では、何も使用しませんhtmlspecialchars
。このような場合は、URLエンコードスキームを使用して、たとえばを使用して入力をエンコードする必要がありますrawurlencode
。そうすれば、ユーザーはjavascript:
他を挿入できません。
Take a whitelist approach and only let through the chars which are good.
。ブラックリストは常に何かを見逃します。+1
Cheekysoftの優れた答えに加えて:
HTMLインジェクション(クロスサイトスクリプティングなど)を防ぐための特効薬は実際にはありませんが、ライブラリまたはテンプレートシステムを使用してHTMLを出力している場合は、より簡単に実現できる可能性があります。物事を適切にエスケープする方法については、そのドキュメントを読んでください。
HTMLでは、状況に応じて異なる方法でエスケープする必要があります。これは、Javascriptに配置される文字列に特に当てはまります。
私は上記の投稿に間違いなく同意しますが、Cheekysoftの回答に返信するために1つ小さなことを追加します。
データベースクエリに関しては、常に準備済みのパラメーター化されたクエリを使用してください。mysqliおよびPDOライブラリはこれをサポートしています。これは、mysql_real_escape_stringなどのエスケープ関数を使用するよりもはるかに安全です。
はい、mysql_real_escape_stringは事実上単なる文字列エスケープ関数です。それは魔法の弾丸ではありません。危険な文字を1つのクエリ文字列で安全に使用できるようにエスケープするだけです。ただし、事前に入力をサニタイズしないと、特定の攻撃ベクトルに対して脆弱になります。
次のSQLを想像してください。
$ result = "テーブルからフィールドを選択WHERE id =" .mysql_real_escape_string($ _ POST ['id']);
これはエクスプロイトに対して脆弱であることを確認できるはずです。idパラメータに一般的な攻撃ベクトルが含まれていると想像してください。
1 OR 1 = 1
エンコードする危険な文字はないため、エスケープフィルターを直接通過します。私たちを残して:
テーブルWHERE id = 1 OR 1 = 1からフィールドを選択
数値ではないものをすべて取り除くデータベースクラスに配置する簡単な関数をコード化しました。それはpreg_replaceを使用するので、もう少し最適化された関数がありますが、ピンチで動作します...
function Numbers($input) {
$input = preg_replace("/[^0-9]/","", $input);
if($input == '') $input = 0;
return $input;
}
だから代わりに
$ result = "テーブルからフィールドを選択WHERE id =" .mysqlrealescapestring( "1 OR 1 = 1");
私は使うだろう
$ result = "テーブルからフィールドを選択WHERE id =" .Numbers( "1 OR 1 = 1");
安全にクエリを実行します
テーブルWHERE id = 111からフィールドを選択
確かに、正しい行が表示されなくなっただけですが、SQLをサイトに挿入しようとしている人にとっては、これは大きな問題ではないと思います;)
return preg_match('/^[0-9]+$/',$input) ? $input : 0;
このパズルの重要な部分はコンテキストです。クエリですべての引数を引用符で囲んだ場合、IDとして「1 OR 1 = 1」を送信する人は問題になりません。
SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"
その結果:
SELECT fields FROM table WHERE id='1 OR 1=1'
これは効果がありません。文字列をエスケープしているので、入力は文字列コンテキストから抜け出すことができません。MySQLのバージョン5.0.45までこれをテストしましたが、整数列に文字列コンテキストを使用しても問題は発生しません。
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];
64ビットシステムではさらにうまく機能します。ただし、多数のアドレスを処理する場合のシステムの制限に注意してください。ただし、データベースIDの場合、これは99%の確率で機能します。
値のクリーンアップにも単一の関数/メソッドを使用する必要があります。この関数がmysql_real_escape_string()の単なるラッパーであっても。どうして?ある日、データの最適なクリーニング方法へのエクスプロイトが見つかった場合、システム全体の検索と置換ではなく、1か所で更新するだけで済みます。
なぜ、ああ、なぜ、あなたはしませんか、あなたのSQL文では、ユーザー入力の前後に引用符が含まれていますか?かなりばかげていないようです!SQLステートメントに引用符を含めると、「1または1 = 1」は無駄な試みになります。
それでは、「ユーザーが入力に引用符(または二重引用符)を含めた場合はどうなるでしょうか」と言います。
まあ、そのための簡単な修正:ユーザー入力の引用符を削除するだけです。例:input =~ s/'//g;
。今、とにかく、ユーザー入力は保護されるようです...