起因是在 wargame.kr 上有一道题 DB is really GOOD, 有一个点是如果输入 / 就会因为路径被分割掉无法访问到指定的sqlite数据库,导致报错,从而获得完整数据库名来下载到整个db文件

感觉这个点很有趣,刚好今年拟态需要出个常规的CTF题,干脆就想着在这个基础上来增加一些更有意思的做法,于是就有了这个题


题目打开后只有一个登录框,而且只有一个用户名的输入框

这里会发现不管输入什么东西都直接将用户名存入session,跳转了过去,并且会发现这里将输入用户名中的 . 替换成了 _,成功登录后会发现这里有一个提示,可以上传某些特定文件

如果随便上传一些文件的话,会在当前页面中将文件内容显示出来,而且可以访问到上传的文件本身

如果这里尝试上传zip文件的话就会发现不一样的地方,因为这个地方会尝试对zip做解压操作,处理上传的代码为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 private function check_mime() {
    $info = array(
        'file_type' => 'text',
        'file_path' => './uploads/' . $this->filename,
        'file_size' => $this->get_size(filesize($this->file)),
        'file_hash' => md5($this->file),
    );
    $type = strtolower(mime_content_type($this->file));
    switch ($type){
        case 'text/php':
        case 'text/x-php':
            $this->status = 'failed';break;
        case 'text/plain':
            $this->info = @serialize($info);break;
        case 'image/png':
        case 'image/gif':
        case 'image/jpeg':
            $info['file_type'] = 'image';
            $this->info = @serialize($info);break;
        case 'application/zip':
            $info['file_type'] = 'zip';
            $info['file_list'] = $this->handle_ziparchive();
            $this->info = @serialize($info);
            $this->flag = false;break;
        default:
            $info['file_type'] = 'other';
            $this->info = @serialize($info);break;
            break;
    }
}

private function handle_ziparchive() {
    try{
        $file_list = array();
        $zip = new PclZip($this->file);
        $save_dir = './uploads/' . substr($this->filename, 0, strlen($this->filename) - 4);
        @mkdir($save_dir, 755);
        $res = $zip->extract(PCLZIP_OPT_PATH, $save_dir, PCLZIP_OPT_EXTRACT_DIR_RESTRICTION, '/var/www/html' , PCLZIP_OPT_BY_PREG,'/^(?!(.*)\.ph(.*)).*$/is');
        foreach ($res as $k => $v) {
            $file_list[$k] = array(
                'name' => $v['stored_filename'],
                'size' => $this->get_size($v['size'])
            );
        }
        return $file_list;
    }
    catch (Exception $ex) {
        print_r($ex);
        $this->status = 'failed';
    }
}

这里限制了解压出来php文件的可能,并且在apache中做了设置,所以上传 .htaccess 也不会正常解析到php

回到一开始的登录,这个地方就是在 wargame.kr 上提到的那个点,所以直接输入 / 可以得到一个报错信息

那么这里就可以直接访问 http://xxx/dbs/mimic_{username}.db 来下载当前用户的数据库了

这里可以发现,其中存的数据都是php序列化后的结果,而上传处的zip解压,其实是可以跨目录的

那么将数据库中的数据做一下修改,比如直接将txt类型的文件 file_path 改成flag文件地址,然后通过zip解压跨目录,将制定的db文件上传回dbs目录中,登录对应的用户名,访问目标txt文件,即可得到flag

这里在上传中放了一个反序列化的点,最开始其实是把flag名称随机字符串了,需要通过在数据库中构造一个反序列化的文件写入getshell去找flag,后来感觉有点多此一举,干脆就直接放根目录下了

1
2
3
4
5
6
7
public function __destruct() {
    if($this->flag){
        file_put_contents('./uploads/' . $this->filename , file_get_contents($this->file));
    }
    $this->conn->insert($this->filename, $this->info);
    echo json_encode(array('status' => $this->status));
}