ホームオートメーション:シンプルなIoT製品に潜む脆弱性が明らかに
インターネット・オブ・シングス(IoT)が私たちの生活を便利にしてくれることは間違いありません。電気や家電製品を遠くから付けたり消したり、オンラインで監視したりしたいのなら、Wi-Fi接続コンセント「スマートプラグ」で手軽に実現できます。しかし、IoTデバイスはセキュリティがしっかりしていないと、いつでもサイバー攻撃の対象になりうるのです。
McAfee Labs のAdvanced Threat Research(ATR)チームは、ソフトウェアとハードウェアの両方のセキュリティー問題を明らかにし、開発者が企業や消費者に安全な製品を提供できるよう支援しています。今回、私たちはBelkin社の消費者向け製品を調査しました。 同社のWemo Insight Smart Plugを調べたところ、libUPnPHndlr.soライブラリのバッファーオーバーフローが未報告だったことがわかりました。この不備(CVE-2018-6692)を突いて攻撃者がリモートコードを実行するおそれがあるため、ポリシーに則りこの調査結果を5月21日にBelkin社に報告しました。
この脆弱性が実害を伴うようなサイバー攻撃につながる可能性はあるのでしょうか? スマートプラグ自体への影響という意味ではわずかでしょう。攻撃者はせいぜいスイッチをオフにするか、最悪でもスイッチに過負荷をかける程度でしょう。しかし、プラグが他のデバイスとネットワーク接続されていると、脅威のポテンシャルが拡大します。その時点でプラグは大規模な攻撃のエントリーポイントになる危険性があります。それについては、レポートの後半で可能性のある攻撃について具体例を取り上げてみたいと思います。
攻撃対象領域を探る
マニュアルに従ってWemo電話アプリケーションを使ってスマートプラグをセットアップしました。これでコンセントを遠隔からオンやオフにすることができます。その後、ポートスキャン、通常のネットワークトラフィックの監視、オンラインリサーチの閲覧などのソフトウェアのテストを行いました。Wemoは、ユニバーサルプラグアンドプレイ(UPnP)ポートTCP 49152および49153を傍受します。マニュアル、分解イメージ、および汎用プログラミング言語(GPL)はすべてオンライン上にあるため、CPUアーキテクチャー、オペレーティングシステム、およびアプリケーションに関する情報を入手できます。
次にハードウェアに移り、デバイスを分解しました。メインボード上のチップを特定し、デバイスと通信するためのヘッダーを見つけ、メモリーをフラッシュから取り出しました。そしてオンラインリサーチを通じて、ボード上の各チップのデータシートを入手しました。
ユニバーサル非同期レシーバー/トランスミッター(UART)パッドがボード上にあることがわかり、ドキュメンテーションで確認しました。そのヘッダーにワイヤーをハンダ付けして、アクティブに送信しているかどうかを確認しました。デバイスとの通信のテストには、以下に示したExodus XI Breakoutボードを使用しました。
通信速度を総当たりしてみると、UARTインターフェースを介してデバッグ情報を取得することができました。次に、UARTによってログインプロンプトが表示されましたが、オンラインリサーチや当てずっぽうでも有効なパスワードにはたどりつけませんでした。
抽出とファームウェア分析
ボード上にあるフラッシュチップはMaxronix MX25L12835Fで、ファームウェアの抽出でよく使われるオープンソースツールのflashromによってサポートされています。flashromとXI Breakoutボードを使用して、Wemoデバイスからファームウェアを抽出し、デバイスに付属の元のファームウェアイメージを取得した後、Wemoモバイルアプリケーションを使用して更新しました。デバイスが更新されると、再びデバイスからファームウェアを抽出し、2番目のイメージが表示されます。以前のソフトウェア偵察が変更されていないことを確認するため、新しいファームウェアで基本的な健全性のチェックを行いました。
さらに、抽出したファームウェアをオープンソースバイナリ解析ツールのbinwalkを使用して分析しました。Binwalkは調査を進めるためにファームウェアからファイルシステムを抽出し、そのファイルシステムにアクセスすることでシステム構成とアクセスバイナリを確認することができました。
脆弱性の発見
ネットワークやリモートの脆弱性はローカルの欠陥よりも危険です。このため、ローカルネットワークを傍受してUPnPポートを詳しく見ていきます。このテスト段階の間に、私たちの主任アナリストはExodus Intelligence Embedded Exploitationに関する講座を受講しました。インストラクターの1人であるElvis Collado(@ b1ack0wl)は、UPnPファザーを開発していて、私たちの調査に力を貸してくれました。このツールを使用して、オープンなUPnPポートのファジー化を開始する一方、WemoのUARTインターフェースを監視すると、間もなく、UARTインターフェースでクラッシュが発生したのです。
11:37:16.702 stuntsx0x46ac6 STUN client transaction destroyed
sending SIGSEGV to wemoApp for invalid write access to
464d4945 (epc == 2ac1fb58, ra == 2ac1fccc)
Cpu 0
$ 0 : 00000000 00000001 0000006d 464d4945
$ 4 : 31d2e654 31d2e770 00000003 00000001
$ 8 : 0000007c fffffff8 00000007 00000002
$12 : 00000200 00000100 00000807 00000800
$16 : 31d2e6f0 31d2e898 004a1cb8 00000002
$20 : 31d2e638 31d2e6c0 004a1388 31d2e640
$24 : 00000400 2ac1fb30
$28 : 2ac77d40 31d2e600 31d2e648 2ac1fccc
Hi : 00000008
Lo : 00000000
epc : 2ac1fb58 Tainted: P
ra : 2ac1fccc Status: 0100fc13 USER EXL IE
Cause : 8080000c
BadVA : 464d4945
PrId : 0001964c
Modules linked in: softdog rt_rdm rt2860v2_ap(P) raeth
Process wemoApp (pid: 2157, threadinfo=80fa0000, task=802c87f0)
Stack : 2a0000d0 fffffffe 31d2e6f0 31d2e770 31d2e76f 31d2e6f0 31d2e6f0 31d2e770
00000000 31d2e604 00000000 00000000 2ac77d40 00000000 4f464751 4a484d4c
4e444241 47454f49 50464658 45414d42 43445044 464d4945 5552414c 46495048
4b524141 41445a4f 44534e4a 4e4e494c 44434357 494a4855 44515455 44494b45
55584a44 584e4f52 545a5247 51545954 595a4c42 4e594a45 484f5158 46474944
…
Call Trace:
Code: 80a20000 50480004 a0600000 <5440fffa> a0620000 a0600000 10a00006 24840004 24a50001
thready: Destructor freeing name “ChildFDTask”.
Aborted
何回も試して結果をよく観察したところ、クラッシュは以下のパケットによって引き起こされたことがわかりました。
スペース上の制限からペイロードの一部は削除しました。(「EnergyPerUnitCostVersion」の元のデータは2828文字でした)。クラッシュデータとパケットを調べたところ、データがスタックに上書きされている典型的なバッファーオーバーフローのようだとわかりました。ファジングを続けた後、「EnergyPerUnitCost」フィールドを詳しく調べると、わずか32文字でアプリケーションをクラッシュできることが判明しました。
クラッシュダンプからは多くの有益な情報を入手できますが、まだわからないことはたくさんあります。たとえば、クラッシュは「WemoApp」で発生し、オフセットを提供しますが、このライブラリのベースアドレスは何かとか、スタックに上書きされたのは何かとかは、実行時にアプリケーションにアクセスしていなければ、答えを見つけるのは困難です。すでにファイルシステムを入手しているのでWemoAppバイナリを静的に分析することはできますが、クラッシュの正確なポイントを簡単に割り出すことは今後もできないでしょう。
これらの質問に答えるためには、2つの道のうちの1つを選ばなければなりません。テストを続けるにあたってはWemoのファームウェアかバイナリをバーチャル化します。つまり、UARTインターフェースのルートパスワードを知ることができれば、デバイス上でデバッグできる可能性があるのです。一般に、ファームウェアをバーチャル化することは簡単ではなく、不正確なテスト結果をもたらすことがあります。デバイス上でデバッグする方がよいのです。偵察中に見つけたすべての情報をもとに、ルートパスワードをバイパスすることが間違いなくできると考えました。(私たちは実際にWemoAppをバーチャル化しようと時間を費やしましたが、成功しませんでした)。
ルートパスワードをバイパス
抽出したファイルシステムから、標準の/etc/passwdファイルまたは/etc/shadowファイルにあるユーザアカウント情報でWemoがエンベッドされたLinuxシステムOpenWRTを実行していることがわかりました。さらに、 /etc/passwdからルートパスワードのハッシュを抽出し、クラッキングリグに提示しましたが、この方法では、必要な時間内で効果を示すことができませんでした。
フラッシュチップは読み取ることができたので、チップに書き込める可能性が大きくなりました。ファームウェアで行われたチェックサムやバリデーションを除外することで、/etc/passwdファイルを既知のパスワードに置き換えることができるとみられたのです。
この理論を試すには、ファームウェアをリパックする必要があります。WemoのGPLは一般公開されているので、開発者が使ったのと同じツールを使用することにしました。GPLを使用して、Izmaで同じバージョンのスカッシュツール3.0をコンパイルし、変更された/etc/passwdファイルを使ってファームウェアファイルシステムをリパックしました。ファームウェアセクションがオリジナルと同じサイズになるように、パディングを追加しました。次に、新しいファイルシステムセグメントをファームウェアバイナリに挿入するために、「dd」を使用しました。このプロセスでは、ファームウェアを抽出するためにbinwalkを使用すると、ファームウェアを正しくリパックできませんでした。binwalkから提供された情報で「dd」を使用し、リパックするための正しいファームウェアバイナリのセクションを抽出しました。
新しいファームウェアバイナリを入手したことで、XI Breakoutボードとフラッシュメモリを使って、ボード上のフラッシュチップにファームウェアを書き込みました。そのうえで、デバイスをリブートすると、新しいパスワードを使用してログインできました。
クラッシュの分析
Wemoにルートアクセスすることで、UPnPファジング中のクラッシュに関する詳細情報を収集することができました。まず、この特定のアーキテクチャーについてより詳細な分析を行うために必要なツールをコンパイルしなければならなかったため、GPLを使ってデバイスのgdbserverとgdbをコンパイルしました。Wemoには「wget」のような大量のインストールされたツールがあるので、ファイルの追加が簡単にでき、 /tmpディレクトリーからツールをダウンロードして実行しました。
何度も試行したものの、gdbをデバイスで直接またはリモートで実行することはできませんでした。そこで、すべてのデバッグにgdbserverをInteractive Disassembler Proを組み合わせて使ってみました。デバッグが接続された状態で、クラッシュの原因となったパケットを送信し、クラッシュの正確な位置を確認しました。セグメンテーションの不備は0x2AC15B98で発生しました。Linux 「proc」ディレクトリーのメモリレイアウトから、そのメモリーアドレスがライブラリlibUPnPHndlr.soにあると判断しました。
2abf3000-2ac4d000 r-xp 00000000 1f:02 82 /rom/lib/libUPnPHndlr.so
クラッシュの原因はUPnPパケットにありました。つまり、このライブラリ内でクラッシュが起きるのは理にかなったことで、 ベースアドレス0x2abf3000を使って、IDAの静的解析のオフセットを0x22b98となるよう計算しました。このアドレスでは、以下の発見がありました。
LOAD:00022B70 # =============== S U B R O U T I N E =======================================
LOAD:00022B70
LOAD:00022B70
LOAD:00022B70 .globl TokenParser
LOAD:00022B70 TokenParser: # CODE XREF: ProcessEnergyPerunitCostNotify+84↓p
LOAD:00022B70 # DATA XREF: LOAD:00004210↑o …
LOAD:00022B70 beqz $a1, locret_22BC0
LOAD:00022B74 move $a3, $zero
LOAD:00022B78 move $a3, $zero
LOAD:00022B7C b loc_22BB4
LOAD:00022B80 li $t0, 0x7C # ‘|’
LOAD:00022B84 # —————————————————————————
LOAD:00022B84
LOAD:00022B84 loc_22B84: # CODE XREF: TokenParser+28↓j
LOAD:00022B84 addiu $a1, 1
LOAD:00022B88 addiu $v1, 1
LOAD:00022B8C
LOAD:00022B8C loc_22B8C: # CODE XREF: TokenParser+48↓j
LOAD:00022B8C lb $v0, 0($a1)
LOAD:00022B90 beql $v0, $t0, loc_22BA4
LOAD:00022B94 sb $zero, 0($v1)
LOAD:00022B98 bnezl $v0, loc_22B84
LOAD:00022B9C sb $v0, 0($v1)
LOAD:00022BA0 sb $zero, 0($v1)
LOAD:00022BA4
LOAD:00022BA4 loc_22BA4: # CODE XREF: TokenParser+20↑j
LOAD:00022BA4 beqz $a1, locret_22BC0
LOAD:00022BA8 addiu $a0, 4
LOAD:00022BAC addiu $a1, 1
LOAD:00022BB0 addiu $a3, 1
LOAD:00022BB4
LOAD:00022BB4 loc_22BB4: # CODE XREF: TokenParser+C↑j
LOAD:00022BB4 slt $v0, $a3, $a2
LOAD:00022BB8 bnezl $v0, loc_22B8C
LOAD:00022BBC lw $v1, 0($a0)
LOAD:00022BC0
LOAD:00022BC0 locret_22BC0: # CODE XREF: TokenParser↑j
LOAD:00022BC0 # TokenParser:loc_22BA4↑j
LOAD:00022BC0 jr $ra
LOAD:00022BC4 move $v0, $a3
LOAD:00022BC4 # End of function TokenParser
開発者はバイナリをはがさずにおいたので、この関数の名前をTokenParserとしました。セグメンテーションの不備は、分岐命令で発生します。ただし、MIPSでは、遅延命令は分岐が発生する前に実行されます。それによって、0x22B9Cでの命令がクラッシュの原因となるのです。ここでは、アプリケーションは$ v1に保存されているアドレスにあるものをロードし、それを$ v0に置換しようとします。レジスターを見てみると、「EnergyPerUnitCostVersion」というXMLタグにあるパケットからのデータが$ v1にあることがわかり、「無効な書き込みアクセス」セグメンテーション不備のエラーが発生します。
関数を静的に解析すると、データがセクションから別のセクションにコピーされ、3回にわたって0x7C、つまり 「|」文字を見つけようとします。「|」が見つからない場合は、静的に定義されたバッファーにコピーを続けます。なぜ上書きが起こるのかを完全に理解するためには、関数を追いながらスタックを見てみましょう:
2EF17630 2AC692F0 MEMORY:2AC692F0
2EF17634 00000000 MEMORY:saved_fp
2EF17638 34333231 MEMORY:34333231 ←以前のコピーデータ
2EF1763C 00000035 MEMORY:retaddr+31 ←次のバイトは0x2EF1763Dに書き込まれる
2EF17640 00000000 MEMORY:saved_fp ←コピー用に準備されたメモリーをゼロで埋める
2EF17644 00000000 MEMORY:saved_fp
2EF17648 00000000 MEMORY:saved_fp
2EF1764C 00000000 MEMORY:saved_fp
2EF17650 2EF17638 MEMORY:2EF17638 ←このアドレスに書き込みが始まり、上書きされる
関数がスタックにデータをコピーすると、最終的に元のバッファーのアドレスにコピーされます。このアドレスが上書きされると、関数は新しい値で次のバイトを書き込もうとしますが、この場合のアドレスは無効です。このオーバーフローは攻撃者に2つの悪用可能なベクトルを与えます。1つは、write-what-where条件により、攻撃者がメモリー内の任意の場所にデータを書き込むことができるということ。もう1つは、スタック上のデータを上書きし続けることによって、攻撃者は$ RAレジスターを上書きしたり、呼び出した関数のアドレスを返送したりして、攻撃者が実行フローを制御できるようになることです。
エクスプロイトの書き込み
脆弱性の内容が理解できれば、それを悪用することは可能でしょうか? 標準的なバッファーオーバーフローという点を踏まえると、2つの質問に対する答えが必要になります。1つは、スタックにはどのくらいの空き容量があるのか、もう1つはスタックに格納できない「不良」バイトがあるのか、です。どの程度の空き容量があるかを確かめるため、有効なアドレスでスタック上に上書きされたアドレスを修復すると、どの程度のペイロードを搭載できるだけの余裕がスタック上にあるかがわかります。結果は、91バイトしかスタックに書き込めないことがわかりました。
次のステップは、「不良」バイトがあるかどうかを決定することです。いくつかのテストを実行した結果、ASCII文字だけがスタックに搭載できることがわかりました。脆弱なコードが実行される前に、オープンソースのXMLパーサ 「mxml」がパケットを解析します。このライブラリは、タグ間にASCII文字とUnicode文字のみが存在するという基準にしばられます。
この基準は、シェルコードとリターン指向プログラミング(ROP)技術の両方にとって非常に問題です。なぜなら、メモリーアドレスとシェルコードの両方がほとんど解読不能な文字を使用する傾向があるからです。スタック上の空間と戦う際に使えるいくつかの技術がありますが、XMLサニタイズプロセスを通過する文字に関する厳しい制限があるため、既にメモリーにロードされている関数を使用するのが最善の方法です。拡張シェルコードを必要としない一つの方法は、システムコマンドを実行するために「return to libc」攻撃を使用することです。通常、システムコールではパラメータとして文字列が使用されるため、フィルターを通過する可能性があります。Wemoはアドレス空間レイアウトのランダム化を使用しないため、ROPを使えば、理論的には、XMLフィルターを通して追加のシェルコードを打ち込む必要もなく、システムを呼び出すことができます。
それでも、なお大きな課題があります。完全にASCII文字を含むアドレスだけがXMLフィルターを通過できるという点です。これは、使用可能なガジェットを探し出す可能性を大幅に制限してしまいます。IDAを使用して、libcとシステムがメモリーにロードされる場所を確認した結果、2つの実装を見つけました。1つはアドレス0x2B0C0FD4のlibuClibc-0.9.33.2.so、もう1つはアドレス0x2AD104F4のlibpthread-0.9.33.2.soです。ただし、これらのアドレスのいずれもXMLフィルターを通過する要件を満たしていません。したがって、たとえROPチェーンを作成することができたとしても、パケットのシステムのアドレスだけを送信することはできません。
悪意のある文字を含むアドレスは、エクスプロイト展開においては新しい問題ではありません。最も一般的なバイパスの一つは、加算または減算ROPガジェットを使ってレジスターに必要なアドレスを作成し、そのレジスターを呼び出すことです。しかし、ここでもXMLフィルターによって、加算式や減算式にどのオペランドが使用できるかという制約に直面します。
メモリレイアウトを調べた結果、libuClibc-0.9.33.2.soはXMLフィルターをバイパスできるアドレスを持つメモリロケーションにありました。幸運なことにこれは大規模で、かつこの空間では唯一のライブラリだったため、それなりのアドレスリストを入手でき、それによってこのエクスプロイトを作り出すのを支援するツールを作成しました。このツールは、使用可能なメモリーアドレスを持つすべてのROPガジェットを取り出し、フィルターをバイパスする値のみを使用することで、メモリ内の2つのシステムコールの1つを加算式または減算式で呼び出すことができるかどうかを判断します。 libuClibc-0.9.33.2.so、0x2B0C0FD4にあるシステムのアドレスは、いかなる使用可能なオペランドも含まれていませんでしたが、0x2AD104F4にはそれがありました。合わせると0x2AD104F4に等しくなるいくつかの「フィルター耐性」オペランドが見つかりました。
ROPチェーンを構築するためにフィルターをバイパスする可能性のあるすべてのROPガジェットに対しツールのアウトプットを適用しました。それによって、追加の命令を使ってシステムの最終アドレスを作成し、$ s0に格納します。追加後、別のガジェットはシステムのアドレスを$ t9に移動させ、システムを呼び出します。この最後のガジェットは、スタックから制御されるアドレスを、システムコールのパラメータを保持するレジスターへと移動させます。ROPチェーン全体は3つのガジェットから構成され、バッファーオーバーフローによって提供されるスタックスペースに簡単に収まります。
全てを集約
これまでに、この脆弱性への2つの攻撃手法が判明しました。write-what-whereと、スタック上のリターンアドレスの上書きです。送信される各パケットは、それぞれの技術を一度だけ使用できます。システムコールへのパラメータ取得には、write-what-whereを使用し、書き込み可能なメモリーアドレスにパラメータを置き、このアドレスをシステムに渡す必要があります。幸運なことに、この脆弱なアプリケーションは、決して使用されない膨大な書き込み可能なメモリーを、フィルターをバイパスする限定されたアドレス一式にアクセスできる範囲に蓄えています。一方、残念なことは、システムを呼び出すROPチェーンは、ROPガジェットの1つで必要以上の命令を処理する時にはwrite-what-whereを使用する必要があるという点です。これは、エクスプロイトを実行するためには2つのパケットが必要だということを示しています。1つ目はシステムのパラメータをメモリーに書き込むこと、2つ目はシステムへの呼び出しを行うこと、です。したがって、最初のパケットがきれいに終了し、プログラムをクラッシュさせないことが重要です。
きちんと実行するための1つの方法は、ペイロード内に適切に配置された3つのパイプ(「|」)を使用して、適切なタイミングで書き込みを終了させ、TokenParserを終了させることです。RAポインタを上書きしないことも重要で、それによりパケットが受信した後にプログラムが正常な実行を継続できるようになります。その後、以前のパケットによって書き込まれたパラメータのアドレスでシステムを呼び出すROPチェーンを含む2つ目のパケットが送信されます。
ペイロード
システムを呼び出すことができる有効なROPチェーンを見つけたら、どのシステムを呼び出すべきかを決める必要があります。システムはルートとして実行されるため、デバイスを完全に制御できます。私たちの調査では、このデバイスには多くのLinuxコマンドがインストールされていることがわかりました。以前はwgetを使ってgdbserverをデバイスにコピーしていました。攻撃者はシステムからwgetを呼び出して、スクリプトをダウンロードして実行することもできます。インストールされたアプリケーションについてさらに調査すると、NetCatを発見しました。これで、攻撃者はリバースシェルを創出するスクリプトを作成できるようになります。攻撃者はwgetを使用してスクリプトをダウンロードし、リバースシェルを創出するためにNetCatコマンドを含むスクリプトを実行できます。私たちはこれをテストし、ルートとしてのリバースシェルをオープンにする単純で効果的な方法だと証明しました。攻撃者は、このエクスプロイトを活用してコードを実行するための他の多くの方法を選ぶことができます。以下のビデオは、リバースシェルによってこのエクスプロイトが作動することを示しています。
現実世界のシナリオ
解説用に攻撃のシナリオを作成しました。プラグが侵害されると、ビルトインされたUPnPライブラリを使ってネットワークルーターに穴を開けることができます。この穴は、攻撃者が遠隔から気づかれずにネットワークと接続するバックドアチャネルを作成します。次のビデオでは、ネットワークに接続されたTCLスマートTVを制御するためにリモートシェルを使用しています。テレビ上のRoku APIの実装では、単純な暗号化されていないHTTP GET/POST要求を使用してコマンドを送り、これらのコマンドを送信しているマシンを認証しません。それによってリモートコントロールはささいな問題になります。Wemoを仲介者として使用することで攻撃者は、テレビの電源をオンやオフにしたり、アプリケーションをインストールまたはアンインストールしたり、任意のオンラインコンテンツにアクセスしたりできます。スマートTVは、Wemoを使って別のデバイスを攻撃する一例に過ぎません。攻撃者がネットワーク上に足場を築き、任意のポートを開くことができると、ネットワークに接続されたすべてのマシンが危険にさらされます。攻撃はWemo経由で行われる可能性があり、このエクスプロイトを使用して生成されたポートマッピングはルーターの管理ページからは見えず、攻撃者の足跡は小さ過ぎて検出するのが困難です。
結論
CVE-2018-6692などの発見は、すべてのデバイスでの安全なコーディングプラクティスがいかに重要かを物語っています。 IoTデバイスはセキュリティの観点からは見過ごされがちです。これはシンプルなホームオートメーションのように表向き当たり障りのない目的に使用されることが多いからかもしれません。ただし、これらのデバイスはオペレーティングシステムを実行し、デスクトップコンピューターと同じように保護される必要があります。私たちが発見したような脆弱性は、攻撃者がビジネスネットワーク全体に侵入し侵害するのに必要な足がかりになるおそれがあります。
McAfee ATRチームの目標の1つは、今日の複雑で絶えず進化する世界において、幅広い脅威を特定し明らかにすることです。分析と責任ある開示を通じて、製品メーカーをより包括的なセキュリティの姿勢に導くことを目指しています。
※本ページの内容は、2018年8月21日(US時間)更新の以下のMcAfee Blogの内容です。
原文: ‘Insight’ into Home Automation Reveals Vulnerability in Simple IoT Product
著者: Douglas McKee