Q1フォームの値を画面側でチェックしているのに、サーバー側でも検証するのはなぜですか。
セキュリティで気をつけること — 入力を信用しない・秘密を守る
この記事は、プログラミングやバイブコーディングをする上で最低限身につけたほうがよい IT の実践的な知識を 1 から身につける「IT 基礎知識講座の一部」です。
利用者の入力を信用しないことと、秘密情報を守ることの 2 つが基本です。SQL インジェクションと XSS が起きる流れを図解で確かめます。
この記事は、セキュリティで気をつけることのうち 2 つを扱います。
利用者が書いた文字をそのまま使わないことと、秘密の値を読める人が増える場所に置かないことです。
- 入力値の検証をサーバー側で行う理由
- 入力を命令文につなぐと起きるSQL インジェクションと、プレースホルダ
- 画面に出す直前のエスケープで防ぐXSS
- API キーやパスワードなどの秘密情報を置いてよい場所
どれも公開したあとに起きると元に戻せず、読まれたデータは回収できません。
「入力値の検証がない」とは、届いた値をそのまま使っていること
入力値の検証(input validation)は、届いた値が想定した形・長さ・範囲に収まっているかを、使う前に確かめることです。
名前は 50 文字まで、星の数は 1 から 5、というように項目ごとに条件を決めます。
| 届いた値 | 決めておいた条件 | サーバーがすること |
|---|---|---|
| 名前欄 山田 | 50 文字まで → 合う | 保存に進む |
| 星の数 7 | 1〜5 の数字 → 合わない | 保存せず断って返す |
| 本文 空のまま | 1 文字以上 → 合わない | 保存せず断って返す |
条件に合った 1 行目だけが保存に進み、残りの 2 行は保存せず、どこが合わないかを返します。
画面のチェックを動かしているのは、利用者の端末に届いた JavaScript(ブラウザの中で動くプログラム)です。
利用者の側で外せますし、ブラウザを使わずにサーバーへ直接送ることもできます。
画面のチェックは書き直してもらうためのもので、使ってよいかを決めているのはサーバー側の検証だけです。
検証を外したまま保存すると、条件に合わない値がそのままデータベースに残ります。
1〜5 の外の星の数は平均や並び順を狂わせ、数字として読めない値は集計の処理を止めます。
入ってしまった値を直すには、残っているデータを 1 件ずつ調べることになります。
届いた値は、使う前にサーバーで確かめる
入力値の検証とは、届いた値が決めておいた条件に合うかを、使う前に見ることです。
画面のチェックは利用者にその場で書き直してもらうためのもので外して送ることもできるので、同じことをサーバー側でもう一度確かめます。
入力を命令文につなぐと、書いた文字が命令として動く
保存したレビューを名前で探すとき、サーバーは SQL(Structured Query Language。データベースへ命令を伝える言語)の命令文を組み立てます。
入力の文字をこの文字列につなぐと、全体が 1 本になって渡ります。
- SELECT * FROM reviews WHERE name =
- reviews という表から、name が一致する行を取り出す指示
- この形はコードにあらかじめ書いてある
- 山田 — ふつうの名前
- ' OR '1'='1 — 記号を含む文字
- 書いた文字がそのままこの位置に入る
開発者が書いた部分と名前欄から来た部分は、渡ってしまえば同じ 1 本の一部です。
データベースは受け取った 1 本を、全部まとめて命令として読みます。
| 名前欄に入れた文字 | つないでできた命令文 | データベースが返すもの |
|---|---|---|
| 山田 | name = '山田' | 山田のレビューだけ |
| 山田' | name = '山田'' | 文が壊れてエラーになる |
| ' OR '1'='1 | name = '' OR '1'='1' | すべてのレビュー |
入力された文字列が値ではなく命令として実行されることがインジェクション(injection)で、データベースで起きるものがSQL インジェクション(SQL injection)です。
防ぐ書き方がプレースホルダ(placeholder。命令文の中に値の入る位置だけを記号で示し、値は別に渡す書き方)です。
分けて渡すと、データベースは値を命令として読まず、その文字を名前として探して 0 件で終わります。
検証は値の形を確かめるもの、プレースホルダは形が何であっても値として扱わせるものです。
役割が違うので、両方を行います。
命令文と入力の文字を、1 本につながない
入力の文字を命令文につなぐと、書いた記号まで命令の一部として読まれます。
命令と値を別々に渡せば記号を含む文字もただの名前として扱われるので、形を確かめる検証と、形に関わらず値として扱わせる書き方は、両方そろえます。
画面に出す直前に変えないと、書き込みが別の利用者のブラウザで動く
同じことが、書き込みを画面に出すところでも起きます。
入力された文字列が別の利用者のブラウザで JavaScript として実行されることがXSS(クロスサイトスクリプティング。Cross-Site Scripting)です。
どちらも入力が命令として読まれる点は同じで、読む相手だけが違います。
画面に出すほうの手当てがエスケープ(escape。特別な意味を持つ文字を、ただの文字として表示される形に変換すること)です。
分かれ目は、保存のときではなく画面に出す直前です。
| HTML で特別な意味を持つ文字 | 置き換えたあとの書き方 | 画面に見える文字 |
|---|---|---|
| < | < | < |
| > | > | > |
| & | & | & |
| " | " | " |
| ' | ' | ' |
出す直前に置き換えれば、書き込みは文字のまま
書き込みをそのまま画面に出すと、中に混ざっていたものが別の利用者のブラウザで動きます。
これを止めるのが画面に出す直前の置き換えで、置き換わるのは記号だけなので、読む人に見える文字は変わりません。
秘密の値は、読める人が増える手前で止める
もう 1 つの指摘が、秘密情報の置き場所です。
ここで見るのは置いた場所を何人が読めるかです。
- .env — API キーとパスワードの値
- 手元だけで使う設定のファイル
- server.js — サーバーで動くコード
- .gitignore — 記録しないファイルの一覧
- .env.example — 名前だけを書いた見本
- index.html — レビューの画面
- script.js — 画面で動くコード
内側の枠ほど読める人が多く、script.js は誰でも、server.js も Git に記録すれば渡した相手の全員が読めます。
.gitignore(Git に記録させないファイルの名前を並べたファイル)に .env を書けば値は履歴に入りませんが、
一度記録したあとでは過去の履歴から消えないので、値そのものを作り直します。
公開リポジトリに入った値は、世界中の誰でも読めます。
- script.js — ブラウザに届くコード
- 呼び出す URL の末尾
- 画面に出るエラーメッセージ
- server.js — Git に記録するコード
- 一度記録した .env — 履歴に残る
- サーバーの環境変数 — API キーとパスワードはここ
いちばん内側の輪に入るのは、サーバーの環境変数だけです。
外側の 2 つの輪に並ぶ場所と、外へ持ち出されることがあるログ(サーバーが残す動作の記録)には書きません。
右の囲みの中で動く my-app は、環境変数から値を読み込みます。
その値は左の 2 つの囲みには入らないので、読める人が増えません。
置き場所は、読める人の数で決まる
秘密の値を置いてよい場所は、そこを何人が読めるかで決まります。
ブラウザに届くファイルは誰でも読め、Git に記録したファイルは渡した相手の全員に届くので、どちらにも書かず、サーバーの環境変数に置きます。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2SQL インジェクションを防ぐプレースホルダの書き方は、何をしていますか。
Q3外部サービスの API キーを置く場所として、正しいのはどれですか。