「引数をコマンドとして解釈するコマンドに信頼できないデータを渡すコマンドを実行する」


18

findutilsのマニュアルより:

たとえば、これらの2つのコマンドなどの構成体

# risky
find -exec sh -c "something {}" \;
find -execdir sh -c "something {}" \;

非常に危険です。この理由は、「{}」がファイル名に展開されるためです。ファイル名には、シェルに特殊なセミコロンまたはその他の文字が含まれる場合があります。たとえば、誰かがファイルを作成した場合、 /tmp/foo; rm -rf $HOME上記の2つのコマンドは誰かのホームディレクトリを削除できます。

したがって、このため、引数をさらに解釈されるコマンドとして解釈するコマンド(たとえば、「sh」)に信頼できないデータ(ファイル名など)を渡すコマンドを実行しないでください。

シェルの場合、この問題に対する巧妙な回避策があります。

# safer
find -exec sh -c 'something "$@"' sh {} \;
find -execdir sh -c 'something "$@"' sh {} \;

このアプローチは、すべての問題を回避することを保証するものではありませんが、攻撃者が選択したデータをシェルコマンドのテキストに置き換えるよりもはるかに安全です。

  1. find -exec sh -c "something {}" \;の置換{}が引用符で囲まれていないため、単一の文字列として扱われないという問題の原因はありますか?
  2. ソリューションではfind -exec sh -c 'something "$@"' sh {} \;

    • 最初{}に置き換えられますが、{}引用符で囲まれていない"$@"ため、元のコマンドと同じ問題はありませんか?たとえば、 "$@"に展開されます"/tmp/foo;""rm""-rf"、と "$HOME"

    • なぜ{}エスケープまたは引用されないのですか?

  3. あなたは他の例(まだして与えることができるsh -c;の有無にかかわらず、またはそれなしに該当する場合はfind、我々が持つ問題と解決策に焦点を当てることができるように、最小限の例であり、問題と解決策の同じ種類が適用され、かつその必要はないとすることができるが)できるだけ気を散らすものはありませんか?`bash -c`によって実行されるコマンドに引数を提供する方法を参照してください

ありがとう。


回答:


29

これは実際には引用に関連するのではなく、引数の処理に関連します。

危険な例を考えてみましょう。

find -exec sh -c "something {}" \;
  • :これはシェルによって解析され、そして6つのワードに分割されfind-execsh-csomething {}、(これ以上引用符なし);。拡張するものは何もありません。findこれらの6つの単語を引数としてシェルが実行されます。

  • ときにfindプロセスに何かを見つけた、と言うfoo; rm -rf $HOME、それが置き換えられ{}foo; rm -rf $HOME、そして実行sh引数を持つsh-csomething foo; rm -rf $HOME

  • sh今見ている-c、そしてその結果としてパースsomething foo; rm -rf $HOME最初の非オプション引数)、その結果を実行します。

次に、より安全なバリアントを検討します。

find -exec sh -c 'something "$@"' sh {} \;
  • シェルが実行されfindた引数でfind-execsh-csomething "$@"sh{};

  • 今際findの発見はfoo; rm -rf $HOME、それが置き換えられ{}、再び、そして走るsh引数を指定してsh-csomething "$@"shfoo; rm -rf $HOME

  • sh見て-c、そしてパースsomething "$@"コマンドとして実行し、sh及びfoo; rm -rf $HOME位置パラメータ(AS から開始$0)、展開"$@"foo; rm -rf $HOME 単一の値として、及び実行 something単一の引数でfoo; rm -rf $HOME

これはを使用して確認できますprintf。新しいディレクトリを作成して入力し、実行します

touch "hello; echo pwned"

最初のバリアントを次のように実行します

find -exec sh -c "printf \"Argument: %s\n\" {}" \;

生産する

Argument: .
Argument: ./hello
pwned

一方、2番目の亜種は、

find -exec sh -c 'printf "Argument: %s\n" "$@"' sh {} \;

生産する

Argument: .
Argument: ./hello; echo pwned

より安全なバリアントでは、なぜ{}エスケープまたは引用されないのですか?
ティム

1
通常、引用符で囲む必要はありません。他の意味を持つシェルで引用する必要があるだけであり、最近使用されているメインシェルのいずれにも当てはまらないと思います。
スティーブンキット

gnu.org/software/findutils/manual/html_mono/…から:「これらの構造(;および{})の両方は、シェルによる展開から保護するために( '\'で)エスケープするか引用する必要があります。bashにとって間違っているということですか?
ティム

3
私はそれが一般的なシェルに対して間違っていることを意味します。POSIXを参照してください。で、その特定の文findutils戻っ少なくとも1996年まで手動日付...もちろんそれは引用にも問題はありません{}どちらかとして、'{}'または"{}"、それは必要ありません。
スティーブンキット

@Timここでは、2つの非常に異なる種類の引数処理が行われているという事実を直観がまだ説明していないという一般的な状況が発生しています:シェルがテキスト行から引数を解析するタイミング/方法(これは引用符が重要な場合)は、オペレーティングシステムの引数をそのまま渡すこととは異なります(引用符は単なる通常の文字です)。シェルコマンドfind some/path -exec sh -c 'something "$@"' {} \;には、実際には3つの層の引数の処理/受け渡しがあります。2種類のシェルと1種類の基本的なrawオペレーティングシステムです。
mtraceur

3

パート1:

find テキスト置換を使用します。

はい、次のように引用符で囲まなかった場合:

find . -type f -exec sh -c "echo {}" \;

攻撃者はというファイルを作成することができたと; echo owned、それは、execう

sh -c "echo ; echo owned"

これは、実行中のシェルにつながるecho、その後echo owned

しかし、引用符を追加した場合、攻撃者は引用符を終了し、次のファイルを作成することで悪意のあるコマンドを挿入できます'; echo owned

find . -type f -exec sh -c "echo '{}'" \;

これにより、シェルが実行さecho ''echo ownedます。

(二重引用符を単一引用符に置き換えた場合、攻撃者は他の種類の引用符も使用できます。)


パート2:

ではfind -exec sh -c 'something "$@"' sh {} \;、は{}シェルによって最初に解釈されず、で直接実行されるexecveため、シェルの引用符を追加しても役に立ちません。

find -exec sh -c 'something "$@"' sh "{}" \;

実行前にシェルが二重引用符を削除するため、効果はありませんfind

find -exec sh -c 'something "$@"' sh "'{}'" \;

シェルが特別に処理しない引用符を追加します。したがって、ほとんどの場合、それはコマンドがあなたが望むことをしないことを意味します。

それが拡大した/tmp/foo;rm-rf$HOMEそれらが引数であるため、問題になることはありませんsomethingし、somethingおそらく実行するコマンドとしてその引数を扱うことはありません。


パート3:

私は、同様の考察は、例えば、信頼できない入力を受け取り、(の一部)コマンドとして、それを実行するものに適用すると仮定xargsしてparallel


xargs-Iまたは-Jが使用されている場合にのみ危険です。通常の操作では、リストの最後に引数を追加するだけ-exec ... {} +です。
チャールズダフィー

1
parallel対照的に、ユーザーからの明示的な要求なしにデフォルトでシェルを実行し、脆弱な表面を増やします;これは多くの議論の主題である問題です。説明については、unix.stackexchange.com / questions / 349483 /…を参照してください。また、lists.gnu.org / archive / html / bug-parallel / 2015-05 / msg00005.htmlへのリンクが含まれています。
チャールズダフィー

@CharlesDuffy「脆弱な表面を減らす」という意味です。OPが示す攻撃は、実際に GNU Parallelではデフォルトで機能しません
オレ丹下

1
はい、SSHを介したパリティのためにそれが必要であることを知っています- ソケットのもう一方の端にリモート「レシーバー」プロセスを生成しなかった場合parallel、直接シェルレスを実行できますexecv。あなたの靴にツールを実装している場合、これは私がやったこととまったく同じです。の非対話型プログラムが現在の値に基づいて異なる動作をすることSHELLは、ほとんど驚きではありません。
チャールズダフィー

1
はい-パイプとリダイレクト、ユーザーがシェルを明示的に開始しない限り不可能であるべきだと思います。その時点で、明示的に開始したシェルの明示的な動作のみを取得します。がexecv提供するものだけを期待できる場合、それはよりシンプルで驚くほど少なく、場所/ランタイム環境間で変わらないルールです。
チャールズダフィー

3

1. find -exec sh -c "something {}" \;の置換{}が引用符で囲まれていないため、単一の文字列として扱われないという問題の原因はありますか?

ある意味では、引用はここでは役に立ちません。代わりに置き換えられるファイル名には、引用符を{}含む任意の文字を含めることができます。どんな形式の引用が使用されたとしても、ファイル名には同じものを含めることができ、引用の「ブレイクアウト」ができます。

2. ...しかし、{}引用符で囲まれていない"$@"ため、元のコマンドと同じ問題はありませんか?たとえば、?"$@"に展開され"/tmp/foo;", "rm", "-rf", and "$HOME"ます。

いいえ。"$@"位置パラメータは個別の単語として展開され、それ以上分割されません。これ{}は、findそれ自体への引数でありfind、現在のファイル名も別個の引数として渡しますsh。シェルスクリプトの変数として直接使用でき、シェルコマンド自体としては処理されません。

...なぜ{}エスケープまたは引用されないのですか?

ほとんどのシェルでは、そうである必要はありません。を実行する場合、次のようfishにする必要がありますfish -c 'echo {}'。空の行を出力します。しかし、引用しても問題はありません。シェルは引用を削除するだけです。

3.他の例を挙げてください...

何らかのコード(*)として解釈される文字列内にファイル名(または別の制御されない文字列)をそのまま展開すると、任意のコマンドが実行される可能性があります。

たとえば、これ$fによりPerlコードが直接展開され、ファイル名に二重引用符が含まれている場合に問題が発生します。ファイル名の引用符は、Perlコードの引用符を終了し、ファイル名の残りの部分には任意のPerlコードを含めることができます。

touch '"; print "HELLO";"'
for f in ./*; do
    perl -le "print \"size: \" . -s \"$f\""
done

(Perlは実行前にコード全体を解析するため、ファイル名は少し奇妙でなければなりません。そのため、解析エラーを回避する必要があります。)

これは引数を通して安全に渡しますが:

for f in ./*; do
    perl -le 'print "size: " . -s $ARGV[0]' "$f"
done

(シェルから別のシェルを直接実行することは意味がありませんが、実行するfind -exec sh ...場合は、ケースに似ています)

(*何らかのコードにはSQLが含まれているため、必須のXKCD:https ://xkcd.com/327/に加えて説明:https : //www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables )


1

あなたの懸念は、GNU Parallelが入力を引用する理由です。

touch "hello; echo pwned"
find . -print0 | parallel -0 printf \"Argument: %s\\n\" {}

これは実行されませんecho pwned

シェルが実行されるため、コマンドを拡張しても、突然の驚きはありません。

# This _could_ be run without spawining a shell
parallel "grep -E 'a|bc' {}" ::: foo
# This cannot
parallel "grep -E 'a|bc' {} | wc" ::: foo

spawning-a-shell問題の詳細については、https//www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shellを参照してください。


1
...とはfind . -print0 | xargs -0 printf "Argument: %s\n"いえ、同じくらい安全です(むしろ、-print0改行でファイル名を正しく処理するためです)。parallelの引用は、シェルが存在しない場合にはまったく存在しない問題の回避策です。
チャールズダフィー

しかし| wc、コマンドに追加したらすぐに、フープをジャンプして安全にする必要があります。GNU Parallelはデフォルトで安全です。
オレ丹下

ITYM:すべてを注意深く引用する複雑なプロセスにより、シェル言語のあらゆる癖をカバーしようとします。これは、コードと混ざっていない変数にデータを適切に保存するほど安全ではありません。今、それを自動的foo {} | wcsh -c 'foo "$1" | wcsh {} `に変換する場合、それは異なるかもしれません。
-ilkkachu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.