CVE-2024-2961 完整 RCE 链详解:1字节 glibc 溢出如何秒杀 PHP
这里算是我为数不多接触到pwn的一次,但是我这次机会想到,我有必要进行对pwn的学习
当然,这次是PHP的多链引发的想法,涉及了一个PWN相关影响深远的CVE,CVE-2025-2961.
我会用WEB手也通俗易懂的语言来讲解,这个CVE网上相关文章也很多,我也就阐述一下自己观点
这个CVE的是发生在iconv函数中,并且基本上是在转义函数可控的情况下发生的,
这里先说说前置概念,这个缓冲区溢出是因为ISO-2022-CN-EXT的SS2/SS3切换缺乏缓冲区校验引起的
ISO-2022-CN-EXT是一个中文字符编码形式,假设这个编码是一个柜子,那么SS2,SS3就是抽屉,用来存个别字符的柜子
我们先看一看简化的关键源码,
else if ((used & SS2_mask) != 0 && (ann & SS2_ann) != (used << 8)) {
const char *escseq;
assert (used == CNS11643_2_set); /* XXX */
escseq = "*H";
*outptr++ = ESC; // 0x1B
*outptr++ = '$'; // 0x24
*outptr++ = *escseq++; // 0x2A
*outptr++ = *escseq++; // 0x48
ann = (ann & ~SS2_ann) | (used << 8);
}
else if ((used & SS3_mask) != 0 && (ann & SS3_ann) != (used << 8)) {
const char *escseq;
assert ((used >> 5) >= 3 && (used >> 5) <= 7);
escseq = "+I+J+K+L+M" + ((used >> 5) - 3) * 2;
*outptr++ = ESC;
*outptr++ = '$';
*outptr++ = *escseq++;
*outptr++ = *escseq++;
ann = (ann & ~SS3_ann) | (used << 8);
}
当识别是SS2/SS3的时候在缓存区写入我注视的这一串命令,而这里并没有校验缓冲区是否够存放,
所以会造成缓冲区溢出,这个理念倒是非常清晰易懂,但是难点是如何利用进行RCE。
在PHP中控制了拥有读文件等操作,在漏洞版本范围内,便可上升为RCE
这里要先从PHP的内存管理机制说起,读文件可以用过滤器php://filter
而filter支持dechunk自定义块,在一块内存中,如果不停发送dechunk也就可以自定义每块的大小,
file_get_contents("php://filter/
dechunk|
convert.iconv.latin1.latin1|
convert.iconv.latin1.latin1|
(重复多次堆喷)
convert.iconv.UTF-8.ISO-2022-CN-EXT
/resource=/etc/passwd");
也就是空闲内存,然后当中插入受害块,也就是ISO-2022-CN-EXT的SS2/SS3
进行编码转换命令时分到的块,而有几个特殊的汉字会使其溢出,这样就会溢出到相邻块,
也就会污染相邻块的最低位,往往是0x48,也就是说最低位0000变为0x48,这样内存释放时
free_solts的这部分内存指向就变了,链表被毒化之后,
攻击者利用堆喷(简略概括),将许多空闲内存都挂上string对象,而其中是有len属性的,
当正常执行一次构造字符串时,地址偏移让正常写字符串的操作写入了len字段
这样len的长度可以被改的超级大,这样就泄漏了大量系统函数的地址
接下来就是RCE
PHP的内存管理器是ZENDMM,他是一个大内存池,
它控制分配这些内存的供给与释放,而其中_zend_mm_heap结构的子结构custom_heap控制内存释放的指针
它控制着内存释放的时候指向的函数指针
如此一来我调用dechunk结合之前泄漏的地址,任意写入custom_heap,将指针覆盖为system()的地址
//简化结构
typedef struct _zend_mm_custom_heap {
void (*_free)(void *ptr); // ← 要改的目标
// ...
} custom_heap;
_zend_mm_heap->custom_heap._free = system(); // 攻击者通过 overlap 实现
也就是每次内存释放都会调用
当然PHP的内存调用是很频繁的,
最后将要执行的命令放入一个可控的zend_string对象中,也就是一个挂载其的对象,然后触发一次内存释放,
命令也就执行了。
1 个帖子 - 1 位参与者