start-03 隔離した実習環境を準備する
コードを直す前に、何を確かめるか
下の説明と処理図を読み、入力した値がどの判断を通って結果になるかを確認します。実行後は「入力」「途中の値」「期待結果と実際の結果」を比較します。最初から正しく動く必要はありません。
演習 start-03 ローカル実習の宛先を検査する
教材を起動する前の確認係として、設定された宛先が指定のループバックとか検査します。localhostという単語を含む外部を見逃さないようにします。
範囲: 文字列の解析だけを実行します。通信・解決・待受の開始終了はせず、許可は実務の診断権限の代わりにはなりません。
処理図と読み方
- 起動設定
- URLの構造
- 許可範囲
- 起動前の判断
起動設定 → URLの構造 / URLの構造 → 許可範囲 / 許可範囲 → 起動前の判断
独自の説明図。文字列の解析だけを実行します。通信・解決・待受の開始終了はせず、許可は実務の診断権限の代わりにはなりません。
起動設定:と、この実習で指定したを読みます。
の構造:protocol・hostname・portを で分離します。
許可範囲:、127.0.0.1またはlocalhost、指定、ユーザー情報なしを検査します。
起動前の判断:設定の一致だけを返し、実サーバーが起動済みとは扱いません。
input → policy:と指定。
policy → process:分離したscheme・hostname・port。
process → result:許可範囲との一致結果。
観測順序:
input:と、この実習で指定したを読みます。policy:protocol・hostname・portを で分離します。process:、127.0.0.1またはlocalhost、指定、ユーザー情報なしを検査します。result:設定の一致だけを返し、実サーバーが起動済みとは扱いません。
誤解しやすい点: にlocalhostという文字があればローカル、ではありません。解析したとを確認し、実際の待受確認とは区別します。
このコードで確かめること
実習用のWebページの住所を確認します。自分のコンピューターを指す localhost または 127.0.0.1 と、指定のだけを許可してください。localhost という文字が途中にある別の住所を許可しないことが確認点です。
プログラムが満たす条件
の部分一致をやめ、127.0.0.1またはlocalhostと完全一致させます。と条件も満たす場合だけを返します。
初めてコードを編集する方へ:入力・記号・実行の読み方
- 入力は教材がケースごとに自動で渡します。例えば input.name は入力の name という項目を読む記述です。下の編集欄に実名や本物のパスワードを入力する必要はありません。
- JSONの
{}は項目の組、[]は順番のある一覧です。例えば{"name":"A"}は name が文字列 A の入力です。true は「はい」、false は「いいえ」、null は値がないことを表します。false が必ず失敗という意味ではありません。 - const は値に名前を付ける記述、=== は同じかの比較、&& は両方、|| はどちらか、! は反対です。return が最後の結果を返し、trace は途中の値を記録します。
- まず変更せず「コードを実行し、結果を比較する」を押します。「見直す」のケースを1つ選び、違う項目を確認します。編集欄の「修正対象」の近くを一か所変えて、同じボタンでもう一度実行します。
- ヒントと参考実装は編集欄の下で開けます。答えを写す前に、どの入力で結果が変わるはずか予想してください。一致したら、修正した条件がなぜ必要かを自分の言葉で説明します。
「修正対象」と書かれた部分を探します。まず変更せず実行し、期待結果と違う箇所を確かめてから、条件や計算を変更してください。
このコードは外部のWebサイトやファイルには接続しません。図の各段階で trace に渡された値を表示します。文字列や計算などのコードは実行しますが、実際の攻撃・送信・削除は行いません。
この説明文は振り返り用に保存します。文章の内容そのものを自動で正誤判定することはありません。
条件を1つ変えて試す
下の入力は最初のケースのコピーです。課題で扱う範囲の値を1つだけ変え、結果を予想してから実行してください。いつもの全ケースに、この入力を1件追加して比較します。入力の意味は上の課題と処理図で確認できます。
修正する課題
修正する課題
の部分一致をやめ、127.0.0.1またはlocalhostと完全一致させます。と条件も満たす場合だけを返します。
初期コード(修正対象)
初期コード(修正対象)
async function run(input, trace) {
trace("input", input);
let url;
try { url = new URL(input.url); } catch { url = null; }
trace("policy", {hostname:url?.hostname ?? null, expectedPort:input.port});
const hostAllowed = url && /* 修正対象:ホスト名の部分一致をやめ、127.0.0.1またはlocalhostと完全一致させます。ポートとHTTP条件も満たす場合だけtrueを返します。 */
url.hostname.includes("localhost") || url?.hostname === "127.0.0.1";
const allowed = Boolean(hostAllowed && url.protocol === "http:" &&
(url.port || "80") === String(input.port) && !url.username && !url.password);
trace("process", {hostAllowed:Boolean(hostAllowed), allowed});
const output = {allowed, serverStarted:false};
trace("result", output);
return output;
}ケースごとの入力・期待結果・理由
ケースごとの入力・期待結果・理由
ケース1:正常:指定した教材
入力:
{"url":"http://127.0.0.1:8765","port":8765}期待結果:
{"allowed":true,"serverStarted":false}理由:設定だけを許可する。起動は別の操作。
ケース2:異常:紛らわしい外部
入力:
{"url":"http://localhost.attacker.test:8765","port":8765}期待結果:
{"allowed":false,"serverStarted":false}理由:localhostという部分文字列では同じにならない。
ケース3:境界:別
入力:
{"url":"http://localhost:8766","port":8765}期待結果:
{"allowed":false,"serverStarted":false}理由:指定した待受との一致も必要。
ケース4:異常:解析できない値
入力:
{"url":"not a URL","port":8765}期待結果:
{"allowed":false,"serverStarted":false}理由:不正を外部へ解決しない。
段階的なヒント
段階的なヒント
文字列全体より、解析後のhostnameを確認します。
includesとのincludesは異なる比較です。
通信を開始していないのでserverStartedはのままです。
参考実装
参考実装
async function run(input, trace) {
trace("input", input);
let url;
try { url = new URL(input.url); } catch { url = null; }
trace("policy", {hostname:url?.hostname ?? null, expectedPort:input.port});
const hostAllowed = url && ["127.0.0.1","localhost"].includes(url.hostname);
const allowed = Boolean(hostAllowed && url.protocol === "http:" &&
(url.port || "80") === String(input.port) && !url.username && !url.password);
trace("process", {hostAllowed:Boolean(hostAllowed), allowed});
const output = {allowed, serverStarted:false};
trace("result", output);
return output;
}別の状況へ適用する
社内診断:書面の対象と期間も照合する。
検証:解決先や第三者サービスへの転送を追加確認する。
開発環境:を変えると保存先や接続先が変わりうる。
検証:起動と実際の待受を照合する。
到達確認
不審と別を拒否する。
文字列の判定と実待受の確認を分離する。
の一致を外部診断の許可と呼ばない。