自衛のためのサイバー攻撃の仕組み講座(AI時代・エンジニア水準)

区分3 Webサイトとサーバーを狙う攻撃★★★

No.07 Webの仕組みと「信じてはいけない入力」

Webの弱点を理解する土台の回です。ブラウザとサーバーが何をやり取りしているかを知り、「相手から届く物は全部、嘘かもしれない」という見方を身に付けます。この後の2回の前提になります。
目次
  1. 第0章 前提知識
  2. 第1章 サーバーは、会話を覚えていない
  3. 第2章 信じてはいけない入力
  4. 第3章 塞ぎ方は「混ぜない」こと
  5. 確認問題

第0章 前提知識

① HTTP :ブラウザとサーバーの会話の約束

HTTP は、ブラウザ(またはアプリ)とWebサーバーが話すときの約束事です。 HTTPS は、その会話を暗号で包んだ物です。今の公開サイトは、ほぼ HTTPS です。

会話は、いつも「お願い」と「返事」の1往復でできています。 お願いを リクエスト、返事を レスポンス と呼びます。

食堂の注文票で考えると分かりやすいです。 お客さんが注文票を書いて出し(リクエスト)、厨房が料理を返します(レスポンス)。 厨房は、注文票を出した人の顔を覚えていません。1枚ごとに、初めての注文として扱います。

② リクエストの中身 必ず覚える

リクエストは、次の部品でできています。

部品 何か 例
メソッド 何をしたいか GET(見せて)、POST(受け取って)
URL どこの何か。後ろの ? 以降が「パラメータ」 /search?q=ことば
ヘッダ 付け足しの情報。ブラウザの種類、Cookie など Cookie: …
ボディ 送る本体。フォームの入力、アップロードしたファイルなど 入力欄の中身

パラメータは、URLの後ろに付けて送る「項目名=値」の組です。 入力欄に書いた文字は、この形か、ボディに入って届きます。

③ 言葉の整理

第1章 サーバーは、会話を覚えていない

① ログイン状態は「印」で持たせている 必ず覚える

HTTP は、1往復ごとに完結します。サーバーは前のリクエストを覚えていません。 それなのにログイン状態が続くのは、Cookie の中のセッションIDを毎回見せているからです。

遊園地のリストバンドと同じです。 入口で身分を確かめたら、あとはバンドを見せるだけで乗り物に乗れます。 その代わり、バンドを他人に取られると、取った人がその人として扱われます。

これが No.05 で見た「セッションの盗難」の土台です。パスワードが強くても、印が盗まれれば入られます。

Cookie には、守るための3つの属性(付け足しの設定)があります。

ログイン用のCookieには、この3つを付けるのが基本です。 自分のサイトで見たいときは、ブラウザの開発者ツール(F12)の「アプリケーション」から Cookie の一覧を開くと、属性の列があります。

② 目に見える画面は、守りにならない 必ず覚える

ブラウザの画面で、ボタンを消す、入力欄の文字数を制限する、といった工夫をしても、守りにはなりません。 リクエストは、ブラウザを通さなくても作れます。道具を使えば、制限のかかっていない値を直接送れるからです。

つまり、サーバーに届いた物は、画面の制限を通っていない可能性があると考えます。 画面側のチェックは、親切のためです。守りは必ずサーバー側でやります。

○ よくある勘違い:「入力欄に文字数制限があるから大丈夫」→ 画面の制限は、サーバーには届きません
○ 関係する単元:No.05 アカウント乗っ取りの仕組み

③ 同一オリジンポリシー:ブラウザが持つ壁 さらっと

ブラウザには、あるサイトのページが別のオリジンのデータを勝手に読めないようにする決まりがあります。同一オリジンポリシーといいます。

たとえば、悪いサイトを開いている間に、別タブの銀行のページの中身が読まれてはいけません。この壁があるから、複数のタブを安心して開けます。 ただし、この壁は「読む」ことを止める物です。「送る」ことまでは完全には止めません。だから SameSite など別の守りが要ります。

深追いは要りません。「ブラウザが、サイトごとに壁を作っている」とだけ言えれば足ります。

第2章 信じてはいけない入力

① 入力は、思っているより多くの所から入ってくる 必ず覚える

「入力」というと、フォームの入力欄だけを思い浮かべがちです。 実際にサーバーが受け取るのは、次の全部です。

どれも、送る側が自由に作れます。 LINEやUTAGEからの通知を受けるあなたの仕組みも、同じです。「LINEからの通知のはず」という思い込みは、本物からの通知かどうかの確認に代わりません。

② 弱点の正体は「データが命令として読まれる」こと 必ず覚える

ここからが、この回の核です。 Webの代表的な弱点の多くは、同じ形をしています。

受け取った「ただの文字(データ)」が、どこかで「命令」として解釈されてしまう

渡した先 本来の役目 命令として読まれると
データベース 条件に合う行を探す 意図しない行の読み出し・書き換え(SQLインジェクション)
ブラウザのページ 文字を表示する 他人のブラウザで悪い JavaScript が動く(クロスサイトスクリプティング、XSS)
OSのコマンド ファイル名として使う 意図しないコマンドが動く(OSコマンドインジェクション)
ファイルのパス 決まったフォルダの中を探す 想定外のフォルダのファイルが読まれる(ディレクトリトラバーサル)

どの行も、仕組みは「データと命令の境目が、文字の並びの中で混ざった」の一言で言えます。 読み上げ機の例で考えると分かりやすいです。「次の文を読み上げて」と頼んだのに、文の中の「やめて」で止まってしまう。命令と本文が同じ文字列に入っているから起きます。

実際に打てる攻撃用の文字列は、この講座では扱いません。 仕組みを説明できて、塞ぎ方を実装できることが目的です。

この考え方は、No.10 で扱うAIの弱点(プロンプトインジェクション。AIが「読んだ文」を命令と取り違える問題)と、そっくりな形です。先の回で見る時にもう一度思い出してください。

③ 見抜く目:どこで混ぜているか 必ず覚える

自作のコードや、AIに書かせたコードを見るときは、1つだけ確かめます。 外から来た値を、文字をつなげる形で、命令の文に入れていないか。

SQLなら、文字をつないで問い合わせの文を作っている所が危険信号です。 HTMLなら、受け取った値をそのままページに書き出している所です。

AIが書いたコードも、この危険信号を含むことがあります。 コードを渡されたときは「外から来た値をどこで使っているか」を聞くと、確認できます。

第3章 塞ぎ方は「混ぜない」こと

① 命令と値を分けて渡す 必ず覚える

根本の対策は、命令の文と値を別々に渡すことです。 SQLでは、これをプレースホルダ(値を入れる穴)を使った書き方といいます。 データベース側が「命令の形」を先に確定させてから、値を後で入れます。値はどんな文字でも、ただの値のままです。

WordPress の PHP なら、次のように書きます。

// 安全な書き方:値の位置を %s(文字)や %d(整数)で示し、後から渡す
$row = $wpdb->get_row(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->posts} WHERE post_author = %d AND post_status = %s",
        $author_id,
        $status
    )
);

$wpdb->prepare() が、値を安全な形にして穴へ入れます。 一方、"... WHERE post_author = " . $_GET['id'] のように文字をつなぐ書き方は、避けます。

他の言語でも同じ考え方です。「問い合わせの文」と「値」は、別の引数で渡します。 IPA の「安全なウェブサイトの作り方」も、SQLについては、この方法を根本的な解決策に挙げています。

② 出す所で、文字を無害にする 必ず覚える

ページに文字を書き出すときは、HTMLで特別な意味を持つ記号を、ただの文字として表示する形に変えます。この処理をエスケープといいます。

// 安全な書き方:出す時点で、場所に合ったエスケープをかける
echo '<p>' . esc_html( $comment_text ) . '</p>';
echo '<a href="' . esc_url( $link ) . '">リンク</a>';

WordPress では、esc_html()(本文)・esc_attr()(タグの属性)・esc_url()(リンク先)が用意されています。 大事なのは、入力された時ではなく、出す時に、出す場所に合わせてかけることです。同じ値でも、本文に出す時と、リンクの中に出す時で、必要な処理が違うからです。

③ 入力の検査は、補助として使う さらっと

受け取る値の形を確かめることを、入力の検査(バリデーション)といいます。 「郵便番号は数字7桁」「並べ替えは決めた3種類だけ」のように、許す物を先に決めておき、それ以外は断ります。許可リスト方式といいます。

検査だけに頼ると、見落としが出ます。 検査は「想定外を早く断る」ための補助で、混ぜない書き方(①②)が本番の守りです。

もう1つ、保険の対策があります。 データベースに繋ぐユーザーの権限を、必要最小限にしておきます。万一破られても、被害が小さく済みます。

○ 受講生によく聞かれる質問:「WordPress なら自動で守ってくれるんですよね」
→ 答え方:本体は、これらの対策を入れて作られています。ただし、プラグインやテーマ、自作のコードは別です。ここで書き方を間違えると、本体の守りを通り抜けます(No.09)
○ 関係する単元:No.05 アカウント乗っ取りの仕組み、No.08 OWASP Top 10、No.10 プロンプトインジェクション(後で出ます)

サーバーは会話を覚えていない。ログイン状態はCookieの印で持つ。届く物は全部、画面を通っていないかもしれない。弱点の多くは「データが命令として読まれる」こと。塞ぎ方は、命令と値を分けて渡し、出す時にエスケープする。

確認問題

問1 入力欄に「最大20文字」と画面側で制限をかけたサイトがあります。これだけで、長すぎる値を防げていると言えますか?

問2 Webの代表的な弱点(SQLインジェクション、XSS など)に共通する形として、正しいのはどれですか?

問3 WordPress のプラグインを自作して、記事の投稿者IDで検索したい。安全な書き方はどれですか?

出典:IPA「安全なウェブサイトの作り方」改訂第7版(https://www.ipa.go.jp/security/vuln/websecurity/about.html)/MDN「同一オリジンポリシー」(https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy)/MDN「HTTP Cookie の使用」(https://developer.mozilla.org/ja/docs/Web/HTTP/Guides/Cookies)/OWASP「SQL Injection Prevention Cheat Sheet」(https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)/WordPress 公式「Escaping Data」(https://developer.wordpress.org/apis/security/escaping/)