
このリポジトリには、複数のD-Linkルーターに影響を与える認証済みOSコマンドインジェクション脆弱性CVE-2018-19987に関する分析のブログ記事があります。
こんにちは!
この短いブログ記事では、CVE-2018-19987 の詳細をより深く理解するために行った簡単な分析を解説します。当初、この CVE には公開情報やエクスプロイトが存在しないと思って調査を始めました。最初の分析を終えた直後に、GitHub 上に二つの公開 PoC を見つけました。こちら と こちら です。
すでにほとんどの作業を終えており、完全な分析を見つけられなかったため、ブログ記事を書くことにしました。
お楽しみいただければ幸いです。
更新: このブログ記事で行き詰まっている間にググったところ、D-Link DIR-818 での非常に類似した脆弱性 (CVE-2018-19986) を説明する以下の記事を見つけました。ルーター脆弱性分析シリーズ (5): CVE-2018-19986 DIR-818LW&828 コマンドインジェクション脆弱性分析と再現 (Google 翻訳) by Ogur1.
私は分析を開始するにあたり、デバイスに関する入手可能な情報をすべて収集しました。
注: すべての分析はファームウェア v3.01B02 で行いました。
この脆弱性に関する公開情報は、Mitre のページ で入手でき、分析を開始するのに十分な情報を提供していました。
D-Link DIR-822 Rev.B 202KRb06、DIR-822 Rev.C 3.10B06、DIR-860L Rev.B 2.03.B03、DIR-868L Rev.B 2.05B02、DIR-880L Rev.A 1.20B01_01_i3se_BETA、および DIR-890L Rev.A 1.21B02_BETA デバイスは、/HNAP1/SetAccessPointMode において IsAccessPoint を誤って処理します。SetAccessPointMode.php ソースコードでは、IsAccessPoint パラメータが正規表現チェックなしで ShellPath スクリプトファイルに保存されます。スクリプトファイルが実行されると、コマンドインジェクションが発生します。脆弱な /HNAP1/SetAccessPointMode XML メッセージは、IsAccessPoint 要素に `telnetd` 文字列などのシェルメタキャラクタを含む可能性があります。
分析を開始するには、何か調べるものが必要だったため、ルーターのファイルシステムを抽出することにしました。
binwalk -eM DIR822C1_FW301WWb02.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 DLOB firmware header, boot partition: "dev=/dev/mtdblock/1"
10380 0x288C LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 4213444 bytes
1376372 0x150074 PackImg section delimiter tag, little endian size: 10505216 bytes; big endian size: 5021696 bytes
1376404 0x150094 Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 5019773 bytes, 2282 inodes, blocksize: 131072 bytes, created: 2016-03-18 09:35:31
ご覧の通り、Binwalk は Linux ファイルシステムを検出しています。入手した情報をさらに詳しく見ると、アドバイザリは /HNAP1 パスに言及しています。そこで、ルーターがこれらの URL をどのように処理するかを調べることから始めましょう。
素晴らしい /dev/tty0 ブログ記事 で説明されているように、これらの URL は最終的に cgibin というバイナリ(htdocs/cgibin に配置)によって処理されます。しかし、私はこれがどのように設定されているかをより深く理解したく、最終的には HTTP サーバーがリクエストを処理するためにどのように設定されているかを理解する必要がありました。httpd.conf というファイル名を探したところ、/etc/services/ フォルダの下に HTTP.php というファイルを見つけました。このファイルには、特に以下の興味深い行が含まれていました。
$httpd_conf = "/var/run/httpd.conf";
fwrite("a",$START, "xmldbc -P /etc/services/HTTP/httpcfg.php > ".$httpd_conf."\n");
fwrite("a",$START, "event PREFWUPDATE add /etc/scripts/prefwupdate.sh\n");
fwrite("a",$START, "httpd -f ".$httpd_conf."\n");
fwrite("a",$START, "event HTTP.UP\n");
fwrite("a",$START, "exit 0\n");
このファイルはいつか実行され、/var/run/httpd.conf ファイルを書き込むと想定しました。Web サーバーの設定を調べるために、/etc/services/HTTP/httpcfg.php ファイルを分析しました(関連部分のみ記載)。
if ($hnap > 0)
{
echo
" Control". "\n".
" {". "\n".
" Alias /HNAP1". "\n".
" Location /htdocs/HNAP1". "\n".
" External". "\n".
" {". "\n".
" /usr/sbin/hnap { hnap }". "\n".
" }". "\n".
" IndexNames { index.hnap }". "\n".
" }". "\n";
}
これですべてのピースが揃いました!Web サーバーは /HNAP1 への HTTP リクエストを /usr/sbin/hnap を使用して処理するように設定されており、/dev/tty0 のブログ記事に基づき、これが htdocs/cgibin バイナリへのリンクであることがわかります。
次のステップは、cgibin バイナリを調べて、HNAP リクエストをどのように処理するかを理解することでした。次の画像では、逆コンパイルされた main 関数の一部を示しています。ここでは、渡された URL パスが、セッション.cgi、authentication.cgi、captcha.cgi など、さまざまなパスと比較され、最終的に hnap が見つかると hnap_main 関数が呼び出されます。
注: main 関数の分析中、Ghidra が初期分析で文字列として検出しなかったため、hnap 文字列の特定に若干の問題がありました。
次のステップは、この関数が何をするかを理解することでした。以下では、関数の動作を理解するのに役立つ関連部分のコードスニペットのみを提供します。また、他の関連部分は擬似コードで補完します(目的を明確にするため、いくつかの変数名を変更しました)。
...
HTTP_SOAPACTION = getenv("HTTP_SOAPACTION");
REQUEST_METHOD = getenv("REQUEST_METHOD");
HNAP_AUTH = getenv("HTTP_HNAP_AUTH");
__haystack = getenv("HTTP_COOKIE");
pcVar1 = getenv("HTTP_REFERER");
...
if (HTTP_SOAPACTION != "") {
if (HTTP_SOAPACTION == GetDeviceSettings) {
...
} else {
// これらのアクションは認証中に発生します。処理を実行
if ("GetCAPTCHAsetting" in HTTP_SOAPACTION) {
sess_generate_captcha();
} else {
if ("Login" in HTTP_SOAPACTION) {
perform_login();
}
// 認証後にここに到達します。
if (HNAP_AUTH != "") {
if ("uid=" in HTTP_COOKIE){
is_valid_auth = perform_auth_process()
if (is_valid_auth) {
if("logout" in HTTP_SOAPACTION){
perform_logout();
// 認証済みでログアウトしようとしていない場合
// コードは要求されたアクションを実行しようとします
} else {
// ここに到達すれば成功
// 以下の興味深いコード
goto LAB_004141d4;
}
}
}
}
// 認証されていない場合、何もできません
Return "You need proper authorization to use this resource"
}
}
} else {
...
}
LAB_004141d4:
hnap_action = get_hnap_operation(HTTP_SOAPACTION);
if (HTTP_SOAPACTION != "") {
hnap_action_len = strlen(hnap_action);
}
snprintf(path_to_hnap_php_file,0x100,"%s/%s.php","/etc/templates/hnap/",hnap_action);
if (!check_file_access(path_to_hnap_php_file)){
return "HNAP ACTION DOES NOT EXIST (FAIL)"
}
if (REQUEST_METHOD == "POST") {
// ここで PHP の引数が抽出されます
parse_request_and_extract_xml()
if (hnap_action == "GetFirmwareStatus") {
system("sh /etc/events/checkfw.sh > /dev/console");
}
// ここで xmldbc_ephp の最終的な引数が作成されます
snprintf(ARGS_FOR_XMLDBC_PHP,0x100,"%s%s.php\nShellPath=%s%s.sh\nPrivateKey=%s\n",
"/etc/templates/hnap/", hnap_action, &ShellPath, hnap_action, &PRIVATE_KEY);
// PHP が実行され(この場合 SetAccessPointMode.php)、シェルスクリプトが ShellPath に書き込まれます
xmldbc_ephp(0,0,ARGS_FOR_XMLDBC_PHP,stdout);
snprintf(hnap_action, 0x100, "%s", hnap_action);
// 以前に書き込まれたシェルファイルを実行するためのシェルコマンドが構築されます
// (PHP スクリプトによって書き込まれたファイル)
shell_command = "sh %s%s.sh > /dev/console &";
}
snprintf(cmd_to_execute, 0x100, shell_command, &PATH, hnap_action);
// コマンドインジェクションを含むファイルが実行されます
system(cmd_to_execute);
}
...
この関数の分析が終わったので、脆弱性を含む PHP ファイル SetAccessPointMode.php を調べ、すべてのピースを組み合わせることにしました。
...
$IsAccessPoint = query("/runtime/hnap/SetAccessPointMode/IsAccessPoint");
...
fwrite("w",$ShellPath, "#!/bin/sh\n");
fwrite("a",$ShellPath, "echo [$0] $1 ... > /dev/console\n");
fwrite("a",$ShellPath, "echo IsAccessPoint = ".$IsAccessPoint." > /dev/console\n");
fwrite("a",$ShellPath, "echo Result = ".$Result."\n");
...
ご覧の通り、$ShellPath 変数は hnap_main 関数によって設定され、$IsAccessPoint 変数はユーザー制御下にあり、送信される XML リクエストで渡されます。これにより、PHP ファイルによって書き込まれる変数での OS コマンドインジェクションがどのように実行されるかを確認できます。こちら で pr0v3rbs がこの問題を悪用するための完全な PoC を見つけることができます。
影響を受けるバージョンと修正されたバージョンについては後ほど詳しく説明します。以下の表からわかるように、パッチには何らかのリグレッションがあり、本来パッチが適用されているはずのバージョンでこの脆弱性が再発したようです。
(*) これらのファームウェアバージョンは、暗号化されていない対応物が脆弱だったため確認していません。
上記の表からわかるように、脆弱性は 一部の ファームウェアバージョンで、影響を受ける PHP ファイル SetAccessPointMode.php を削除することで修正されました。ファイルが存在しなくなると、関数内のチェックが失敗し、何も実行されません。ファームウェアバージョン FW v3.131 および 3.15B02 WW で hnap_function を分析してこれを確認しました。hnap_main のコードはこの問題に関連して変更されていませんでしたが、PHP ファイルはもはや存在しませんでした。
さらに興味深いのは、このバグがバージョン 3.11B01_icjg_WW でパッチされた後、FW v3.11 から FW v3.12B04 までで何らかの形で再発したことです。
最初の結論として、これらのデバイスを真に安全なデバイスとして決して信頼してはいけないと言えます。アップデートで起こったことは、その理由の明確な例です。また、実施した分析の後、このブログ記事に記載されている最初の PoC は、特定のバージョンでは機能しないと結論付けられます。脆弱な PHP ファイルを実行するには、いくつかの条件を満たす必要があるためです。
そして最も重要な結論は、CVSS スコアに基づいて脆弱性の重大性を判断する際には細心の注意を払うべきだということです。この CVE を分析しようと思ったのは、それが 9.8 (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) と評価されていたからですが、少なくともこのバージョンでは、攻撃者がそれを悪用するには資格情報が必要であることが明らかです。
次のステップとして、ルーター自体にアクセスせずにこの脆弱性を悪用できるように、このバイナリをエミュレートできるように取り組みますが、それはまた別の記事の題材になるでしょう!
お読みいただきありがとうございました。
| ファームウェア | バージョン | MD5 ハッシュ | リリース日 | 脆弱性あり |
|---|
| DIR822C1_FW315WWb02.bin | 3.15B02 WW | 7121771c3e1706ba76fbf244023efad3 | 2019/11/06 | いいえ |
| DIR822C1_FW313WWb01.bin | FW v3.13 | aa16c7016f67be384e0784e439ce26d2 | 2019/07/10 | いいえ |
| DIR822C1_FW303WWb04_i4sa_middle.bin | FW v3.12B04 | c3b9a3f115c02e739690616aba2f2d99 | 2019/04/26 | はい |
| DIR822C1_FW312WWb04.bin | FW v3.12B04 | eb11afbd136a5b29cea18141f727bfa8 | 2019/04/26 | はい (*) |
| DIR822C1_FW311WWb01.bin | FW v3.11 | 6d7c90eaaae835667faea65c862b3c82 | 2019/01/01 | はい |
| DIR822C1_FW311bWWb01_icjg.bin | 3.11B01_icjg_WW | 75e361e1465604aeda5d5dbcaecca977 | 2018/12/21 | いいえ |
| DIR822C1_FW303WWb04_i4sa_middle.bin | FW v3.10B06 | c3b9a3f115c02e739690616aba2f2d99 | 2018/08/17 | はい |
| DIR822C1_FW310WWb06.bin | FW v3.10B06 | e33db75d0801fddb1c90308982e69fe5 | 2018/08/17 | はい (*) |
| DIR-822_C1_FW302WWb05.bin | FW v3.02 | 0dbf840c0ff5d3a5b593d33690e15d82 | 2017/09/14 | はい |
| DIR822C1_FW301WWb02.bin | FW v3.01B02 | 1bff7ec8b4da0643f65b4d44c630e92b | 2016/04/27 | はい |