Affected Version
V1.0.0.20200506 beta (latest release)
Exploitation Requirements
In /application/config.php:
//是否开启前台会员中心
'usercenter' => true,
The member center feature must be enabled.
Vulnerability Analysis
/application/index/User.phpfile
Lines 58–67:
public function _empty($name)
{
$data = Hook::listen("user_request_empty", $name);
foreach ($data as $index => $datum) {
$this->view->assign($datum);
}
return $this->view->fetch($name);
}
user_request_emptyis a developer hook and can be ignored. Focus on return $this->view->fetch($name);
In this method,$nameThe parameter is controllable, and$namepasses the value intofecth()function.
fetch()is ThinkPHP's template parsing function and returns the rendered contents of the template file.
fetch()The important part of the function is:
public function fetch($template, $data = [], $config = [])
{
if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
// 获取模板文件名
$template = $this->parseTemplate($template);
}
// 模板不存在 抛出异常
if (!is_file($template)) {
throw new TemplateNotFoundException('template not exists:' . $template, $template);
}
// 记录视图信息
App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info');
$this->template->fetch($template, $data, $config);
}
Following the call stack shows that fetch() invokes the built-in template engine's fetch method. It assigns the rendered page content to a variable. For convenience, ThinkPHP supports PHP tags during template rendering, allowing the template to parse PHP code.
In one sentence, this is a template injection vulnerability caused by insufficient filtering of an input variable. If an attacker controls the template file, the template renderer can be abused as a file-inclusion primitive to obtain a shell.
Also note that when verifying whether the supplied template is a file, the code usesis_file()function. It behaves differently on Linux and Windows, as follows:
1. Exploiting on Linuxis_file()to test paths such as/****/../../../../etc/passwdWhen checking the file, if****is a nonexistent directory and therefore returns false. On Windows it returns true whether or not the directory exists, as shown below:


2. On Linux,is_file()can be used to test symbolic links.
3. On Linux,is_fileis affected by permissions. If the current user lacks permission or the parent directory does not have +x permission,is_file()returns false.
4. On Windows/and\ both forms work, but on Linux only/ as the path separator, which causesis_file()returns different results on different operating systems.


5、is_file()fails when checking files larger than 2^32 bytes.
Verification
As shown above, the exploitation point is in_empty()function. Note that the official documentation usually says_empty()checks whether a method exists and enters this function if it does not. Because this is a developer-defined method, directly supply_emptymethod by supplying the name parameter.
The exploitation process is:
In the member center, open the profile page and upload a new avatar:

Capture the request and modify the image data, retaining a valid image header:

After recording the path, the shell is obtained successfully.

This method fails on Linux because in/publicthe path does not containuserdirectory. As explained above, when this directory does not exist, no amount of directory traversal will makeis_file()The function always returns false, so this approach cannot exploit the vulnerability, as shown below:

When we are in/publicCreate a directory under/user, then exploit it to succeed:

Finally, thanks to Joseph. I learned something new again.