0x00 はしがき
- 著者: Qsl1pknotp、Cital Company のセキュリティ コンサルタント
- タイトル: PHP のメモリ破損バグの悪用 パート 3: リモート シェルのポップ
- アドレス: http://www.inulledmyself.com/2015/05/exploiting-memory-corruption-bugs-in.html
この記事は思ったより時間がかかりました、長いです、しかし、せっかくの時間なので、この脆弱性を悪用する方法をビデオで説明したいと思います。そのため、この記事は前の 2 つほど詳細ではありません。
一部の人はがっかりするかもしれません。はい、この記事ではその方法を紹介するだけです。 POC を作成します (実際に POC をリリースするわけではありません)。記事の最後にあるビデオでは、自動化されたリモート活用ツールと、実際の環境でこの POC を正常に実行するために使用する手順を示します。
0x01 Find data
この脆弱性の悪用方法をより簡単に説明するために、次のコードをデモンストレーションとして使用し、特定の状況を詳細に分析します。
#!php<?phpecho serialize(unserialize(base64_decode($_GET['data'])));?>
私たちの目標は、任意の PHP コードを実行できるようにすることです。もちろん、目標を達成するためにシェルコードを挿入することもできますが、この方法は創造的でもエレガントでもありません (パート 1 を覚えている場合は、より上位のバージョンでは成功しない可能性があります)。 、任意の PHP コードを実行できるようにするには、php_execute_script と zend_eval_string を呼び出す必要があります。ただし、リモート攻撃を実行できるようにしたいので、executor_globals と JMP_BUF を見つける必要があります。詳細は後ほど紹介します。
つまり、(順不同) 以下を見つける必要があります。
- executor_globals
- zend_eval_string
- JMP_BUF
- スタックに入る方法
幸いなことに、上記の要件の一部はバイナリに直接ダンプされるため、比較的簡単に見つけることができます。 PHP binay の strtab。
素晴らしいですね。そこから zend_eval_string のアドレスを直接見つけて、このアドレスが GDB で正しいかどうかを確認します。
zend_eval_string のアドレスを検索します
-
print zend_executor_globals->bailout
-
わかりました, このアドレスを取得しましたが、このアドレスは何に使われますか? さて、PHP では JMP_BUF は PHP の「try{} - catch{}」メカニズムを実装するために使用されます。これについては後で詳しく説明します。
0x02 利用方法 1 - ROP
残ることは 1 つだけです。任意のデータをスタックに書き込む方法です。利用方法 2 では、Syscan 2010 で Stefan によって公開された方法について詳しく説明します。任意のメモリを解放できます。次に何をすべきでしょうか? 書き込まれたデータが将来上書きされないようにする方法は? RFC、より正確には: RFC 1867
この RFC は、multipart/form-data を使用した POST リクエストからのデータをスタックに書き込むことができ、(さまざまな理由により) PHP によって上書きされないことを指定します。 「通常の」ファイルをアップロードします。
素晴らしいですね。スタックに何を書き込むことができますか?
ヒント:以前に探していたもの : )
以前は非常に多くのアドレスを見つけるのに非常に多くの時間を費やしたので、今度はそれらを使用することにしました。では、いくつかの調査を経て、以前のアドレスをどのように使用すればよいでしょうか?次のように配置する必要があります。 eval_string へのポインタを 2 回入力します。
POP - ????
XCHG ESP; ; RET - ??
Zend_Eval_String - 0x082da150Zend_Bailout - 0x00000000
Pointer_To_Eval_String - 0xbfffda04
Ret ポインタ000000 0
- Pointer_To_Eval_String - 0xbfffda04
- これでほぼ完成です。個人的には ROP ガジェットがまだ必要なようですが、他のツールでも機能します。 XCHG EAX、ESP (0x94 0xc3) を探す必要があり、POP EBP (0x5d 0x3c) も見つける必要があります。
-
我们拿到了这两个地址了(为啥这些地址相差那么大, 因为它们是相对地址), 我们可以继续完成 stack 中的数据:
- POP; RET - 0x000e8e68
- XCHG EAX, ESP; RET - 0x000057b7
- Zend_Eval_String - 0x082da150
- Zend_Bailout - 0x00000000
- Pointer_To_Eval_String - 0xbfffda04
- Ret Pointer - 0x00000000
- Pointer_To_Eval_String - 0xbfffda04
好了, 该是时候测试了.
Hmmm, 这不是我想要的结果, 现在怎么办? 看起来好像我们的代码尝试跳到我们的 gadget (c394). 不幸的是, 你还需要知道一些事情. SPLObjectStorage 要求这些 gadget 在 php 是可以访问的, 所以我们还需要修改一下. 经过修改之后:
0x03 利用方法 2 - Stefan
方法 1 就到此为止了, 方法 1 只能影响老版本的 PHP. 我们继续研究新版本的 PHP 利用方法.
比较走运的是, 之前找到 php_execute_script 和 jmp_buf 地址, 在新 exploit 中都会被用到.
jmp_buf 在 setjmp & longjmp 中被用来保存 "环境" 以预防 "不可恢复" 的错误. 在 32 位系统中, jmp_buf 是一个存储 6 个 int 的数组, 在 64 位系统中, jmp_buf 存储的是 8 个 int 的数组. 不幸的是, 需要自己查看代码来判断 jmp_buf 保存的寄存器的顺序. 这里有个 jmp_buf 样例布局. 让我们看一下 PHP 中的内容...
在我的机器上, 寄存器的顺序是: ebx, esp, ebp, esi, edi, eip. 值得完成的事情一般都不怎么容易完成, 在这里也一样, 我们的 edi & eip 看起来貌似被 Glibc 混淆了, Glibc 有个宏叫 PTR_MANGLE , 在视频中, 我们会讲解如何破解 JMPBUF.
一旦破解出了 edi & eip, 我们就可以继续重写和释放内存了. 幸运的是, 我们可以继续利用 SPLObjectStorage 远程释放内存. 剩下的事情就是将如何写到 stack 中. 和Part 2, 我们可以任意操纵 PHP 内存. 我们先释放一些内存, 然后再写 7 byte 数据填充, 当 php 重写我们的数据时, 再重复之前的操作. 第二次重写能够让我们写入任意长度的数据到 stack 中 (我测试的时候, 这个长度大概可以达到 2048 byte). 我们写入的数据和之前使用 ROP 的那个例子差不多. 我们还要继续 "加密" 我们写入 stack 中的数据. 这是攻击效果:
0x04 视频地址
video , 自备梯子
视频笔记
- x86 Instruction Chart - http://sparksandflames.com/files/x86InstructionChart.html
- Elf Header lowest 3 bits are 000
- Elf layout - http://geezer.osdevbrasil.net/osd/exec/elf.txt
- PMAP is your friend when trying to find the "Magic"
- A look at PTR_MANGLE http://hmarco.org/bugs/CVE-2013-4788.html
0x05 译者总结
就和作者说的一样, 这篇文章没有之前两篇写得详细.
1. 作者那个 PHP binary 文件从哪来的?
原文下有评论, 作者说他通过 memory leak 获取了整个 php binary 文件.
正常情况下, 一般的套路就是:
- 查找 ELF magic header \x7fELF 找到起始地址
- 通过 strtab , symtab 找到 zend_eval_string , php_execute_script , executor_globals 地址.
2. jmpbuf 是什么?
jmpbuf 是 setjmp, longjmp 所使用的数据结构, 以实现 try--catch 机制的东西, 和 goto 语法效果差不多, setjmp 相当于在某个位置的 label, longjmp 相当于 goto, 但是 goto 语法并不能跨函数跳转. jmpbuf 主要保存着 caller 的寄存器信息以方便 longjmp 恢复. 另外 glibc 会混淆一些寄存器的值(除了有漏洞的 glibc ).
3. 如何解出 jmpbuf?
先查看 setjmp 代码:
#!bash(gdb) disassemble setjmpDump of assembler code for function setjmp: 0xb7c94410 <+0>: mov eax,DWORD PTR [esp+0x4] 0xb7c94414 <+4>: mov DWORD PTR [eax],ebx # 1. 保存 ebx 0xb7c94416 <+6>: mov DWORD PTR [eax+0x4],esi # 2. 保存 esi 0xb7c94419 <+9>: mov DWORD PTR [eax+0x8],edi # 3. 保存 edi 0xb7c9441c <+12>: lea ecx,[esp+0x4] 0xb7c94420 <+16>: xor ecx,DWORD PTR gs:0x18 0xb7c94427 <+23>: rol ecx,0x9 0xb7c9442a <+26>: mov DWORD PTR [eax+0x10],ecx # 4. 保存 esp 0xb7c9442d <+29>: mov ecx,DWORD PTR [esp] 0xb7c94430 <+32>: xor ecx,DWORD PTR gs:0x18 0xb7c94437 <+39>: rol ecx,0x9 0xb7c9443a <+42>: mov DWORD PTR [eax+0x14],ecx # 5. 保存 eip 0xb7c9443d <+45>: mov DWORD PTR [eax+0xc],ebp # 6. 保存 ebp 0xb7c94440 <+48>: push 0x1 0xb7c94442 <+50>: push DWORD PTR [esp+0x8] 0xb7c94446 <+54>: call 0xb7c943c0 <__sigjmp_save> 0xb7c9444b <+59>: pop ecx 0xb7c9444c <+60>: pop edx 0xb7c9444d <+61>: ret
上面的寄存器保存的都是 caller 的寄存器状态, 其中 esp, eip 都被混淆过了(作者自己的图也是 esp 和 eip 被混淆), 就是使用 PTR_MANGLE 进行混淆.
PTR_MANGLE 和 PTR_DEMANGLE 宏定义如下:
#!cpp# define PTR_MANGLE(reg) xorl %gs:POINTER_GUARD, reg; \ roll $9, reg# define PTR_DEMANGLE(reg) rorl $9, reg; \ xorl %gs:POINTER_GUARD, reg
其中 gs:0x18 就是上面的 POINTER_GUARD
setjmp() 使用 PTR_MANGLE 进行混淆寄存器, longjmp() 使用 PTR_DEMANGLE 解出正常的寄存器. 为了后续能过正常覆盖 jmpbuf, 所以我们需要获得 POINTER_GUARD 的值, 由于 jmpbuf 数据结构可以越界读, caller 的 eip 也可以拿到, 所以通过 PTR_DEMANGLE 就可以获得 POINTER_GUARD 的值.
4. 如何获取到 setjmp caller 的 eip ?
通过阅读代码, 我们可以知道 php_execute_script 调用了 setjmp, 并将 jmpbuf 保存到 EG(bailout) 中, 通过泄漏 php_execute_script 地址 即可知道调用 setjmp 时到 eip.
5. 如何覆盖到 jmpbuf ?
jmpbuf 地址向前搜索数值 XX 00 00 00 (XX>0x0c and XX
先看看 ZMM 的几个结构体:
#!cpp/* mm block type */typedef struct _zend_mm_block_info { size_t _size; size_t _prev;} zend_mm_block_info;
.
#!cpptypedef struct _zend_mm_free_block { zend_mm_block_info info; struct _zend_mm_free_block *prev_free_block; struct _zend_mm_free_block *next_free_block; struct _zend_mm_free_block **parent; struct _zend_mm_free_block *child[2];} zend_mm_free_block;
.
#!cppstruct _zend_mm_heap { int use_zend_alloc; void *(*_malloc)(size_t); void (*_free)(void*); void *(*_realloc)(void*, size_t); size_t free_bitmap; size_t large_free_bitmap; size_t block_size; size_t compact_size; zend_mm_segment *segments_list; zend_mm_storage *storage; size_t real_size; size_t real_peak; size_t limit; size_t size; size_t peak; size_t reserve_size; void *reserve; int overflow; int internal;#if ZEND_MM_CACHE unsigned int cached; zend_mm_free_block *cache[ZEND_MM_NUM_BUCKETS];#endif zend_mm_free_block *free_buckets[ZEND_MM_NUM_BUCKETS*2]; zend_mm_free_block *large_free_buckets[ZEND_MM_NUM_BUCKETS]; zend_mm_free_block *rest_buckets[2]; int rest_count;};
我们需要关注的是 _zend_mm_heap 中的 cached. ZMM 会将 0x10 大小的内存块放进 cached 中, 所以当我们找到一个可以当做 memory block 之后, 最后几个字节(7 byte 数据)伪造一个 memory header (_zend_mm_block_info), 然后再用 string 重用这个伪造后的 memory block, 如果写入的长度不足以覆盖 jmpbuf, 继续伪造 memory header 相关的操作, 直到能够覆盖 jmpbuf 为止.
6. 能够覆盖 jmpbuf 之后 ?
将 eip 设置为 zend_eval_string , 将 esp 设置为一个直接可控的 stack(比如说 jmpbuf 之后), 填充好 jmpbuf, 该混淆的寄存器继续混淆. 然后在这个可控的 stack 上设置好 zend_eval_string 的参数, zend_eval_string 的定义如下:
#!cZEND_API int zend_eval_string(char *str, zval *retval_ptr, char *string_name TSRMLS_DC)
最后触发一个 exception, 即可执行我们想要的代码.
7. PHP7 的变化 ?
php7 的 zval 格式有很大的变化, 通过字符串数据覆盖 zval 结构没法再做到读取任意地址数据了, 只能向后读取数据(drops 这篇文章的作者 libnex 说他有办法, 期待新文章).
#!cstruct _zval_struct { zend_value value; /* value */ union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* active type */ zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; } u1; union { uint32_t var_flags; uint32_t next; /* hash collision chain */ uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ } u2;};
.
#!cstruct _zend_string { zend_refcounted_h gc; zend_ulong h; /* hash value */ size_t len; char val[1];};
如果通过数据去覆盖 zval_struct , 只能通过修改 len 来实现向后读取.
总结 exploit 利用步骤
- 利用 part 2 介绍的方法可以泄漏 std_object_handlers 信息, 随便找一个数值较小的地址
- 利用 part 2 介绍的任意地址读取的方法向前读取数据, 直到出现 \x7FELF .
- 通过 strtab, symtab 可以泄漏 zend_eval_string , php_execute_script , executor_globals (作者图省事, 文章直接本地 readelf)
- 通过 excutor_globals 可以拿到 bailout 地址 (也就是 jmpbuf 地址)
- 通过 php_execute_script 获取到调用 setjmp 时的 eip
- 获取到了 setjmp caller 的 eip, 再获取到 jmpbuf 地址中 eip 混淆后的值, 通过 PTR_DEMANGLE 即可获得 POINTER_GUARD 的值.
- 通过反复释放重用内存, 直到能过覆盖 jmpbuf
- 将 zend_eval_string 的地址与之前的 POINTER_GUARD 进行 PTR_MANGLE 写入到 jmpbuf 的 eip 中.
- 将 esp 设置为一个我们可写的 stack 范围, 比如说 jmpbuf 之后的内存, 进行 PTR_MANGLE 之后写入到 jmpbuf 的 esp 中.
- 在刚刚能覆盖 jmpbuf 的内存块后面依次写入 返回地址, php 代码地址, php 函数名, php 结果返回地址, php 文件名, php 代码.
- 触发一个 exception.
如果还有疑惑的地方, 可以去看看作者的视频以及树人的 paper. 如果我补充的有不正确的地方, 请不吝赐教.

PHP and Python each have their own advantages, and the choice should be based on project requirements. 1.PHPは、シンプルな構文と高い実行効率を備えたWeb開発に適しています。 2。Pythonは、簡潔な構文とリッチライブラリを備えたデータサイエンスと機械学習に適しています。

PHPは死にかけていませんが、常に適応して進化しています。 1)PHPは、1994年以来、新しいテクノロジーの傾向に適応するために複数のバージョンの反復を受けています。 2)現在、電子商取引、コンテンツ管理システム、その他の分野で広く使用されています。 3)PHP8は、パフォーマンスと近代化を改善するために、JITコンパイラおよびその他の機能を導入します。 4)Opcacheを使用してPSR-12標準に従って、パフォーマンスとコードの品質を最適化します。

PHPの将来は、新しいテクノロジーの傾向に適応し、革新的な機能を導入することで達成されます。1)クラウドコンピューティング、コンテナ化、マイクロサービスアーキテクチャに適応し、DockerとKubernetesをサポートします。 2)パフォーマンスとデータ処理の効率を改善するために、JITコンパイラと列挙タイプを導入します。 3)パフォーマンスを継続的に最適化し、ベストプラクティスを促進します。

PHPでは、特性は方法が必要な状況に適していますが、継承には適していません。 1)特性により、クラスの多重化方法が複数の継承の複雑さを回避できます。 2)特性を使用する場合、メソッドの競合に注意を払う必要があります。メソッドの競合は、代替およびキーワードとして解決できます。 3)パフォーマンスを最適化し、コードメンテナビリティを改善するために、特性の過剰使用を避け、その単一の責任を維持する必要があります。

依存関係噴射コンテナ(DIC)は、PHPプロジェクトで使用するオブジェクト依存関係を管理および提供するツールです。 DICの主な利点には、次のものが含まれます。1。デカップリング、コンポーネントの独立したもの、およびコードの保守とテストが簡単です。 2。柔軟性、依存関係を交換または変更しやすい。 3.テスト可能性、単体テストのために模擬オブジェクトを注入するのに便利です。

SplfixedArrayは、PHPの固定サイズの配列であり、高性能と低いメモリの使用が必要なシナリオに適しています。 1)動的調整によって引き起こされるオーバーヘッドを回避するために、作成時にサイズを指定する必要があります。 2)C言語アレイに基づいて、メモリと高速アクセス速度を直接動作させます。 3)大規模なデータ処理とメモリに敏感な環境に適していますが、サイズが固定されているため、注意して使用する必要があります。

PHPは、$ \ _ファイル変数を介してファイルのアップロードを処理します。セキュリティを確保するための方法には次のものが含まれます。1。アップロードエラー、2。ファイルの種類とサイズを確認する、3。ファイル上書きを防ぐ、4。ファイルを永続的なストレージの場所に移動します。

JavaScriptでは、nullcoalescingoperator(??)およびnullcoalescingsignmentoperator(?? =)を使用できます。 1.??最初の非潜水金または非未定されたオペランドを返します。 2.??これらの演算子は、コードロジックを簡素化し、読みやすさとパフォーマンスを向上させます。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

WebStorm Mac版
便利なJavaScript開発ツール

MantisBT
Mantis は、製品の欠陥追跡を支援するために設計された、導入が簡単な Web ベースの欠陥追跡ツールです。 PHP、MySQL、Web サーバーが必要です。デモおよびホスティング サービスをチェックしてください。

SecLists
SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

VSCode Windows 64 ビットのダウンロード
Microsoft によって発売された無料で強力な IDE エディター

AtomエディタMac版ダウンロード
最も人気のあるオープンソースエディター
