Cisco Japan Blog

TALOS、7-Zip に複数の脆弱性を発見

2 min read



7-Zip の脆弱性は Marcin Noga が発見しました。
この記事は、Marcin NogaJaeson Schultz が執筆しました。

2016 年 5 月 12 日更新:本ブログで取り上げた 7-Zip 問題に関連するアドバイザリは以下のブログで確認できます。
http://www.talosintel.com/reports/TALOS-2016-0093/popup_icon
http://www.talosintel.com/reports/TALOS-2016-0094/popup_icon

7-Zip はオープンソースのファイル アーカイブ アプリケーションです。AES-256 の暗号化方式や大容量ファイルをサポートし、「あらゆる圧縮、変換、暗号化方式popup_icon」に対応していることを特徴としています。Cisco Talos はこのほど、不正利用の恐れがある複数の脆弱性が 7-Zip に存在することを発見しました。このタイプの脆弱性は注意が必要です。影響を受けるライブラリを、さまざまなベンダーがそうとは知らずに使用している可能性があるためです。また、セキュリティ デバイスやアンチウイルス製品などの場合は特に注意が必要です。7-Zip は主なプラットフォームのすべてでサポートされており、現在、最も一般的に利用されているアーカイブ ユーティリティの 1 つです。どれだけ多くの製品やアプライアンスpopup_iconが影響を受けるかを知れば、きっと驚かれることと思います。

TALOS-CAN-0094、領域外メモリ参照の脆弱性 [CVE-2016-2335]

領域外メモリ参照の脆弱性の原因は、7-Zip によるユニバーサル ディスク フォーマット(UDF)ファイルの処理方法にあります。UDF ファイル システムは ISO-9660 ファイル フォーマットに代わるものとして策定され、最終的に DVD-Video や DVD-Audio の公式のファイル システムとして採用されました。

7-Zip による UDF ファイルの処理の中心となるのは、CInArchive::ReadFileItem メソッドです。ボリュームは複数のパーティション マップを保持できるため、それらのオブジェクトはオブジェクト ベクトルに格納されます。アイテムを探す際、このメソッドはまずパーティション マップのオブジェクト ベクトルと Long Allocation Descriptor の「PartitionRef」フィールドを使用して、適切なオブジェクトを参照しようと試みます。この際、パーティション マップ オブジェクトの数よりも「PartitionRef」フィールドが大きいかどうかの確認が行われないために領域外メモリ参照が発生し、特定の状況下において任意のコード実行が可能となる恐れがあります。

脆弱性を示すコード:

CPP\7zip\Archive\Udf\UdfIn.cpp

Line 898    FOR_VECTOR (fsIndex, vol.FileSets)
Line 899    {
Line 900      CFileSet &fs = vol.FileSets[fsIndex];
Line 901      unsigned fileIndex = Files.Size();
Line 902      Files.AddNew();
Line 903      RINOK(ReadFileItem(volIndex, fsIndex, fs.RootDirICB, kNumRecursionLevelsMax));
Line 904      RINOK(FillRefs(fs, fileIndex, -1, kNumRecursionLevelsMax));
Line 905    }
(...)
Line 384        HRESULT CInArchive::ReadFileItem(int volIndex, int fsIndex,
const CLongAllocDesc &lad, int numRecurseAllowed)
Line 385        {
Line 386          if (Files.Size() % 100 == 0)
Line 387                RINOK(_progress->SetCompleted(Files.Size(), _processedProgressBytes));
Line 388          if (numRecurseAllowed-- == 0)
Line 389                return S_FALSE;
Line 390          CFile &file = Files.Back();
Line 391          const CLogVol &vol = LogVols[volIndex];
Line 392          CPartition &partition = 
Partitions[vol.PartitionMaps[lad.Location.PartitionRef].PartitionIndex];

 

この脆弱性は、不正な形式の Long Allocation Descriptor を含むエントリによってトリガーされます。上記のコードの Line 898 ~ 905 からわかるように、プログラムは特定のボリュームから要素を探し、RootDirICB の Long Allocation Descriptor に基づいてファイル セットが開始されます。このレコードを故意に不正なものとすることで、悪意のある目的に使用される可能性があります。脆弱性は、Line 392 で PartitionRef フィールドが PartitionMaps ベクトルの要素数を超える場合に発生します。

TALOS-CAN-0093、ヒープ オーバーフローの脆弱性 [CVE-2016-2334]

7-Zip の Archive::NHfs::CHandler::ExtractZlibFile メソッドの機能には、不正利用可能なヒープ オーバーフローによる脆弱性が存在します。HFS+ ファイル システムでは、ファイルは zlib を使用した圧縮形式で格納できます。データを zlib 形式で保存する場合、データのサイズによって 3 つの方法があります。圧縮後のサイズが 3800 バイトを超えるファイルのデータは、複数のブロックに分けられリソース フォークに格納されます。

ブロック サイズ情報とそれらのオフセットはリソース フォーク ヘッダーの直後のテーブルに保持されます。ExtractZlibFile メソッドは展開前に、ブロック サイズとオフセットをファイルから読み取ります。その後、サイズが固定されたバッファ「buf」にブロック データが読み込まれます。ここで、ブロックのサイズがバッファ「buf」のサイズよりも大きいかどうかが確認されないため、「buf」のサイズを超える不正な形式のブロック サイズが読み込まれる可能性があります。これにより、バッファ オーバーフローが発生し、ヒープ破損が発生します。

脆弱性を示すコード:

7zip\src\7z1505-src\CPP\7zip\Archive\HfsHandler.cpp

Line 1496        HRESULT CHandler::ExtractZlibFile(
Line 1497                ISequentialOutStream *outStream,
Line 1498                const CItem &item,
Line 1499                NCompress::NZlib::CDecoder *_zlibDecoderSpec,
Line 1500                CByteBuffer &buf,
Line 1501                UInt64 progressStart,
Line 1502                IArchiveExtractCallback *extractCallback)
Line 1503        {
Line 1504          CMyComPtr inStream;
Line 1505          const CFork &fork = item.ResourceFork;
Line 1506          RINOK(GetForkStream(fork, &inStream));
Line 1507          const unsigned kHeaderSize = 0x100 + 8;
Line 1508          RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize));
Line 1509          UInt32 dataPos = Get32(buf);
Line 1510          UInt32 mapPos = Get32(buf + 4);
Line 1511          UInt32 dataSize = Get32(buf + 8);
Line 1512          UInt32 mapSize = Get32(buf + 12);
(...)
Line 1538          RINOK(ReadStream_FALSE(inStream, tableBuf, tableSize));
Line 1539          
Line 1540          UInt32 prev = 4 + tableSize;
Line 1541
Line 1542          UInt32 i;
Line 1543          for (i = 0; i < numBlocks; i++)
Line 1544          {
Line 1545                UInt32 offset = GetUi32(tableBuf + i * 8);
Line 1546                UInt32 size = GetUi32(tableBuf + i * 8 + 4);
Line 1547                if (size == 0)
Line 1548                  return S_FALSE;
Line 1549                if (prev != offset)
Line 1550                  return S_FALSE;
Line 1551                if (offset > dataSize2 ||
Line 1552                        size > dataSize2 - offset)
Line 1553                  return S_FALSE;
Line 1554                prev = offset + size;
Line 1555          }
Line 1556
Line 1557          if (prev != dataSize2)
Line 1558                return S_FALSE;
Line 1559
Line 1560          CBufInStream *bufInStreamSpec = new CBufInStream;
Line 1561          CMyComPtr bufInStream = bufInStreamSpec;
Line 1562
Line 1563          UInt64 outPos = 0;
Line 1564          for (i = 0; i < numBlocks; i++)
Line 1565          {
Line 1566                UInt64 rem = item.UnpackSize - outPos;
Line 1567                if (rem == 0)
Line 1568                  return S_FALSE;
Line 1569                UInt32 blockSize = kCompressionBlockSize;
Line 1570                if (rem < kCompressionBlockSize)
Line 1571                  blockSize = (UInt32)rem;
Line 1572
Line 1573                UInt32 size = GetUi32(tableBuf + i * 8 + 4);
Line 1574
Line 1575                RINOK(ReadStream_FALSE(inStream, buf, size)); // !!!HEAP OVERFLOW !!!

 

HFS+ イメージからの解凍中、圧縮されたファイルには「com.apple.decmpfs」属性が付与され、リソース フォークにデータが格納されます。その後、上記のコードが示す結果となります。

圧縮されたファイル データは複数のブロックに分けられ、展開前の各ブロックは「buf」に読み込まれます(Line 1575)。ReadStream_FALSE(実際の中身は ReadFile API)は、「size」の値に基づいて、データの一部を「buf」バッファに読み込みます。

バッファ「buf」の定義とそのサイズは、ExtractZlibFile の呼び出し元「CHandler::Extract」メソッドでわかります。見てわかるように、サイズは固定で 0x10010 バイトです。

Line 1633        STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
Line 1634                Int32 testMode, IArchiveExtractCallback *extractCallback)
Line 1635        {
(...)
Line 1652          
Line 1653          const size_t kBufSize = kCompressionBlockSize; // 0x10000
Line 1654          CByteBuffer buf(kBufSize + 0x10); // we need 1 additional bytes for 
uncompressed chunk header
(...)
Line 1729          HRESULT hres = ExtractZlibFile(realOutStream, item, _zlibDecoderSpec, buf,
Line 1730            currentTotalSize, extractCallback);

 

ExtractZlibFile メソッドに戻ると、Line 1573 で、tableBuf から値を読み込んで、ブロックの「size」を設定しています。tableBuf は、Line 1538 でファイルから読み込まれています。つまり、「size」はファイルのデータの一部であり、値を操作することが可能です。0x10010 よりも大きい値が「size」に設定されると、バッファ オーバーフローが発生し、ヒープが破損します。

Line 1573 より前では、「size」変数の値が Line 1543 ~ 1555 のループで読み込まれます。コードのこのブロックでは、データ ブロックに整合性があるかどうかを確認します。つまり、以下を確認します。
– データ ブロックは tableBuf 直後に始まる(Line 1540)
– 次のデータ ブロックは前のデータ サイズ + オフセットから始まる(Line 1549)
– オフセットは dataSize2(圧縮データのサイズ)より大きい値であってはならない(Line 1551)
– 「size」は残りのデータより大きい値であってはならない(Line 1552)

ここでわかるように、「size」のサイズが「buf」のサイズよりも大きいかどうかに関しては確認されていません。また、上記の制約が、「size」のサイズが「buf」のサイズよりも大きいかどうかに影響を与えることもありません。

まとめ

残念なことに、入力データを適切に検証できていないアプリケーションにより、数多くのセキュリティ脆弱性が発生しています。7-Zip のこれらの脆弱性は両方とも、入力に関する検証の欠如が原因です。信頼できないソースからデータが入力される可能性があるため、データ入力の検証はすべてのアプリケーションのセキュリティにおいて非常に重要な要素です。Talos はこの問題を解決するために、7-Zip と協力し、責任をもってこれらの脆弱性を開示しました。また、対応するパッチを作成しました。脆弱なバージョンから最新バージョンである 16.00 にできるだけ早くアップデートすることをお勧めします。

TALOS-CAN-0093 は SID 38323 ~ 38326 で検出できます。
TALOS-CAN-0094 は SID 38293 ~ 38296 で検出できます。

本稿は 2016年5月11日に Talos Grouppopup_icon のブログに投稿された「MULTIPLE 7-ZIP VULNERABILITIES DISCOVERED BY TALOSpopup_icon」の抄訳です。

コメントを書く