typecho源码阅读-安装部分

  • 首发于 https://blog.lou00.top/index.php/archives/12/

首先是判断安装的条件

//第一
file_exists(dirname(__FILE__) . '/config.inc.php')
//第二
$db = Typecho_Db::get();
$installed = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'installed'));

挡掉可能的跨站请求

if (!empty($_GET) || !empty($_POST)) {
    if (empty($_SERVER['HTTP_REFERER'])) {
        exit;
    }

    $parts = parse_url($_SERVER['HTTP_REFERER']);
    if (!empty($parts['port'])) {
        $parts['host'] = "{$parts['host']}:{$parts['port']}";
    }

    if (empty($parts['host']) || $_SERVER['HTTP_HOST'] != $parts['host']) {
        exit;
    }
}

没感觉这步有啥用,referer和host头都是可以自己设的

获取版本号以及语言

$options = new stdClass();
$options->generator = 'Typecho ' . Typecho_Common::VERSION;
list($soft, $currentVersion) = explode(' ', $options->generator);

$options->software = $soft;
$options->version = $currentVersion;

list($prefixVersion, $suffixVersion) = explode('/', $currentVersion);

/** 获取语言 */
$lang = _r('lang', Typecho_Cookie::get('__typecho_lang'));
$langs = Widget_Options_General::getLangs();

if (empty($lang) || (!empty($langs) && !isset($langs[$lang]))) {
    $lang = 'zh_CN';
}

if ('zh_CN' != $lang) {
    $dir = defined('__TYPECHO_LANG_DIR__') ? __TYPECHO_LANG_DIR__ : __TYPECHO_ROOT_DIR__ . '/usr/langs';
    Typecho_I18n::setLang($dir . '/' . $lang . '.mo');
}

Typecho_Cookie::set('__typecho_lang', $lang);

接下来就比较乱了,它把html与php写在了一起,都9012年了,不推荐这种写法,按照访问顺序介绍好了

第一次访问

第一次访问是不带任何请求的,只是但存访问其install.php
这里可以看到其多次使用了_e函数

// /var/Typecho/Common.php
function _e() {
    $args = func_get_args();
    echo call_user_func_array('_t', $args);
}


function _t($string) {
    if (func_num_args() <= 1) {
        return Typecho_I18n::translate($string);
    } else {
        $args = func_get_args();
        array_shift($args);
        return vsprintf(Typecho_I18n::translate($string), $args);
    }
}

_e的作用是如果传入的是数组的话可以通过call_user_func_array调用_t,然后百度了一下I18n原来是国际化的意思,跟进Typecho_I18n::translate

    public static function translate($string)
    {
        self::init();
        return self::$_loaded ? self::$_loaded->translate($string) : $string;
    
    private static function init()
    {
        /** GetText支持 */
        if (false === self::$_loaded && self::$_lang && file_exists(self::$_lang)) {
            self::$_loaded = new Typecho_I18n_GetTextMulti(self::$_lang);
        }
    }

默认self::$_loaded = flase而且没有那个文件
所以直接返回了字符

第二次访问

点击我准备好了, 开始下一步进行下一步
发现它自动给你带上一个config的post请求
现在访问的应该是install.php/?config

首先是_p函数

function _p($adapter) {
    switch ($adapter) {
        case 'Mysql':
            return Typecho_Db_Adapter_Mysql::isAvailable();
        case 'Mysqli':
            return Typecho_Db_Adapter_Mysqli::isAvailable();
        case 'Pdo_Mysql':
            return Typecho_Db_Adapter_Pdo_Mysql::isAvailable();
        case 'SQLite':
            return Typecho_Db_Adapter_SQLite::isAvailable();
        case 'Pdo_SQLite':
            return Typecho_Db_Adapter_Pdo_SQLite::isAvailable();
        case 'Pgsql':
            return Typecho_Db_Adapter_Pgsql::isAvailable();
        case 'Pdo_Pgsql':
            return Typecho_Db_Adapter_Pdo_Pgsql::isAvailable();
        default:
            return false;
    }
}
// 以Pdo_SQLite为例
// /var/Typecho/Db/Adapter/Pdo/SQLite.php
public static function isAvailable()
    {
        return parent::isAvailable() && in_array('sqlite', PDO::getAvailableDrivers());
    }

会寻找对应的函数,如果函数存在则说明扩展存在在数据库配置是会多一个选项

第三次访问

第三次访问增加了一堆post请求并通过_rForm赋值

function _rFrom() {
    $result = array();
    $params = func_get_args();

    foreach ($params as $param) {
        $result[$param] = isset($_REQUEST[$param]) ?
            (is_array($_REQUEST[$param]) ? NULL : $_REQUEST[$param]) : NULL;
    }

    return $result;
}

检测数据库连接

try {
    $installDb->query('SELECT 1=1');
} catch (Typecho_Db_Adapter_Exception $e) {
    $success = false;
    echo '

'. _t('对不起, 无法连接数据库, 请先检查数据库配置再继续进行安装') . '

'; } catch (Typecho_Db_Exception $e) { $success = false; echo '

'. _t('安装程序捕捉到以下错误: " %s ". 程序被终止, 请检查您的配置信息.',$e->getMessage()) . '

'; }

存在时可以对应前面的一个安装条件,以免反回404

if($success) {
    // 重置原有数据库状态
    if (isset($installDb)) {
        try {
            $installDb->query($installDb>update('table.options')->rows(array('value' => 0))->where('name = ?', 'installed'));
            } catch (Exception $e) {
        // do nothing
    }
}

下面就是我们的config.inc.php

$lines = array_slice(file(__FILE__), 1, 31);
$lines[] = "
/** 定义数据库参数 */
\$db = new Typecho_Db('{$adapter}', '" . _r('dbPrefix') . "');
\$db->addServer(" . (empty($config) ? var_export($dbConfig, true) : $config) . ", Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set(\$db);
";

第四次访问

创建好之后
跳转到/install.php?start
接下来就是一个比较有意思的点了

 $config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));

跟入

public static function get($key, $default = NULL)
    {
        $key = self::$_prefix . $key;
        $value = isset($_COOKIE[$key]) ? $_COOKIE[$key] : (isset($_POST[$key]) ? $_POST[$key] : $default);
        return is_array($value) ? $default : $value;
    }

Typecho_Cookie::get('__typecho_config')可以自己控制
意思说如果你可以任意删除文件,那么利用删除config.inc.php直接将洞扩大为RCE,注:www-data用户要有config.inc.php权限
接下来一大段的数据库初始化操作,由于太长了就不贴代码了
总之在数据库执行错误时,也就是原有库中存在数据时会返回选项,删除(postdelete=1)或者使用原有数据(postgoahead=1)

使用原有数据

会跳转header('Location: ./install.php?finish&use_old');

删除

if(_r('delete')) {
    //删除原有数据
    $dbPrefix = $config['prefix'];
    $tableArray = array($dbPrefix . 'comments',$dbPrefix . 'contents', $dbPrefix . 'fields',$dbPrefix . 'metas', $dbPrefix . 'options', $dbPrefix . 'relationships', $dbPrefix . 'users',);
    foreach($tableArray as $table) {
        if($type == 'Mysql') {
            $installDb->query("DROP TABLE IF EXISTS `{$table}`");
        } elseif($type == 'Pgsql') {
            $installDb->query("DROP TABLE {$table}");
        } elseif($type == 'SQLite') {
        $installDb->query("DROP TABLE {$table}");
        }
    }
    echo '

' . _t('已经删除完原有数据') . '

'; }

第五次访问

?finish
变回1,代表已完成,之后访问都会是404了

$db->query($db->update('table.options')->rows(['value' => 1])->where('name = ?', 'installed'));

到此结束

你可能感兴趣的:(typecho源码阅读-安装部分)