diff --git a/docs/labs/ja_shell-injection.html b/docs/labs/ja_shell-injection.html new file mode 100644 index 00000000..8a6c8c0b --- /dev/null +++ b/docs/labs/ja_shell-injection.html @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + +
+

ラボ shell-injection

+

+これはセキュアなソフトウェア開発に関するラボ演習です。 +ラボの詳細については、概要をご覧ください。 + +

+

タスク

+

+シェルインジェクション脆弱性を取り除きます + +

+

背景

+

+多くのケースでプログラマは作成したアプリケーションからシステムコマンドを実行します。 +これには複数の理由があります:複雑なツールを実行したい、他のツールが持つ機能を再度プログラムすることを避けたい、単純にアプリケーション(これはしばしば Web アプリケーションです)がサーバシステム上でツールを実行するためのインターフェースになっている、など。 +しかしながら、誤った方法による実行は脆弱性につながります。 +

+ +

+呼び出す外部のツールに渡されるパラメータは、非常に多くのケースで静的(いつも同じ値)ではありません。 +一つまたは複数のパラメータは、信頼できないユーザから与えらえれます。 +例えばそれは、プロジェクト名などかもしれません。 +どうしてもシェルが必要という場合でない限り、アプリケーションがこれらのパラメータをシェル経由で渡す必要は全くありません。 +

+ +

+さらに、 +アプリケーションは与えられたパラメータを慎重に扱い、 コマンドの末尾(多くの場合「;」)などの特殊文字が含まれていないことを確認する必要があります。そうしないと、攻撃者が入力内容を巧妙に操作し、アプリケーションに用意されているコマンドに加えて任意のコマンドを実行できてしまう可能性があります。 +これは、許可リスト(allowlist)、つまり許可するコマンドを明示的にリストする(それ以外の全てのコマンドを禁止する)ことで実現できます。 +

+ +

+

タスクの詳細

+

+このタスクでは、シェルコマンドインジェクションが発生し得る状況を修正します。 +様々なユーザ入力によって何が起こるかをそれぞれテストしてみたくなるかもしれません。 +

+ +

+ここでのシナリオはシンプルです。Python アプリケーションによって、アプリケーションがデータ処理に使うテンポラリディレクトリの中のファイルをリストしたいとします。 +元々のコードはこうなっています。 +

+ +
  subprocess.run("ls -l", shell=True)
+ +

+そこへ他の開発者が、ユーザが入力したディレクトリ名をそのまま格納した dir_to_list という変数を入力とする以下のコードを追加しました。 +

def list_directory(dir_to_list):
+    subprocess.run(f"ls -l {dir_to_list}", shell=True)
+
+ +

+これはシェルインジェクションに対する脆弱性を含んでいます。 +攻撃者は、シェルによって実行される任意のテキストをパラメータに埋め込むことができます。 +安全な文字だけが処理されるようにする必要があります。さらに、 シェルの起動はここでは全く 必要ありません。 +この2つのステップを踏むことで、攻撃者が回避策を見つけることをはるかに困難にできます。 +

+ +

+ヒントをいくつか。 +文字列には英数字のみが含まれるように修正する必要があります。 +Python の re.sub(PATTERN, REPLACETEXT, ORIGINAL_VALUE) +は、ORIGINAL_VALUE にある PATTERNREPLACETEXT に変換した結果を返します。 +ここで PATTERN は文字列として書かれた正規表現です。 +re.sub はデフォルトでマッチした全ての部分を置換します(これはここで望まれる挙動です)。 +Python における正規表現は、通常「raw文字列」、つまり r'...' の形式で表記されます。 +Python の subprocess.run によるシェルの起動をやめることと、更に引数を分離する必要があります。 +

+ +

+公平のため補足すると、典型的な Python プログラムは直接 "ls" を呼び出しません。 +今後シェルを呼び出す必要もあると思われるので、ここでは例をシンプルに保つために直接呼び出すことにしています。 +

+ +

+必要に応じて、「ヒント」ボタンと「諦める」ボタンを使用してください。 +

+ +

+

演習 ()

+

+以下の関数を安全に書き換えてください。 +

+

+
def list_directory(dir_to_list):
+  # ディレクトリ名が a-zA-Z0-9 のみを含むよう修正してください
+
+  # 安全な方法で subprocess を使用してください
+
+
+ + + +

+

+このラボは Marta Rybczynska によって開発されました。 +

+

+

+ +

+
+ + diff --git a/docs/labs/shell-injection.js b/docs/labs/shell-injection.js index f53a7eca..b2c29b3e 100644 --- a/docs/labs/shell-injection.js +++ b/docs/labs/shell-injection.js @@ -7,15 +7,18 @@ info = { absent: String.raw`^[\n\r]*\x20\x20[^\x20]`, text: "Python is an indentation-sensitive language, so your indentation must be consistent. In this case, your line in the first section must start with exactly 2 spaces followed by a non-space.\n", + text_ja: "Python はインデントに依存する言語なので、インデントは構造と整合していなければなりません。この場合では、最初のセクションの最初の行は2つの空白で始まり、そのあとに空白ではない文字がこなければいけません。\n", }, { absent: String.raw`^\x20\x20[^\x20]`, index: 1, text: "Python is an indentation-sensitive language, so your indentation must be consistent. In this case, your line in the second section must start with exactly 2 spaces followed by a non-space.\n", + text_ja: "Python はインデントに依存する言語なので、インデントは構造と整合していなければなりません。この場合では、2つめのセクションの最初の行は2つの空白で始まり、そのあとに空白ではない文字がこなければいけません。\n", }, { absent: String.raw`re \. sub`, text: "Use re.sub(PATTERN, REPLACETEXT, dir_to_list) to substitute anything that is not an alphanumeric character (removing the rest).\n", + text_ja: "re.sub(PATTERN, REPLACETEXT, dir_to_list) を使って英数字以外の文字を置換(削除)してください。\n", examples: [ [ " clean_dir = dir_to_list", @@ -27,6 +30,7 @@ info = absent: String.raw`clean_dir = re \. sub \(`, present: String.raw`re \. sub`, text: "You need to compute a new string using re.sub and assign the result to `clean_dir`. Your first line should look like `clean_dir = re.sub(PATTERN, REPLACETEXT, dir_to_list)`.\n", + text_ja: "re.sub を使って新しい文字列を生成し、結果を `clean_dir` に代入する必要があります。最初の行は `clean_dir = re.sub(PATTERN, REPLACETEXT, dir_to_list)` のようになるはずです。\n", examples: [ [ " clean_dir re.sub = dir_to_list", @@ -37,6 +41,7 @@ info = { present: "PATTERN", text: "We use `PATTERN` as a placeholder (metavariable) for the expression you should use. The answer won't actually say PATTERN anywhere. Your first line should look like `clean_dir = re.sub(PATTERN, REPLACETEXT, dir_to_list)` but `PATTERN` is a string with the regex of the pattern of text you want to replace, and `REPLACETEXT` is what you want to replace it with. The PATTERN would probably look like `r'...'` where the `...` is the regular expression matching what you want to eliminate.\n", + text_ja: "`PATTERN` は説明のためのプレースホルダ(メタ変数)であり、回答に PATTERN は出てきません。最初の行は `clean_dir = re.sub(PATTERN, REPLACETEXT, dir_to_list)` のようになるはずですが、`PATTERN` は置換元の文字のパターンを表す正規表現の文字列に、`REPLACETEXT` は置換後の文字になります。PATTERN はおそらく `r'...'` の形で、`...` は削除したい部分にマッチする正規表現になります。\n", examples: [ [ " clean_dir = re.sub(PATTERN, PATTERN, dir_to_list)", @@ -47,14 +52,17 @@ info = { absent: String.raw`re \. sub \( r`, text: "Python re.sub uses strings to indicate a regex pattern. By convention these strings are usually 'raw' strings, so they have the form `r'PATTERN'`. We would recommend that you use raw strings, in the pattern `re.sub(r'...', ...)` even though raw strings don't make this *specific* example easier.\n", + text_ja: "Python の re.sub は正規表現の文字列を引数に取ります。慣例的にこの文字列には `r'PATTERN'` の形の raw 文字列を用います。raw 文字列はこの例を *特に* 簡単にするものではありませんが、パターン `re.sub(r'...', ...)` として raw 文字列を使うことをお勧めします。\n", }, { absent: String.raw`re \. sub \( r['"]`, text: "Python re.sub uses strings to indicate a regex pattern. By convention these strings usually 'raw' strings, so they have the form `r'PATTERN'`. You have the \"r\" but not the following single or double quote character.\n", + text_ja: "Python の re.sub は正規表現の文字列を引数に取ります。慣例的にこの文字列には `r'PATTERN'` の形の raw 文字列を用います。回答には \"r\" に続くシングルまたはダブルクオーテーションがないようです。\n", }, { present: String.raw`re \. sub \( r?['"]\(`, text: "It is syntactically *legal* to use unnecessary parentheses in a regular expression, e.g., `([^a-zA-Z0-9])`. However, it's usually best to make regular expressions as simple as possible. So please don't use unnecessary parentheses.\n", + text_ja: "文法的には不必要なカッコを正規表現の中に書くことは *許されて* います。例:`([^a-zA-Z0-9])` しかし通常、正規表現はできる限りシンプルにすることが望まれます。不必要なカッコは使用しないでください。\n", examples: [ [ " clean_dir = re.sub(r'([^a-zA-Z0-9])', '', dir_to_list)", @@ -65,6 +73,7 @@ info = { absent: String.raw`re \. sub \( r?['"]\[`, text: "Use re.sub(r'[...]', ...) to indicate that you want to replace every character matching a certain pattern. Note the square brackets in the regular expression. Replace the `...` with the pattern of characters to be replaced.\n", + text_ja: "マッチした全ての文字を置換することを示すために re.sub(r'[...]', ...) を使用してください。正規表現には大カッコ([])を使用してください。`...` が置換されるべき文字のパターンです。\n", examples: [ [ " clean_dir = re.sub(r'', '', dir_to_list)", @@ -75,6 +84,7 @@ info = { absent: String.raw`re \. sub \( r?['"]\[\^`, text: "Use re.sub(r'[^ALPHANUMERIC_PATTERN]', '', dir_to_list) to indicate that you want to replace everything that is not an alphanumeric character. Notice the use of the caret symbol `^`; we are replacing everything *not* matching a certain pattern. It's okay to use a caret here, because we aren't validating input, we are filtering (removing) all the input *not* permitted. Be sure to replace ALPHANUMERIC_PATTERN with a regular expression pattern that describes alphanumerics!\n", + text_ja: "英数字以外の文字を全て置換することを示すために、re.sub(r'[^ALPHANUMERIC_PATTERN]', '', dir_to_list) としてください。ここでは指定したパターンにマッチした文字 *以外* を置換したいので、キャレット `^` を使用します。入力検証は行っていないので、許容される入力 *以外* は全てフィルター(削除)する必要があります。そのためキャレットの使用は適切です。ALPHANUMERIC_PATTERN を、英数字を表す正規表現に変更することを忘れないでください!\n", examples: [ [ " clean_dir = re.sub(r'[a-zA-Z0-9]', '', dir_to_list)", @@ -85,6 +95,7 @@ info = { absent: "a-zA-Z0-9", text: "Use `a-zA-Z0-9` in your substitution pattern.", + text_ja: "置換される文字のパターンは `a-zA-Z0-9` としてください。", examples: [ [ " clean_dir = re.sub(r'[^]', '', dir_to_list)", @@ -95,6 +106,7 @@ info = { absent: String.raw`re \. sub \( r?('\[\^a-zA-Z0-9\]'|"\[\^a-zA-Z0-9\]") , r?(''|"")`, text: "The second parameter of `re.sub` should be an empty string, that is, `''` or `\"\"`. You are matching everything you don't want, and replacing it with nothing at all.\n", + text_ja: "`re.sub` の第二引数は空の文字列、つまり `''` または `\"\"` とすべきです。望ましくないもの全てにマッチさせ、それらを無に置換します。\n", examples: [ [ " clean_dir = re.sub(r'[^a-zA-Z0-9]', NOWWHAT, dir_to_list)", @@ -106,11 +118,13 @@ info = absent: "subprocess.run", index: 1, text: "Use subprocess.run", + text_ja: "subprocess.run を使用してください。", }, { present: "shell = [Tt]rue", index: 1, text: "Don't say `shell = True`; we don't want to unnecessarily run the shell.", + text_ja: "`shell = True` は使用しないでください。不必要なシェルの実行は望ましくありません。", examples: [ [ " clean_dir = re.sub(r'[^a-zA-Z0-9]', '', dir_to_list)", @@ -122,6 +136,7 @@ info = present: String.raw`f["']ls\s+-l`, index: 1, text: "You should generally avoid using string concatenation when creating a command to execute. Formatted strings like f\"...\" are a form of string concatenation. In addition, you MUST avoid string concatenation in this case. The shell uses spaces to separate arguments, but we're trying to avoid using the shell when it's not needed. You must instead provide the arguments as a list that contains separate parameters. The parameters should be something like \"ls\", \"-l\", clean_dir", + text_ja: "一般的に、実行コマンドを生成するための文字列連結は避けるべきです。f\"...\" は文字列連結のためのフォーマットです。このケースでは文字列連結を使ってはいけません。シェルは引数を空白で区切りますが、ここでは不必要なシェルの使用を避けることを目指します。ここではそれぞれ独立したパラメータのリストとして引数を渡します。パラメータは、\"ls\", \"-l\", clean_dir のようになるはずです。", examples: [ [ " clean_dir = re.sub(r'[^a-zA-Z0-9]', '', dir_to_list)", @@ -133,6 +148,7 @@ info = present: String.raw`["']ls\s+-l`, index: 1, text: "The shell uses spaces to separate arguments, but we're trying to avoid using the shell when it's not needed. You must instead provide the arguments as a list that contains separate parameters. The parameters should be something like \"ls\", \"-l\", clean_dir", + text_ja: "シェルは引数を空白で区切りますが、ここでは不必要なシェルの使用を避けることを目指します。ここではそれぞれ独立したパラメータのリストとして引数を渡します。パラメータは、\"ls\", \"-l\", clean_dir のようになるはずです。", examples: [ [ " clean_dir = re.sub(r'[^a-zA-Z0-9]', '', dir_to_list)", @@ -144,6 +160,7 @@ info = present: String.raw`subprocess \. run \( [A-Za-z0-9"']`, index: 1, text: "The `subprocess.run` function takes a LIST of parameters as its command, not just comma-separated parameters. This means you need something like the form `subprocess.run([P1, P2, ...])`. Note the square brackets inside the parentheses.", + text_ja: "`subprocess.run` は実行するコマンドをパラメータのリストとして受け取ります。これは単純にカンマで区切ったパラメータではありません。つまり、`subprocess.run([P1, P2, ...])` のような形で渡す必要があるということです。カッコの中に大カッコ([])があることに注意してください。", examples: [ [ " clean_dir = re.sub(r'[^a-zA-Z0-9]', '', dir_to_list)", @@ -155,6 +172,7 @@ info = present: String.raw`\{(dir_to_list|clean_dir)\}`, index: 1, text: "You don't need {...} in your result. Each parameter should be free-standing, not an expression concatenated within a formatted string.", + text_ja: "{...} は不要です。各パラメータは独立しており、フォーマット文字列で連結する必要はありません。", examples: [ [ " clean_dir = re.sub(r'[^a-zA-Z0-9]', '', dir_to_list)", @@ -166,16 +184,19 @@ info = present: String.raw`dir_to_list\"`, index: 1, text: "You have a double-quote after `dir_to_list`; you don't want that.", + text_ja: "`dir_to_list` の後に不要なダブルクオーテーションがあります。", }, { present: String.raw`clean_dir\"`, index: 1, text: "You have a double-quote after `clean_dir`; you don't want that.", + text_ja: "`clean_dir` の後に不要なダブルクオーテーションがあります。", }, { present: "dir_to_list", index: 1, text: "The parameter `dir_to_list` is what was provided, but you don't want to use that. You want to use the cleaned value `clean_dir` instead.", + text_ja: "`dir_to_list` は入力値なので、subprocess.run のパラメータとしては不適当です。不要な文字が削除されている `clean_dir` を代わりに使うべきです。", examples: [ [ " clean_dir = re.sub(r'[^a-zA-Z0-9]', '', dir_to_list)", @@ -187,6 +208,7 @@ info = absent: String.raw`run \(.*\)`, index: 1, text: "You need a pair of matching parentheses in the second section.", + text_ja: "2つ目のセクションには対になったカッコが必要です。", examples: [ [ " clean_dir = re.sub(r'[^a-zA-Z0-9]', '', clean_dir)", @@ -198,6 +220,7 @@ info = absent: String.raw`run \( \[.*\]`, index: 1, text: "You need a pair of matching square brackets in the second section.", + text_ja: "2つ目のセクションには対になった大カッコ([])が必要です。", examples: [ [ " clean_dir = re.sub(r'[^a-zA-Z0-9]', '', clean_dir)", @@ -209,6 +232,7 @@ info = absent: String.raw`^ subprocess . run \( \[ ('ls'|"ls") , ('-l'|"-l") , clean_dir \] \) $`, index: 1, text: "You are getting close. The `subprocess.run` line should look like subprocess.run([\"ls\", \"-l\", clean_dir]) or similar.", + text_ja: "惜しい!`subprocess.run` の行は、subprocess.run([\"ls\", \"-l\", clean_dir]) のようになるはずです。", examples: [ [ " clean_dir = re.sub(r'[^a-zA-Z0-9]', '', clean_dir)",