
CVE-2022-25173のエクスプロイト。Jenkins Pipeline Groovy PluginのCPSインタープリターサンドボックスバイパスを標的とし、巧妙に細工されたPipelineスクリプトを介してJenkinsコントローラー内で任意のコード実行を可能にする。
Pipeline プラグインスイートの主要コンポーネントであるこのプラグインは、Jenkins コントローラープロセス内で実行されるカスタム Groovy インタープリターに基づいて、Pipeline ステップの標準実行エンジンを提供します。
(原理的には、FlowDefinition を API エントリーポイントとして他の実行エンジンもサポート可能ですが、プロトタイプは作成されておらず、作成するには非常に大きな労力が必要になると思われます。)
次のような Pipeline Groovy スクリプトコードは
retry(3) {
for (int i = 0; i < 10; i++) {
branches["branch${i}"] = {
node {
retry(3) {
checkout scm
}
sh 'make world'
}
}
}
parallel branches
Groovy プログラムとして実行され、steps と呼ばれる特定の特別な関数呼び出しが Jenkins 固有の操作を実行します。 この例では、 ステップはこのプラグインで定義されていますが、、、、 は Pipeline スイートの他のプラグインで定義されています。 グローバル変数は Pipeline Multibranch プラグインで定義されています。
parallelnoderetrycheckoutshscmGroovy スクリプトは WorkflowScript という名前のクラスにコンパイルされるため、スタックトレースにはスクリプトのファイル名 (例: Jenkinsfile) ではなくこの名前が表示されます。
コマンドラインから実行される通常の Groovy プログラムとは異なり、Pipeline ビルドのプログラムの完全な状態は、非同期 操作が実行されるたびにディスクに保存されます。これにはほとんどの Pipeline ステップが含まれます。
ビルドの実行中に Jenkins が再起動された場合、中断したところからプログラムの実行を再開します。
これは効率性を意図したものではないため、Jenkins の機能に直接関連する高レベルの「グルー」コードに限定する必要があります。
プロジェクト独自のビルドロジックは、sh または bat ステップで、ビルドノード上の外部プログラムから実行する必要があります。
JIRA の Pipeline Groovy エピック には、Groovy インタープリターの既知の制限がいくつか記載されています。 これらの問題は、Pipeline が Groovy を直接実行できず、プログラムの状態を保存するために各操作をインターセプトする必要があることに起因します。
JIRA の Pipeline Sandbox エピック には、悪意のある Pipeline スクリプトによる Jenkins の制御を防ぐために使用される Groovy サンドボックス に関する問題が記載されています。 サンドボックスを無効にして実行されるスクリプトは、Jenkins 内部 API を直接呼び出すことができます。これは不足しているステップ機能の有用な回避策となりますが、セキュリティ上の理由から、このようなスクリプトを承認できるのは管理者のみです。
JIRA の Pipeline Snippet Generator エピック には、ライブ設定フォームに基づいてステップ構文のサンプルを提供するツールに関する問題が記載されています。
このプラグインは以前は「Workflow CPS プラグイン」または「Workflow Groovy プラグイン」と呼ばれていました。そのため、Maven の artifactId は pipeline-groovy ではなく workflow-cps です。
このプラグインは Groovy CPS ライブラリ を使用して、プログラムのコンパイル時に 継続渡しスタイル変換 を実装します。
標準の Groovy コンパイラが AST の作成に使用されますが、バイトコードの生成は CompilationCustomizer によってインターセプトされ、ほとんどの操作が特別な「エラー」である CpsCallableInvocation をスローする変種に置き換えられます。
これはエンジンによってキャッチされ、エンジンはそこから得た情報 (メソッド呼び出しに渡されようとしている引数など) を使用して、次の継続に制御を渡します。
Pipeline スクリプトは、指定したメソッドに @NonCPS アノテーションを付けることができます。
これらのメソッドは (サンドボックスのセキュリティチェックを除いて) 通常どおりコンパイルされるため、Java プラットフォーム、Groovy ランタイム、Jenkins コアまたはプラグインコードの「バイナリ」メソッドとほぼ同様に動作します。
@NonCPS メソッドは、ローカル変数として非 Serializable オブジェクトを安全に使用できますが、非シリアライズ可能なパラメータを受け入れたり、非シリアライズ可能な値を返したり保存したりすることはできません。
@NonCPS メソッドから通常の (CPS 変換された) メソッドや Pipeline ステップを呼び出すことはできないため、@NonCPS メソッドは、要約をメインスクリプトに返す前に何らかの計算を実行するために使用するのが最適です。
特に、バイナリクラスで定義されたメソッドの @Override、
例: Object.toString() などは、
通常はバイナリコードから呼び出されるため、一般的に @NonCPS としてマークする必要があることに注意してください。
ある種のオブジェクトは、そのままシリアライズするのは本質的に安全ではありませんが、プログラムグラフ内にそれらへの参照を保持したいと考えています。
例としては、Executor (~ 組み込みノードまたはエージェントノード上のエグゼキュータスロット) があり、これは node ステップがそのブロック内の任意のステップ (特に sh/bat) に渡すコンテキストの一部です。
Pipeline は Pickle API を使用して、これらのオブジェクトのシリアライズ安全なバージョンに置き換えます。
再起動後に WorkflowRun がディスクからロードされると、プログラムの状態がデシリアライズされ、ピクルが並行してデシリアライズ (「再水和」) されます。
すべてのピクルが正常にデシリアライズされ、結果のオブジェクトがプログラムの状態に戻されると、プログラムは再び実行を開始し、StepExecution.onResume が呼び出されてタイマーなどが復元されます。
すべてのプログラムロジックは「CPS VM スレッド」内で実行されます。これは、バイナリメソッドを実行して次にどの継続を実行するかを決定できる Java スレッドプールにすぎません。
parallel ステップは「グリーンスレッド」(協調的マルチタスクとしても知られる) を使用します。さまざまなアクションの論理スレッド (~ ブランチ) 名を記録しますが、文字通り同時に実行するわけではありません。
プログラムはタスクを並行して実行しているように見えるかもしれませんが、それは VM スレッドがアイドル状態の間にほとんどのステップが非同期で実行され、時間的に重複する可能性があるためです。
Groovy コードが実際に VM スレッドで実行される通常は短い間隔を除いて、Java スレッドは消費されません。
エグゼキュータウィジェットは、VM スレッドがビジー状態のときのみ、組み込みノード上の「フライウェイト」エグゼキュータのエントリを表示します。通常は非表示です。