Emlog 6.0 beta version, a code audit about PHP language CMS article, detailed record the complete process of code audit, learn code audit, might as well start from this article!

The preface

The article the author aimed at Emlog CMS audit process is basically a complete record, may seem tedious, but code audit process is such, findings may, then carefully constructed to verify, this process we will encounter many times go to the wall, adhere to the test, some active thinking, basic can learn, sincere hope that others can read patiently, Of course, it would also be helpful to enlighten.

It is important to note that the purpose of code audit is to learn and avoid similar errors in SDL, and also to help open source systems fix related problems. Of course, it can also be used to discover new 0days and help us find new breakthroughs in the penetration testing process.

0 x00 Emlog 6.0 beta

Official website: www.emlog.net/

Emlog 6.0 Beta

www.emlog.net/download

As the official limit forum members (registration fee) can only download, here is an original download address: www.lanzous.com/i1l5gad

File verification:

File: C:\Users\stdy\Desktop\emlog_6.0.0.zip Size: 607725 bytes Modified time: August 6, 2018, 20:53:50 MD5: 7844FE6FEAE7AF68052DC878B8811FAC SHA1: E06A050D2A0AA879DB9F5CFCAA4703B6AC7B8352 CRC32: 4963E489Copy the code

The blogger’s blog is based on this set of blog system, in fact, many big guy in the circle are in use, for the CMS audit article is not, the author will come to CMS as a comprehensive study case of PHP code audit, complete audit of the CMS source code.

0x01 Preliminary Test

First, we have to install it! Home page after successful installation:

The default login address is./admin/

After successful login:

By the way, 6.0 looks a lot better than 5.3.1

After installation, we should gather as much information as possible about the CMS, which will help us audit the code.

Therefore, the general structure of the CMS is obtained through analysis. Emlog is an MVC design pattern, and the general structure is shown in the figure below:

So we’ll focus on files in the admin and include folders.

Database table:

In the init.php file in the root directory

Error level specified as 7:


      
// Disable error reporting
error_reporting(0);

// Report a runtime error
error_reporting(E_ERROR | E_WARNING | E_PARSE);

// Report all errors
error_reporting(E_ALL);

error_reporting(7);
/* Set PHP error detection level E_ERROR - fatal runtime error (1) E_WARNING - runtime warning (non-fatal) (2) E_PARSE - compile-time parsing error (4) 1+2+4 = 7 */
? >
Copy the code

0x02 Use vulnerability scanner

May have a friend will say why do you want to use “leakage scan”? Isn’t it code audit?

To correct this point of view, miss sweep is an automated black box test, in a local environment, we do not affect any business.

It is an efficient way for us to quickly locate vulnerabilities through leakage and scanning. This is also the inspiration given to the author by the members of the team who got several high-risk vulnerabilities in Baidu through leakage and scanning.

A heavy-duty scanner, AWVS, was used and the results were reported as follows:

However, when scanning locally, XAMPP Windows10 PHP5.6 environment was used, which led to many false positives in the vulnerability report. Several XSS vulnerabilities and CSRF vulnerabilities were mainly scanned

So we first verify these two types of vulnerabilities

0x03 Article editor stored XSS

In the background editor, edit the article./admin/admin_log.php

After successful release, came to the home page

After entering the article page

Emlog has a member/author feature. In some templates in Emlog, you can register as a member at the front desk, and when you log in, you can edit and post articles, comments and so on. Emlog also officially provides article submission plug-in, which calls the official default Kindeditor editor. This editor has its own HTML editing mode, and even without this mode, attackers can also capture and modify to achieve the purpose of attack.

Why didn’t the front desk filter it? In order for the article to support HTML code output, the kindeditor save output is not escaped.

Repair suggestion: refer to other CMS to do a good job of article content keyword detection, and do a good filtering or escape

0x04 Uploadify SWF XSS

Emlog using uploadify. SWF way to upload file, the file path/include/lib/js/uploadify/uploadify SWF

Tectonic Payload:http://www.test.com//include/lib/js/uploadify/uploadify.swf?uploadifyID=00%22%29%29; }catch%28e%29{alert%281%29; }//%28%22&movieName=%22])}catch(e){if(! window.x){window.x=1; alert(document.cookie)}}//&.swf

Effect, regardless of browser filter:

0x05 Reflective XSS

The XSS here mainly happens on the cookie, because some pages such as admin/admin_log, admin/sort. PHP, admin/link. The PHP page need to be added in the form of the token hidden attribute values, This token value is taken directly from the user’s cookie, resulting in a reflective XSS

Packet capture Modify the token value in the cookie as follows:

Effect:

Secondly, the CSRF vulnerability is verified. The CSRF of the foreground search box has no value at all

Then there is XSS, where the administrator adds links that are verified to be non-existent, and the background function limits the word count

Then we started to carry out the original code audit, mainly using Seay code audit tool and Rips. This audit tool mainly relies on the regular matching of PHP functions that may lead to danger to judge the possible vulnerabilities. In a semi-automated way, the pressure of code audit is relieved to some extent.

0x06 Basic function

The View::getView method is often used.

In the include/lib/view.php file, the source is as follows:


      
/** * View control *@copyright (c) Emlog All Rights Reserved
 */

class View {
	public static function getView($template.$ext = '.php') {
		if(! is_dir(TEMPLATE_PATH)) { emMsg('The current template has been deleted or damaged. Please log in to the background to replace another template. ', BLOG_URL . 'admin/template.php');
		}
		return TEMPLATE_PATH . $template . $ext;
	}

	public static function output() {
		$content = ob_get_clean();
        ob_start();
		echo $content;
		ob_end_flush();
		exit; }}Copy the code

LoginAuth::checkToken(), which also acts as a permission control, begins at about line 209 under \include\lib\loginauth.php

/** * Generates tokens to defend against CSRF attacks */
public static function genToken() {
	$token_cookie_name = 'EM_TOKENCOOKIE_' . md5(substr(AUTH_KEY, 16.32) . UID);
	if (isset($_COOKIE[$token_cookie_name]) {return $_COOKIE[$token_cookie_name];
	} else {
		$token = md5(getRandStr(16));
		setcookie($token_cookie_name.$token.0.'/');
		return $token; }}/** * Check tokens to defend against CSRF attacks */
public static function checkToken(){
	$token = isset($_REQUEST['token'])? addslashes($_REQUEST['token') :' ';
	if ($token! =self::genToken()) {
		emMsg('Insufficient permission, token error'); }}Copy the code

Verified Rips scan out of the file contains the problem (the first time using Rips), found unable to reproduce, because Rips scan is in the form of files, and there is no strict logic of the reference program, resulting in false positives!

Go to the \admin\admin_log.php file, starting at line 78:

// Work with the article
if ($action= ='operate_log') {
    $operate = isset($_REQUEST['operate'])?$_REQUEST['operate'] : ' ';
    $pid = isset($_POST['pid'])?$_POST['pid'] : ' ';
    $logs = isset($_POST['blog'])? array_map('intval'.$_POST['blog') :array(a);$sort = isset($_POST['sort'])? intval($_POST['sort') :' ';
    $author = isset($_POST['author'])? intval($_POST['author') :' ';
    $gid = isset($_GET['gid'])? intval($_GET['gid') :' ';

    LoginAuth::checkToken();

    if ($operate= =' ') {
        emDirect("./admin_log.php? pid=$pid&error_b=1");
    }
    if (empty($logs) && empty($gid)) {
        emDirect("./admin_log.php? pid=$pid&error_a=1");
    }

    switch ($operate) {
        case 'del':
            foreach ($logs as $val)
            {
                doAction('before_del_log'.$val);
                $Log_Model->deleteLog($val);
                doAction('del_log'.$val);
            }
            $CACHE->updateCache();
            if ($pid= ='draft')
            {
                emDirect("./admin_log.php? pid=draft&active_del=1");
            } else{
                emDirect("./admin_log.php? active_del=1");
            }
            break;
        case 'top':
            foreach ($logs as $val)
            {
                $Log_Model->updateLog(array('top'= >'y'), $val);
            }
            emDirect("./admin_log.php? active_up=1");
            break;
        case 'sortop':
            foreach ($logs as $val)
            {
                $Log_Model->updateLog(array('sortop'= >'y'), $val);
            }
            emDirect("./admin_log.php? active_up=1");
            break;
        case 'notop':
            foreach ($logs as $val)
            {
                $Log_Model->updateLog(array('top'= >'n'.'sortop'= >'n'), $val);
            }
            emDirect("./admin_log.php? active_down=1");
            break;
        case 'hide':
            foreach ($logs as $val)
            {
                $Log_Model->hideSwitch($val.'y');
            }
            $CACHE->updateCache();
            emDirect("./admin_log.php? active_hide=1");
            break; .// The middle code is to verify the management identity, so omit

        case 'uncheck':
            if(ROLE ! = ROLE_ADMIN) { emMsg('Insufficient permissions! '.'/');
            }
            $Log_Model->checkSwitch($gid.'n');
            $CACHE->updateCache();
            emDirect("./admin_log.php? active_unck=1");
            break; }}Copy the code

So we’re overstepping our authority to delete the article http://www.test.com/admin/admin_log.php?action=operate_log&operate=del&blog=29&token=994132a26661c8c244a91063c4701a7e \include\model\log_model.php

/** * delete article **@param int $blogId
 */
function deleteLog($blogId) {
	$author = ROLE == ROLE_ADMIN ? ' ' : 'and author=' . UID;
	$this->db->query("DELETE FROM " . DB_PREFIX . "blog where gid=$blogId $author");  // The author can only delete his own article
	if ($this->db->affected_rows() < 1) {
		emMsg('Insufficient permissions! '.'/');
	}
	/ / comment
	$this->db->query("DELETE FROM " . DB_PREFIX . "comment where gid=$blogId");
	/ / label
	$this->db->query("UPDATE " . DB_PREFIX . "tag SET gid= REPLACE(gid,',$blogId,',',') WHERE gid LIKE '%" . $blogId . "%' ");
	$this->db->query("DELETE FROM " . DB_PREFIX . "tag WHERE gid=',' ");
	/ / accessories
	$query = $this->db->query("select filepath from " . DB_PREFIX . "attachment where blogid=$blogId ");
	while ($attach = $this->db->fetch_array($query)) {
		if (file_exists($attach['filepath']) {$fpath = str_replace('thum-'.' '.$attach['filepath']);
			if ($fpath! =$attach['filepath']) {
				@unlink($fpath);
			}
			@unlink($attach['filepath']); }}$this->db->query("DELETE FROM " . DB_PREFIX . "attachment where blogid=$blogId");
}
Copy the code

This overreach loophole does not exist. At the same time, the following functions are judged to have done similar processing

In fact, we are already familiar with the architecture of the whole CMS, and can find the corresponding function position directly and manually according to the corresponding function function.

Sadly, none of the results obtained through the Rips code audit tool have been successfully replicated…

0x07 Seay Auxiliary Audit

I believe many people know the master of this tool, mainly because of Chinese, easy to use, but completely rely on the regular way to match the function, can only find those functions directly control vulnerabilities, logic vulnerabilities can sometimes be found according to the inverse, but this situation is very rare.

Use this tool to scan out a total of 120 possible situations (more than 98% are not reproducible according to experience), and then check one by one, some such as SQL statements such as back single quotes, it is easy to judge the law, do not need to consider.

/admin/store.php

Here is my thinking, if you have any URL links to jump in emlog website, you can download remote any file to the web site structure, but not jump links to test the website, so we try to download other plug-ins (link to jump, etc.), or a hacker carefully constructed a plugin or template, and then reuse, it is a feasible scheme.

Administrator permissions are required here, as a reference for code audit, not to discover what 0day, but hope that you can learn from code audit.

(1). SQL injection

For SQL injection, the Seay tool has never been able to do this. Instead, I recommend using a global search $_GET[or $_PSOT[to see if SQL queries are being applied, and then verifying each one.

Then I found such an unfiltered IP parameter

Then go to admin/comment.php to check

Look again at the delCommentByIp($IP) function

This confirms the existence of SQL injection

The verification is as follows:

(2). A CSRF+ arbitrary file deletion

$_POST[] = $_POST[]; $_POST[] = $_POST[]

CSRF ::checkToken() {toknen ::checkToken() {toknen ::checkToken() {toknen ::checkToken() {toknen ::checkToken()

This is the successful deletion of the file

(3). The TAG SQL injection

No filtering is found in the POST argument, and an SQL query is substituted in the deleteTag() function, so it’s another SQL injection

Look at the deleteTag() function:

The getBlogIdsFromTagId() function is called again, again without filtering

So use packet capture to verify:

However, there is no echo when other statements are used. The author does not know the reason and did not explore it carefully, but time blind annotation can be adopted.

Now that your semi-automated audit with the tool is over, you’re ready for manual testing

0x08 Manual Test

Manual test is not a simple file flipping, should be gray box test as the leading, from logic, permissions, sensitive information and other aspects

(1) There is a risk of brute force cracking in background login

Here, as I mentioned before the verification code is not in time to destroy the history of the problem still exists, here no longer detail, please refer to the blog.csdn.net/dyboy2017/a…

(2). An error message causes physical path leakage

Don’t think this is trivial. When SQL injection existed, we had the opportunity to write shell files directly

A low-privilege way to test it in the visitor’s condition

payload:http://www.test.com/admin/attachment.php?action[]=

The reason is: Addslashes () expects parameter 1

(3). Cookies can be calculated

In include/lib/loginauth. PHP 134 line

/** * write for login authentication cookie **@param int $user_id User ID
 * @param bool $remember Whether to remember the user or not
 */
public static function setAuthCookie($user_login.$ispersis = false) {
	if ($ispersis) {
		$expiration  = time() + 3600 * 24 * 30 * 12;
	} else {
		$expiration = null;
	}
	$auth_cookie_name = AUTH_COOKIE_NAME;
	$auth_cookie = self::generateAuthCookie($user_login.$expiration);
	setcookie($auth_cookie_name.$auth_cookie.$expiration.'/');
}

/** * Generates a login authentication cookie **@param int $user_id user login
 * @param int $expiration Cookie expiration in seconds
 * @return string Authentication cookie contents
 */
private static function generateAuthCookie($user_login.$expiration) {
	$key = self::emHash($user_login . '|' . $expiration);
	$hash = hash_hmac('md5'.$user_login . '|' . $expiration.$key);
	$cookie = $user_login . '|' . $expiration . '|' . $hash;
	return $cookie;
}
Copy the code

As you can see, cookies can be computed directly in the config.php root directory

//auth key
define('AUTH_KEY'.'dx1&CH^En86GZnxd9CLO7GwC0Q5eYHKM450f598bbd148b6a62f7d263623e31c3');
//cookie name
define('AUTH_COOKIE_NAME'.'EM_AUTHCOOKIE_VzfVniPWDqd1LM3BFocnrcjpAGH4lUbz');
Copy the code

Can.

(4). Sidebar storage XSS

In order to support the output of HTML code as well, no corresponding script code tag is escaped, resulting in the existence of stored XSS

0x09 Getshell

(1).SQL injection get shell

As mentioned above, with SQL injection and access to physical paths, you can write the Shell directly

(2). Upload zip by background plug-in

Because the background can directly upload the local ZIP file, here we go to the official website to download a plug-in, and add our shell file (such as dybo.php) to the zip, upload and install the plug-in, and then the shell address is: http://www.test.com/content/plugins/ plugin name/dyboy. PHP

(3) upload zip of background template

And plug-in the same principle, the shell address is: http://www.test.com/content/templates/ template name/dyboy. PHP

(4). Take shell for backup files

SELECT * from ‘SELECT * from’ where ‘
” into outfile ‘D:\\Server\\htdocs\\safe\\dyboy.php’;

Then import backup to restore local data

This generates a dybo.php shell in the site’s directory

0 x0a summary

EMLOG is a very small and light blog system, running very low resources, so it is very suitable for bloggers to use blogs. In fact, as long as you do not open the membership function, there is no big threat without weak password.

This article serves as a primer for PHP code audit, and the method described in this article can also be applied to other CMS code audit and analysis.

Creation is not easy, point a like, share to more like-minded people, also hope that this article can enlighten everyone!

Welcome wechat search concern public number: DYBOY, push bytedance, learn Web security together!