CVE-2024-2961 完整 RCE 链详解:1字节 glibc 溢出如何秒杀 PHP(Web手视角)

CVE-2024-2961 完整 RCE 链详解:1字节 glibc 溢出如何秒杀 PHP 这里算是我为数不多接触到pwn的一次,但是我这次机会想到,我有必要进行对pwn的学习 当然,这次是PHP的多链引发的想法,涉及了一个PWN相关影响深远的CVE,CVE-2025-2961. 我会用WEB手也通...
CVE-2024-2961 完整 RCE 链详解:1字节 glibc 溢出如何秒杀 PHP(Web手视角)
CVE-2024-2961 完整 RCE 链详解:1字节 glibc 溢出如何秒杀 PHP(Web手视角)

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 位参与者

阅读完整话题

来源: linux.do查看原文