This post is also available in: English (英語)
概要
先月、Zyxelのネットワーク接続ストレージ(NAS)デバイスにおける脆弱性(CVE-2020-9054)の概念実証(PoC)コードが公開されました。この直後からMiraiの新しい亜種であるMukashiは、本脆弱性を悪用して脆弱なバージョンの同社NASデバイスへの感染を試みています。
Mukashi はデフォルトの資格情報をさまざまに組み合わせてログイン ブルートフォース攻撃を行い、ログイン試行が成功するとコマンド&コントロール(C2)サーバーに通知します。バージョン5.21までのファームウェアを実行している複数のZyxel NAS製品がこの事前認証コマンドインジェクション脆弱性に対して脆弱です。Zyxelのアドバイザリはこちらです。
手元のZyxel NASデバイスの脆弱性は、こちらのサイトでテストして確認できます。
本脆弱性は悪用がきわめて容易(trivial)なため、CVSSv3.1におけるスコアは「緊急」(9.8)が付与されています。脅威攻撃者たちが当該脆弱性を兵器化し、モノのインターネット(IoT)分野での深刻な事態を引き起こしてもなんら不思議はありません。本脆弱性が発見されたきっかけはこれがゼロデイ エクスプロイト コードとして販売されていたことで、その時点ではまだベンダへの報告がされていませんでした。最初に発見されたさいのブログでも「当該エクスプロイトはいまEmotetにこのエクスプロイトに組み入れようとする攻撃者たちに使われている」と述べられています。
本稿では画像やIoCを提示しつつ、キルチェーン全体の概要を説明します。
脆弱性の分析
本件の脆弱性は、CGI実行可能ファイルweblogin.cgiが、認証処理でユーザー名パラメータを適切にサニタイズしていないことにあります。攻撃者は「'」(シングルクォート)を使って文字列を閉じ、その後に「;」(セミコロン)をつづけて任意のコマンドをつなげることにより、コマンドインジェクションを実行することができます。weblogin.cgiはHTTP GETリクエストとPOSTリクエストの両方を受け入れるため、攻撃者はいずれかのHTTPリクエストに悪意のあるペイロードを埋め込むことでコードを実行できます。
実際に行われているエクスプロイト
私たちが確認した最初のインシデントは2020年3月12日19時07分(UTC時間。日本時間では2020年03月13日04時07分)に発生し、弊社次世代ファイアウォールによって検出されました。以下の図1、図2に示すように、このときの脅威攻撃者は zi というシェルスクリプトをtmpディレクトリにダウンロードし、ダウンロードした当該スクリプトを実行後、脆弱性のあるデバイス上の証拠を削除しようとしていました。
図1 実際の悪用が確認されたエクスプロイト用HTTPリクエスト
このziスクリプトが実行されると、さまざまなアーキテクチャ用のMiraiボットがダウンロードされ、ダウンロードされたバイナリが実行後に削除されます。弊社次世代ファイアウォールによる検出時点では、これらのバイナリはいずれもVirusTotalで情報が提供されていませんでした。ただし本稿執筆時点では8種中4種の情報がVirusTotalで提供されています。
図2 ボットをダウンロードし、起動するシェルスクリプト
Miraiの新亜種Mukashi
Mukashiは、ランダムなホストの23/tcpをスキャンし、さまざまな組み合わせのデフォルト認証情報でログイン ブルートフォース攻撃を行い、ログイン試行に成功するとC2サーバーに報告するボットです。ほかのMirai亜種同様、MukashiはC2コマンドを受け取ってDDoS攻撃を仕掛けることもできます。
実行されると、Mukashiはコンソールに「Protecting your device from further infections.(デバイスをさらなる感染から保護します)」というメッセージを出力し、自身のプロセス名をdvrhelperに変更します。このことから、Mukashiは自身の祖先となる亜種から特定の機能を継承していたようすがうかがえます。
Mukashiは意図したオペレーションの開始前に23448/tcpにバインドし、感染システム上で1インスタンスしか実行されないようにします。
このほか、初期化処理中にいくつかの文字列をデコードします。次の表に示すように、こうしてデコードされる文字列には資格情報やC2コマンドが含まれています。従来のXOR暗号を使用する祖先とは異なり、Mukashiはこれらのコマンドや資格情報の暗号化にカスタム復号ルーチンを使っています。復号スクリプトは付録に記載しておきます。
/cmdline | .udprand | .udpbypass | .udphex | user | tsgoingon |
/proc/ | .udpplain | .tcpbypass | default | daemon | zlxx. |
/status | .http | .tcp | admin | juantech | Zte521 |
/maps | /exe | killallbots | root | 123456 | hunt5759 |
/proc/self/cmdline | None | ./ | guest | solokey | samsung |
self | POST | killer | dvr2580222 | xc3511 | vizxv |
ping | GET | scanner | support | 12345 | |
.udp | / | world | guest | xmhdipc |
表1 デコードされた資格情報とコマンド
Mukashiは、資格情報のブルートフォース攻撃実行時、スキャンの前段階でデコードしておいた資格情報のほかに、よく知られているデフォルト パスワードも使用します。たとえば t0talc0ntr0l4! や taZz@23495859 などです。以下の図3は、Mukashiがランダムなホストをスキャン中にキャプチャされた開始トラフィックです。図4は、Mukashiがブルートフォース攻撃により認証を試みている様子です。
図3 ランダムなホストの23/tcpをスキャンしているところ
図4 ブルートフォース攻撃
ログイン試行が成功すると、Mukashiは資格情報の有効な組み合わせをC2サーバー45[.]84[.]196[.]75に対し、34834/tcp経由で報告します。
報告時のメッセージは<ホストのIPアドレス>:23 <ユーザー名>:<パスワード>という形式です。次の図はこのメッセージのサンプルです。
図5 ログイン試行の成功を報告するメッセージ
起動・初期化がすむと、Mukashiはビーコンを4864/tcpでリッスンしているC2サーバー45[.]84[.]196[.]75に送り返し、コマンド受け入れ準備が整ったことを通知します。ビーコンの例を以下の図6に示します。このビーコンは <名前>.<入力引数> という形式です。この <名前> の部分文字列は、ソケット生成時の戻り値に依存しています。ソケットがうまく生成されればこの <名前> はrootになり、そうでなければdefaultになります。 <入力引数> の部分文字列は、実行時にバイナリに渡される入力引数です。入力引数が指定されていなければビーコンの文字列はNoneになります。
図6 x86ボットからのC2ビーコン
Miraiとその亜種のDDoS攻撃の仕組み(UDP、TCP、UDPバイパス、TCPバイパスなど)については既に詳細に分析されていますが、MukashiのDDoS機能にこれらの亜種との違いはありません。なお、MukashiにもアンチDDoSディフェンス機能が備わっており、これは「Mukashiはdvrhelperの亜種の特定機能を継承している」という以前からの推測を裏付けるものとなります。次の表に、MukashiがサポートするC2コマンドを示します。
PING | scanner | .udpplain | .tcp |
killallbots | .udp | .udpbypass | .tcpbypass |
killer | .udprand | .udphex | .http |
表2 C2コマンド
MukashiがC2サーバーから受信したC2コマンド文字列の処理を担当するのはattack_parsing() 関数です。C2コマンド文字列には、コマンドの種類や標的のアドレスに加え、SYNフラグ、ACKフラグ、URGフラグ、PSHフラグ、RSTフラグ、TTLフィールド、宛先ポート番号、パケット長など、Mukashiがパケットヘッダを構築するさいに必要とされる関連情報が含まれています。そのさい宛先ポート番号が提供されていなければランダムなポート番号が選択され、パケット長が指定されていなければデフォルトのポート番号として1458が使用されます。
Mukashiのバイナリはさまざまなアーキテクチャ向けに多数コンパイルされていますが、いずれも機能的にはほぼ同じです。ただしx86バージョンには、プロセス コマンド ライン、特定文字列、パーミッションによりプロセスをkillするcleaner()関数は含まれていません。次の図は、x86バージョンとarm7バージョンの違いを示したものです。
図7 メインルーチン(arm7)
図8 メインルーチン(x86)
結論 / 緩和策
ファームウェアを更新し、攻撃者を寄せ付けないようにすることを強くお勧めします。ファームウェアの最新バージョンはこちらからダウンロード可能です。複雑なログインパスワードで総当たり攻撃を防止することも推奨されます。
パロアルトネットワークス製品をご利用中のお客様は、次の製品とサービスによって当該脆弱性から保護されています。
- ベストプラクティスと、Threat Preventionライセンスを有効にした次世代ファイアウォールの脅威防止シグネチャ 57806により、攻撃はブロックされます。
- WildFireでは、静的シグネチャによる検出で同マルウェアを阻止します。
IoC
ファイル(Sha256値)
- 8c0c4d8d727bff5e03f6b2aae125d3e3607948d9dff578b18be0add2fff3411c (arm.bot)
- 5f918c2b5316c52cbb564269b116ce63935691ee6debe06ce1693ad29dbb5740 (arm5.bot)
- 8fa54788885679e4677296fca4fe4e949ca85783a057750c658543645fb8682f (arm6.bot)
- 90392af3fdc7af968cc6d054fc1a99c5156de5b1834d6432076c40d548283c22 (arm7.bot)
- 675f4af00520905e31ff96ecef2d4dc77166481f584da89a39a798ea18ae2144 (mips.bot)
- 46228151b547c905de9772211ce559592498e0c8894379f14adb1ef6c44f8933 (mpsl.bot)
- 753914aa3549e52af2627992731ca18e702f652391c161483f532173daeb0bbd (sh4.bot)
- ce793ddec5410c5104d0ea23809a40dd222473e3d984a1e531e735aebf46c9dc (x86.bot)
- a059e47b4c76b6bbd70ca4db6b454fd9aa19e5a0487c8032fe54fa707b0f926d (zi)
ネットワーク
- 45[.]84[.]196[.]75:34834 (ログイン試行が成功した場合の報告先)
- 45[.]84[.]196[.]75:4864 (C2)
- 0[.]0[.]0[.]0:23448 (単一インスタンス)
以前の活動
以下の表は、同一IPアドレス上にホスティングされている同亜種サンプルのハッシュ値です。ただし、CVE-2020-9054のエクスプロイトでこれらが配布されたという証拠は確認されていません。
初出 | URL | SHA256値 |
2020-03-04 | 45.84.196[.]75/bins/arc.corona | 3e8af889a10a7c8efe6a0951a78f3dbadae1f0aa28140552efa0477914afd4fd |
2020-03-04 | 45.84.196[.]75/bins/arm5.corona | 213cdcf6fd5ca833d03d6f5fa0ec5c7e5af25be8c140b3f2166dccccf1232c3e |
2020-03-04 | 45.84.196[.]75/bins/m68k.corona | 4f1fe9dc48661efe2c21b42bd5779f89db402b5caa614939867508fa6ba22cd6 |
2020-03-04 | 45.84.196[.]75/bins/arm6.corona | 0f7fb7fb27ce859b8780502c12d16611b3a7ae72086142a4ea22d5e7eaa229bc |
2020-03-04 | 45.84.196[.]75/bins/mips.corona | 9a983a4cee09e77100804f6dae7f678283e2d2ff32d8dbcf356ef40dcdff8070 |
2020-03-04 | 45.84.196[.]75/bins/arm.corona | 060547ee0be2d5e588e38d1ad11e1827ba6ce7b443b67e78308571e9d455d79b |
2020-03-04 | 45.84.196[.]75/bins/ppc.corona | dcb52fbd54fd38b6111670554a20a810b9caccc0afce7669ba34fc729afe2049 |
2020-03-04 | 45.84.196[.]75/bins/mpsl.corona | 60be483526d1ae9576617907b80a781296404220affcf01d47e9e2bfa2cdc55f |
2020-03-04 | 45.84.196[.]75/bins/x86.corona | 12d3d391462f7b66985f216dbca330ac13a75263d0f9439692fd53065eeb5657 |
2020-03-04 | 45.84.196[.]75/bins/arm7.corona | 0c016ce7576b5c041ea1e36e8561214dee85d7ce87a50bb092def026881183f4 |
2020-03-04 | 45.84.196[.]75/bins/spc.corona | 4e21b2547a8fc15b1435441fa6567b4626dfa3049c2dd6911b333449dd6756fd |
2020-03-04 | 45.84.196[.]75/bins/sh4.corona | 049a1570e76c025d431997fb7a9963d465959a6c470eeeab4ac8420f6e3829a6 |
2020-03-09 | 45.84.196[.]75/bins/arc.bot | 3df226be94f99ece7875032e41b025b5a19152e1d63bd0cda2af204f667cd140 |
2020-03-09 | 45.84.196[.]75/bins/arm5.bot | 768430ee908a6fc5fa6d5785b2ec15cd334fbc302d98ee3045aa44c2137a7a35 |
2020-03-09 | 45.84.196[.]75/bins/arm6.bot | 228eac174dcf166c97a7baa854ab3803ade9934915ef701dd0634f033ca252fe |
2020-03-09 | 45.84.196[.]75/bins/arm7.bot | eac71fd11ebb70ab256afa417e6621de0b66ec4830eb229b04192f9f866037ca |
2020-03-09 | 45.84.196[.]75/bins/arm.bot | 1734610c5d09be7a0e4459f8bd2a9373ae3da8812165f08733b3a5efdd38ff29 |
2020-03-09 | 45.84.196[.]75/bins/m68k.bot | b6e859812efecce70041ad5fda2b4881b1b1a89e6ae982cb43af67b301640620 |
2020-03-09 | 45.84.196[.]75/bins/mips.bot | 8f047170fceb05164429968ae24839f1419e58e30fd10057ab14291bfe0945c1 |
2020-03-09 | 45.84.196[.]75/bins/mpsl.bot | 7dbd6923a425d3464318e22c3bd88ea1e8f2d0ae914ac29664f95cef5cb4d748 |
2020-03-09 | 45.84.196[.]75/bins/ppc.bot | 635d7bb69b758cb7df9b9fcab9de7671139fdbe3f03f79299476706cfe54553d |
2020-03-09 | 45.84.196[.]75/bins/sh4.bot | d400cb7c2bb69011c8b21d8f24da08ac31cc55ee88b45f21cf4e4a1683548e38 |
2020-03-09 | 45.84.196[.]75/bins/spc.bot | 83022c991d5da2725b8e39128862e5ae987d53846e0539655ab66f7ed3355a6b |
2020-03-09 | 45.84.196[.]75/bins/x86.bot | bca0cffe842196be283d28572d7c43a53c1e5e5a231ad3d7969aa40965e2406b |
2020-03-10 | 45.84.196[.]75/bins/arc.bot | a3a674b3481e3b9e5e12b332f4508134db6405f59d3c8dc74aaa4943c84fafb6 |
2020-03-10 | 45.84.196[.]75/bins/arm5.bot | c9c546967620830745796b87993e9b89d3405e0a8cc083f09bfbf08675ef87ba |
2020-03-10 | 45.84.196[.]75/bins/arm6.bot | 72d44204ad26a974b1bdbed2970955670ce2697bfe99e697eb7df255cccea0be |
2020-03-10 | 45.84.196[.]75/bins/arm7.bot | 62ad931aa37a227211ccb1d89050630c9122e2d24eecef824416e913f578f969 |
2020-03-10 | 45.84.196[.]75/bins/arm.bot | be1d0f53d7647a46047102ffdc063d06be511ffc9832a72cca1420ac2811f807 |
2020-03-10 | 45.84.196[.]75/bins/m68k.bot | 46d868913a330e5b36673c229240dc971b535f95f091fc9bd9c9fa315c7cf838 |
2020-03-10 | 45.84.196[.]75/bins/mips.bot | 7b0176099dd032a5c2d6834e8840af78f91332a0b7cee000746bcaec5fbb3e9b |
2020-03-10 | 45.84.196[.]75/bins/mpsl.bot | 940fa7d9ef770a3e70c5f227a0ad1aaac88071f3c4879a2c92e7c155d9626d73 |
2020-03-10 | 45.84.196[.]75/bins/ppc.bot | 514e5ca58df6ba22708046cd034af05e3a88f80da893e4d7e2124137086468b0 |
2020-03-10 | 45.84.196[.]75/bins/sh4.bot | af6a51c012062078d6fcf112b3e4239eb029fc895f5f74fb5e40eb0b71fe67ce |
2020-03-10 | 45.84.196[.]75/bins/spc.bot | 3ae3b155c274edb389fe9d06bf9349bfd829c0e55db34238c3a8f53da16b4d98 |
2020-03-10 | 45.84.196[.]75/bins/x86.bot | 5060a00c235566726cdf0e0a07f022cdbf2f59cff636f37b19576bf98ea70027 |
2020-03-12 | 45.84.196[.]75/bins/arc.bot | 906d945b00465b1b7f6a828eb47edc0e875e745b7638258afbe8032d4c2d6ac6 |
2020-03-12 | 45.84.196[.]75/bins/arm5.bot | 27f26c710b4d461396749acfbe8fadc57ba19dcb70b1e1890599ca938c0d6aec |
2020-03-12 | 45.84.196[.]75/bins/arm6.bot | 162add056aef065ff0e19242ca8674698586b295b2f75c03f9f22a14f6e16ff3 |
2020-03-12 | 45.84.196[.]75/bins/arm7.bot | 948776a3c50a8e6a2f58f27f29095b63f7bbc0f8b5aeb08c6a4ba27558b13a0d |
2020-03-12 | 45.84.196[.]75/bins/arm.bot | 3061fd4a4a57e8c1948c30728f82a82213a1907ee8fccb7037dd1649e1c51e0e |
2020-03-12 | 45.84.196[.]75/bins/m68k.bot | 941e2833d313d33e53db5416718ba4c68609ac0537d3f16bf600c0bee2f562d0 |
2020-03-12 | 45.84.196[.]75/bins/mips.bot | 8473645820c828758a7655730ab6bd6967c97872687f4b6d5eff769387f59059 |
2020-03-12 | 45.84.196[.]75/bins/mpsl.bot | 1a4efe25a8f660e44abdb82d84912cf24db7eabfe9ad3c4c12080ca05636d73b |
2020-03-12 | 45.84.196[.]75/bins/ppc.bot | dbcd46dabd2fbddb40e17c2f7790950086b0108370d2448ff5fe407a9cd83103 |
2020-03-12 | 45.84.196[.]75/bins/sh4.bot | 751b0fe6616034a72235c7d3021e3f54f0634b9b5b29fed56cd44843389da0e9 |
2020-03-12 | 45.84.196[.]75/bins/spc.bot | 5a69a7c079555b53263a64dc0757f2168e255b29bc17ab846aceb2f8d08f3830 |
2020-03-12 | 45.84.196[.]75/bins/x86.bot | 47f9e2e65b17b937bc32fc6bb5bfbbb0efd2b86305b9d29a976512cbcc049d28 |
付録
IDApython 6.x - 7.3用スクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
import ida_kernwin from idc import * from idautils import * from idaapi import * def decode_str(encoded_str): if len(encoded_str) == 0: return "" buf = list(encoded_str) result = "" buf[0] = chr(ord(buf[0]) - 2) slen = len(encoded_str) v1 = slen / 2; if v1 > 0: i = v1 while True: if i >= slen: break; buf[i] = chr(ord(buf[i]) - 1); i += 1 v2 = slen / 4; if v2 > 0: j = v2 while True: if j >= slen: break; buf[j] = chr(ord(buf[j]) - 1) j += 1 for k in xrange(0, slen): buf[k] = chr(ord(buf[k]) - 1) v3 = 0 if slen > 24: if slen > 99: v3 = slen / 5 - 3; else: v3 = slen / 5 - 1; else: v3 = slen / 5; l = v3 while True: if l >= slen: break buf[l]= chr(0); l += 1 result = "".join(buf) return result def main(): for addr in XrefsTo(0x080482A0, flags=0): print("[*] addr.frm: {0}".format(hex(addr.frm))) prev_addr = PrevHead(addr.frm) encoded_str = "" if GetMnem(prev_addr) == "push": str_addr = GetOperandValue(prev_addr, 0) elif GetMnem(prev_addr) == "mov": str_addr = GetOperandValue(prev_addr, 1) print("\tstr_addr: {0}".format(hex(str_addr))) encoded_str = GetString(str_addr) print("\tencoded_str: {0}".format(encoded_str)) decoded_str = decode_str(encoded_str) print("\tdecoded_str: {0}".format(decoded_str)) if __name__ == '__main__': main() |
IDApython 7.4 用スクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
def decrypt_string(enc_str): strlen = len(enc_str) str = chr(ord(enc_str[0])-2) + enc_str[1:] v1 = strlen/2 if v1>0: str = str[0:v1] + ''.join([chr(ord(x)-1) for x in str[v1:]]) v2 = strlen/4 if v2>0: str = str[0:v2] + ''.join([chr(ord(x)-1) for x in str[v2:]]) str = ''.join([chr(ord(x)-1) for x in str]) if strlen>24: if strlen>99: v9 = strlen/5 - 3 else: v9 = strlen/5 - 1 else: v9 = strlen/5 str = str[:v9] + chr(0) + str[v9+1:] return str def main(): strrefs = [] for addr in XrefsTo(0x080482a0, flags=0): prev_ins = prev_head(addr.frm) ref = get_operand_value(prev_ins, 1) if ref>0: strrefs.append(ref) for ref in strrefs: enc_str = get_strlit_contents(ref) print "Encrypted string: %s" %enc_str dec_str = decrypt_string(enc_str) print "Decrypted string: %s" %dec_str if __name__ == '__main__': main() |