WAGO 社はプログラマブル自動化コントローラの製造元であり、自動車、鉄道、発電、製造、建物管理をはじめとする多くの業種で同社の製品が利用されています。Cisco Talos は、同社製のコントローラ PFC200 および PFC100 に 41 件の脆弱性が存在することを発見しました。Talos は情報開示方針に従って WAGO 社と協力し、今回の脆弱性が解決されたこと、および影響を受ける顧客向けに修正ファームウェアが提供されていることを確認しています。
修正ファームウェアの公開後、一定の期間が経過したことから、同社のクラウド接続クライアント「dataagent」をエクスプロイトしてデバイスへのルートアクセスを取得する数種の攻撃チェーンについて、この機会に取り上げます。これらの脆弱性に関する技術解説は、2020 年 10 月 22 日開催の仮想会議 CS3Sthlm でも取りあげています。
WAGO 社は、デバイスの計測データに遠隔アクセスできるだけでなく、ファームウェアの遠隔更新もできるクラウド接続機能をユーザ向けに提供しています。このクラウド接続機能については、興味深い攻撃ベクトルが存在します。信頼できるクラウドプロバイダーが攻撃の発信元となる一方で、クラウドインスタンス自体は攻撃者が支配しているというものです。今回詳しく分析するのは、攻撃者が正規のクラウド インフラストラクチャにアクセスし、WAGO 社の独自プロトコルを悪用してデバイスのルート特権を取得できる状況です。
まず、個々の脆弱性自体の技術的な詳細を掘り下げます。次に、それらの脆弱性を 2 種類の攻撃チェーンでエクスプロイトしてデバイスのルート特権を取得する手法について論じます。
調査
脆弱性の内容
ファームウェア更新コマンドによってリモートからコードが実行される(TALOS-2019-0954/CVE-2019-5161)
dataagent によって実現する機能の 1 つに、クラウドからのデバイスファームウェアの更新があります。この機能は、複数のファイルが関係し、最終的にはデバイス上でシステムコマンドを実行して更新を行うものであるため、すぐに Cisco Talos の目にとまりました。調査の結果、dataagent サービスによるファームウェア更新コマンドの処理に関して、複数の脆弱性が存在していることが判明しました。このサービスは、MQ Telemetry Transport(MQTT)プロトコルに対応している各種のクラウドプラットフォームとの通信を処理するものです。
ファームウェアのリモート更新機能では、JSON エンコードのメッセージをクラウドアプリケーションが MQTT プロトコルで送信する設計になっています。以下に、ファームウェアの更新を開始するコマンドの例を示します。
ファームウェアを更新するプロセスは、ステートマシンを使用して処理されます。ファームウェアを更新するプロセスの最初のステートは、クラウド「`CommandId: 506`」からファームウェア更新メッセージを受信したときが始点となります。dataagent のプロトコル解析コードによって、上のファームウェア更新 JSON メッセージに含まれている個々の値が抽出されます。それぞれの値は `fwupdate` という外部ユーティリティに `-i “<keyname>=<user-supplied-value>” の形式で渡され、`system()` によって実行されるコマンド全体が生成されます。[2]
`sudo fwupdate` コマンドが `system()` への呼び出しを使用して入力のサニタイズなしで実行されるため、各 `<cloud-supplied-value>` パラメータは OS コマンドインジェクションに対して脆弱な状態です。これらの脆弱性は、TALOS-2019-0948 ~ TALOS-2019-0950(CVE-2019-5155 ~ CVE-2019-5157)に分類されています。
OS コマンドインジェクションの可能性があることはわかりましたが、どのような対策をとれるでしょうか。結論として、できる対策は特にありません。dataagent サービスが `iot` という名前の非ルートユーザとして実行されるためです。つまり、クラウドからルートアクセスが取得されることはないでしょう。
ファームウェア更新のステートマシンが移行する次のステートは、`DOWNLOAD_FIRMWARE` です。このステートでは、dataagent プロセスが `FirmwareStorageAccount`、`FirmwareStoragePath`、`FirmwareControlFile` を連結して使用し、デバイスの `/tmp` ディレクトリに curl 経由でダウンロードされるファイルの URL を生成します。上のファームウェア更新メッセージの場合は、`https://attackerstorage.blob.core.windows.net/test/poc_controlFile.xml` でホストされているファイルがダウンロードされます。ダウンロードされたファイルは libxml2 が解析し、以下のようなフォーマットで結果を生成します。
下のコードスニペットは、`FirmwareControlFile` の内容に対して実行される検証の一部を示しています。このコードは、ダウンロードされる追加ファイルのリストの構成要素です。ファイルがダウンロードリストに追加されるには、`File` ノードの `Type` 属性が `raucb` と等しくなければならないことがわかります。
次に、元のファームウェア更新コマンドにある `FirmwareStorageAccount` および `FirmwareStoragePath` と、`FirmwareControlFile` の `File` ノードの ‘Name’ 属性で指定された URL から、`FirmwareControlFile` で指定された追加ファイルがダウンロードされます。したがって、今回の例では、`https://attackerstorage.blob.core.windows.net/test/update_V030039_12_r38974.raucb` が curl を使用してダウンロードされ、`FirmwareControlFile` の `TargetPath` 属性で指定された場所に格納されます。
これで、任意の数の追加ファイルをダウンロードし、格納先としてデバイス上のパスを指定できるようになりました。`Type` 属性が `raucb` であれば、`TargetPath` の場所や指定するファイル数に制限はありません。しかし `Type` 属性に関しては、`Name` または `TargetPath` のファイル拡張子と一致している必要はありません。ただし、ここでも IoT ユーザが書き込めるデバイス上の場所には制限があり、`/tmp` に限定されます。
この時点で、必要なファイルがすべてダウンロードされました。ファームウェアを更新するプロセスの次のステートは、`START` です。このステートでは、外部ユーティリティ `fwupdate` が実行されます。ここでのパラメータは `start` で、実行されるコマンド全体は `system(‘sudo fwupdate start’)` となります。この `fwupdate` ユーティリティは、他の fwupdate* ユーティリティのラッパーです。`fwupdate start` コマンドが実行されると、最終的に `fwupdate_background_service start-update` が呼び出されます。以下に、このシェル関数のコードスニペットを示します。
ここで、ファームウェア更新のプレインストールフックが存在している場合は実行されることがわかります。なお、この `fwupdate_background_service` スクリプトは sudo 特権で実行されるため、プレインストールフックもルートユーザとして実行されます。定義されている変数は以下のとおりです。
この段階で、任意のファイルをデバイスの `/tmp` に書き込めるようになりました。ファイルの書き込み先が `/tmp/fwupdate/fwupdate_hook_preinstall.sh` である場合は、ルートとして実行されます。
不十分なホスト検証(TALOS-2019-0953/CVE-2019-5160)
dataagent でサポートされているファームウェア更新コマンドに脆弱性が存在することがわかったので、当該コマンドを送信できるクラウドプラットフォームに関して、制約が存在するかどうかを見てみましょう。dataagent サービスは、WAGO Cloud、Microsoft Azure、IBM Cloud、Amazon Web Service、SAP IoT Services をはじめとする複数のクラウド インフラストラクチャ ベンダーに接続するように設定できます。
ここで、ファームウェア更新のプレインストールフックが存在している場合は実行されることがわかります。なお、この `fwupdate_background_service` スクリプトは sudo 特権で実行されるため、プレインストールフックもルートユーザとして実行されます。定義されている変数は以下のとおりです。
上のコードスニペットは、dataagent サービスが `WAGO Cloud` クラウドタイプを使用するように設定されている場合のみ、ファームウェア更新コマンドが処理されることを示しています。条件に該当しない場合はメッセージが破棄されます。このサービスは、`WAGO Cloud` 用に設定されている場合、カスタム WAGO Cloud アプリケーションを実行する Azure IoT Hub インスタンスとの通信に Azure IoT SDK を使用します。このアプリケーションは、`wagocloud.azure-devices.net` でホスティングされている WAGO 社のサブスクリプションサービスです。
dataagent の設定は、デバイスで提供される Web ベース管理(WBM)の Web アプリケーションを通じて実行されます。クラウドプラットフォームとして `WAGO Cloud` を選択した場合、指定されたホスト名が検証されることはありません。Azure IoT Hub インスタンスはすべてワイルドカード証明書を使用して認証され、ホスト名が `*.azure-devices.net` であることが確認されます。このこと自体に特に問題はありません。しかしこの挙動は、WAGO 社によるファームウェア更新コマンドの実装と組み合わさったときに問題となります。WAGO 社が意図していたのは、同社のクラウドアプリケーションのみがファームウェア更新機能を使用できるように機能制限を課すことでした。ここで問題となるのは、Azure IoT Hub のクライアントであるデバイスについて認証を受けるための有効な証明書を、Azure IoT Hub インスタンスが保有していることです。つまり、あらゆる Azure IoT Hub インスタンスで WAGO 社のファームウェア更新機能を使用できるのです。設定済みの WAGO 社クラウドアプリケーションのホスト名が検証されないという挙動は、TALOS-2019-0953/CVE-2019-5160 に該当します。この脆弱性がエクスプロイトされると、攻撃者の支配する Azure IoT Hub インスタンスに接続するように dataagent サービスを設定できるようになります。
WBM における管理者クレデンシャルの情報漏えい(TALOS-2019-0923/CVE-2019-5134 および TALOS-2019-0924/CVE-2019-5135)
攻撃者が支配しているホストの名前で dataagent を設定するには、WBM の管理者クレデンシャルが必要です。WBM のユーザは、デバイス上のセキュリティ対策として Linux システムユーザとは分離されています。`login.php` の `PasswordCorrect` 関数には 2 件の脆弱性が存在していて、ハッシュ済みのユーザクレデンシャルが漏えいするおそれがあります。以下に `PasswordCorrect` 関数を示します。
パスワードファイルは Linux ユーザとは分離され、`/etc/lighttpd/lighttpd-htpassword.user` に格納されます。`PasswordCorrect` 関数で使用される正規表現にはアンカーが使用されていないため、ユーザ入力で正規表現フィルタを迂回することができます(TALOS-2019-0923/CVE-2019-5134)。たとえば、`(?x)admin….` という `$username` 値は、`admin:$6$` が記述されているパスワードファイルの行と一致します。正規表現フィルタを迂回できることから、パスワードファイルに収められているパスワードハッシュと照らし合わせてユーザ入力をテストできるようになっています。パスワードファイルの行にユーザ名が含まれているかどうかという最初のチェックを通過した場合、PHP の crypt() 関数が呼び出されます。crypt() 関数では顕著な応答遅延が発生することから、タイミング攻撃(TALOS-2019-0924/CVE-2019-5135)を実行できます。迂回するためのユーザ名の値である `(?x)admin….` を使用すると、ASCII 文字をこの値に付加して応答遅延を測定し、`crypt` 関数が呼び出されたかどうかを特定できます。結果として、パスワードハッシュにどの文字が含まれているかが判明するのです。
iocheckCache.xml のコマンドインジェクションおよびコード実行(TALOS-2019-0961/CVE-2019-5166、TALOS-2019-0963/CVE-2019-5182)
先ほど、dataagent ファームウェア更新制御ファイルに加えてファイル名とパスを使用すると、デバイスにダウンロードするファイルを指定できることを指摘しました。しかし、制限付きの IoT ユーザの書き込み先は `/tmp` ディレクトリに限定されています。
ところが、デバイス上の iocheckd という別のサービスが、適切に設定する上で複数のメッセージが必須となる一部のネットワーク設定について、`/tmp` ディレクトリの `iocheckCache.xml` というファイルを使用してキャッシュファイルを保存していることがわかりました。通常、iocheckd サービスにメッセージが送信されると、ホスト名や IP アドレスなどの新規パラメータが収められた `/tmp/iocheckCache.xml` が生成されます。次に、パラメータ保存のメッセージによって `iocheckCache.xml` の内容が読み取られ、デバイスの設定を修正するシステムコマンドが実行されます。同様の脆弱性を持つおそれのある XML ノードは数多く存在しているため、iocheckd によるこのキャッシュファイルの解析に関しては合計で 18 件の脆弱性が存在します。今回の記事では、OS コマンドインジェクション(TALOS-2019-0962/CVE-2019-5167-CVE-2019-5175)に焦点を当てます。以下は、脆弱性のあるコードの抜粋です。
OS コマンドインジェクションが可能になるのは、sprintf() を使用して、XML ファイルの `xmlHostnameNodeValue` がコマンドバッファに直接コピーされるためです。検証が実行されないまま、その文字列が `system()` の呼び出しに渡されます。シェル拡張文字を追加すれば、追加の OS コマンドを実行できるようになります。iocheckd サービスはルートユーザとして実行されることから、OS のコマンドもルートとして実行されます。
エクスプロイト
ここでは、2 種類のエクスプロイトチェーンを取り上げます。各チェーンでクラウドからコマンドが実行されると、結果的にデバイス上でコードがルートとして実行されます。最初のチェーンは、ファームウェア更新のプレインストールフックを使用するものです。2 番目のチェーンは、ファームウェア更新のメカニズムを使用して iocheckCache.xml ファイルをデバイスに配置し、このファイルを利用することでルートユーザとしてコマンドを実行することを可能にするというものです。これらのエクスプロイトの事例は、こちらで入手可能な PFC 200 ファームウェアのバージョン 13 上で試行されました。
攻撃者が支配しているクラウドへの接続
それぞれの攻撃シナリオでは、攻撃者の支配下にある Azure IoT Hub に接続するようにデバイスを設定することが必須条件です。TALOS-2019-0923 と TALOS-2019-0924 を悪用して、WBM 管理者のユーザハッシュ クレデンシャルを抽出した後、hashcat を使用してパスワードをクラックします。デバイス上で dataagent サービスを設定するには、WBM 管理者の特権が必要になるためです。上で説明したとおり、PHP の crypt() 関数にタイミング攻撃を仕掛けることにより、Web 管理者のパスワードハッシュを抽出します。以下は、抽出した Web 管理者ユーザのパスワードハッシュです。
これでハッシュ済みのパスワードを入手したので、hashcat を使用してこのパスワードをクラックできます。
上に示したとおり、WBM 管理者ユーザのパスワードは `T@l0s` です。TALOS-2019-0953 の脆弱性を突くことで、以下の POST リクエストを使用して、IoT Hub インスタンス `attackerIotHub.azure-devices.net` に接続するように dataagent サービスを設定できます。
攻撃チェーン 1:プレインストールフック
攻撃者の支配するクラウドに接続するようにデバイスが設定されたので、ファームウェア更新のプレインストールフックに存在する脆弱性(TALOS-2019-0954)をエクスプロイトすることで OS のコマンドをルートユーザとして実行できます。この脆弱性をエクスプロイトするには、2 つのファイルを作成する必要があります。XML 制御ファイルと、システム上でルートとして実行されるシェルスクリプトです。これらの 2 つのファイルは、デバイスで curl を使用してダウンロードできるよう、サーバ上でホスティングされている必要があります。今回の例では、ファイルを Microsoft Azure ストレージアカウントでホスティングします。以下に、XML 制御ファイルの内容を示します。
すでに述べたとおり、Type 属性が “rauc” である各ファイルノードが、TargetPath 属性で指定されたデバイス上の場所に書き込まれます。プレインストールフックを実行するには、パス `/tmp/fwupdate` にファイル拡張子 `.raucb` のファイルが存在している必要があるため、“raucb.txt” というファイルが指定されています。この脆弱性をエクスプロイトする上で、`.raucb` ファイルの内容は特に重要ではありません。デバイス上でルートとして実行されるスクリプトが記述されているのは、`rootShell.sh` ファイルです。
このスクリプトは、TELNET プロトコルサーバである telnetd を起動します。これは BusyBox が提供している telnetd ユーティリティです。パラメータ `-l` で、新しい接続の確立時に実行される実行ファイルを指定しています。このオプションの目的はログイン機能を提供することですが、ここでは、ログインではなくルートシェルを取得するために使用します。パラメータ `-p` で指定しているのは、telnetd でリッスンするポートです。
Microsoft が提供している Visual Studio Code の有用な拡張機能を利用すると、IoT デバイスからのメッセージをユーザがモニタできます。デバイスは、Azure IoT Hub に初めて接続する際、以下の 2 つのメッセージを送信して、デバイスでサポートされているプロトコルのバージョンを通知します。
メッセージを IoT Hub デバイスに送信するため、Microsoft Azure の azure-sdk-for-js を使用します。攻撃スクリプトでは、WAGO 社のクラウドアプリケーションと同一のメッセージシーケンスおよび構造を採用して、同社のクラウドアプリケーションの挙動を模倣します。このスクリプトは、デバイスとの間でプロトコルのバージョンをネゴシエートするための `CloudHello` メッセージを送信するものです。その後、`Heartbeat` メッセージを受領した時点で、このスクリプトはデバイスをエクスプロイトするファームウェア更新コマンドを送信します。ファームウェアの更新は失敗しますが、その時点でプレインストールフックのスクリプトは実行されています。スクリプトが実行された後は、ポート 1234 上で任意の telnet クライアントを使用した接続が可能になります。
攻撃チェーン 2:iocheckCache.xml
ファームウェア更新コマンドを使用して iocheckCache ファイルを `/tmp` ディレクトリに配置した後は、iocheckd サービスにメッセージを送信してエクスプロイトを開始できます(TALOS-2019-0962)。この脆弱性をエクスプロイトする場合も、同様に XML 制御ファイルと iocheckCache XML ファイルという 2 つのファイルが必要です。以下に、XML 制御ファイルの内容を示します。
このエクスプロイトの場合は、iocheckCache ファイル内のホスト名ノードを使用して OS のシステムコマンドを挿入します。
この場合も、telnetd サーバを使用してポート 1234 経由でルートシェルを提供します。先ほどと同一の攻撃スクリプトを使用しますが、ここで指定するのは上の制御ファイルです。今回も、有効なファームウェアイメージをコマンドで指定していないためファームウェアの更新は失敗しますが、ファイル `/tmp/iocheckCache.xml` はデバイス上に残っています。
後ほど、PLC 上で実行されている iocheckd サービスにポート 6626 経由で別のメッセージを送信できます。このメッセージは、iocheckCache.xml の解析を開始して、挿入済みのコマンドを実行するものです。iocheckd でキャッシュファイルの解析が完了した後は、ポート 1234 経由で任意の telnet クライアントを使用した接続が可能になります。
まとめ
今回の記事では、攻撃者が支配しているクラウド インフラストラクチャを使用してベンダーのクラウドアプリケーションを偽装するという、興味深い攻撃ベクトルを中心に解説しました。Azure IoT Hub が暗号化と認証の機能を備えているのに対して、WAGO 社は、クラウドアプリケーション自体に対する追加的な検証を実装していませんでした。クラウド接続の機能は、産業用途での採用が増加しています。製造業の場合、クラウドが発信元となるデータについては、信頼できないデータとして取り扱う必要があります。今回の記事で取り上げた脆弱性が連鎖的にエクスプロイトされると、リモートからのルートアクセスを攻撃者が取得することになります。攻撃者はルートアクセスを使用して、WAGO 社の PFC が処理している重要なプロセスを妨害する危険性があります。
カバレッジ
脆弱性のエクスプロイトは、以下の SNORTⓇ ルールで検出できます。今後、脆弱性に関する新たな情報が追加されるまでの間は、ルールが追加されたり、現行のルールが変更されたりする場合がありますのでご注意ください。最新のルールの詳細については、Firepower Management Center または Snort.org を参照してください。
本稿は 2020 年 10 月 21 日に Talos Group のブログに投稿された「 Vulnerability Spotlight: A deep dive into WAGO’s cloud connectivity and the vulnerabilities that arise 」の抄訳です。