php Opcache插件进行RCE
php opcache缓存进行RCE
前言
关于opcache
我是在去年三四月份从B站上的nepnep战队的一个分享会上看到的,当时觉得很难,就记录了一下操作,记住了这个词,然后就没出现过了
再次见到就是Boogipop大佬的文章,不过也没看,一直到现在复现DASCTF X 0psu3的一个题目用到了,就来看一遍,相当于是重新学一遍了
复现一遍Boogipop大佬文章,我这里详细给出了环境搭建的命令(当然若是大佬可以直接看原文)
环境搭建
docker pull php:7.1-apache
//docker pull php:8.2-apache
docker run -d -p 9000:80 --name opcache7 php:7.1-apache
apt-get update
apt install vim
vim phpinfo.php //写phpinfo
//安装opcache插件
docker-php-ext-configure opcache --enable-opcache && docker-php-ext-install opcache
vim /usr/local/etc/php/php.ini-production
//找到opcache部分添加下面三行句子
[opcache]
opcache.enable=1
opcache.file_cache="/tmp"
opcache.file_cache_only=1
//docker的php是没有php.ini配置文件的,修改了php.ini-production后复制并重命名为php.ini即可
cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
//修改了php.ini要重启服务
service apache2 reload
看到这一行就说明配置成功了
看一下docker中的文件
当Opcache第一次缓存文件时, /tmp/system_id/var/www/html/phpinfo.php.bin
php7 Opcache
首先说一下Opcache rce的原理,Opache是php中一个生成缓存文件的拓展,当我们访问一个php文件时,他会产生一个缓存文件,下次访问该php文件时,就是直接根据缓存文件回显页面了。
那很明显伪造文件替换即可
缓存文件夹是1116d566fdc53f79abce6c01e3a0308d
这个是今天的重点,我们怎么去算这个system_id
呢?
在之前的笔记中,我记录它为文件上传 LFI + phpinfo,是可以根据phpinfo的信息计算出来system_id
的
在PHP环境下已经有前人给出我们计算脚本https://github.com/GoSecure/php7-opcache-override
php7.3版本好像太高算不对,于是Boogipop大佬降低了版本为7.1
我们怎么利用呢?当我们存在任意文件写入或者覆盖时,我们可以通过覆盖.php.bin
文件达到RCE的目的
我们把缓存文件下载下来分析一下
可以看到最开头是OPACHE+systemid,因此假如我们获取到了SYSTEMID,我们就可以伪造一份缓存文件了,但是还需要注意一点
我们这里的opcache
拓展开启了timestamp
,也就是时间戳验证,那么假如我们创建的文件时间戳不对的话,我们也无法覆盖成功的QWQ,那么就需要题目有一个获取时间的地方,假如可以下载任意文件或者直接获取时间戳,那么我们可以用010editor直接修改。
干到这里时我实在是受不了原来那版010了,又耗费精力找新的破解版,耗了不少时间,服了
从0040h
开始,即第五行开始的8个字节(图中蓝色部分)即是时间戳
这里我们再启动一个容器就行,与原来一样的配置(只是在phpinfo.php
文件中写入一句话木马),这样就省下计算system_id
这一步了
同样会生成一个phpinfo.php.bin
文件,弄下来修改为前一个的system_id(已省略)和时间戳
然后把这个修改过的文件替换掉环境中的文件即可
我是把原来的直接删掉,然后通过base64写入文件
然后访问phpinfo.php就会变得空白,说明一句话木马写入成功
这样就成功getshell了,需要注意一下生成恶意bin文件的php版本需要大致吻合,不能差太多。
php8 opcache
在PHP8之后,opcache生成system_id的方法有些许改变,所以之前的脚本是跑不出来了
可以看到算出的system_id
不同
Boogipop大佬并没有说出规律是什么
经过我的尝试,规律很简单,就是修改API前的版本号
我这里是8.2.14
再加上框框中的内容
所以得出计算代码
<?php
var_dump(md5("8.2.14API420220829,NTSBIN_4888(size_t)8\002"));
后面的步骤是一样的,就不试了
春秋杯2023-php_again
给出Boogipop大佬文章中的例题,
考点就是opache8的缓存文件利用:
<?php
$action = $_GET['action'];
if (empty($action)) {
highlight_file(__FILE__);
die();
}
switch ($action) {
case 0_0:
phpinfo();
break;
case 0o0_111:
exec('zip -r /tmp/www.zip *');
readfile('/tmp/www.zip');
break;
case 0b0_111:
var_dump(scandir('/var/www/html/'));
break;
case 0x0_555:
file_put_contents('/tmp/tmp.zip',base64_decode($_POST['data']));
break;
case 777_777:
exec('cd /tmp && unzip -o tmp.zip');
break;
}
首先是一个软链接读文件,然后是unzip解压覆盖文件,满足了所有条件后就可以按照上述步骤进行处理,getshell了
这感觉好难啊
DASCTF X 0psu3 十一月挑战赛 single_php
就给个代码,看文章复现吧
DASCTF X 0psu3 Web Writeup - Boogiepop Doesn’t Laugh (boogipop.com)
好像都是解压覆盖这种
<?php
error_reporting(0);
highlight_file(__FILE__);
$allowed_ip = "127.0.0.1";
if ($_SERVER['REMOTE_ADDR'] !== $allowed_ip) {
die("S* has the kanojo but you don't");
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if (finfo_file($finfo, $_FILES["file"]["tmp_name"]) === 'application/x-tar'){
exec('cd /tmp && tar -xvf ' . $_FILES["file"]["tmp_name"]);
}
<?php
error_reporting(0);
class siroha{
public $koi;
public function __destruct(){
$this->koi['zhanjiangdiyishenqing']();
}
}
$kanozyo = $_GET['LuckyE'](__FILE__);
var_dump($kanozyo);
$suki = unserialize($_POST['suki']);
reference
PHP8 OPCACHE缓存文件导致RCE - Boogiepop Doesn’t Laugh (boogipop.com)