gawkの-iオプションまたは@includeディレクティブを安全に使用する方法は?

gawkの-iオプションまたは@includeディレクティブを安全に使用する方法は?

gawk -i inplace some-awk-code some-file内部で(またはスクリプト@include "inplace"から)awkファイル(または他の拡張子)を編集します。セキュリティホールです

なぜ?

この問題をどのように解決できますか?

答え1

awkGNUには、実行するコードを指定するという点で、標準に対するいくつかの拡張があります。

標準では、コードを読み取るファイルパスと見なされる1つ以上の最初の非オプション引数(gawkなど)でのみawkコードを渡すことができます。 gawkにはより多くのオプションがあります。-f filepathfilepathawk -- 'literal code here'

  • -e 'literal code'(または--source 'literal code')のように、sedコードを複数のパラメータに分割し、-f filepathそのパラメータをパラメータ間で分散できます。
  • -E filepath(または--exec filepath)、-f1つしか存在できないこと、およびそれ以降のすべての項目は、オプションまたは変数の割り当てを考慮せずにファイルパス(または-標準入力)のみを考慮することを除いて同じです。
  • --file filepath:エイリアス-f
  • -i filepath(または):--include filepath動作に似ていますが、若干の変更があります。-fマニュアルに記載されているように

今質問はgawkファイルパス上記のすべてが常にファイルパスと見なされるわけではありません。

  1. もしファイルパス存在しない場合は、gawk拡張子が追加された同じファイルを開こうとします.awk。つまり、意図しないコードを解釈する可能性がありますが、実行しようとしているファイルが存在しないため、実際に問題になる可能性はほとんどありません。--traditionalorではそうしませんが、-W traditionalほとんどのgawk拡張機能を使用することはできません。
  2. もしファイルパス/文字が含まれていない場合(およびそうでない場合-)、awkプログラムは$AWKPATHシェルと同様の方法で環境変数を検索するか、withおよびwithを含むすべての//(および説明されているように、またはwithでexecvp()スラッシュのないコマンドを検索します。の拡張子が追加された場合)。$PATH--posix--traditional-f-i-E.awk

2番目のポイントはここで問題の中心です。

以下では、デフォルトのAWKPATHを見つけることができます。

$ (unset -v AWKPATH && gawk 'BEGIN{print ENVIRON["AWKPATH"]}')
.:/usr/share/awk

(メンションにはそんな変数がないのにENVIRON!)

現在の作業ディレクトリから始めて、その後にいくつかの拡張機能または..NETに付属の他のawkサードパーティモジュールを含むシステムの場所が続きますgawk。このシステムでは:

$ls /usr/share/awk
Assert.awk getlong.awk intdiv0.awk ord.awk rewind.awk
bit2str.awk getopt.awk isnumeric.awk passwd.awk round.awk
Cliff_rand.awk gettime.awk Join.awk processarray.awk shellquote.awk
ctime.awk グループ.awk libintl.awk fastsort.awk strtonum.awk
dpkg-awk.awk have_mpfr.awk nosign.awk 読み取り可能.awk walkarray.awk
ftrans.awk      所定の位置に.awk    ns_passwd.awk ファイルの読み取り.awk ゼロファイル.awk

つまり、-f/の場合は現在の作業ディレクトリにロードし-Eたい場合に必要であり、現在の作業ディレクトリにない場合は他の場所からロードできます(または)。シェルで実行するには、現在の作業ディレクトリが必要なのと同じです(セキュリティ上の理由から通常は含まれておらず、上記のようにロードしようとしていることを除いて)。filegawk -f ./filegawk -f filefilefile.awkfile./cmdcmd$PATH.gawkfile.awk

これは一般的なものに加えて適用されます-i-i含むこの場合、ライブラリのgawk拡張するそれらがなければならないディレクトリにそれらを見つけることを期待し、する拡張を追加したいのですが.awk(ライブラリの拡張には通常これらの拡張があるため)。

(またはシステムにインストールされている場所)を探しgawk -i inplace 'some code' some-fileたいが、ここで問題はデフォルトのAWKPATHです。gawk/usr/share/awk/inplace.awkinplace.awkスタートand.なので、gawkandで最初に照会されます。./inplace./inplace.awk

/tmp書き込み可能であるか、すでに他の人が書き込んだり、通常は信頼できないディレクトリでこのファイルを実行すると、マルウェアが実行される可能性があります。

たとえば、次のようにします。

echo 'BEGIN{system("reboot")}' > /tmp/inplace

awk -i inplace現在の作業ディレクトリで実行されているすべてのスクリプトが/tmpシステムを再起動することがわかります。

この問題を解決するには:

  • inplace各システムまたはGawk展開に合わせてパスを調整する必要があるかもしれませんが、拡張awk -i /usr/share/awk/inplace.awkパスをハードコーディングする代わりに使用してください。awk -i inplace

  • または、.すべての相対パスコンポーネントを削除します$AWKPATH

    export AWKPATH="$(LC_ALL=C gawk 'BEGIN {
      n = split(ENVIRON["AWKPATH"], dirs, ":")
      for (i = 1; i <= n; i++)
        if (substr(dirs[i], 1, 1) == "/") {
          newawkpath = newawkpath sep dirs[i]
          sep = ":"
        }
      if (newawkpath == "") newawkpath = "/dev/null"
      print newawkpath}')"
    

    現在の作業ディレクトリでファイルを使用またはgawk -f ./fileロードする必要があることに注意してください(上記の変更を加えずにすでにこれを行っている可能性があります)。また、4.1.2より前のgawkバージョンがレビュー中であることに注意してください。awk -E ./file$AWKPATH$AWKPATH

    この方法は起動時に環境にすでに存在している必要があるため、#! /usr/bin/gawk -E使用するスクリプトでは使用できません。したがって、使用するスクリプトがある場合は、ユーザーに拡張パスを変更するか、上記のように拡張パスをハードコードするように指示する必要があります。@include$AWKPATHgawkgawk@include "some-extension"$AWKPATH

  • または、何十年もperl使用されていた-i内部編集オプションを使用して、すべての可能なタスクを実行し、awkよりスマートな構文²とより少ない移植性の問題でより多くのタスクを実行できます。しかし--inを忘れないでくださいperl -i -ne 'perl code' -- *.txt。そうしないと、コード注入の脆弱性が発生する可能性があります(またはを使用してください./*.txt。を参照)。perl -ne '...'実行のセキュリティリスク*)!


¹そうでない場合ファイルパス-この場合、ほとんどの実装はawkこれを標準入力からコードを読み取ると解釈します。

sと同じであると考えられる²perlオプションは、他の相対パスを含まず、含まないデフォルト検索パスを使用します(参照)ま​​たはでモジュールを検索する-Mgawk-iM$PERL5LIB$PERLLIB(unset -v PERL5LIB PERLLIB && perl -le 'print for @INC'.

答え2

まず、私が書いたすべてのフォーラムで、私が何年も言ってきたことを教えてくれた@StephaneChazelasに感謝sed -iしますawk -i inplace

すでに述べた内容に加えて(これは私にとって新しい内容であり、思ったよりも悪いです):

  1. 「-所定の位置に」か。まさか!

    sed -iどちらもawk -i inplace「所定の位置で」編集するふりをしますが、そうではありません。実際、それらは(隠された)一時ファイルを出力として生成し、最終的に移動して元のファイルを上書きします。デフォルトでは、POSIX検証バリアントを使用するのと同じですが、自動です。これは良い考えのように聞こえますが、「内部」の観点から見ると、所有権とファイルモードだけでなく、inode番号も保存したいと思います。そうではありません!実際に正しい前提条件が満たされると、3つのプロパティがすべて変更されます(たとえば、ユーザーはファイルに書き込むことができますが、ファイルとは異なるデフォルトグループ、固定ビットを含むディレクトリなど)。

    誤解しないでください。このようなことが起こるには何の問題もありません。プロセスが一時ファイルに書き込まれ、それ自体がコピーされても、同じ方法で発生します。しかし、この場合、私はこれに気づくそして変更後はファイルモードなどが修正されているか確認してください。このふりは効果があるから所定の位置にユーザーはこの効果を認識しない可能性が高いです。

  2. 存在しない一時ファイル

    次の質問は次のとおりです。その過程でファイルが修正され、一時ファイルが生成された場合は予防措置を講じます。一時ファイルを保存するのに十分なスペースが必要です。その後、必ず一時ファイルを削除します。一時ファイルがどこに行くのかわからないため(マンページにこれに関する情報がなく、すべてが「その場で」発生すると仮定しています)、これを制御できず、スクリプトでシステムがクラッシュする場合(これが起こる)、ディスクスペースを占めるいくつかのアーティファクトを残したのかわかりません。

答え3

また、gawkには、環境で見つからない場合のデフォルト値を持つAWKLIBPATH変数があります。この変数は、@load "library"ライブラリファイルが見つかる場所を制御します。 共有ライブラリのロード

.デフォルトは(私がインストールしたバージョンの場合)ディレクトリを使用していないようですが、変更される可能性があると思います。

答え4

@includeは私ですcppawk。 Cプリプロセッサ、対応する#includeマクロ、およびすべてを使用できるようにするawkの周りの小さなシェルスクリプトラッパーです。

#include現在のディレクトリは検索されません。 1つのより良い機能があります。ヘッダー名が二重引用符で囲まれている場合は、ディレクティブを含む#includeファイルと同じディレクトリでその名前を見つけます。これにより、cppawk複数のファイルを含むプログラムを簡単に作成できます。デフォルトファイルは、#include "..."ディレクティブの相対パスを使用して他のファイルを簡単に見つけることができます。

cppawk独自のライブラリヘッダーがいくつかありますが、内部ファイルを編集するためのソリューションを提供するものはありません。このユーティリティを使用すると、ソリューションを簡単に再利用できます。

これは品質の低いプロトタイプです。

$ cat file.bak
alpha
bravo
charlie
$ cp file.bak file
$ cppawk '
#include "inplace.h"
{ out(NR, $0) }
' file
$ cat file
1 alpha
2 bravo
3 charlie

コンテンツinplace.h:

BEGIN {
  __inplace_tmpfile = "xyz.tmp"
  __inplace_origfile = ARGV[1]
}

END {
  close(__inplace_tmpfile)
  system("mv " __inplace_tmpfile " " __inplace_origfile)
}

#define out(...) print __VA_ARGS__ >  __inplace_tmpfile

これには少なくとも以下が必要です。一時ファイルをインポートし、内容をシェルからエスケープしてコマンドにARGV[1]安全に挿入するためのより良い方法です。mv

outリダイレクトのない基本的な実装を持つことができます。その後、コードが含まれるときにコードを変更する必要がないように、プログラムの代わりにprintそれを使用する習慣があります。cppawkinplace.h

-fスクリプト資料を含めることができるため、前処理なしでこれらの目標の一部を達成できます。inplace.hヘッダーの代わりに、次の内容を含むファイルを準備しますinplace.awk

BEGIN {
  inplace = "xyz.tmp"
  __inplace_origfile = ARGV[1]
}

END {
  close(inplace)
  system("mv " inplace " " __inplace_origfile)
}

一時ファイルを保持する変数名の匿名化を解除しました。これはインターフェイスの一部です。

残念ながら、コマンドライン内のスクリプトエントリとインクルードを混在させるには、-fGNU固有の-eオプションが必要です。

$ mv file.bak file
$ awk -f inplace.awk -e '{ print NR, $0 > inplace }' file
$ cat file
1 alpha
2 bravo
3 charlie

引用方法に関する質問もありますinplace.awk。それをどこに置き、どのように見つけることができますか?#includeそのような問題はありません。コードと一緒に送信すると、それ自体の横にあります。ライブラリヘッダーに入れても問題cppawkはあり<inplace.h>ません。また、cppawk --prepro-onlyプリプロセッサなしで実行できる完全な「翻訳単位」キャプチャを使用するオプションもありますcpp

関連情報