
このリポジトリには、2020年8月に実施したTiandyのIPC/NVRファームウェアに関する調査結果が含まれています。リモートから管理者パスワードを復元し、デバイスへのrootアクセスを取得するために悪用される可能性のある2つの脆弱性を発見しました。
このリポジトリには、2020年8月に実施したTiandyのIPC/NVRファームウェア(これらのデバイスはOMNYとしても販売されています)の調査結果を掲載しています。この「調査」は網羅的なものではありませんが、管理者パスワードをリモートで回復したり、telnetを有効にしたり、rootパスワードを変更したりする複数の方法を見つけました。
どのバージョンが影響を受けるかを正確に言うのは難しいです。ダウンロードできるのは最近のものだけだからです。ダウンロード可能なこれらのバージョンはすべて影響を受けます:
DVRS_V9.12.7.20200422
DVRS_V11.7.4.20200721
NVSS_V13.6.1.20200723
NVSS_V22.1.0.20200722
デバイスごとに異なるブランチがあるだけでなく、Web APIのように、コンポーネントによっては個別にバージョン管理・アップグレードされるものもあります。そのWeb APIでは、ファームウェアのバージョン番号にかかわらず2019年半ば以降にリリースされたバージョンでのみ機能する認証バイパスを見つけました。もし影響を受けるバージョンについて詳しい情報をご存知でしたら、ご協力いただけるとありがたいです。
ここで完全開示を行いますが、これは妥当なことです。ベンダーのパッチ(彼らが応答しないため可能性は低いですが)は、問題を消し去ることはないでしょう。特に、オンラインのデバイスで最新ファームウェアを搭載しているものは(どころか)ほとんどありませんから。実際の脆弱性は、それらのデバイスがインターネットに露出していることです。そしてこれはTiandyではなく、エンドユーザーが修正すべきことです。
おまけとして、ファームウェアのアンパッカーと、RTSP/RTMPでストリームにアクセスする方法に関する情報も含めています(マニュアルで見つけるのは難しいでしょう)。
最初にスクリプトを紹介します:
次に、これらのスクリプトが何をするのか、そしてなぜそうするのかを簡単に説明します。コードは繰り返しませんが、コードを理解できるように十分な文脈を説明します:
最後に、比較的技術的な内容になります:
Python 3とPyCryptoが必要です。
まず、recover.pyを試してください。これにはポート3001へのアクセスが必要です:
python3 recover.py [HOST]
うまくいけば、管理者の認証情報が表示されます。
そのポートにアクセスできない場合は、Webの方法が機能するかもしれません。これにはURLが必要です:
python3 cgi_recover.py http://123.45.67.89
python3 cgi_recover.py https://123.45.67.89
上記のどれも機能しない場合は、telnetが有効かどうかを確認してください。有効なら、デバイスを直接root化できます。次のハッシュをクラックするだけです:
support:$1$$AErA9BQgLjrxTJB1748k71:501:501:Linux User,,,:/home/support:/bin/sh
(実際にクラックできたら、PRを開いてくださいね :D)
古いV7 NVRファームウェアでは、コマンドを直接実行できます:
python3 ftpupdate.py [host] [adminpass] '[cmd]'
ただし出力はありません。簡単にするために、次のショートカットも含めました:
python3 ftpupdate.py [host] [adminpass] adduser [username] [password]
これにより、uid 0の別のユーザーが追加されます。
まず、telnetを有効にします:
python3 telnet.py [host] [adminpw]
または最近のデバイスの場合:
python3 cgi_recover.py [host] telnet
次に、/etc/passwdを上書きできます(この仕組みはご存知でしょう)。まず(NVR向けに)filetransport.pyを試してください:
python3 filetransport.py [host] [adminpass] put /etc/passwd <[source_file]
これにはフィードバックがないため、ログインを試して確認する必要があります...
filetransport.pyが機能しないIPCモデルでは、upgrade_rw.pyを試してください:
python3 upgrade_rw.py [url] [adminpass] /etc/passwd <[source_file]
(これにもフィードバックはありません)
最後に、さらに新しいデバイスでは、Web API経由でもこれを行うことができます:
python3 cgi_recover.py [url] write /etc/passwd <[source_file]
上記のどれも機能しなかった場合(ログインできるか確認してください)、それらのすべての方法を再試行し、代わりに/config/etc/passwdを上書きしてください。一部のファームウェアバージョンでは、/etc/passwdはそこへのシンボリックリンクです。最後に、/tdfs/etc/passwdの上書きも試せますが、その後デバイスの再起動が必要になる場合があります。再起動するには、次を使用します:
python3 reboot.py [host] [adminpass]
古いV7(IPCおよびNVR)ファームウェアは影響を受けていないように見えますが、管理者パスワードを持っている場合、NVRには認証済みRCE(ftpupdate.py)があり、IPCではupgrade-rw.pyスクリプトを使って/etc/passwdを上書きできる可能性があります。
NVRファームウェアの新しいバージョン(V9およびV11)にはデフォルトアカウントがあり、「受動的」な権限昇格と組み合わせることで、管理者パスワードを復元できます。その後、filetransport.pyを使って/etc/passwdを上書きできます。
IPCファームウェアにはデフォルトアカウントはありませんが、別の復元方法、PSW方式が登場します。これは、まったく安全性のないパスワード復元メカニズムです。これはV9以降のダウンロード可能なすべてのファームウェアバージョンに存在します。filetransport.pyはNVRでのみ機能しますが、upgrade_rw.pyはアップグレードメカニズムを利用してIPCで同じ目的を達成するため、rootアクセスを依然として取得できます。
2019年のファームウェアでは、別の攻撃ベクトル、Web APIを使用した認証バイパスが導入されています。認証なしで設定ファイルをエクスポートすることで、パスワードを復元し、任意のファイルを上書きするアップグレードパッケージを準備できます。
脆弱性と言えば、4つあります:
「クラウド」機能、つまりデバイスを列挙して、インターネットに公開されていないデバイスに接続できるかどうか(Xiongmaiデバイスのように)については調査していないことに注意してください。
古いバージョンでは、telnetがデフォルトで有効になっており、/etc/passwdファイルには次のような記述があります:
support:$1$$AErA9BQgLjrxTJB1748k71:501:501:Linux User,,,:/home/support:/bin/sh
(rootパスワードは動的に更新されます。また、このハッシュはクラックしていないので、プルリクエストは大歓迎です :D)
supportユーザー(実際にはすべてのファームウェアバージョンに存在します)は特権がないように見えるかもしれませんが、もちろんこのユーザーには、Adminパスワードを読んだり、/etc/init.d内のワールドライタブルなinitスクリプトを上書きしたり、新しいスクリプトを作成したりするのに十分な特権があります :)
概念的に、この方法は本当に単純です。ログインパケットを送信して応答を読むだけです。それだけです。「ログイン成功」の応答には、自分の権限に関係なくすべてのユーザーの認証情報が含まれているからです。V7でも同様でしたが、実際にこの方法が有用になったのは、NVRファームウェアにデフォルトアカウントが導入されてからです。削除できない「Default」にはリモート権限がないため、それを使って何もできません。まあ、管理者パスワードを読むこと以外は...
これは簡単に聞こえますが、実装はそれほど簡単ではありませんでした。通信には独自プロトコルが使用され、パスワードはDESで暗号化されていますが、ビットが反転されています(それを突き止めるのが最も困難でした)。キーはサーバーから送信されます。キー派生がないため、盗聴者は簡単にすべてを復号化できます。それでも、ビットが反転されていることを突き止めるか、全体をゼロから再実装する必要があります...
実装については、recover.pyファイルのrecover_with_default関数を参照してください。
バイナリを解析していると、このメカニズムに気づかないのは難しいです。その目的は... パスワード復元を可能にすることであり、実際にその役割をしっかり果たしています。むしろやりすぎなくらいです...
ここで何が起きているのでしょうか?これはパスワード復元メカニズムとして意図されたものだと思います。おそらく、ベンダーがデバイス所有者に自分たちのパスワードを復元する手段を提供できるように作られたのでしょう。
フローは次のようになるはずだったと推測できます:
それはすべて良いのですが、1つ欠けているものがあります... セキュリティはどこにあるのでしょうか? 結局、どこにもありません。このパケットを送信し、セキュリティコードを導出し、アクセス可能な任意のデバイスのパスワードを復元することを妨げるものは何もありません。
これは驚くべきことです。なぜなら、セキュリティを打ち負かすメカニズムの欠陥があるわけではないからです。そもそもセキュリティがまったく存在しないだけです。修正するものは何もありませんが、これが私には明白なバックドアのようにも見えません。これはログに痕跡を残し、3つの異なる導出スキームがあり、それぞれが前のものよりも洗練されています。実際に実装には時間がかかりました...
方法に戻ると、これが機能するには、デバイスに電話番号/メールアドレスが関連付けられている必要があります。古いバージョンを見ると、これを実行できるのは管理者だけでしたが、その後バイパスを見つけました。驚くべきことに、新しいファームウェアでは、認証なしでデバイスのメールを変更することが明示的に可能になっているため、そのバイパスはもはや必要ありません。さて、_ここ_がバックドアがあるべき場所だったと思います :)
技術的な面では、このメカニズムは実際にはかなり複雑で、リバースエンジニアリングと再実装が最も困難でした。このメカニズムには3つのバージョンがあり、それぞれがセキュリティコードの導出に異なるアルゴリズムを使用しています。ビット反転DESに加えて、ハードコードされたキーを使った独自の換字式暗号が関与します。しかし、これはすべて無駄です。Tiandyがどんなに努力しても、ハードコードされたキーを使った対称暗号化を安全にすることはできないからです。
観察したことの1つは、セキュリティコードが毎分変わるため、ステップ3のパケットとステップ5のパケットの間に送信時間がほとんどなくても、その間にコードが変わっただけで元のプロセスが失敗する可能性があるということです。これを考慮して、スクリプトはコードが無効であることが判明した場合にプロセスを再試行します。
メールの設定(所有する必要はありません)を含むプロセス全体は、recover.pyファイルに実装されています。
これは、「モダン」なWebインターフェース(「マップ」があるやつ。オーストラリアが少し歪んでいるように見えますが、あのマップは好きです)を備えた新しいファームウェアバージョン(2019年以降)で機能します。
このバイパスは単純です。ほとんどのAPIエンドポイントは認証されますが、いくつかの例外があります。ただし、認証をスキップするかどうかのチェックと、どのエンドポイントを有効にするかのマッチングは、異なる場所に実装されています。ほとんどの場合、URLパスが特定の文字列と等しいときに認証がスキップされるため、これは安全です。
しかし、最近のバージョンでは別の例外が導入されました。URLパスのどこかに文字列Record/DownLoadとID=が存在するだけで有効になります。
さて、完全なパスがマッチングされるエンドポイントでは、これは依然として安全です。Security/usersはそうしたエンドポイントの1つであるため、直接パスワードを復元することはできません。幸いなことに、設定エクスポートエンドポイントは、URLパスが特定の文字列で始まるかどうかをチェックする(strncmpを使用)ことによって選択されるため、それらの文字列をパスに追加して設定ファイルをエクスポートするだけで済みます。
この欠陥を使用したパスワード復元は、cgi_recover.pyに実装されています。
理論上、管理者はファームウェアをアップグレードできますが、ファームウェアは署名も暗号化もされていません。しかし、カスタムファームウェアパッケージを準備する必要は本当にあるのでしょうか?必要ないこともあります。必要なこともあり、私はそれを実際に実装するほどクレイジーでした...
パスワードを持っているなら、利用できるコマンドインジェクションの脆弱性があります。ftpupdate.pyスクリプトです。ここで行われているのは、デバイスにftpでアップグレードを取得するように指示し、そのタスクにftpgetが使用されることです。当然のことながら、私たちのパラメータはsystem()関数に直接流れ込みます。
NVRファームウェアでは、バイナリプロトコルにFILETRANSPORTコマンドがあり、これはまさにその名の通りのことを行います。実際、これ以上言うことはありません。SDKをダウンロードして同じコマンドを使うこともできるからです。もちろん私はそれを再実装したかったので、仕組みを見るにはfiletransport.pyを参照してください。
ただし、IPCモデルにはこのコマンドがありません。しかし、先ほど言ったように、ファームウェアをアップグレードすることは常に可能です。フラッシュ全体を準備するのは現実的ではありませんが、Tiandyのアップグレードパッケージでは個々のファイルを置き換えることができるため、まさに必要なことができます(ファームウェアの展開を参照)。
ただ、それほど単純ではありません。「box」ファイル形式にはメタデータがあり、その後にファイルの配列があります。最初のファイルはProductModuleという名前でなければならず、一致するデバイスパラメータを含んでいる必要があります。そうでないとアップグレードは進行しません。それらのパラメータの値だけでなく、パラメータ自体も必要です。さらに、メタデータ部分(boxファイルのバージョンを含む)も検証されます。