当社のAIエンジンNeutronが、UCバークレーのCyberGymベンチマークで97.1%のスコアを記録しました。 詳細を見る

ディープダイブ

ディープダイブ

TightVNC Viewerの認証前RCE、2.8.90で修正

悪意のあるVNCサーバーが、パスワード入力を求める前にTightVNC Viewer上でコードを実行します。Viewerを2.8.90へ更新し、TCP 5500のリスニングモードを制限してください。

TL;DR

  • Windows向けTightVNC Viewerはピクセルバッファのサイズを32ビットに切り詰めます。そのため、巨大な画面を告示する悪意のあるVNCサーバーは、パスワード入力を求める前にViewer上でコードを実行できます。SSD Secure Disclosureが2026年10月29日、動作するエクスプロイトとともにこの欠陥を公表しました。
  • 2.8ラインで2.8.89より前のすべてのViewerが対象で、研究者は2.8.88 x64でエクスプロイトを実証しました。リスニングモードを使うヘルプデスク環境はViewerをTCP 5500で露出し、パスワードもIP制限もないため、この欠陥は認証前のリモートコード実行になります。
  • 開発元はViewerを2.8.89と2.8.90で修正しました。それぞれ2026年10月2日と10月5日のリリースで、2.8.90を推奨しています。CVEは割り当てられておらず、開発元のアドバイザリも存在せず、悪用は知られていません。2026年11月2日時点でCISAのKEVカタログにも入っていません。
  • すべてのTightVNC Viewerを2.8.90へ更新してください。更新が待てる場合の当面の措置として、リスニングモードを無効化するかTCP 5500を信頼できるホストに限定し、Viewerを信用できないサーバーへ決して接続しないでください。2つ目の経路はネットワーク制限でカバーできません。

影響を受けるのは?

以下がすべて当てはまる場合に露出します。

  • 対象のマシンがWindows向けTightVNC Viewerを2.8.88または2.8ラインのそれ以前のバージョンで実行していること。研究者は2.8.88 x64でエクスプロイトを実証し、それを止める検証は2.8.89で初めて登場しました。
  • 2つの攻撃経路のどちらかが到達可能であること。リスニングモード(tvnviewer.exe -listen またはトレイメニューから起動)では、Viewerは0.0.0.0:5500で待ち受け、接続してくるすべてのサーバーを受け入れます。パスワードもIP制限もありません。別の経路として、ユーザーがViewerを攻撃者の管理下または侵害されたサーバーへ接続すると、パスワード入力の前に接続時点で侵害されます。
  • 攻撃者がリスニング経路ではポート5500に到達できるか、Viewerが接続するVNCサーバーを運用または乗っ取れること。
ブランチ 対象バージョン 修正バージョン
TightVNC 2.8.x 2.8.88まで 2.8.90(2.8.89は一部のみ修正)
Windows XP世代のホスト上のTightVNC 2.8.x 2.8.88まで 2.8.90(XPを引き続きサポートする最後のライン)
TightVNC 4.x アドバイザリなし、修正後にリリース 該当なし、現行リリースは4.0.2(2026年10月28日)

2.8.88のViewerは2026年6月19日にリリースされたため、2026年11月2日時点で対象コードは現行のインストールに136日間存在し、古いインストールではさらに長く存在します。開示資料はリスニングモードを「ヘルプデスクで通常の運用形態」と説明しており、リスニング経路が実務上の露出です。

インストール済みViewerのバージョンと、待ち受け中かどうかを確認します。

(Get-Item "C:\Program Files\TightVNC\tvnviewer.exe").VersionInfo.FileVersion
netstat -ano | findstr :5500

1つ目のコマンドは既定のインストールでのViewerのファイルバージョンを表示します。TightVNCを別の場所にインストールした場合はパスを調整してください。2つ目はTCP 5500で待ち受け中のローカルソケットを表示し、これが露出したリスニングモードです。結果が空ならこの経路は閉じています。

修正するには?

すべてのTightVNC Viewerを2.8.90へ更新します。

  1. 開発元のダウンロードページから、該当プラットフォームの2.8.90インストーラーをダウンロードして実行します。Viewerがその場で置き換えられます。
  2. 上のバージョン確認で修正を確かめます。ファイルバージョンは2.8.90と表示される必要があります。
  3. リスニングモードで動くViewerは再起動し、新しいバイナリが5500番ポートにバインドされるようにします。

2.8.90に関する開発元自身のリリース通知は、バージョン2.8.89と2.8.90が「Viewerのいくつかのセキュリティ問題に対処する」と述べ、2.8.90を強く推奨しています。更新までの間、次の2点が露出を狭めますが、どちらにも限界があります。

  1. ヘルプデスク機でリスニングモードを無効化するか、ファイアウォールでTCP 5500を信頼できるVNCサーバーのアドレスに限定します。限界は明白で、欠陥はViewerに残り、5500への信頼できない経路が残っていれば開いたままです。
  2. オペレーターに、管理していないサーバーへViewerを接続しないよう指示します。この経路はアウトバウンド接続のため、ネットワーク制限ではカバーできません。

根本原因は何か?

Viewerは32ビットに切り詰めた結果からピクセルバッファのサイズを決めます。 その後の書き込みには、サーバーが告示した完全な寸法から導いたオフセットを使います。このコードはViewerのrfb層にあるFrameBufferにあり、2.8.88のソース配布から引用しています。

サイズ計算は64ビットで乗算した後、32ビットを超える分をすべて捨てます。

int FrameBuffer::getBufferSize() const
{
  return (int)((UINT64)m_dimension.area() * m_pixelFormat.bitsPerPixel / 8);
}

bool FrameBuffer::resizeBuffer()
{
  if (m_buffer != 0) {
    delete []m_buffer;
  }
  if ((m_buffer = new UINT8[getBufferSize()]) == 0) {
    return false;
  }
  return true;
}

32768×32769、32ビット/ピクセルの画面を告示するサーバーは、この積を折り返させます。64ビットの積は4 GBを超え、(int)キャストは128 KBだけを残し、Viewerは128 KBを確保します。保存された寸法は告示された32768×32769のままなので、論理的なキャンバスと確保はちょうど2^32バイト(4,294,967,296)乖離します。

書き込みオフセットは符号付き32ビットintで、告示された幅に対して計算され、確保されたサイズとの比較は一切ありません。

void *FrameBuffer::getBufferPtr(int x, int y) const
{
  char *ptr = (char *)m_buffer;
  ptr += (y * m_dimension.width + x) * getBytesPerPixel();

  return (void *)ptr;
}

大きなyは積を折り返して符号拡張するため、画面更新の矩形はそのピクセルをバッファから任意の符号付きオフセット位置に置けます。更新はRawまたはTightの矩形としてサーバーから届き、RemoteViewerCore::receiveFbUpdateRectangle経由でデコーダーに入ります。RFBプロトコルは、形状も各矩形も、すべてサーバーが選べます。

どのように悪用されるか?

公開されたエクスプロイトに必要なのは接続1回だけで、資格情報は不要です。

  1. 攻撃者のRFBサーバーはViewerに挨拶し、セキュリティタイプを1つだけ、Noneで提示します。RFB認証は一方向で、サーバーはViewerに対して自分を証明せず、TightVNCのViewerはNoneを受け入れます。
  2. ServerInitが毒入り形状、32768×32769、32ビット/ピクセルを告示します。ハンドシェイクは45バイトで、エクスプロイトがFB1と呼ぶ128 KBのバッファを確保します。
  3. 座標(7492, 32767)のRaw矩形1つがFB1 - 0x18AF0に着地します。これはframebufferの下にあるBufferedOutputStreamオブジェクトのm_dataLengthフィールドで、エクスプロイトはここに大きな値を書き込みます。
s.sendall(b"RFB 003.008\n")                 # greet as the server
s.sendall(bytes([1, 1]))                    # offer one security type: None
s.sendall(struct.pack(">I", 0))             # SecurityResult = OK
s.sendall(struct.pack(">HH", 32768, 32769)  # ServerInit: the poisoned geometry
          + PIXEL_FORMAT + struct.pack(">I", 3) + b"PWN")
  1. 次のflush()が隣接ヒープの約100 KBをソケット経由で送り返します。エクスプロイトはこれらのバイトからtvnviewer.exeのイメージベースとframebufferのアドレスを復元し、アドレス空間のランダム化を打ち破ります。
  2. エクスプロイトは128 KBのframebuffer内に偽オブジェクトとROPチェーンを書き込み、BufferedOutputStreamのvtableポインタを上書きします。次の仮想呼び出しは偽オブジェクトをたどり、スタックをframebufferへピボットさせ、チェーンはCreateProcessWで終わります。

このエクスプロイトはASLRとDEPが有効な既定構成の新しいViewerに対して動作します。バイナリにはcontrol-flow guardがありません。開示資料は、リスニングViewerとアウトバウンド接続の両方の攻撃経路に対応する完全なエクスプロイトスクリプトを公開しており、実行後にViewerを再起動する再実行モードも含まれます。

攻撃はどのように流れるか?

攻撃者にとって必要なのはViewerがセッションを受け入れることだけで、切り詰められた確保がそのセッションをViewerプロセスの制御に変えます。

flowchart LR A["攻撃者が悪意のある
RFBサーバーを稼働"] --> B["Viewerが受け入れ、
セキュリティタイプNone"] B --> C["ServerInitが32768 x 32769
32 bppを告示"] C --> D["バッファが32ビットで算出、
4 GBのキャンバスが128 KB上に、
境界チェックなし"] D --> E["矩形が確保の外側へ
書き込む"] E --> F["ヒープリーク、vtable乗っ取り、
CreateProcessWへのROP:
Viewer上でコード実行"]

攻撃者に何ができるか?

実証された影響は、セッションが開いた瞬間の、Viewerを実行するアカウント権限でのプロセス内コード実行です。

  • リスニングモードでは、TCP 5500に到達できるすべてのホストが、パスワードなし、IP許可リストなし、オペレーターの操作なしでこれを得られます。開示資料はこの形態をヘルプデスクの通常運用と説明しており、露出するのはサーバーではなく支援ツール側です。
  • アウトバウンド接続では、侵害は接続時、パスワード入力の前に完了します。そのため、本物のサーバー側のVNCパスワードは、そのサーバーが攻撃者のものになれば役に立ちません。
  • エクスプロイトはCREATE_NO_WINDOWでペイロードのウィンドウを隠し、実行後にViewerを再起動でき、意のままにViewerをクラッシュさせるサービス拒否にもなります。

条件は限定的です。攻撃者はViewerが話す相手のサーバーになるか、リスニング中のViewerに到達する必要があります。TightVNCサーバー(tvnserver)はここでは標的ではなく、攻撃の向きが要点で、支援される側のマシンが支援する側のマシンを攻撃します。

なぜ修正は機能するか?

2.8.89のソースは新しいファイルviewer-core/ViewerBufferSize.hを追加し、64ビットのサイズ述語で、確保の前にサーバーの告示を検証します。

inline bool fitsFrameBuffer(int width, int height, int bytesPerPixel)
{
  if (width < 0 || height < 0 || bytesPerPixel <= 0) {
    return false;
  }

  const UINT64 alignedWidth = (static_cast<UINT64>(width) + 3) & ~static_cast<UINT64>(3);
  const UINT64 alignedHeight = (static_cast<UINT64>(height) + 3) & ~static_cast<UINT64>(3);
  return alignedWidth * alignedHeight <= INT_MAX / bytesPerPixel;
}

ServerInitの形状を消費するRemoteViewerCore::setFbPropertiesは、これを先頭で拒否するようになりました。

  if (!ViewerBufferSize::fitsFrameBuffer(width, height, bitsPerPixel / 8)) {
    throw Exception(_T("Framebuffer is too large"));
  }

同じパターンがリッチカーソルの経路を守り、2.8.89ではTightデコーダーも過大なJPEG矩形を拒否します。2.8.90でTightデコーダーはさらに2つの検証を加え、宣言された矩形とサイズが一致しない伸張済みデータと、空の圧縮ブロックを拒否します。これらが開発元のリリース通知が名指しする「Viewerのいくつかのセキュリティ問題」です。

この修正は、実証された経路については完全です。形状がプロトコル境界で、バッファのサイズ決定の前に検証されるため、サーバーの告示は切り詰められた確保に到達できなくなりました。切り詰め演算そのものはFrameBuffer::getBufferSizeに2.8.90でも変わらず残っているため、安全性はすべての呼び出し側が先に検証することに依存します。これはバグクラスの除去ではなく、検証済み入口への狭め込みです。

侵害されたか確認するには?

開示資料は侵害指標を公開していません。攻撃者の痕跡はViewerのホスト側にあり、以下の各確認は公開された仕組みから導かれたものです。

Get-WinEvent -LogName Application | Where-Object { $_.Message -match "tvnviewer" } | Select -First 20

アプリケーションログに説明のないViewerのクラッシュがあれば、サービス拒否の形態を示します。エクスプロイトのペイロードはViewerの子プロセスとして動くため、プロセス生成テレメトリで親がtvnviewer.exeであるプロセスを探してください。Sysmon有効なホストでは、その親を持つイベントID 1がそのクエリです。

リスニングViewerでは、ネットワークログでTCP 5500への着信接続と、それに続くViewerからの約100 KBのアウトバウンド流出を調べてください。この流出が、エクスプロイトが読み返すヒープリークです。

Viewerが露出していたうえこれらの兆候が1つでもあれば、オペレーターのアカウントは侵害されたとみなし、そのアカウントが到達できたすべてをローテートしてください。

年表

  • 2026-06-19:対象バージョンとして実証されたTightVNC 2.8.88がリリース。
  • 2026-10-02:TightVNC 2.8.89が、過大なframebuffer、RichCursor更新、Tight JPEG矩形に対する検証を搭載。開発元は更新を推奨。
  • 2026-10-05:TightVNC 2.8.90が残りのViewer修正を搭載し、伸張サイズを検証して空の圧縮ブロックを拒否。ソースにこの日付が記録されています。
  • 2026-10-29:SSD Secure Disclosureが解析とエクスプロイトを公開(SSD Labs KoreaのSeongJoon Choの功績)。開発元は報告に一度も答えなかったと記しています。
  • 2026-11-02:CVEは割り当てられておらず、開発元のアドバイザリもなく、悪用は知られていません。KEVカタログにも入っていません。

修正は対象の2.8.88リリースから105日後、エクスプロイト公開の24日前にリリースされた
図1:6月19日の対象バージョン、10月2日と10月5日の2つの修正、10月29日の公開エクスプロイト。出典:tightvnc.comのリリースノートとSSD Secure Disclosure、2026年11月2日確認

FAQ

TightVNCサーバーも影響を受けますか?

いいえ。標的はViewer、tvnviewer.exeで、攻撃はサーバーからViewerへ向かいます。TightVNCサーバーだけを稼働しているホストは、その運用者がViewerを使って外へ接続する場合に限って露出します。

VNCパスワードで防げますか?

いいえ。ViewerはセキュリティタイプNoneを受け入れ、RFB認証はそもそもサーバーの身元をViewerに証明しません。アウトバウンド経路はパスワード入力の前に完了するため、本物のサーバーの強力なパスワードも、そのサーバーが攻撃者のものになった時点で役に立ちません。

このクラスはTightVNCで過去にも起きていますか?

はい。2回とも悪意のあるサーバー経由でした。TightVNC 1.3.10は、Core Security Technologiesが報告したViewerの整数オーバーフローを修正し、改変されたVNCサーバーがViewerのヒープを破壊できるのを防ぎました。2.8.63は「悪意ある」サーバーがViewerマシン上でコードを実行できるバッファオーバーランを修正しました。開発元自身のリリースノートに両方が記録されています。

TightVNC 4は影響を受けますか?

4.xラインを対象とするアドバイザリはありません。このラインは2026年10月25日、2.8.89と2.8.90の修正の後にリリースされました。4.0の発表が述べているのは新しいセキュリティモデルで、このViewerの欠陥ではありません。4.xを実行している場合は開発元に確認してください。2.8.xでは何であれ2.8.90に更新してください。

なぜCVEがないのですか?

開発元はアドバイザリを公開しておらず、研究者の報告は無回答のままだったため、2026年11月2日時点でCVEは割り当てられていません。したがって依存関係・脆弱性スキャナーは対象範囲を検出できず、「影響を受けるのは?」のバージョン確認だけが確実な検出手段です。

修正済みViewerはどこで入手できますか?

開発元のダウンロードページで、4.0.2ラインと並んで2.8.90のインストーラーが提供されています。2.8.90のソースは同じ場所からGPLで公開されており、本記事の修正解析はそこから得ています。

OstorlabでリスニングViewerを公開したホストを見つける

Ostorlabの攻撃面の発見はインターネットに公開されたホストの棚卸しを行い、どの支援マシンがTCP 5500でリスニングViewerとして応答するかを特定して、優先して更新できるようにします。

参考文献

  • SSD Secure Disclosure「TightVNC Viewer Pre-auth RCE」。解析とエクスプロイト、SSD Labs KoreaのSeongJoon Choの功績:https://ssd-disclosure.com/tightvnc-viewer-pre-auth-rce/(2026年10月29日公開、2026年11月2日確認)
  • TightVNCリリースノート、2.8.89と2.8.90のViewer修正:https://www.tightvnc.com/whatsnew.php(2026年11月2日確認)
  • TightVNCニュース、10月2日と10月5日の発表、および6月19日の2.8.88リリース:https://www.tightvnc.com/news.php(2026年11月2日確認)
  • TightVNCダウンロードページ、2.8.90と4.xのインストーラー、GPLソースアーカイブ:https://www.tightvnc.com/download/(2026年11月2日確認)
  • TightVNC 2.8.88ソース配布、脆弱なFrameBuffer:https://www.tightvnc.com/download/2.8.88/tightvnc-2.8.88-src-gpl.zip(2026年11月2日確認)
  • TightVNC 2.8.90ソース配布、ViewerBufferSize.hと検証の呼び出し箇所:https://www.tightvnc.com/download/2.8.90/tightvnc-2.8.90-src-gpl.zip(2026年11月2日確認)
  • CyberSecurityNews「TightVNC Pre-Auth RCE Vulnerability Lets Hackers Execute Code on Windows PCs, PoC Released」:https://cybersecuritynews.com/tightvnc-pre-auth-rce-vulnerability/(2026年11月2日公開、2026年11月2日確認)
  • CISAのKEVカタログ。TightVNCの項目について確認:https://www.cisa.gov/known-exploited-vulnerabilities-catalog(2026年11月2日確認)