セキュリティで気をつけること — 入力を信用しない・秘密を守る

この記事は、プログラミングやバイブコーディングをする上で最低限身につけたほうがよい IT の実践的な知識を 1 から身につける「IT 基礎知識講座の一部」です。
利用者の入力を信用しないことと、秘密情報を守ることの 2 つが基本です。SQL インジェクションと XSS が起きる流れを図解で確かめます。

この記事は、セキュリティで気をつけることのうち 2 つを扱います。

利用者が書いた文字をそのまま使わないことと、秘密の値を読める人が増える場所に置かないことです。

  • 入力値の検証をサーバー側で行う理由
  • 入力を命令文につなぐと起きるSQL インジェクションと、プレースホルダ
  • 画面に出す直前のエスケープで防ぐXSS
  • API キーやパスワードなどの秘密情報を置いてよい場所

どれも公開したあとに起きると元に戻せず、読まれたデータは回収できません。

「入力値の検証がない」とは、届いた値をそのまま使っていること

入力値の検証(input validation)は、届いた値が想定した形・長さ・範囲に収まっているかを、使う前に確かめることです。

名前は 50 文字まで、星の数は 1 から 5、というように項目ごとに条件を決めます。

届いた値決めておいた条件サーバーがすること
名前欄 山田50 文字まで → 合う保存に進む
星の数 71〜5 の数字 → 合わない保存せず断って返す
本文 空のまま1 文字以上 → 合わない保存せず断って返す

条件に合った 1 行目だけが保存に進み、残りの 2 行は保存せず、どこが合わないかを返します。

画面のチェックを動かしているのは、利用者の端末に届いた JavaScript(ブラウザの中で動くプログラム)です。

利用者の側で外せますし、ブラウザを使わずにサーバーへ直接送ることもできます。

同じ 7 が、送られ方で 2 つの道に分かれる
星の数に7 を送るフォームからそのまま送る画面のチェックを外して送る画面のチェックで止まるチェックを通らずサーバーに届くサーバーの検証で断って返す
左の 1 つの値が、送られ方で上下に分かれます。下の道は画面のチェックを通らないので、サーバーの検証がなければそのまま保存されます。

画面のチェックは書き直してもらうためのもので、使ってよいかを決めているのはサーバー側の検証だけです。

検証を外したまま保存すると、条件に合わない値がそのままデータベースに残ります。

1〜5 の外の星の数は平均や並び順を狂わせ、数字として読めない値は集計の処理を止めます。

入ってしまった値を直すには、残っているデータを 1 件ずつ調べることになります。

届いた値は、使う前にサーバーで確かめる

入力値の検証とは、届いた値が決めておいた条件に合うかを、使う前に見ることです。

画面のチェックは利用者にその場で書き直してもらうためのもので外して送ることもできるので、同じことをサーバー側でもう一度確かめます。

入力を命令文につなぐと、書いた文字が命令として動く

保存したレビューを名前で探すとき、サーバーは SQL(Structured Query Language。データベースへ命令を伝える言語)の命令文を組み立てます。

入力の文字をこの文字列につなぐと、全体が 1 本になって渡ります。

データベースに渡る 1 本の文字列の中身
データベースが受け取る 1 本の文字列
開発者が書いた部分
  • SELECT * FROM reviews WHERE name =
  • reviews という表から、name が一致する行を取り出す指示
  • この形はコードにあらかじめ書いてある
名前欄から来た部分
  • 山田 — ふつうの名前
  • ' OR '1'='1 — 記号を含む文字
  • 書いた文字がそのままこの位置に入る
外側が、サーバーからデータベースへ渡る 1 本の文字列。中の 2 つは、渡ったあとは見分けがつきません。

開発者が書いた部分と名前欄から来た部分は、渡ってしまえば同じ 1 本の一部です。

データベースは受け取った 1 本を、全部まとめて命令として読みます。

名前欄に入れた文字つないでできた命令文データベースが返すもの
山田name = '山田'山田のレビューだけ
山田'name = '山田''文が壊れてエラーになる
' OR '1'='1name = '' OR '1'='1'すべてのレビュー

入力された文字列が値ではなく命令として実行されることがインジェクション(injection)で、データベースで起きるものがSQL インジェクション(SQL injection)です。

防ぐ書き方がプレースホルダ(placeholder。命令文の中に値の入る位置だけを記号で示し、値は別に渡す書き方)です。

同じ入力が、サーバー側の書き方で 2 つに分かれる
名前欄に' OR '1'='1命令文にそのままつなぐ命令と値を別々に渡す1 本の文字列を命令として読む値を名前として探すだけすべてのレビューが返る0 件で終わる
左の 1 つの入力が、書き方で上下に分かれます。命令と値を分けて渡すと、記号を含む文字も名前として扱われます。

分けて渡すと、データベースは値を命令として読まず、その文字を名前として探して 0 件で終わります。

検証は値の形を確かめるもの、プレースホルダは形が何であっても値として扱わせるものです。

役割が違うので、両方を行います。

命令文と入力の文字を、1 本につながない

入力の文字を命令文につなぐと、書いた記号まで命令の一部として読まれます。

命令と値を別々に渡せば記号を含む文字もただの名前として扱われるので、形を確かめる検証と、形に関わらず値として扱わせる書き方は、両方そろえます。

画面に出す直前に変えないと、書き込みが別の利用者のブラウザで動く

同じことが、書き込みを画面に出すところでも起きます。

入力された文字列が別の利用者のブラウザで JavaScript として実行されることがXSS(クロスサイトスクリプティング。Cross-Site Scripting)です。

同じ入力が、混ざる先で 2 つの呼び名に分かれる
利用者が書いた文字SQL の命令文に混ざるデータベースが命令として読む命令と値を分けて渡す画面の HTML に混ざる別の利用者のブラウザが読む出す直前にエスケープ
左が利用者の書いた文字です。上の道が SQL の命令文に混ざる場合、下の道が画面の HTML に混ざる場合。読む相手が違うので、手当ても違います。

どちらも入力が命令として読まれる点は同じで、読む相手だけが違います。

画面に出すほうの手当てがエスケープ(escape。特別な意味を持つ文字を、ただの文字として表示される形に変換すること)です。

分かれ目は、保存のときではなく画面に出す直前です。

HTML で特別な意味を持つ文字置き換えたあとの書き方画面に見える文字
<&lt;<
>&gt;>
&&amp;&
"&quot;"
'&#x27;'

出す直前に置き換えれば、書き込みは文字のまま

書き込みをそのまま画面に出すと、中に混ざっていたものが別の利用者のブラウザで動きます。

これを止めるのが画面に出す直前の置き換えで、置き換わるのは記号だけなので、読む人に見える文字は変わりません。

秘密の値は、読める人が増える手前で止める

もう 1 つの指摘が、秘密情報の置き場所です。

ここで見るのは置いた場所を何人が読めるかです。

my-app のファイルが、どこまで届くか
手元の作業フォルダ
Git に記録しない(手元だけに残る)
  • .env — API キーとパスワードの値
  • 手元だけで使う設定のファイル
Git に記録する(コードを渡した相手の全員に届く)
  • server.js — サーバーで動くコード
  • .gitignore — 記録しないファイルの一覧
  • .env.example — 名前だけを書いた見本
ブラウザに届く(URL を開いた全員が読める)
  • index.html — レビューの画面
  • script.js — 画面で動くコード
外側が手元の作業フォルダ。内側の枠に入るファイルほど読める人が増えるので、内側には秘密の値を書きません。

内側の枠ほど読める人が多く、script.js は誰でも、server.js も Git に記録すれば渡した相手の全員が読めます。

.gitignore(Git に記録させないファイルの名前を並べたファイル)に .env を書けば値は履歴に入りませんが、

一度記録したあとでは過去の履歴から消えないので、値そのものを作り直します。

Git に記録したファイルが、GitHub でどこまで届くか
コードだけを記録するGitHub の公開リポジトリ世界中の誰でも読める.env も記録してしまう履歴にファイルごと残る消しても履歴から読める.gitignore に書いておく履歴に入らない手元だけに残る
上の段が公開リポジトリに置いたコード、真ん中が .env も記録してしまった場合、下が .gitignore に書いた場合です。左から右へ、置いた先と読める人が決まります。

公開リポジトリに入った値は、世界中の誰でも読めます。

API キーを置く場所と、そこを読める人の輪
URL を開いた全員が読める
  • script.js — ブラウザに届くコード
  • 呼び出す URL の末尾
  • 画面に出るエラーメッセージ
コードを渡した相手の全員が読める
  • server.js — Git に記録するコード
  • 一度記録した .env — 履歴に残る
サーバーを操作できる人だけが読める
  • サーバーの環境変数 — API キーとパスワードはここ
外側の輪ほど、読める人が多くなります。値を置いてよいのは、いちばん内側の輪に並ぶ場所だけです。

いちばん内側の輪に入るのは、サーバーの環境変数だけです。

外側の 2 つの輪に並ぶ場所と、外へ持ち出されることがあるログ(サーバーが残す動作の記録)には書きません。

右の囲みの中で動く my-app は、環境変数から値を読み込みます。

その値は左の 2 つの囲みには入らないので、読める人が増えません。

置き場所は、読める人の数で決まる

秘密の値を置いてよい場所は、そこを何人が読めるかで決まります。

ブラウザに届くファイルは誰でも読め、Git に記録したファイルは渡した相手の全員に届くので、どちらにも書かず、サーバーの環境変数に置きます。

QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1フォームの値を画面側でチェックしているのに、サーバー側でも検証するのはなぜですか。

Q2SQL インジェクションを防ぐプレースホルダの書き方は、何をしていますか。

Q3外部サービスの API キーを置く場所として、正しいのはどれですか。